Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 25 additions & 2 deletions src/Plugins/ITKImageProcessing/docs/ITKImageReaderFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,31 @@ The following image types are supported:
- NRRD
- MHA

The user is required to set the origin and spacing (Length units per pixel) for the imported image. The default values are an origin
of (0,0,0) and a spacing of (1,1,1). Any values stored in the actual input file **will be overridden** by the values from the user interface
### Origin & Spacing Options

The user can optionally override the origin and spacing (length units per pixel) for the imported image. 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.

The **Origin & Spacing Processing Order** parameter controls when origin and spacing overrides are applied:
- **Preprocessed**: Origin and spacing are applied before any cropping operations
- **Postprocessed**: Origin and spacing are applied after cropping operations

### Data Type Conversion

The user can optionally convert the image data to a different data type by enabling the "Set Image Data Type" option. Supported output data types include:
- uint8
- uint16
- uint32

### Cropping Options

The user can crop the incoming 2D image using the Cropping Options section. The cropping type options are:
- **No Cropping**: Read the full image into an image geometry
- **Voxel Subvolume**: Crop the image using voxel (pixel) coordinates
- **Physical Subvolume**: Crop the image using physical coordinates

Both subvolume cropping types have checkboxes to turn on/off cropping in the X and Y 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

% Auto generated parameter table will be inserted here

Expand Down
52 changes: 49 additions & 3 deletions src/Plugins/ITKImageProcessing/docs/ITKImportImageStackFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,57 @@ ITKImageProcessing (ITKImageProcessing)

Read in a stack of 2D images and stack the images into a 3D Volume using the ITK library. Supports most common scalar pixel types and the many file formats supported by ITK.

The filter will create a new Image Geometry. The user can specify a value for the origin and the spacing if the defaults are not appropriate. The default value for the origin will be at (0, 0, 0) and the default spacing value will be (1.0, 1.0, 1.0). If the user needs to have the create Image Geometry located in a different location in the global reference frame, the user can change the default origin value. The "origin" of the image is at a normal Cartesian style origin.
### Processing Order

The user can decide to scale the images as they are being read in by turning on the Scale Images option, and setting a scale value. A scale value of 10.0 resamples the images in the stack to one-tenth the number of pixels, a scale value of 200.0 resamples the images in the stack to double the number of pixels. The default scale value is 100.0.
Image operations are applied in the following order:
1. Read image
2. Crop image
3. Resample image
4. Convert to grayscale
5. Flip image in X or Y

The user can also decide to crop the incoming image geometry using the Cropping Options section. The cropping type options 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.
### Origin & Spacing Options

The filter will create a new Image Geometry. The user can optionally override the origin and spacing for the created geometry. The default values from the input files will be used unless the user explicitly enables the "Set Origin" and/or "Set Spacing" options. If the user needs to have the created Image Geometry located in a different location in the global reference frame, the user can change the default origin value. The "origin" of the image is at a normal Cartesian style origin.

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.

The **Origin & Spacing Processing Order** parameter controls when origin and spacing overrides are applied relative to the Processing Order above:

- **Preprocessed**: Origin and spacing are applied before the image cropping step, so the order becomes:
1. Read image
2. Set origin and spacing values
3. Crop image
4. Resample image
5. Convert to grayscale
6. Flip image in X or Y
- **Postprocessed**: Origin and spacing are applied after the image flipping step, so the order becomes:
1. Read image
2. Crop image
3. Resample image
4. Convert to grayscale
5. Flip image in X or Y
6. Set origin and spacing values

### Resampling Options

The user can decide to scale the images as they are being read in by turning on the Scale Images option, and setting a scale value. A scale value of 10.0 resamples the images in the stack to one-tenth the number of pixels, a scale value of 200.0 resamples the images in the stack to double the number of pixels. The default scale value is 100.0.

### Data Type Conversion

The user can optionally convert the image data to a different data type by enabling the "Set Image Data Type" option. Supported output data types include:
- uint8
- uint16
- uint32

### Cropping Options

The user can crop the incoming image geometry using the Cropping Options section. The cropping type options are:
- **No Cropping**: Read the full volume into an image geometry
- **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. For example, if **Physical Subvolume** is selected, **Crop Y Dimension** is enabled, and **Crop X Dimension** and **Crop Z Dimension** are disabled, then the incoming volume will be cropped in the Y dimension only and the cropping bounds will be in physical units.

## Image Operations

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
#include "ReadImageUtils.hpp"

#include "simplnx/Common/TypesUtility.hpp"
#include "simplnx/Core/Application.hpp"
#include "simplnx/Filter/Actions/DeleteDataAction.hpp"
#include "simplnx/Filter/FilterHandle.hpp"
#include "simplnx/Parameters/VectorParameter.hpp"

using namespace nx::core;

namespace
{
const Uuid k_SimplnxCorePluginId = *Uuid::FromString("05cc618b-781f-4ac0-b9ac-43f26ce1854f");
const Uuid k_CropImageGeomFilterId = *Uuid::FromString("e6476737-4aa7-48ba-a702-3dfab82c96e2");
const FilterHandle k_CropImageGeomFilterHandle(k_CropImageGeomFilterId, k_SimplnxCorePluginId);

void ApplyImageOriginAndSpacingOverrides(const cxItkImageReaderFilter::ImageReaderOptions& imageReaderOptions, FloatVec3& origin, FloatVec3& spacing, const std::vector<usize>& dims)
{
if(imageReaderOptions.OverrideSpacing)
{
spacing = imageReaderOptions.Spacing;
}

if(imageReaderOptions.OverrideOrigin)
{
origin = imageReaderOptions.Origin;
if(imageReaderOptions.OriginAtCenterOfGeometry)
{
DataStructure junk;
ImageGeom* imageGeomPtr = ImageGeom::Create(junk, "Junk");
imageGeomPtr->setDimensions(dims);
imageGeomPtr->setOrigin(origin);
imageGeomPtr->setSpacing(spacing);
BoundingBox3Df bounds = imageGeomPtr->getBoundingBoxf();
FloatVec3 centerPoint(bounds.center());
origin = origin - (centerPoint - origin);
}
}
}
} // namespace

namespace cxItkImageReaderFilter
{

Expand Down Expand Up @@ -46,28 +80,85 @@ Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath i
spacing[i] = static_cast<float32>(imageIO->GetSpacing(i));
}

if(imageReaderOptions.OverrideSpacing)
if(imageReaderOptions.ProcessingTiming == OriginSpacingProcessingTiming::Preprocessed)
{
spacing = imageReaderOptions.Spacing;
ApplyImageOriginAndSpacingOverrides(imageReaderOptions, origin, spacing, dims);
}

if(imageReaderOptions.OverrideOrigin)
bool cropImage = imageReaderOptions.CroppingOptions.type != CropGeometryParameter::CropValues::TypeEnum::NoCropping;
bool crop2dImage = cropImage && (imageReaderOptions.CroppingOptions.cropX || imageReaderOptions.CroppingOptions.cropY);
if(crop2dImage)
{
DataStructure junk;
ImageGeom* imageGeomPtr = ImageGeom::Create(junk, "Junk");
FilterList* filterListPtr = Application::Instance()->getFilterList();
if(!filterListPtr->containsPlugin(k_SimplnxCorePluginId))
{
IFilter::PreflightResult errorResult = IFilter::MakePreflightErrorResult(-18542, "The plugin SimplnxCore was not instantiated in this instance, so image cropping is not available.");
return errorResult.outputActions;
}

origin = imageReaderOptions.Origin;
std::unique_ptr<IFilter> cropImageGeomFilter = filterListPtr->createFilter(k_CropImageGeomFilterHandle);
if(nullptr == cropImageGeomFilter)
{
IFilter::PreflightResult errorResult = IFilter::MakePreflightErrorResult(-18543, "Unable to create an instance of the crop image geometry filter, so image cropping is not available.");
return errorResult.outputActions;
}

imageGeomPtr->setDimensions(dims);
imageGeomPtr->setOrigin(origin);
imageGeomPtr->setSpacing(spacing);
DataStructure tmpDs;
DataPath tmpGeomPath = DataPath({"tmpGeom"});
ImageGeom* tmpGeom = ImageGeom::Create(tmpDs, tmpGeomPath.getTargetName());
AttributeMatrix* am = AttributeMatrix::Create(tmpDs, "CellData", std::vector<usize>(dims.crbegin(), dims.crend()), tmpGeom->getId());
tmpGeom->setCellData(*am);
tmpGeom->setDimensions(dims);
tmpGeom->setOrigin(origin);
tmpGeom->setSpacing(spacing);

Arguments cropImageGeomArgs;
cropImageGeomArgs.insertOrAssign("input_image_geometry_path", std::make_any<DataPath>(tmpGeomPath));
cropImageGeomArgs.insertOrAssign("use_physical_bounds", std::make_any<bool>(imageReaderOptions.CroppingOptions.type == CropGeometryParameter::CropValues::TypeEnum::PhysicalSubvolume));
cropImageGeomArgs.insertOrAssign("crop_x_dim", std::make_any<bool>(imageReaderOptions.CroppingOptions.cropX));
cropImageGeomArgs.insertOrAssign("crop_y_dim", std::make_any<bool>(imageReaderOptions.CroppingOptions.cropY));
cropImageGeomArgs.insertOrAssign("crop_z_dim", std::make_any<bool>(false)); // Do this because we're cropping a 2D image
if(imageReaderOptions.CroppingOptions.type == CropGeometryParameter::CropValues::TypeEnum::VoxelSubvolume)
{
cropImageGeomArgs.insertOrAssign("min_voxel", std::make_any<VectorUInt64Parameter::ValueType>({static_cast<uint64>(imageReaderOptions.CroppingOptions.xBoundVoxels[0]),
static_cast<uint64>(imageReaderOptions.CroppingOptions.yBoundVoxels[0]),
static_cast<uint64>(imageReaderOptions.CroppingOptions.zBoundVoxels[0])}));
cropImageGeomArgs.insertOrAssign("max_voxel", std::make_any<VectorUInt64Parameter::ValueType>({static_cast<uint64>(imageReaderOptions.CroppingOptions.xBoundVoxels[1]),
static_cast<uint64>(imageReaderOptions.CroppingOptions.yBoundVoxels[1]),
static_cast<uint64>(imageReaderOptions.CroppingOptions.zBoundVoxels[1])}));
}
else
{
cropImageGeomArgs.insertOrAssign("min_coord", std::make_any<VectorFloat64Parameter::ValueType>({static_cast<float64>(imageReaderOptions.CroppingOptions.xBoundPhysical[0]),
static_cast<float64>(imageReaderOptions.CroppingOptions.yBoundPhysical[0]),
static_cast<float64>(imageReaderOptions.CroppingOptions.zBoundPhysical[0])}));
cropImageGeomArgs.insertOrAssign("max_coord", std::make_any<VectorFloat64Parameter::ValueType>({static_cast<float64>(imageReaderOptions.CroppingOptions.xBoundPhysical[1]),
static_cast<float64>(imageReaderOptions.CroppingOptions.yBoundPhysical[1]),
static_cast<float64>(imageReaderOptions.CroppingOptions.zBoundPhysical[1])}));
}
cropImageGeomArgs.insertOrAssign("remove_original_geometry", std::make_any<bool>(true));

IFilter::PreflightResult cropImageResult = cropImageGeomFilter->preflight(tmpDs, cropImageGeomArgs);
if(cropImageResult.outputActions.invalid())
{
return cropImageResult.outputActions;
}

if(imageReaderOptions.OriginAtCenterOfGeometry)
Result<> actionsResult = cropImageResult.outputActions.value().applyAll(tmpDs, IDataAction::Mode::Preflight);
if(actionsResult.invalid())
{
BoundingBox3Df bounds = imageGeomPtr->getBoundingBoxf();
FloatVec3 centerPoint(bounds.center());
origin = origin - (centerPoint - origin);
return {ConvertResultTo<OutputActions>(std::move(actionsResult), {})};
}

auto croppedGeom = tmpDs.getDataRefAs<ImageGeom>(tmpGeomPath);
dims = croppedGeom.getDimensions().toContainer<std::vector<usize>>();
spacing = croppedGeom.getSpacing().toContainer<std::vector<float32>>();
origin = croppedGeom.getOrigin().toContainer<std::vector<float32>>();
}

if(imageReaderOptions.ProcessingTiming == OriginSpacingProcessingTiming::Postprocessed)
{
ApplyImageOriginAndSpacingOverrides(imageReaderOptions, origin, spacing, dims);
}

uint32 nComponents = imageIO->GetNumberOfComponents();
Expand Down
Loading
Loading