Skip to content

Commit

Permalink
ENH: Crop Image Geometry with Physical Coordinate Bounds (#818)
Browse files Browse the repository at this point in the history
Added ability to define crop with coordinates
Added test case
Updated documentation
  • Loading branch information
nyoungbq authored Jan 16, 2024
1 parent f282e77 commit 3e63732
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 59 deletions.
66 changes: 54 additions & 12 deletions src/Plugins/SimplnxCore/docs/CropImageGeometry.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,66 @@

## Description

This **Filter** allows the user to crop a region of interest (ROI) from an **Image Geometry**. The input parameters are in units of voxels. For example, if a **Image Geometry** has dimensions of 100x100x100 voxels and each voxel was 0.25 x 0.25 x 0.25 units per voxel, then if the user wanted to crop the last 5 microns in the X direction, then the user would enter the following:
This **Filter** allows the user to crop a region of interest (ROI) from an **Image Geometry**. The input parameters are in units of voxels or physical coordinates.

Bounds: 100 voxels * 0.25 micron/voxel = 25 microns
## Examples

Xmin = 80 (20 micron),
Xmax = 99 (25 micro),
Ymin = 0 (0 micron),
Ymax = 99 (25 micron),
Zmin = 0 (0 micron),
Zmax = 99 (25 micron)
In the following examples, the following image is being used.

*Note:* The input parameters are *inclusive* and begin at *0*, so in the above example *0-99* covers the entire range of **Cells** in a given dimension.
- Origin: [0.0, 0.0, 0.0]
- Spacing: {0.5, 0.5, 1.0}
- Dimensions: {100, 100, 1}

## Global Position of Cropped Region
So the bounds of the image is (0-50 micron, 0-50 micron, 0-1 micron)

Figure 1 shows the position of the cropped region relative to the original image.
![Base image for examples](Images/CropImageGeometry_1.png)

![Figure 1](Images/CropImageGeometry_1.png)
### Example 1

If the user wanted to crop the last 50 voxels in the X and Y axis then the user would use the following values:

Xmin = 50,
Xmax = 99,
Ymin = 50,
Ymax = 99,
Zmin = 0,
Zmax = 0

![Cropped image using voxels as the bounds](Images/CropImageGeometry_2.png)

**Note:** the units in the above image is in microns.

**Note:** The input parameters are *inclusive* and begin at *0*, so in the above example *50-99* will include the last 50 voxels.

### Example 2

If the user would like to crop out the `middle` 50 voxels from the image, these are the inputs:

Xmin = 25,
Xmax = 74,
Ymin = 25,
Ymax = 74,
Zmin = 0,
Zmax = 0

![Cropped image using voxels as the bounds](Images/CropImageGeometry_3.png)

### Example 3

In this example the user is going to define the crop using physical coordinates and also selecting an upper bound that exceeds the actual bounds of the image. In this case, the filter will instead use the maximum bounds from that axis.

Xmin = 30 microns,
Xmax = 65 microns,
Ymin = 30 microns,
Ymax = 65 microns,
Zmin = 0 microns,
Zmax = 65 microns

**Note:** This will work because at least some portion of the cropped image is within the original image. If **ALL** cropped values fall out side of the image bounds then the filter will error out in preflight.

![Cropped image using voxels as the bounds](Images/CropImageGeometry_4.png)

User may note that the way the bounds are determined are affected by the origin and spacing, so be sure to take these into account when supplying coordinate bounds for the crop.

## Renumber Features

Expand Down
Binary file modified src/Plugins/SimplnxCore/docs/Images/CropImageGeometry_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
202 changes: 158 additions & 44 deletions src/Plugins/SimplnxCore/src/SimplnxCore/Filters/CropImageGeometry.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ namespace nx::core
class SIMPLNXCORE_EXPORT CropImageGeometry : public IFilter
{
public:
CropImageGeometry() = default;
~CropImageGeometry() noexcept override = default;
CropImageGeometry();
~CropImageGeometry() noexcept override;

CropImageGeometry(const CropImageGeometry&) = delete;
CropImageGeometry(CropImageGeometry&&) noexcept = delete;
Expand All @@ -21,8 +21,11 @@ class SIMPLNXCORE_EXPORT CropImageGeometry : public IFilter
CropImageGeometry& operator=(CropImageGeometry&&) noexcept = delete;

// Parameter Keys
static inline constexpr StringLiteral k_UsePhysicalBounds_Key = "use_physical_bounds";
static inline constexpr StringLiteral k_MinVoxel_Key = "min_voxel";
static inline constexpr StringLiteral k_MaxVoxel_Key = "max_voxel";
static inline constexpr StringLiteral k_MinCoord_Key = "min_coord";
static inline constexpr StringLiteral k_MaxCoord_Key = "max_coord";
// static inline constexpr StringLiteral k_UpdateOrigin_Key = "update_origin";
static inline constexpr StringLiteral k_SelectedImageGeometry_Key = "selected_image_geometry";
static inline constexpr StringLiteral k_CreatedImageGeometry_Key = "created_image_geometry";
Expand Down Expand Up @@ -102,6 +105,9 @@ class SIMPLNXCORE_EXPORT CropImageGeometry : public IFilter
*/
Result<> executeImpl(DataStructure& dataStructure, const Arguments& args, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler,
const std::atomic_bool& shouldCancel) const override;

private:
int32 m_InstanceId;
};
} // namespace nx::core

Expand Down
74 changes: 73 additions & 1 deletion src/Plugins/SimplnxCore/test/CropImageGeometryTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ TEST_CASE("SimplnxCore::CropImageGeometry(Execute_Filter)", "[SimplnxCore][CropI
args.insert(CropImageGeometry::k_FeatureAttributeMatrix_Key, std::make_any<DataPath>(k_CellFeatureAMPath));
args.insert(CropImageGeometry::k_RemoveOriginalGeometry_Key, std::make_any<bool>(false));

const auto oldDimensions = dataStructure.getDataRefAs<ImageGeom>(k_ImageGeomPath).getDimensions();
// Preflight the filter and check result
auto preflightResult = filter.preflight(dataStructure, args);
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions);
Expand Down Expand Up @@ -243,3 +242,76 @@ TEST_CASE("SimplnxCore::CropImageGeometry(Execute_Filter)", "[SimplnxCore][CropI
ExecuteDataFunction(CompareDataArrayFunctor{}, exemplarArray.getDataType(), exemplarArray, calculatedArray);
}
}

TEST_CASE("SimplnxCore::CropImageGeometry: Crop Physical Bounds", "[SimplnxCore][CropImageGeometry]")
{
const std::vector<float64> k_MinVector{10, 15, 0};
const std::vector<float64> k_MaxVector{60, 40, 50};

const DataPath k_ImageGeomPath({Constants::k_DataContainer});
const DataPath k_NewImageGeomPath({"7_0_Cropped_ImageGeom"});
DataPath destCellDataPath = k_NewImageGeomPath.createChildPath(Constants::k_CellData);
static constexpr bool k_RenumberFeatures = true;
const DataPath k_FeatureIdsPath({Constants::k_DataContainer, Constants::k_CellData, Constants::k_FeatureIds});
const DataPath k_CellFeatureAMPath({Constants::k_DataContainer, Constants::k_CellFeatureData});
DataPath k_DestCellFeatureDataPath = k_NewImageGeomPath.createChildPath(Constants::k_CellFeatureData);

const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "6_5_test_data_1.tar.gz", "6_5_test_data_1");

CropImageGeometry filter;
// Read the Small IN100 Data set
auto baseDataFilePath = fs::path(fmt::format("{}/6_5_test_data_1/6_5_test_data_1.dream3d", nx::core::unit_test::k_TestFilesDir));
DataStructure dataStructure = UnitTest::LoadDataStructure(baseDataFilePath);
Arguments args;

args.insert(CropImageGeometry::k_UsePhysicalBounds_Key, std::make_any<bool>(true));
args.insert(CropImageGeometry::k_MinCoord_Key, std::make_any<std::vector<float64>>(k_MinVector));
args.insert(CropImageGeometry::k_MaxCoord_Key, std::make_any<std::vector<float64>>(k_MaxVector));
args.insert(CropImageGeometry::k_SelectedImageGeometry_Key, std::make_any<DataPath>(k_ImageGeomPath));
args.insert(CropImageGeometry::k_CreatedImageGeometry_Key, std::make_any<DataPath>(k_NewImageGeomPath));
args.insert(CropImageGeometry::k_RenumberFeatures_Key, std::make_any<bool>(k_RenumberFeatures));
args.insert(CropImageGeometry::k_CellFeatureIdsArrayPath_Key, std::make_any<DataPath>(k_FeatureIdsPath));
args.insert(CropImageGeometry::k_FeatureAttributeMatrix_Key, std::make_any<DataPath>(k_CellFeatureAMPath));
args.insert(CropImageGeometry::k_RemoveOriginalGeometry_Key, std::make_any<bool>(false));

// const auto oldDimensions = dataStructure.getDataRefAs<ImageGeom>(k_ImageGeomPath).getDimensions();
// const auto oldOrigin = dataStructure.getDataRefAs<ImageGeom>(k_ImageGeomPath).getOrigin();
// const auto oldSpacing = dataStructure.getDataRefAs<ImageGeom>(k_ImageGeomPath).getSpacing();
// Preflight the filter and check result
auto preflightResult = filter.preflight(dataStructure, args);
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions);

auto result = filter.execute(dataStructure, args);
SIMPLNX_RESULT_REQUIRE_VALID(result.result);

auto& newImageGeom = dataStructure.getDataRefAs<ImageGeom>(k_NewImageGeomPath);
auto newDimensions = newImageGeom.getDimensions();

for(usize i = 0; i < 3; i++)
{
REQUIRE(newDimensions[i] == (k_MaxVector[i] - k_MinVector[i] + 1));
}

DataPath exemplarGeoPath({"6_5_Cropped_ImageGeom"});
DataPath exemplarCellDataPath = exemplarGeoPath.createChildPath(Constants::k_CellData);
DataPath exemplarCellFeatureDataPath = exemplarGeoPath.createChildPath(Constants::k_CellFeatureData);

// check the data arrays
const auto exemplarCellDataArrays = GetAllChildArrayDataPaths(dataStructure, exemplarCellDataPath).value();
const auto calculatedCellDataArrays = GetAllChildArrayDataPaths(dataStructure, destCellDataPath).value();
for(usize i = 0; i < exemplarCellDataArrays.size(); ++i)
{
const IDataArray& exemplarArray = dataStructure.getDataRefAs<IDataArray>(exemplarCellDataArrays[i]);
const IDataArray& calculatedArray = dataStructure.getDataRefAs<IDataArray>(calculatedCellDataArrays[i]);
::ExecuteDataFunction(CompareDataArrayFunctor{}, exemplarArray.getDataType(), exemplarArray, calculatedArray);
}

const auto exemplarFeatureDataArrays = GetAllChildArrayDataPaths(dataStructure, exemplarCellFeatureDataPath).value();
const auto calculatedFeatureDataArrays = GetAllChildArrayDataPaths(dataStructure, k_DestCellFeatureDataPath).value();
for(usize i = 0; i < exemplarFeatureDataArrays.size(); ++i)
{
const IDataArray& exemplarArray = dataStructure.getDataRefAs<IDataArray>(exemplarFeatureDataArrays[i]);
const IDataArray& calculatedArray = dataStructure.getDataRefAs<IDataArray>(calculatedFeatureDataArrays[i]);
ExecuteDataFunction(CompareDataArrayFunctor{}, exemplarArray.getDataType(), exemplarArray, calculatedArray);
}
}

0 comments on commit 3e63732

Please sign in to comment.