Skip to content

Commit 18498ae

Browse files
authored
ENH: ITKImageReader - Allow user to set/override the origin/spacing (BlueQuartzSoftware#834)
Signed-off-by: Michael Jackson <[email protected]>
1 parent 19b009c commit 18498ae

File tree

15 files changed

+224
-34
lines changed

15 files changed

+224
-34
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ endif()
7878
file(TO_CMAKE_PATH "${CMAKE_COMMAND}" CMAKE_COMMAND_NORM)
7979

8080
project(simplnx
81-
VERSION 1.2.1
81+
VERSION 1.2.4
8282
DESCRIPTION "SIMPL Redesign"
8383
HOMEPAGE_URL "https://github.com/bluequartzsoftware/simplnx"
8484
LANGUAGES CXX

conda/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set name = "simplnx" %}
2-
{% set version = "1.2.3" %}
2+
{% set version = "1.2.4" %}
33

44
package:
55
name: {{ name|lower }}

conda/recipe.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
context:
2-
version: "1.2.3"
2+
version: "1.2.4"
33
name: simplnx
44

55
package:

src/Plugins/ITKImageProcessing/docs/ITKImageReader.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ ITKImageProcessing (ITKImageProcessing)
88

99
## Description
1010

11-
Reads images through ITK
11+
Reads images through the ITK software library [https://www.itk.org](https://www.itk.org)
12+
13+
The following image types are supported:
14+
15+
- PNG
16+
- TIFF
17+
- BMP
18+
- JPG
19+
- NRRD
20+
- MHA
21+
22+
The user is required to set the origin and spacing (Length units per pixel) for the imported image. The default values are an origin
23+
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
1224

1325
% Auto generated parameter table will be inserted here
1426

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ReadImageUtils.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace cxItkImageReader
44
{
55

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

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

3232
std::vector<size_t> dims = {1, 1, 1};
33-
std::vector<float32> origin = {0.0f, 0.0f, 0.0f};
34-
std::vector<float32> spacing = {1.0f, 1.0f, 1.0f};
33+
FloatVec3 origin = {0.0f, 0.0f, 0.0f};
34+
FloatVec3 spacing = {1.0f, 1.0f, 1.0f};
3535

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

43+
if(imageReaderOptions.OverrideSpacing)
44+
{
45+
spacing = imageReaderOptions.Spacing;
46+
}
47+
48+
if(imageReaderOptions.OverrideOrigin)
49+
{
50+
DataStructure junk;
51+
ImageGeom* imageGeomPtr = ImageGeom::Create(junk, "Junk");
52+
53+
origin = imageReaderOptions.Origin;
54+
55+
imageGeomPtr->setDimensions(dims);
56+
imageGeomPtr->setOrigin(origin);
57+
imageGeomPtr->setSpacing(spacing);
58+
59+
if(imageReaderOptions.OriginAtCenterOfGeometry)
60+
{
61+
BoundingBox3Df bounds = imageGeomPtr->getBoundingBoxf();
62+
FloatVec3 centerPoint(bounds.center());
63+
origin = origin - (centerPoint - origin);
64+
}
65+
}
66+
4367
uint32 nComponents = imageIO->GetNumberOfComponents();
4468

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

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

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

5378
} catch(const itk::ExceptionObject& err)

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ReadImageUtils.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,24 @@ Result<> ReadImageExecute(const std::string& fileName, ArgsT&&... args)
167167
}
168168
}
169169

170+
struct ImageReaderOptions
171+
{
172+
bool OverrideOrigin = false;
173+
bool OriginAtCenterOfGeometry = false;
174+
bool OverrideSpacing = false;
175+
FloatVec3 Origin;
176+
FloatVec3 Spacing;
177+
};
178+
170179
//------------------------------------------------------------------------------
171-
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath);
180+
/**
181+
* @brief
182+
* @param fileName
183+
* @param imageGeomPath
184+
* @param cellDataName
185+
* @param arrayPath
186+
* @return
187+
*/
188+
Result<OutputActions> ReadImagePreflight(const std::string& fileName, DataPath imageGeomPath, std::string cellDataName, DataPath arrayPath, const ImageReaderOptions& imageReaderOptions);
172189

173190
} // namespace cxItkImageReader

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageReader.cpp

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
#include "simplnx/DataStructure/DataStore.hpp"
55
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
66
#include "simplnx/Filter/Actions/CreateArrayAction.hpp"
7+
#include "simplnx/Filter/Actions/UpdateImageGeomAction.hpp"
78
#include "simplnx/Parameters/ArrayCreationParameter.hpp"
89
#include "simplnx/Parameters/BoolParameter.hpp"
910
#include "simplnx/Parameters/DataGroupCreationParameter.hpp"
1011
#include "simplnx/Parameters/DataObjectNameParameter.hpp"
1112
#include "simplnx/Parameters/FileSystemPathParameter.hpp"
1213

13-
#include "ITKImageProcessing/Common/ITKArrayHelper.hpp"
1414
#include "ITKImageProcessing/Common/ReadImageUtils.hpp"
1515

1616
#include <filesystem>
@@ -59,6 +59,24 @@ Parameters ITKImageReader::parameters() const
5959
Parameters params;
6060

6161
params.insertSeparator(Parameters::Separator{"Input Parameters"});
62+
63+
params.insert(std::make_unique<ChoicesParameter>(k_LengthUnit_Key, "Length Unit", "The length unit that will be set into the created image geometry",
64+
to_underlying(IGeometry::LengthUnit::Micrometer), IGeometry::GetAllLengthUnitStrings()));
65+
66+
params.insertLinkableParameter(std::make_unique<BoolParameter>(k_ChangeOrigin_Key, "Set Origin", "Specifies if the origin should be changed", false));
67+
params.insert(
68+
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));
69+
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},
70+
std::vector<std::string>{"X", "Y", "Z"}));
71+
72+
params.insertLinkableParameter(std::make_unique<BoolParameter>(k_ChangeSpacing_Key, "Set Spacing", "Specifies if the spacing should be changed", false));
73+
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},
74+
std::vector<std::string>{"X", "Y", "Z"}));
75+
76+
params.linkParameters(k_ChangeOrigin_Key, k_Origin_Key, std::make_any<bool>(true));
77+
params.linkParameters(k_ChangeOrigin_Key, k_CenterOrigin_Key, std::make_any<bool>(true));
78+
params.linkParameters(k_ChangeSpacing_Key, k_Spacing_Key, std::make_any<bool>(true));
79+
6280
params.insert(std::make_unique<FileSystemPathParameter>(k_FileName_Key, "File", "Input image file", fs::path(""),
6381
FileSystemPathParameter::ExtensionsType{{".png"}, {".tiff"}, {".tif"}, {".bmp"}, {".jpeg"}, {".jpg"}, {".nrrd"}, {".mha"}},
6482
FileSystemPathParameter::PathType::InputFile, false));
@@ -83,19 +101,28 @@ IFilter::PreflightResult ITKImageReader::preflightImpl(const DataStructure& data
83101
const std::atomic_bool& shouldCancel) const
84102
{
85103
auto fileName = filterArgs.value<fs::path>(k_FileName_Key);
86-
auto imageGeometryPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
104+
auto imageGeomPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
87105
auto cellDataName = filterArgs.value<std::string>(k_CellDataName_Key);
88106
auto imageDataArrayPath = filterArgs.value<DataPath>(k_ImageDataArrayPath_Key);
107+
auto shouldChangeOrigin = filterArgs.value<bool>(k_ChangeOrigin_Key);
108+
auto shouldCenterOrigin = filterArgs.value<bool>(k_CenterOrigin_Key);
109+
auto shouldChangeSpacing = filterArgs.value<bool>(k_ChangeSpacing_Key);
110+
auto origin = filterArgs.value<std::vector<float64>>(k_Origin_Key);
111+
auto spacing = filterArgs.value<std::vector<float64>>(k_Spacing_Key);
89112

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

92-
Result<> check = cxItkImageReader::ReadImageExecute<cxItkImageReader::PreflightFunctor>(fileNameString);
93-
if(check.invalid())
94-
{
95-
return {ConvertResultTo<OutputActions>(std::move(check), {})};
96-
}
115+
cxItkImageReader::ImageReaderOptions imageReaderOptions;
116+
117+
imageReaderOptions.OverrideOrigin = shouldChangeOrigin;
118+
imageReaderOptions.OverrideSpacing = shouldChangeSpacing;
119+
imageReaderOptions.OriginAtCenterOfGeometry = shouldCenterOrigin;
120+
imageReaderOptions.Origin = FloatVec3(static_cast<float32>(origin[0]), static_cast<float32>(origin[1]), static_cast<float32>(origin[2]));
121+
imageReaderOptions.Spacing = FloatVec3(static_cast<float32>(spacing[0]), static_cast<float32>(spacing[1]), static_cast<float32>(spacing[2]));
97122

98-
return {cxItkImageReader::ReadImagePreflight(fileNameString, imageGeometryPath, cellDataName, imageDataArrayPath)};
123+
Result<OutputActions> result = cxItkImageReader::ReadImagePreflight(fileNameString, imageGeomPath, cellDataName, imageDataArrayPath, imageReaderOptions);
124+
125+
return {result};
99126
}
100127

101128
//------------------------------------------------------------------------------
@@ -105,9 +132,14 @@ Result<> ITKImageReader::executeImpl(DataStructure& dataStructure, const Argumen
105132
auto fileName = filterArgs.value<FileSystemPathParameter::ValueType>(k_FileName_Key);
106133
auto imageGeometryPath = filterArgs.value<DataPath>(k_ImageGeometryPath_Key);
107134
auto imageDataArrayPath = filterArgs.value<DataPath>(k_ImageDataArrayPath_Key);
108-
109-
const IDataArray* inputArray = dataStructure.getDataAs<IDataArray>(imageDataArrayPath);
110-
if(inputArray->getDataFormat() != "")
135+
// auto shouldChangeOrigin = filterArgs.value<bool>(k_ChangeOrigin_Key);
136+
// auto shouldCenterOrigin = filterArgs.value<bool>(k_CenterOrigin_Key);
137+
// auto shouldChangeSpacing = filterArgs.value<bool>(k_ChangeSpacing_Key);
138+
auto origin = filterArgs.value<std::vector<float64>>(k_Origin_Key);
139+
auto spacing = filterArgs.value<std::vector<float64>>(k_Spacing_Key);
140+
141+
const IDataArray* inputArrayPtr = dataStructure.getDataAs<IDataArray>(imageDataArrayPath);
142+
if(!inputArrayPtr->getDataFormat().empty())
111143
{
112144
return MakeErrorResult(-9999, fmt::format("Input Array '{}' utilizes out-of-core data. This is not supported within ITK filters.", imageDataArrayPath.toString()));
113145
}
@@ -117,7 +149,9 @@ Result<> ITKImageReader::executeImpl(DataStructure& dataStructure, const Argumen
117149
ImageGeom& imageGeom = dataStructure.getDataRefAs<ImageGeom>(imageGeometryPath);
118150
imageGeom.getLinkedGeometryData().addCellData(imageDataArrayPath);
119151

120-
return cxItkImageReader::ReadImageExecute<cxItkImageReader::ReadImageIntoArrayFunctor>(fileNameString, dataStructure, imageDataArrayPath, fileNameString);
152+
auto result = cxItkImageReader::ReadImageExecute<cxItkImageReader::ReadImageIntoArrayFunctor>(fileNameString, dataStructure, imageDataArrayPath, fileNameString);
153+
154+
return result;
121155
}
122156

123157
namespace

src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageReader.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ class ITKIMAGEPROCESSING_EXPORT ITKImageReader : public IFilter
2929
static inline constexpr StringLiteral k_ImageDataArrayPath_Key = "image_data_array_path";
3030
static inline constexpr StringLiteral k_CellDataName_Key = "cell_data_name";
3131

32+
static inline constexpr StringLiteral k_LengthUnit_Key = "length_unit";
33+
34+
static inline constexpr StringLiteral k_ChangeOrigin_Key = "change_origin";
35+
static inline constexpr StringLiteral k_CenterOrigin_Key = "center_origin";
36+
static inline constexpr StringLiteral k_Origin_Key = "origin";
37+
38+
static inline constexpr StringLiteral k_ChangeSpacing_Key = "change_spacing";
39+
static inline constexpr StringLiteral k_Spacing_Key = "spacing";
40+
3241
/**
3342
* @brief Reads SIMPL json and converts it simplnx Arguments.
3443
* @param json

src/Plugins/ITKImageProcessing/test/ITKImageReaderTest.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,95 @@ TEST_CASE("ITKImageProcessing::ITKImageReader: Read PNG", "[ITKImageProcessing][
6060
const std::vector<usize> expectedArrayComponentDims = {3};
6161
REQUIRE(arrayComponentDims == expectedArrayComponentDims);
6262
}
63+
64+
TEST_CASE("ITKImageProcessing::ITKImageReader: Override Origin", "[ITKImageProcessing][ITKImageReader]")
65+
{
66+
ITKImageReader filter;
67+
DataStructure dataStructure;
68+
Arguments args;
69+
70+
bool k_ChangeOrigin = false;
71+
bool k_ChangeResolution = false;
72+
73+
std::vector<float64> k_Origin{-32.0, -32.0, 0.0};
74+
std::vector<float64> k_Spacing{1.0, 1.0, 1.0};
75+
76+
fs::path filePath = fs::path(unit_test::k_SourceDir.view()) / "test/data/PngTest.png";
77+
DataPath arrayPath{{"ImageGeom", "ImageArray"}};
78+
DataPath imagePath = arrayPath.getParent();
79+
args.insertOrAssign(ITKImageReader::k_FileName_Key, filePath);
80+
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, imagePath);
81+
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, arrayPath);
82+
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, true);
83+
84+
args.insert(ITKImageReader::k_ChangeOrigin_Key, std::make_any<bool>(k_ChangeOrigin));
85+
args.insert(ITKImageReader::k_CenterOrigin_Key, std::make_any<bool>(false));
86+
args.insert(ITKImageReader::k_ChangeSpacing_Key, std::make_any<bool>(k_ChangeResolution));
87+
args.insert(ITKImageReader::k_Origin_Key, std::make_any<std::vector<float64>>(k_Origin));
88+
args.insert(ITKImageReader::k_Spacing_Key, std::make_any<std::vector<float64>>(k_Spacing));
89+
90+
auto preflightResult = filter.preflight(dataStructure, args);
91+
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions)
92+
93+
auto executeResult = filter.execute(dataStructure, args);
94+
SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result)
95+
96+
const auto* imageGeom = dataStructure.getDataAs<ImageGeom>(imagePath);
97+
REQUIRE(imageGeom != nullptr);
98+
99+
SizeVec3 imageDims = imageGeom->getDimensions();
100+
const SizeVec3 expectedImageDims = {64, 64, 1};
101+
REQUIRE(imageDims == expectedImageDims);
102+
103+
std::vector<float64> imageOrigin = imageGeom->getOrigin().toContainer<std::vector<float64>>();
104+
REQUIRE(imageOrigin == k_Origin);
105+
106+
std::vector<float64> imageSpacing = imageGeom->getSpacing().toContainer<std::vector<float64>>();
107+
REQUIRE(imageSpacing == k_Spacing);
108+
}
109+
110+
TEST_CASE("ITKImageProcessing::ITKImageReader: Centering Origin in Geometry", "[ITKImageProcessing][ITKImageReader]")
111+
{
112+
ITKImageReader filter;
113+
DataStructure dataStructure;
114+
Arguments args;
115+
116+
bool k_ChangeOrigin = false;
117+
bool k_ChangeResolution = false;
118+
119+
std::vector<float64> k_Origin{0.0, 0.0, 0.0};
120+
std::vector<float64> k_Spacing{1.0, 1.0, 1.0};
121+
122+
fs::path filePath = fs::path(unit_test::k_SourceDir.view()) / "test/data/PngTest.png";
123+
DataPath arrayPath{{"ImageGeom", "ImageArray"}};
124+
DataPath imagePath = arrayPath.getParent();
125+
args.insertOrAssign(ITKImageReader::k_FileName_Key, filePath);
126+
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, imagePath);
127+
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, arrayPath);
128+
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, true);
129+
130+
args.insert(ITKImageReader::k_ChangeOrigin_Key, std::make_any<bool>(k_ChangeOrigin));
131+
args.insert(ITKImageReader::k_CenterOrigin_Key, std::make_any<bool>(true));
132+
args.insert(ITKImageReader::k_ChangeSpacing_Key, std::make_any<bool>(k_ChangeResolution));
133+
args.insert(ITKImageReader::k_Origin_Key, std::make_any<std::vector<float64>>(k_Origin));
134+
args.insert(ITKImageReader::k_Spacing_Key, std::make_any<std::vector<float64>>(k_Spacing));
135+
136+
auto preflightResult = filter.preflight(dataStructure, args);
137+
SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions)
138+
139+
auto executeResult = filter.execute(dataStructure, args);
140+
SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result)
141+
142+
const auto* imageGeom = dataStructure.getDataAs<ImageGeom>(imagePath);
143+
REQUIRE(imageGeom != nullptr);
144+
145+
SizeVec3 imageDims = imageGeom->getDimensions();
146+
const SizeVec3 expectedImageDims = {64, 64, 1};
147+
REQUIRE(imageDims == expectedImageDims);
148+
149+
std::vector<float64> imageOrigin = imageGeom->getOrigin().toContainer<std::vector<float64>>();
150+
REQUIRE(imageOrigin == std::vector<float64>{-32.0, -32.0, -0.5});
151+
152+
std::vector<float64> imageSpacing = imageGeom->getSpacing().toContainer<std::vector<float64>>();
153+
REQUIRE(imageSpacing == k_Spacing);
154+
}

src/Plugins/ITKImageProcessing/test/ITKTestBase.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ Result<> ReadImage(DataStructure& dataStructure, const fs::path& filePath, const
173173
args.insertOrAssign(ITKImageReader::k_ImageGeometryPath_Key, geometryPath);
174174
args.insertOrAssign(ITKImageReader::k_CellDataName_Key, cellDataPath.getTargetName());
175175
args.insertOrAssign(ITKImageReader::k_ImageDataArrayPath_Key, imagePath);
176+
args.insertOrAssign(ITKImageReader::k_ChangeOrigin_Key, false);
177+
args.insertOrAssign(ITKImageReader::k_ChangeSpacing_Key, false);
176178
auto executeResult = filter.execute(dataStructure, args);
177179
return executeResult.result;
178180
}

src/Plugins/SimplnxCore/pipelines/ReplaceElementAttributesWithNeighbor.d3dpipeline

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
{
123123
"args": {
124124
"change_origin": true,
125-
"change_resolution": true,
125+
"change_spacing": true,
126126
"image_geom": "DataContainer",
127127
"origin": [
128128
0.0,

0 commit comments

Comments
 (0)