Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
02e2b75
BUG: Fix DataPath::attemptRename for prefixes of different depth
imikejackson Oct 5, 2026
472a3a0
BUG: Fix child DataPath renaming by substring in 7 places
imikejackson Oct 5, 2026
050b3ca
BUG: Fix physical crop at the Image Geometry upper bound
imikejackson Oct 5, 2026
057cb38
BUG: Fix ReadImage physical crop below the origin
imikejackson Oct 5, 2026
a9f8a5d
BUG: Fix ReadImageStack Z crop and grayscale input check
imikejackson Oct 5, 2026
e526e01
BUG: Fix ComputeFeatureBounds edge geometry and bounds buffer size
imikejackson Oct 5, 2026
063b88b
BUG: Fix ComputeLargestCrossSections Feature Ids location check
imikejackson Oct 5, 2026
6bd77b6
BUG: Fix Silhouette sparse ids, mask check and cluster 0 mean
imikejackson Oct 5, 2026
741a457
BUG: Fix RegularGridSampleSurfaceMesh custom output type and -1 labels
imikejackson Oct 5, 2026
f77d7dc
BUG: Fix QuickSurfaceMesh feature DataArray tuple check
imikejackson Oct 5, 2026
922437b
BUG: Fix WriteASCIIData overwrite of DataArrays with the same name
imikejackson Oct 5, 2026
96961e3
BUG: Fix SurfaceNets input checks and feature DataArray bound
imikejackson Oct 5, 2026
d17b0e9
BUG: Fix CropEdgeGeometry shared clip vertices and String/NeighborLis…
imikejackson Oct 5, 2026
edd7173
BUG: Accept a physical Z crop maximum at the ReadImageStack upper bound
imikejackson Oct 9, 2026
2822a87
BUG: Check cell DataArray tuple counts instead of their parent
imikejackson Oct 9, 2026
1c992e6
TEST: Check CopyDataObject created paths when Use New Parent is on
imikejackson Oct 9, 2026
56b940a
STY: Move ReadImageStackCropping helpers out of an anonymous namespace
imikejackson Oct 9, 2026
8ad6cf4
STY: Remove a redundant comparison in the RegularGridSampleSurfaceMes…
imikejackson Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/Plugins/ImageProcessing/docs/ReadImageFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The origin/spacing/data-type override and cropping options below apply to all ba

The user can optionally override the origin and spacing for the imported image. *Spacing* is the physical size of each pixel and *Origin* is the coordinate of the lower-left corner of the geometry; both are expressed in the same physical length units (for example microns). The default values from the input file will be used unless the user explicitly enables the "Set Origin" and/or "Set Spacing" options.

When setting a custom origin, the user can choose whether to place the origin at the corner of the geometry (default) or at the center of the geometry by enabling the "Put Input Origin at the Center of Geometry" option.
When **Set Origin** is enabled, the **Center Geometry on (0, 0, 0)** option controls centering. When on, the origin is set so that the center of the Image Geometry is at (0, 0, 0). The Origin value is not used.

### Origin & Spacing Processing

Expand Down Expand Up @@ -70,6 +70,10 @@ The user can crop the incoming image using the Cropping Options section. The cro

Both subvolume cropping types have checkboxes to turn on/off cropping in the X, Y, and Z dimensions. For example, if **Physical Subvolume** is selected and only **Crop Y Dimension** is enabled, the image will be cropped in the Y dimension only using physical coordinate bounds. Z cropping only applies to a genuine multi-page/volume input (`Z > 1`); for a 2D image the Z crop flag is ignored.

A physical crop maximum equal to `origin + dimensions * spacing` on an axis includes the final cell on that axis. The maximum cell index is clamped to `dimensions - 1`. Physical coordinates use the Image Geometry's length units. A maximum beyond the upper bound still produces a warning and is clamped when the requested range overlaps the geometry.

A physical crop minimum below the Image Geometry's origin is clamped to the first cell when the requested range overlaps the geometry. Preflight reports warning `-50503`, and execution reads the clamped region.

## Required Input Sources

None — this filter reads directly from a single image file on disk.
Expand Down
4 changes: 2 additions & 2 deletions src/Plugins/ImageProcessing/docs/ReadImageStackFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The user can optionally resample each image as it is read in. The *Resample Imag

### Grayscale Conversion

When *Convert To GrayScale* is enabled, RGB image data is converted to a scalar grayscale array using the luminosity algorithm. The luminosity algorithm computes each gray value as a weighted sum of the red, green, and blue channels (gray = wR·R + wG·G + wB·B), where the weights come from the supplied *Color Weighting* values (dimensionless). This produces a perceptually weighted brightness rather than a simple channel average. Only uint8 input data is supported for grayscale conversion.
When *Convert To GrayScale* is enabled, RGB image data is converted to a scalar grayscale array using the luminosity algorithm. The luminosity algorithm computes each gray value as a weighted sum of the red, green, and blue channels (gray = wR·R + wG·G + wB·B), where the weights come from the supplied *Color Weighting* values (dimensionless). This produces a perceptually weighted brightness rather than a simple channel average. Only uint8 input DataArrays with at least 3 components are supported for grayscale conversion. Scalar input is rejected during preflight.

### Output Data Type

Expand All @@ -86,7 +86,7 @@ The user can crop the incoming image geometry using the Cropping Options section
- **Voxel Subvolume**: Read a subvolume into an image geometry using voxel coordinates
- **Physical Subvolume**: Read a subvolume into an image geometry using physical coordinates

Both subvolume cropping types have checkboxes to turn on/off cropping in each of the X, Y, and Z dimensions.
Both subvolume cropping types have checkboxes to turn on/off cropping in each of the X, Y, and Z dimensions. A Z crop moves the Z origin to the first kept slice. A postprocessed origin override replaces this shifted origin after cropping.

### Image Operations

Expand Down
4 changes: 4 additions & 0 deletions src/Plugins/ImageProcessing/docs/ReadMhaFileFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Supported header features:
- `ElementSpacing` (or `ElementSize`) and `Offset` / `Position` / `Origin`.
- 2D images are loaded with `Z = 1`; 3D images load natively.

### Physical Cropping

A physical crop maximum equal to `origin + dimensions * spacing` on an axis includes the final cell on that axis. The maximum cell index is clamped to `dimensions - 1`. Physical coordinates use the Image Geometry's length units. A maximum beyond the upper bound still produces a warning and is clamped when the requested range overlaps the geometry.

### Transformation Matrix

If **Apply Image Transformation To Geometry** is enabled, the header
Expand Down
10 changes: 5 additions & 5 deletions src/Plugins/ImageProcessing/docs/ReadNIfTIFileFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ Two cropping modes are supported:
axis. The filter builds a temporary axis-aligned ImageGeom from the
source file's origin and spacing (the same values the Orientation
section would use) and converts the requested bounds to voxel indices
via `ImageGeom::getIndex()`. Bounds that fall outside the volume
produce a descriptive error rather than silently clamping.
using the Image Geometry's origin and spacing.

A physical crop maximum equal to `origin + dimensions * spacing` on an axis includes the final cell on that axis. The maximum cell index is clamped to `dimensions - 1`. Physical coordinates use the Image Geometry's length units. A maximum beyond the upper bound still produces a warning and is clamped when the requested range overlaps the geometry.

Each axis can be toggled on or off independently via the *Crop X / Y / Z*
flags. An axis whose flag is off is *not* cropped — its full extent from
Expand Down Expand Up @@ -156,9 +157,8 @@ large compressed files read faster.
* Cropping saves memory, not wall-clock read time. A `.nii.gz` file must
still be fully decompressed regardless of how small the cropped region
is; plain `.nii` files are also read linearly in this version.
* Physical-subvolume crop bounds must map to voxels inside the source
volume. Bounds outside the extent produce an error rather than
silently clamping.
* A physical crop maximum at the upper boundary includes the final cell.
A range wholly outside the source volume is rejected.
* `start <= end` is required on each cropped axis; reversed ranges are
rejected at parameter-validation time.

Expand Down
2 changes: 2 additions & 0 deletions src/Plugins/ImageProcessing/docs/ReadZeissTxmFileFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The volume is read into a new **Image Geometry**. The geometry's spacing and ori

The cropping type options in the Cropping Options section are `No Cropping` to read the full volume into an image geometry, `Voxel Subvolume` to read a subvolume into an image geometry using voxel coordinates, and `Physical Subvolume` to read a subvolume into an image geometry using physical coordinates. Both subvolume cropping types have checkboxes to turn on/off cropping in each of the X, Y, and Z dimensions. So for example, if the cropping type `Physical Subvolume` is selected, `Crop Y Dimension` is turned on, and `Crop X Dimension` and `Crop Z Dimension` are turned off, then the incoming volume will be cropped in the Y dimension only and the cropping bounds will be in physical units.

A physical crop maximum equal to `origin + dimensions * spacing` on an axis includes the final cell on that axis. The maximum cell index is clamped to `dimensions - 1`. Physical coordinates use the Image Geometry's length units. A maximum beyond the upper bound still produces a warning and is clamped when the requested range overlaps the geometry.

## WARNING

Be aware of how large of data you are reading into DREAM3D-NX as these files can become quite large and will overwhelm the visualization system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <fmt/format.h>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <limits>
Expand Down Expand Up @@ -258,7 +259,7 @@ Result<> ReadRasterBackend(DataStructure& dataStructure, const ReadImageInputVal
if(croppingOptions.cropX)
{
const float64 xMin = static_cast<float64>(croppingOptions.xBoundPhysical[0]);
const int64 voxelX = static_cast<int64>((xMin - static_cast<float64>(srcOrigin[0])) / static_cast<float64>(srcSpacing[0]));
const int64 voxelX = xMin >= srcOrigin[0] ? static_cast<int64>(std::floor((xMin - static_cast<float64>(srcOrigin[0])) / static_cast<float64>(srcSpacing[0]))) : 0;
if(voxelX < 0 || static_cast<usize>(voxelX) >= stream.srcWidth)
{
return MakeErrorResult(-2002, fmt::format("Physical crop X minimum {} is outside the source image extent [{}, {}) given file origin {} and spacing {}", xMin, srcOrigin[0],
Expand All @@ -269,7 +270,7 @@ Result<> ReadRasterBackend(DataStructure& dataStructure, const ReadImageInputVal
if(croppingOptions.cropY)
{
const float64 yMin = static_cast<float64>(croppingOptions.yBoundPhysical[0]);
const int64 voxelY = static_cast<int64>((yMin - static_cast<float64>(srcOrigin[1])) / static_cast<float64>(srcSpacing[1]));
const int64 voxelY = yMin >= srcOrigin[1] ? static_cast<int64>(std::floor((yMin - static_cast<float64>(srcOrigin[1])) / static_cast<float64>(srcSpacing[1]))) : 0;
if(voxelY < 0 || static_cast<usize>(voxelY) >= stream.srcHeight)
{
return MakeErrorResult(-2003, fmt::format("Physical crop Y minimum {} is outside the source image extent [{}, {}) given file origin {} and spacing {}", yMin, srcOrigin[1],
Expand All @@ -280,7 +281,7 @@ Result<> ReadRasterBackend(DataStructure& dataStructure, const ReadImageInputVal
if(zCropActive)
{
const float64 zMin = static_cast<float64>(croppingOptions.zBoundPhysical[0]);
const int64 voxelZ = static_cast<int64>((zMin - static_cast<float64>(srcOrigin[2])) / static_cast<float64>(srcSpacing[2]));
const int64 voxelZ = zMin >= srcOrigin[2] ? static_cast<int64>(std::floor((zMin - static_cast<float64>(srcOrigin[2])) / static_cast<float64>(srcSpacing[2]))) : 0;
if(voxelZ < 0 || static_cast<usize>(voxelZ) >= metadata.numPages)
{
return MakeErrorResult(-2004, fmt::format("Physical crop Z minimum {} is outside the source image extent [{}, {}) given file origin {} and spacing {}", zMin, srcOrigin[2],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "ReadImageStack.hpp"
#include "ReadImageStackCropping.hpp"

#include "ImageProcessing/Filters/ReadImageFilter.hpp"

Expand Down Expand Up @@ -242,46 +243,18 @@ Result<> ReadImageStackImpl(DataStructure& dataStructure, const ReadImageStackIn
}

DataPath destImageGeomPath = imageGeomPath;
auto& initialImageGeom = dataStructure.getDataRefAs<ImageGeom>(destImageGeomPath);

Result<> outputResult;

usize startSlice = 0;
usize endSlice = files.size() - 1;
if(croppingOptions.cropZ && croppingOptions.type == CropGeometryParameter::ValueType::TypeEnum::VoxelSubvolume)
auto zRangeResult = ComputeCroppedZRange(croppingOptions, files.size(), origin, spacing, shouldChangeOrigin, shouldChangeSpacing, originSpacingProcessing);
if(zRangeResult.invalid())
{
startSlice = static_cast<usize>(croppingOptions.zBoundVoxels[0]);
endSlice = static_cast<usize>(croppingOptions.zBoundVoxels[1]);
}
else if(croppingOptions.cropZ && croppingOptions.type == CropGeometryParameter::ValueType::TypeEnum::PhysicalSubvolume)
{
SizeVec3 destDims = initialImageGeom.getDimensions();
FloatVec3 destOrigin = initialImageGeom.getOrigin();

// Reject physical crop bounds outside the geometry. A full-range fallback
// would silently create the wrong output volume.
std::optional<usize> result = initialImageGeom.getIndex(destOrigin[0], destOrigin[1], croppingOptions.zBoundPhysical[0]);
if(!result.has_value())
{
return MakeErrorResult(-64512, fmt::format("Physical Z crop minimum {} is outside the destination image-geometry Z extent", croppingOptions.zBoundPhysical[0]));
}
startSlice = result.value() / (destDims[0] * destDims[1]);

result = initialImageGeom.getIndex(destOrigin[0], destOrigin[1], croppingOptions.zBoundPhysical[1]);
if(!result.has_value())
{
return MakeErrorResult(-64513, fmt::format("Physical Z crop maximum {} is outside the destination image-geometry Z extent", croppingOptions.zBoundPhysical[1]));
}
endSlice = result.value() / (destDims[0] * destDims[1]);
}

if(startSlice > endSlice || endSlice >= files.size())
{
return MakeErrorResult(-64514, fmt::format("Computed slice range [{}, {}] is invalid for {} input files", startSlice, endSlice, files.size()));
return ConvertResult(std::move(zRangeResult));
}
const auto& zRange = zRangeResult.value();

usize slice = 0;
for(usize i = startSlice; i <= endSlice; i++)
for(usize i = zRange.zMin; i <= zRange.zMax; i++)
{
const std::string& filePath = files[i];
messageHandler.sendInfoMessage(fmt::format("Importing: {}", filePath));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include "simplnx/Common/Result.hpp"
#include "simplnx/Parameters/VectorParameter.hpp"
#include "simplnx/Utilities/ImageIO/ImageStackCropping.hpp"

#include <fmt/format.h>

#include <cmath>

namespace nx::core
{
/**
* @struct ZRange
* @brief Stores inclusive slice indices in the full input file list.
*/
struct ZRange
{
usize zMin;
usize zMax;
};

/**
* @brief Computes the same full-stack slice range for preflight and execution.
* @param croppingOptions Specifies voxel or physical Z bounds.
* @param zDim Gives the number of input files before cropping.
* @param origin Specifies the origin override.
* @param spacing Specifies the spacing override.
* @param shouldChangeOrigin Enables the origin override.
* @param shouldChangeSpacing Enables the spacing override.
* @param originSpacingProcessing Selects whether overrides apply before cropping.
* @return Inclusive slice indices, or an error for an invalid crop range.
*/
inline Result<ZRange> ComputeCroppedZRange(const CropGeometryParameter::CropValues& croppingOptions, usize zDim, const VectorFloat32Parameter::ValueType& origin,
const VectorFloat32Parameter::ValueType& spacing, bool shouldChangeOrigin, bool shouldChangeSpacing, OriginSpacingProcessing originSpacingProcessing)
{
if(zDim == 0)
{
return MakeErrorResult<ZRange>(-64511, "Input file list is empty; nothing to read.");
}

ZRange range{0, zDim - 1};
if(croppingOptions.cropZ && croppingOptions.type == CropGeometryParameter::CropValues::TypeEnum::VoxelSubvolume)
{
range = {static_cast<usize>(croppingOptions.zBoundVoxels[0]), static_cast<usize>(croppingOptions.zBoundVoxels[1])};
}
else if(croppingOptions.cropZ && croppingOptions.type == CropGeometryParameter::CropValues::TypeEnum::PhysicalSubvolume)
{
const auto dimensionResult = ComputeCroppedZDimension(croppingOptions, zDim, origin, spacing, shouldChangeOrigin, shouldChangeSpacing, originSpacingProcessing);
Comment thread
imikejackson marked this conversation as resolved.
if(dimensionResult.invalid())
{
return {nonstd::make_unexpected(dimensionResult.errors())};
}

// Physical bounds refer to the full stack before cropping or deferred spatial overrides.
const float64 originZ = (shouldChangeOrigin && originSpacingProcessing == OriginSpacingProcessing::Preprocessed) ? origin[2] : 0;
const float64 spacingZ = (shouldChangeSpacing && originSpacingProcessing == OriginSpacingProcessing::Preprocessed) ? spacing[2] : 1;
range.zMin = static_cast<usize>(std::floor((croppingOptions.zBoundPhysical[0] - originZ) / spacingZ));
range.zMax = range.zMin + dimensionResult.value() - 1;
}

if(range.zMin > range.zMax || range.zMax >= zDim)
{
return MakeErrorResult<ZRange>(-64514, fmt::format("Computed Z slice range [{}, {}] is invalid for {} input files.", range.zMin, range.zMax, zDim));
}
return {range};
}
} // namespace nx::core
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ Parameters ReadImageFilter::parameters() const

params.insertSeparator(Parameters::Separator{"Origin & Spacing Options"});
params.insertLinkableParameter(std::make_unique<BoolParameter>(k_ChangeOrigin_Key, "Set Origin", "Specifies if the origin should be changed", false));
params.insert(
std::make_unique<BoolParameter>(k_CenterOrigin_Key, "Put Input Origin at the Center of Geometry", "Specifies if the origin should be aligned with the corner (false) or center (true)", false));
params.insert(std::make_unique<BoolParameter>(k_CenterOrigin_Key, "Center Geometry on (0, 0, 0)",
"When on, the origin is set so that the center of the Image Geometry is at (0, 0, 0). The Origin value is not used.", false));
params.insert(std::make_unique<VectorFloat32Parameter>(k_Origin_Key, "Origin (Physical Units)", "Specifies the new origin values in physical units.", std::vector<float32>{0.0F, 0.0F, 0.0F},
std::vector<std::string>{"X", "Y", "Z"}));

Expand Down Expand Up @@ -358,6 +358,8 @@ IFilter::PreflightResult ReadImageFilter::preflightImpl(const DataStructure& dat
{
return cropImageResult;
}
const auto& cropWarnings = cropImageResult.outputActions.warnings();
resultOutputActions.warnings().insert(resultOutputActions.warnings().end(), cropWarnings.begin(), cropWarnings.end());

Result<> actionsResult = cropImageResult.outputActions.value().applyAll(tmpDs, IDataAction::Mode::Preflight);
if(actionsResult.invalid())
Expand Down
Loading
Loading