Skip to content

Commit

Permalink
ENH: Allow Crop Image Geometry to only crop a specific dimension (#1021)
Browse files Browse the repository at this point in the history
Signed-off-by: Joey Kleingers <[email protected]>
Signed-off-by: Michael Jackson <[email protected]>
Co-authored-by: Michael Jackson <[email protected]>
  • Loading branch information
joeykleingers and imikejackson authored Jul 18, 2024
1 parent b9d4744 commit c3be139
Show file tree
Hide file tree
Showing 9 changed files with 1,076 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/Plugins/SimplnxCore/docs/CropImageGeometryFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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.

It is possible to also crop specific dimensions of the **Image Geometry** by toggling **Crop X Dimension**, **Crop Y Dimension**, and **Crop Z Dimension** ON and OFF.

## WARNING: NeighborList Removal

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ Parameters CropImageGeometryFilter::parameters() const
params.insertSeparator(Parameters::Separator{"Input Parameter(s)"});
params.insertLinkableParameter(
std::make_unique<BoolParameter>(k_UsePhysicalBounds_Key, "Use Physical Units For Bounds", "If true define physical coordinates for bounds, If false define voxel indices for bounds", false));
params.insert(std::make_unique<BoolParameter>(k_CropXDim_Key, "Crop X Dimension", "Enable cropping in the X dimension.", true));
params.insert(std::make_unique<BoolParameter>(k_CropYDim_Key, "Crop Y Dimension", "Enable cropping in the Y dimension.", true));
params.insert(std::make_unique<BoolParameter>(k_CropZDim_Key, "Crop Z Dimension", "Enable cropping in the Z dimension.", true));
params.insert(std::make_unique<VectorUInt64Parameter>(k_MinVoxel_Key, "Min Voxel", "Lower bound of voxels of the volume to crop out", std::vector<uint64>{0, 0, 0},
std::vector<std::string>{"X (Column)", "Y (Row)", "Z (Plane)"}));
params.insert(std::make_unique<VectorUInt64Parameter>(k_MaxVoxel_Key, "Max Voxel [Inclusive]", "Upper bound in voxels of the volume to crop out", std::vector<uint64>{0, 0, 0},
Expand Down Expand Up @@ -240,6 +243,11 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct
auto cellFeatureAmPath = filterArgs.value<DataPath>(k_FeatureAttributeMatrixPath_Key);
auto pRemoveOriginalGeometry = filterArgs.value<bool>(k_RemoveOriginalGeometry_Key);
auto pUsePhysicalBounds = filterArgs.value<bool>(k_UsePhysicalBounds_Key);
auto pCropXDim = filterArgs.value<BoolParameter::ValueType>(k_CropXDim_Key);
auto pCropYDim = filterArgs.value<BoolParameter::ValueType>(k_CropYDim_Key);
auto pCropZDim = filterArgs.value<BoolParameter::ValueType>(k_CropZDim_Key);

auto& srcImageGeom = dataStructure.getDataRefAs<ImageGeom>(srcImagePath);

uint64& xMin = s_HeaderCache[m_InstanceId].xMin;
uint64& xMax = s_HeaderCache[m_InstanceId].xMax;
Expand All @@ -248,30 +256,34 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct
uint64& zMax = s_HeaderCache[m_InstanceId].zMax;
uint64& zMin = s_HeaderCache[m_InstanceId].zMin;

xMin = minVoxels[0];
xMax = maxVoxels[0];
yMax = maxVoxels[1];
yMin = minVoxels[1];
zMax = maxVoxels[2];
zMin = minVoxels[2];
if(!pCropXDim && !pCropYDim && !pCropZDim)
{
return {MakeErrorResult<OutputActions>(-4010, "At least one dimension must be selected to crop!")};
}

nx::core::Result<OutputActions> resultOutputActions;
xMin = pCropXDim ? minVoxels[0] : 0;
xMax = pCropXDim ? maxVoxels[0] : srcImageGeom.getNumXCells() - 1;
yMin = pCropYDim ? minVoxels[1] : 0;
yMax = pCropYDim ? maxVoxels[1] : srcImageGeom.getNumYCells() - 1;
zMin = pCropZDim ? minVoxels[2] : 0;
zMax = pCropZDim ? maxVoxels[2] : srcImageGeom.getNumZCells() - 1;

nx::core::Result<OutputActions> resultOutputActions;
std::vector<PreflightValue> preflightUpdatedValues;

if(!pUsePhysicalBounds)
{
if(xMax < xMin)
if(pCropXDim && xMax < xMin)
{
const std::string errMsg = fmt::format("X Max (%1) less than X Min (%2)", xMax, xMin);
const std::string errMsg = fmt::format("X Max ({}) less than X Min ({})", xMax, xMin);
return {MakeErrorResult<OutputActions>(-5550, errMsg)};
}
if(yMax < yMin)
if(pCropYDim && yMax < yMin)
{
const std::string errMsg = fmt::format("Y Max ({}) less than Y Min ({})", yMax, yMin);
return {MakeErrorResult<OutputActions>(-5551, errMsg)};
}
if(zMax < zMin)
if(pCropZDim && zMax < zMin)
{
const std::string errMsg = fmt::format("Z Max ({}) less than Z Min ({})", zMax, zMin);
return {MakeErrorResult<OutputActions>(-5552, errMsg)};
Expand All @@ -290,7 +302,20 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct
auto min = filterArgs.value<VectorFloat64Parameter::ValueType>(k_MinCoord_Key);

// Validate basic information about the coordinates
if(max == min)
bool equalCoords = true;
if(pCropXDim && min[0] != max[0])
{
equalCoords = false;
}
if(pCropYDim && min[1] != max[1])
{
equalCoords = false;
}
if(pCropZDim && min[2] != max[2])
{
equalCoords = false;
}
if(equalCoords)
{
const std::string errMsg = "All minimum and maximum values are equal. The cropped region would be a ZERO volume. Please change the maximum values to be larger than the minimum values.";
return {MakeErrorResult<OutputActions>(-50556, errMsg)};
Expand All @@ -301,38 +326,39 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct
const Point3Df& maxPoint = bounds.getMaxPoint();

std::vector<std::string> errLabels = {"X", "Y", "Z"};
std::vector<bool> dimEnabled = {pCropXDim, pCropYDim, pCropZDim};
for(uint8 i = 0; i < 3; i++)
{
if(max[i] < min[i])
if(dimEnabled[i] && max[i] < min[i])
{
const std::string errMsg =
fmt::format("The max value {} ({}) is lower then the min value {} ({}). Please ensure the maximum value is greater than the minimum value.", errLabels[i], max[i], errLabels[i], min[i]);
return {MakeErrorResult<OutputActions>(-50559, errMsg)};
}

if(max[i] < minPoint[i] && min[i] < minPoint[i])
if(dimEnabled[i] && max[i] < minPoint[i] && min[i] < minPoint[i])
{
const std::string errMsg = fmt::format(
"Both the Minimum and Maximum {} crop values are less than the minimum {} bounds ({}). Please ensure at least part of the crop is within the bounding box of min=[{}] and max=[{}]",
errLabels[i], errLabels[i], maxPoint[i], fmt::join(minPoint.begin(), minPoint.end(), ","), fmt::join(maxPoint.begin(), maxPoint.end(), ","));
return {MakeErrorResult<OutputActions>(-50560, errMsg)};
}

if(max[i] > maxPoint[i] && min[i] > maxPoint[i])
if(dimEnabled[i] && max[i] > maxPoint[i] && min[i] > maxPoint[i])
{
const std::string errMsg = fmt::format(
"Both the Minimum and Maximum {} crop values are greater than the maximum {} bounds ({}). Please ensure at least part of the crop is within the bounding box of min=[{}] and max=[{}]",
errLabels[i], errLabels[i], maxPoint[i], fmt::join(minPoint.begin(), minPoint.end(), ","), fmt::join(maxPoint.begin(), maxPoint.end(), ","));
return {MakeErrorResult<OutputActions>(-50560, errMsg)};
}

if(min[i] < minPoint[i])
if(dimEnabled[i] && min[i] < minPoint[i])
{
resultOutputActions.m_Warnings.push_back(
Warning({-50503, fmt::format("The {} minimum crop value {} is less than the {} minimum bounds value of {}. The filter will use the minimum bounds value instead.", errLabels[i], min[i],
errLabels[i], minPoint[i])}));
}
if(max[i] > maxPoint[i])
if(dimEnabled[i] && max[i] > maxPoint[i])
{
resultOutputActions.m_Warnings.push_back(
Warning({-50503, fmt::format("The {} maximum crop value {} is greater than the {} maximum bounds value of {}. The filter will use the maximum bounds value instead.", errLabels[i], max[i],
Expand All @@ -342,28 +368,28 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct

// if we have made it here the coordinate bounds are valid so figure out and assign index values to xMax, xMin, ...
auto srcSpacing = srcImageGeomPtr->getSpacing();
xMin = (min[0] < srcOrigin[0]) ? 0 : static_cast<uint64>(std::floor((min[0] - srcOrigin[0]) / spacing[0]));
yMin = (min[1] < srcOrigin[1]) ? 0 : static_cast<uint64>(std::floor((min[1] - srcOrigin[1]) / spacing[1]));
zMin = (min[2] < srcOrigin[2]) ? 0 : static_cast<uint64>(std::floor((min[2] - srcOrigin[2]) / spacing[2]));
xMin = (pCropXDim && min[0] >= srcOrigin[0]) ? static_cast<uint64>(std::floor((min[0] - srcOrigin[0]) / spacing[0])) : 0;
yMin = (pCropYDim && min[1] >= srcOrigin[1]) ? static_cast<uint64>(std::floor((min[1] - srcOrigin[1]) / spacing[1])) : 0;
zMin = (pCropZDim && min[2] >= srcOrigin[2]) ? static_cast<uint64>(std::floor((min[2] - srcOrigin[2]) / spacing[2])) : 0;

xMax = (max[0] > maxPoint[0]) ? srcImageGeomPtr->getNumXCells() - 1 : static_cast<uint64>(std::floor((max[0] - srcOrigin[0]) / spacing[0]));
yMax = (max[1] > maxPoint[1]) ? srcImageGeomPtr->getNumYCells() - 1 : static_cast<uint64>(std::floor((max[1] - srcOrigin[1]) / spacing[1]));
zMax = (max[2] > maxPoint[2]) ? srcImageGeomPtr->getNumZCells() - 1 : static_cast<uint64>(std::floor((max[2] - srcOrigin[2]) / spacing[2]));
xMax = (pCropXDim && max[0] <= maxPoint[0]) ? static_cast<uint64>(std::floor((max[0] - srcOrigin[0]) / spacing[0])) : srcImageGeomPtr->getNumXCells() - 1;
yMax = (pCropYDim && max[1] <= maxPoint[1]) ? static_cast<uint64>(std::floor((max[1] - srcOrigin[1]) / spacing[1])) : srcImageGeomPtr->getNumYCells() - 1;
zMax = (pCropZDim && max[2] <= maxPoint[2]) ? static_cast<uint64>(std::floor((max[2] - srcOrigin[2]) / spacing[2])) : srcImageGeomPtr->getNumZCells() - 1;
}

if(xMax > srcImageGeomPtr->getNumXCells() - 1)
if(pCropXDim && xMax > srcImageGeomPtr->getNumXCells() - 1)
{
const std::string errMsg = fmt::format("The X Max ({}) is greater than the Image Geometry X extent ({})", xMax, srcImageGeomPtr->getNumXCells() - 1);
return {MakeErrorResult<OutputActions>(-5553, errMsg)};
}

if(yMax > srcImageGeomPtr->getNumYCells() - 1)
if(pCropYDim && yMax > srcImageGeomPtr->getNumYCells() - 1)
{
const std::string errMsg = fmt::format("The Y Max ({}) is greater than the Image Geometry Y extent ({})", yMax, srcImageGeomPtr->getNumYCells() - 1);
return {MakeErrorResult<OutputActions>(-5554, errMsg)};
}

if(zMax > srcImageGeomPtr->getNumZCells() - 1)
if(pCropZDim && zMax > srcImageGeomPtr->getNumZCells() - 1)
{
const std::string errMsg = fmt::format("The Z Max ({}) is greater than the Image Geometry Z extent ({})", zMax, srcImageGeomPtr->getNumZCells() - 1);
return {MakeErrorResult<OutputActions>(-5555, errMsg)};
Expand Down Expand Up @@ -441,6 +467,12 @@ IFilter::PreflightResult CropImageGeometryFilter::preflightImpl(const DataStruct
}

// Store the preflight updated value(s) into the preflightUpdatedValues vector using the appropriate methods.
std::string cropOptionsStr = "This filter will crop the image in the following dimension(s): ";
cropOptionsStr.append(pCropXDim ? "X" : "");
cropOptionsStr.append(pCropYDim ? "Y" : "");
cropOptionsStr.append(pCropZDim ? "Z" : "");
preflightUpdatedValues.push_back({"Crop Dimensions", cropOptionsStr});

preflightUpdatedValues.push_back({"Input Geometry Info", nx::core::GeometryHelpers::Description::GenerateGeometryInfo(srcImageGeomPtr->getDimensions(), srcImageGeomPtr->getSpacing(),
srcImageGeomPtr->getOrigin(), srcImageGeomPtr->getUnits())});
preflightUpdatedValues.push_back(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class SIMPLNXCORE_EXPORT CropImageGeometryFilter : public IFilter

// Parameter Keys
static inline constexpr StringLiteral k_UsePhysicalBounds_Key = "use_physical_bounds";
static inline constexpr StringLiteral k_CropXDim_Key = "crop_x_dim";
static inline constexpr StringLiteral k_CropYDim_Key = "crop_y_dim";
static inline constexpr StringLiteral k_CropZDim_Key = "crop_z_dim";
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";
Expand Down
2 changes: 1 addition & 1 deletion src/Plugins/SimplnxCore/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ if(EXISTS "${DREAM3D_DATA_DIR}" AND SIMPLNX_DOWNLOAD_TEST_FILES)
file(MAKE_DIRECTORY "${DREAM3D_DATA_DIR}/TestFiles/")
endif()

download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_5_test_data_1.tar.gz SHA512 6e21118a882c6a0cc54341eec8928b89ee84ac3a41b1d5b534193f4fabcb49c363db22028055622ad777787be0163cf5525e6c548c11c2c369748feb23031651)
download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_5_test_data_1_v2.tar.gz SHA512 585b51ba1da9784a204fe88073ca562b45afd7007cf451b0193079b885c4b4caff7cf21b13e016433b84155546ac0f73f003a8b8ebb1c58360b2c56de3027d6c)
download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_5_Goldfeather.tar.gz SHA512 3ad4a8f2bd578d81ce1ca67213ec846b7de4394e0c0e7796077bac51f7b58ae36340e2abd315665d9ee5fe745315458d5c1559766765e20dbe2c35eb693e8e26)
download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_6_align_sections_feature_centroid.tar.gz SHA512 e79a4c8e59bc856d40e91daf4cdce8b82c2e5ccfa6de51e23a0b8c6628282b03701bd5b5d7ddde76c4378142e3d9fe7c1cd6db261360c91751cf7c63747b054b)
download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_6_align_sections_feature_centroids.tar.gz SHA512 96ca08eca2ac3839a1d7ded6287c138cac49140d1b80713d747f6143a90b75335b351cd291e2e438621cdfb32bcb5f0ec5bf2f59ec0e0d61250e423f289bdad8)
Expand Down
4 changes: 2 additions & 2 deletions src/Plugins/SimplnxCore/test/ComputeSurfaceFeaturesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ void test_impl(const std::vector<uint64>& geometryDims, const std::string& featu
TEST_CASE("SimplnxCore::ComputeSurfaceFeaturesFilter: 3D", "[SimplnxCore][ComputeSurfaceFeaturesFilter]")
{
Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true);
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");
const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "6_5_test_data_1_v2.tar.gz", "6_5_test_data_1_v2");

// 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));
auto baseDataFilePath = fs::path(fmt::format("{}/6_5_test_data_1_v2/6_5_test_data_1_v2.dream3d", nx::core::unit_test::k_TestFilesDir));
DataStructure dataStructure = UnitTest::LoadDataStructure(baseDataFilePath);

const std::string k_SurfaceFeaturesExemplar("SurfaceFeatures");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ template <typename T>
void testElementArray(const DataPath& cellDataPath)
{
Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true);
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");
const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "6_5_test_data_1_v2.tar.gz", "6_5_test_data_1_v2");

// 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));
auto baseDataFilePath = fs::path(fmt::format("{}/6_5_test_data_1_v2/6_5_test_data_1_v2.dream3d", nx::core::unit_test::k_TestFilesDir));
DataStructure dataStructure = UnitTest::LoadDataStructure(baseDataFilePath);
DataPath smallIn100Group({nx::core::Constants::k_DataContainer});

Expand Down
Loading

0 comments on commit c3be139

Please sign in to comment.