Skip to content

Commit 10389fb

Browse files
cpinterlassoan
authored andcommitted
ENH: Support 5D files in NRRD IO
Several modern imaging devices and processing algorithms now creates 5D data sets, including: - time sequence of 3D displacement fields (result of deformable registration of 4D images) - time sequence of 3D velocity fields (acquired by 4D MRI flow images) - time sequence of 3D color (RGB or RGBA) images (result of colorized volumes provided by AI segmentation) NRRD IO can now read and write all these data types, and in general any 5D arrays (2 or 3 spatial dimensions + scalar/vector pixel type + optional list axis). 1. Writing Previously the NRRD writer handled one component axis, and all the other dimensions were space. This commit allows specifying one of the axes as list (by setting "NRRD_kinds[i]=list" metadata key). Example: File header before this commit: dimension: 5 space dimension: 4 sizes: 4 320 320 1 5 space directions: none (1,0,0,0) (0,1,0,0) (0,0,1,0) (0,0,0,1) kinds: RGBA-color domain domain domain domain encoding: gzip space origin: (0,0,0,1) File header after this commit: dimension: 5 space: left-posterior-superior sizes: 4 320 320 1 5 space directions: none (-1,0,0) (0,-1,0) (0,0,1) none kinds: vector domain domain domain list encoding: gzip space origin: (0,0,0) 2. Reading NRRD IO now allows having more than one non-spatial axis. The non-spatial axes of the NRRD file can be used in the ITK image as pixel components or as additional image dimensions. The default strategy (UseAnyRangeAxisAsPixel) implements the same behavior as before (first non-spatial NRRD axis always becomes pixel component). The new UseNonListRangeAxisAsPixel option allows "list" dimension (e.g., time points) to always mapped to additional image dimension. This new option simplifies application implementation, as time sequences are loaded the same way for scalar and vector volumes. 3. Add support for axis unit in NRRD IO; Save label and unit for range axis NRRD IO did not support the "units" metadata for axes, now it is added both for reading and writing. Per-axis metadata (kinds, labels, units) was only saved for spatial axes, now it is added for all axes. For pixel components, "pixel" is used instead of axis ("NRRD_labels[pixel]" and "NRRD_units[pixel]" fields). Added many tests for reading/writing 3D, 4D, 5D images in NRRD format. Co-authored-by: Andras Lasso <[email protected]>
1 parent a0cf385 commit 10389fb

File tree

8 files changed

+1355
-239
lines changed

8 files changed

+1355
-239
lines changed

Modules/IO/NRRD/include/itkNrrdImageIO.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ struct NrrdEncoding_t;
2727

2828
namespace itk
2929
{
30+
/** \class NrrdImageIOEnums
31+
* \brief Contains all enum classes used by NrrdImageIO class.
32+
* \ingroup ITKIONRRD
33+
*/
34+
class NrrdImageIOEnums
35+
{
36+
public:
37+
/** \class AxesReorder
38+
* \ingroup ITKIONRRD
39+
* Enum type that specifies how to reorder axes in NRRD file when reading it into an ITK image. */
40+
enum class AxesReorder : uint8_t
41+
{
42+
UseAnyRangeAxisAsPixel = 0,
43+
UseNonListRangeAxisAsPixel = 1,
44+
UseScalarPixel = 2
45+
};
46+
};
47+
// Define how to print enumeration
48+
extern ITKIONRRD_EXPORT std::ostream &
49+
operator<<(std::ostream & out, const NrrdImageIOEnums::AxesReorder value);
50+
51+
using AxesReorderEnum = NrrdImageIOEnums::AxesReorder;
52+
3053
/**
3154
* \class NrrdImageIO
3255
*
@@ -109,6 +132,73 @@ class ITKIONRRD_EXPORT NrrdImageIO : public ImageIOBase
109132
void
110133
Write(const void * buffer) override;
111134

135+
/** Set/Get the strategy for axis reordering.
136+
* ITK image can be an Image object that can store scalar, RGB, CovariantVector, etc. pixels in an arbitrarily high
137+
* dimensional array. If the number of components of the pixel has to be dynamic then the ITK image has to be a
138+
* VectorImage object that can store multi-component pixels in an arbitrarily high dimensional array.
139+
*
140+
* NRRD file can contain domain (space, time) axes and range (list, RGB, covariant-vector, etc.) axes in an arbitrary
141+
* order. Therefore, axes of the NRRD file may have to be reordered to be able to read the NRRD file into an ITK
142+
* image. In general, NRRD domain axes are mapped to ITK image axes then all range axes are used as additional image
143+
* dimensions. The only exception is that it makes sense to pick one a range axis and use that as pixel component.
144+
*
145+
* In early ITK versions, any kind of NRRD range axis was used as pixel component by default (this is the
146+
* UseAnyRangeAxisAsPixel strategy). However, this resulted in more complicated application code (developers need to
147+
* instantiate an Image or VectorImage depending on what kind of axes are in the NRRD file) and potential unnecessary
148+
* reordering of axes (e.g., sequence of 3D images was always converted into a single multi-component 3D image).
149+
*
150+
* Therefore additional strategies were introduced to allow more control over how NRRD axes are mapped to ITK image
151+
* axes and pixel component. UseNonListRangeAxisAsPixel to ensure that the image can be always read into an Image
152+
* object. UseScalarPixel to ensure that the image can be always read into an Image object with a scalar pixel type.
153+
*
154+
* \li UseAnyRangeAxisAsPixel Use the first a range axis (i.e., non-domain axis) of the NRRD file as pixel component.
155+
* This is the default (for backward compatibility).
156+
* If the NRRD file contains a non-list type (e.g., RGB, point, covariant-vector) range axis then the first one
157+
* will be used as pixel component; the image will be read into an Image object. If the NRRD file only contains
158+
* list-type range axis then it will be used as pixel component; the image will be read into a VectorImage.
159+
* \li UseNonListRangeAxisAsPixel Use the first non-list range axis (i.e., non-domain axis) of the NRRD file as pixel
160+
* component. The image is always read into an Image object, with potentially a non-scalar pixel type.
161+
* \li UseScalarPixel Pixel type is scalar. All range (i.e., non-domain) axes of the NRRD file are added as additional
162+
* domain axes. The image is always read into an Image object with a scalar pixel type.
163+
*
164+
* Examples:
165+
*
166+
* NRRD file: list domain domain domain RGB-Color (or: domain domain domain list RGB-Color)
167+
* - UseAnyRangeAxisAsPixel: Image<RGBPixel<double>,4> with axes: domain domain domain list
168+
* - UseNonListRangeAxisAsPixel: Image<RGBPixel<double>,4> with axes: domain domain domain list
169+
* - UseScalarPixel: Image<double,5> with axes: domain domain domain list RGB-Color
170+
*
171+
* NRRD file: list domain domain domain (or: domain domain domain list)
172+
* - UseAnyRangeAxisAsPixel: VectorImage<double,3> with axes: domain domain domain
173+
* - UseNonListRangeAxisAsPixel: Image<double,4> with axes: domain domain domain list
174+
* - UseScalarPixel: Image<double,4> with axes: domain domain domain list
175+
*/
176+
itkSetMacro(AxesReorder, AxesReorderEnum);
177+
itkGetConstMacro(AxesReorder, AxesReorderEnum);
178+
179+
/** Use the first a range axis (i.e., non-domain axis) of the NRRD file as pixel component. */
180+
void
181+
SetAxesReorderToUseAnyRangeAxisAsPixel()
182+
{
183+
this->SetAxesReorder(AxesReorderEnum::UseAnyRangeAxisAsPixel);
184+
}
185+
186+
/** UseNonListRangeAxisAsPixel Use the first non-list range axis (i.e., non-domain axis) of the NRRD file as pixel
187+
* component. */
188+
void
189+
SetAxesReorderToUseNonListRangeAxisAsPixel()
190+
{
191+
this->SetAxesReorder(AxesReorderEnum::UseNonListRangeAxisAsPixel);
192+
}
193+
194+
/** UseScalarPixel Pixel type is scalar. All range (i.e., non-domain) axes of the NRRD file are added as additional
195+
* domain axes. */
196+
void
197+
SetAxesReorderToUseScalarPixel()
198+
{
199+
this->SetAxesReorder(AxesReorderEnum::UseScalarPixel);
200+
}
201+
112202
protected:
113203
NrrdImageIO();
114204
~NrrdImageIO() override;
@@ -127,6 +217,8 @@ class ITKIONRRD_EXPORT NrrdImageIO : public ImageIOBase
127217
NrrdToITKComponentType(const int) const;
128218

129219
const NrrdEncoding_t * m_NrrdCompressionEncoding{ nullptr };
220+
221+
AxesReorderEnum m_AxesReorder{ AxesReorderEnum::UseAnyRangeAxisAsPixel };
130222
};
131223
} // end namespace itk
132224

Modules/IO/NRRD/itk-module.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ itk_module(
1313
PRIVATE_DEPENDS
1414
ITKNrrdIO
1515
TEST_DEPENDS
16+
ITKImageCompose
17+
ITKImageGrid
1618
ITKTestKernel
1719
FACTORY_NAMES
1820
ImageIO::Nrrd

0 commit comments

Comments
 (0)