Skip to content

Commit

Permalink
ENH: ITKImageReader - Allow user to set/override the origin/spacing (#…
Browse files Browse the repository at this point in the history
…834)

Signed-off-by: Michael Jackson <[email protected]>
  • Loading branch information
imikejackson authored Jan 30, 2024
1 parent 19b009c commit 18498ae
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 34 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ endif()
file(TO_CMAKE_PATH "${CMAKE_COMMAND}" CMAKE_COMMAND_NORM)

project(simplnx
VERSION 1.2.1
VERSION 1.2.4
DESCRIPTION "SIMPL Redesign"
HOMEPAGE_URL "https://github.com/bluequartzsoftware/simplnx"
LANGUAGES CXX
Expand Down
2 changes: 1 addition & 1 deletion conda/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% set name = "simplnx" %}
{% set version = "1.2.3" %}
{% set version = "1.2.4" %}

package:
name: {{ name|lower }}
Expand Down
2 changes: 1 addition & 1 deletion conda/recipe.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
context:
version: "1.2.3"
version: "1.2.4"
name: simplnx

package:
Expand Down
14 changes: 13 additions & 1 deletion src/Plugins/ITKImageProcessing/docs/ITKImageReader.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@ ITKImageProcessing (ITKImageProcessing)

## Description

Reads images through ITK
Reads images through the ITK software library [https://www.itk.org](https://www.itk.org)

The following image types are supported:

- PNG
- TIFF
- BMP
- JPG
- 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

% Auto generated parameter table will be inserted here

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace cxItkImageReader
{

//------------------------------------------------------------------------------
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath)
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath, const ImageReaderOptions& imageReaderOptions)
{
OutputActions actions;

Expand All @@ -30,8 +30,8 @@ Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath i
uint32 nDims = imageIO->GetNumberOfDimensions();

std::vector<size_t> dims = {1, 1, 1};
std::vector<float32> origin = {0.0f, 0.0f, 0.0f};
std::vector<float32> spacing = {1.0f, 1.0f, 1.0f};
FloatVec3 origin = {0.0f, 0.0f, 0.0f};
FloatVec3 spacing = {1.0f, 1.0f, 1.0f};

for(uint32 i = 0; i < nDims; i++)
{
Expand All @@ -40,14 +40,39 @@ Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath i
spacing[i] = static_cast<float32>(imageIO->GetSpacing(i));
}

if(imageReaderOptions.OverrideSpacing)
{
spacing = imageReaderOptions.Spacing;
}

if(imageReaderOptions.OverrideOrigin)
{
DataStructure junk;
ImageGeom* imageGeomPtr = ImageGeom::Create(junk, "Junk");

origin = imageReaderOptions.Origin;

imageGeomPtr->setDimensions(dims);
imageGeomPtr->setOrigin(origin);
imageGeomPtr->setSpacing(spacing);

if(imageReaderOptions.OriginAtCenterOfGeometry)
{
BoundingBox3Df bounds = imageGeomPtr->getBoundingBoxf();
FloatVec3 centerPoint(bounds.center());
origin = origin - (centerPoint - origin);
}
}

uint32 nComponents = imageIO->GetNumberOfComponents();

// DataArray dimensions are stored slowest to fastest, the opposite of ImageGeometry
std::vector<usize> arrayDims(dims.crbegin(), dims.crend());

std::vector<usize> cDims = {nComponents};

actions.appendAction(std::make_unique<CreateImageGeometryAction>(std::move(imageGeomPath), std::move(dims), std::move(origin), std::move(spacing), cellDataName));
actions.appendAction(std::make_unique<CreateImageGeometryAction>(std::move(imageGeomPath), std::move(dims), origin.toContainer<CreateImageGeometryAction::OriginType>(),
spacing.toContainer<CreateImageGeometryAction::SpacingType>(), cellDataName));
actions.appendAction(std::make_unique<CreateArrayAction>(*numericType, std::move(arrayDims), std::move(cDims), std::move(arrayPath)));

} catch(const itk::ExceptionObject& err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,24 @@ Result<> ReadImageExecute(const std::string& fileName, ArgsT&&... args)
}
}

struct ImageReaderOptions
{
bool OverrideOrigin = false;
bool OriginAtCenterOfGeometry = false;
bool OverrideSpacing = false;
FloatVec3 Origin;
FloatVec3 Spacing;
};

//------------------------------------------------------------------------------
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath);
/**
* @brief
* @param fileName
* @param imageGeomPath
* @param cellDataName
* @param arrayPath
* @return
*/
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath, const ImageReaderOptions& imageReaderOptions);

} // namespace cxItkImageReader
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
#include "simplnx/DataStructure/DataStore.hpp"
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
#include "simplnx/Filter/Actions/CreateArrayAction.hpp"
#include "simplnx/Filter/Actions/UpdateImageGeomAction.hpp"
#include "simplnx/Parameters/ArrayCreationParameter.hpp"
#include "simplnx/Parameters/BoolParameter.hpp"
#include "simplnx/Parameters/DataGroupCreationParameter.hpp"
#include "simplnx/Parameters/DataObjectNameParameter.hpp"
#include "simplnx/Parameters/FileSystemPathParameter.hpp"

#include "ITKImageProcessing/Common/ITKArrayHelper.hpp"
#include "ITKImageProcessing/Common/ReadImageUtils.hpp"

#include <filesystem>
Expand Down Expand Up @@ -59,6 +59,24 @@ Parameters ITKImageReader::parameters() const
Parameters params;

params.insertSeparator(Parameters::Separator{"Input Parameters"});

params.insert(std::make_unique<ChoicesParameter>(k_LengthUnit_Key, "Length Unit", "The length unit that will be set into the created image geometry",
to_underlying(IGeometry::LengthUnit::Micrometer), IGeometry::GetAllLengthUnitStrings()));

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<VectorFloat64Parameter>(k_Origin_Key, "Origin (Physical Units)", "Specifies the new origin values in physical units.", std::vector<float64>{0.0, 0.0, 0.0},
std::vector<std::string>{"X", "Y", "Z"}));

params.insertLinkableParameter(std::make_unique<BoolParameter>(k_ChangeSpacing_Key, "Set Spacing", "Specifies if the spacing should be changed", false));
params.insert(std::make_unique<VectorFloat64Parameter>(k_Spacing_Key, "Spacing (Physical Units)", "Specifies the new spacing values in physical units.", std::vector<float64>{1, 1, 1},
std::vector<std::string>{"X", "Y", "Z"}));

params.linkParameters(k_ChangeOrigin_Key, k_Origin_Key, std::make_any<bool>(true));
params.linkParameters(k_ChangeOrigin_Key, k_CenterOrigin_Key, std::make_any<bool>(true));
params.linkParameters(k_ChangeSpacing_Key, k_Spacing_Key, std::make_any<bool>(true));

params.insert(std::make_unique<FileSystemPathParameter>(k_FileName_Key, "File", "Input image file", fs::path(""),
FileSystemPathParameter::ExtensionsType{{".png"}, {".tiff"}, {".tif"}, {".bmp"}, {".jpeg"}, {".jpg"}, {".nrrd"}, {".mha"}},
FileSystemPathParameter::PathType::InputFile, false));
Expand All @@ -83,19 +101,28 @@ IFilter::PreflightResult ITKImageReader::preflightImpl(const DataStructure& data
const std::atomic_bool& shouldCancel) const
{
auto fileName = filterArgs.value<fs::path>(k_FileName_Key);
auto imageGeometryPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
auto imageGeomPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
auto cellDataName = filterArgs.value<std::string>(k_CellDataName_Key);
auto imageDataArrayPath = filterArgs.value<DataPath>(k_ImageDataArrayPath_Key);
auto shouldChangeOrigin = filterArgs.value<bool>(k_ChangeOrigin_Key);
auto shouldCenterOrigin = filterArgs.value<bool>(k_CenterOrigin_Key);
auto shouldChangeSpacing = filterArgs.value<bool>(k_ChangeSpacing_Key);
auto origin = filterArgs.value<std::vector<float64>>(k_Origin_Key);
auto spacing = filterArgs.value<std::vector<float64>>(k_Spacing_Key);

std::string fileNameString = fileName.string();

Result<> check = cxItkImageReader::ReadImageExecute<cxItkImageReader::PreflightFunctor>(fileNameString);
if(check.invalid())
{
return {ConvertResultTo<OutputActions>(std::move(check), {})};
}
cxItkImageReader::ImageReaderOptions imageReaderOptions;

imageReaderOptions.OverrideOrigin = shouldChangeOrigin;
imageReaderOptions.OverrideSpacing = shouldChangeSpacing;
imageReaderOptions.OriginAtCenterOfGeometry = shouldCenterOrigin;
imageReaderOptions.Origin = FloatVec3(static_cast<float32>(origin[0]), static_cast<float32>(origin[1]), static_cast<float32>(origin[2]));
imageReaderOptions.Spacing = FloatVec3(static_cast<float32>(spacing[0]), static_cast<float32>(spacing[1]), static_cast<float32>(spacing[2]));

return {cxItkImageReader::ReadImagePreflight(fileNameString, imageGeometryPath, cellDataName, imageDataArrayPath)};
Result<OutputActions> result = cxItkImageReader::ReadImagePreflight(fileNameString, imageGeomPath, cellDataName, imageDataArrayPath, imageReaderOptions);

return {result};
}

//------------------------------------------------------------------------------
Expand All @@ -105,9 +132,14 @@ Result<> ITKImageReader::executeImpl(DataStructure& dataStructure, const Argumen
auto fileName = filterArgs.value<FileSystemPathParameter::ValueType>(k_FileName_Key);
auto imageGeometryPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
auto imageDataArrayPath = filterArgs.value<DataPath>(k_ImageDataArrayPath_Key);

const IDataArray* inputArray = dataStructure.getDataAs<IDataArray>(imageDataArrayPath);
if(inputArray->getDataFormat() != "")
// auto shouldChangeOrigin = filterArgs.value<bool>(k_ChangeOrigin_Key);
// auto shouldCenterOrigin = filterArgs.value<bool>(k_CenterOrigin_Key);
// auto shouldChangeSpacing = filterArgs.value<bool>(k_ChangeSpacing_Key);
auto origin = filterArgs.value<std::vector<float64>>(k_Origin_Key);
auto spacing = filterArgs.value<std::vector<float64>>(k_Spacing_Key);

const IDataArray* inputArrayPtr = dataStructure.getDataAs<IDataArray>(imageDataArrayPath);
if(!inputArrayPtr->getDataFormat().empty())
{
return MakeErrorResult(-9999, fmt::format("Input Array '{}' utilizes out-of-core data. This is not supported within ITK filters.", imageDataArrayPath.toString()));
}
Expand All @@ -117,7 +149,9 @@ Result<> ITKImageReader::executeImpl(DataStructure& dataStructure, const Argumen
ImageGeom& imageGeom = dataStructure.getDataRefAs<ImageGeom>(imageGeometryPath);
imageGeom.getLinkedGeometryData().addCellData(imageDataArrayPath);

return cxItkImageReader::ReadImageExecute<cxItkImageReader::ReadImageIntoArrayFunctor>(fileNameString, dataStructure, imageDataArrayPath, fileNameString);
auto result = cxItkImageReader::ReadImageExecute<cxItkImageReader::ReadImageIntoArrayFunctor>(fileNameString, dataStructure, imageDataArrayPath, fileNameString);

return result;
}

namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ class ITKIMAGEPROCESSING_EXPORT ITKImageReader : public IFilter
static inline constexpr StringLiteral k_ImageDataArrayPath_Key = "image_data_array_path";
static inline constexpr StringLiteral k_CellDataName_Key = "cell_data_name";

static inline constexpr StringLiteral k_LengthUnit_Key = "length_unit";

static inline constexpr StringLiteral k_ChangeOrigin_Key = "change_origin";
static inline constexpr StringLiteral k_CenterOrigin_Key = "center_origin";
static inline constexpr StringLiteral k_Origin_Key = "origin";

static inline constexpr StringLiteral k_ChangeSpacing_Key = "change_spacing";
static inline constexpr StringLiteral k_Spacing_Key = "spacing";

/**
* @brief Reads SIMPL json and converts it simplnx Arguments.
* @param json
Expand Down
92 changes: 92 additions & 0 deletions src/Plugins/ITKImageProcessing/test/ITKImageReaderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,95 @@ TEST_CASE("ITKImageProcessing::ITKImageReader: Read PNG", "[ITKImageProcessing][
const std::vector<usize> expectedArrayComponentDims = {3};
REQUIRE(arrayComponentDims == expectedArrayComponentDims);
}

TEST_CASE("ITKImageProcessing::ITKImageReader: Override Origin", "[ITKImageProcessing][ITKImageReader]")
{
ITKImageReader filter;
DataStructure dataStructure;
Arguments args;

bool k_ChangeOrigin = false;
bool k_ChangeResolution = false;

std::vector<float64> k_Origin{-32.0, -32.0, 0.0};
std::vector<float64> k_Spacing{1.0, 1.0, 1.0};

fs::path filePath = fs::path(unit_test::k_SourceDir.view()) / "test/data/PngTest.png";
DataPath arrayPath{{"ImageGeom", "ImageArray"}};
DataPath imagePath = arrayPath.getParent();
args.insertOrAssign(ITKImageReader::k_FileName_Key, filePath);
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, imagePath);
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, arrayPath);
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, true);

args.insert(ITKImageReader::k_ChangeOrigin_Key, std::make_any<bool>(k_ChangeOrigin));
args.insert(ITKImageReader::k_CenterOrigin_Key, std::make_any<bool>(false));
args.insert(ITKImageReader::k_ChangeSpacing_Key, std::make_any<bool>(k_ChangeResolution));
args.insert(ITKImageReader::k_Origin_Key, std::make_any<std::vector<float64>>(k_Origin));
args.insert(ITKImageReader::k_Spacing_Key, std::make_any<std::vector<float64>>(k_Spacing));

auto preflightResult = filter.preflight(dataStructure, args);
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions)

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

const auto* imageGeom = dataStructure.getDataAs<ImageGeom>(imagePath);
REQUIRE(imageGeom != nullptr);

SizeVec3 imageDims = imageGeom->getDimensions();
const SizeVec3 expectedImageDims = {64, 64, 1};
REQUIRE(imageDims == expectedImageDims);

std::vector<float64> imageOrigin = imageGeom->getOrigin().toContainer<std::vector<float64>>();
REQUIRE(imageOrigin == k_Origin);

std::vector<float64> imageSpacing = imageGeom->getSpacing().toContainer<std::vector<float64>>();
REQUIRE(imageSpacing == k_Spacing);
}

TEST_CASE("ITKImageProcessing::ITKImageReader: Centering Origin in Geometry", "[ITKImageProcessing][ITKImageReader]")
{
ITKImageReader filter;
DataStructure dataStructure;
Arguments args;

bool k_ChangeOrigin = false;
bool k_ChangeResolution = false;

std::vector<float64> k_Origin{0.0, 0.0, 0.0};
std::vector<float64> k_Spacing{1.0, 1.0, 1.0};

fs::path filePath = fs::path(unit_test::k_SourceDir.view()) / "test/data/PngTest.png";
DataPath arrayPath{{"ImageGeom", "ImageArray"}};
DataPath imagePath = arrayPath.getParent();
args.insertOrAssign(ITKImageReader::k_FileName_Key, filePath);
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, imagePath);
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, arrayPath);
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, true);

args.insert(ITKImageReader::k_ChangeOrigin_Key, std::make_any<bool>(k_ChangeOrigin));
args.insert(ITKImageReader::k_CenterOrigin_Key, std::make_any<bool>(true));
args.insert(ITKImageReader::k_ChangeSpacing_Key, std::make_any<bool>(k_ChangeResolution));
args.insert(ITKImageReader::k_Origin_Key, std::make_any<std::vector<float64>>(k_Origin));
args.insert(ITKImageReader::k_Spacing_Key, std::make_any<std::vector<float64>>(k_Spacing));

auto preflightResult = filter.preflight(dataStructure, args);
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions)

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

const auto* imageGeom = dataStructure.getDataAs<ImageGeom>(imagePath);
REQUIRE(imageGeom != nullptr);

SizeVec3 imageDims = imageGeom->getDimensions();
const SizeVec3 expectedImageDims = {64, 64, 1};
REQUIRE(imageDims == expectedImageDims);

std::vector<float64> imageOrigin = imageGeom->getOrigin().toContainer<std::vector<float64>>();
REQUIRE(imageOrigin == std::vector<float64>{-32.0, -32.0, -0.5});

std::vector<float64> imageSpacing = imageGeom->getSpacing().toContainer<std::vector<float64>>();
REQUIRE(imageSpacing == k_Spacing);
}
2 changes: 2 additions & 0 deletions src/Plugins/ITKImageProcessing/test/ITKTestBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ Result<> ReadImage(DataStructure& dataStructure, const fs::path& filePath, const
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, geometryPath);
args.insertOrAssign(ITKImageReader::k_CellDataName_Key, cellDataPath.getTargetName());
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, imagePath);
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, false);
args.insertOrAssign(ITKImageReader::k_ChangeSpacing_Key, false);
auto executeResult = filter.execute(dataStructure, args);
return executeResult.result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
{
"args": {
"change_origin": true,
"change_resolution": true,
"change_spacing": true,
"image_geom": "DataContainer",
"origin": [
0.0,
Expand Down
Loading

0 comments on commit 18498ae

Please sign in to comment.