Skip to content

Commit 33d1031

Browse files
authored
Merge pull request InsightSoftwareConsortium#4864 from gdevenyi/MINC_IMAGE_RAS_LPS
2 parents a025ba0 + 18bd4b7 commit 33d1031

13 files changed

+497
-156
lines changed

Modules/IO/MINC/include/itkMINCImageIO.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ class ITKIOMINC_EXPORT MINCImageIO : public ImageIOBase
123123
void
124124
Write(const void * buffer) override;
125125

126+
/** Set to automatically convert from PositiveCoordinateOrientation RAS to PositiveCoordinateOrientation LPS*/
127+
itkSetMacro(RAStoLPS, bool);
128+
itkGetConstMacro(RAStoLPS, bool);
129+
itkBooleanMacro(RAStoLPS);
130+
126131
protected:
127132
MINCImageIO();
128133
~MINCImageIO() override;
@@ -153,6 +158,7 @@ class ITKIOMINC_EXPORT MINCImageIO : public ImageIOBase
153158

154159
// complex type images, composed of complex numbers
155160
// int m_Complex;
161+
bool m_RAStoLPS;
156162
};
157163
} // end namespace itk
158164

Modules/IO/MINC/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
set(ITKIOMINC_SRC itkMINCImageIO.cxx itkMINCImageIOFactory.cxx)
23

34
add_library(ITKIOMINC ${ITK_LIBRARY_BUILD_TYPE} ${ITKIOMINC_SRC})

Modules/IO/MINC/src/itkMINCImageIO.cxx

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include <memory> // For unique_ptr.
3131

32+
3233
extern "C"
3334
{
3435
void
@@ -235,6 +236,7 @@ MINCImageIO::CloseVolume()
235236

236237
MINCImageIO::MINCImageIO()
237238
: m_MINCPImpl(std::make_unique<MINCImageIOPImpl>())
239+
, m_RAStoLPS(false)
238240
{
239241
for (auto & dimensionIndex : m_MINCPImpl->m_DimensionIndices)
240242
{
@@ -280,6 +282,7 @@ MINCImageIO::PrintSelf(std::ostream & os, Indent indent) const
280282

281283
os << indent << "MINCPImpl: " << m_MINCPImpl.get() << std::endl;
282284
os << indent << "DirectionCosines: " << m_DirectionCosines << std::endl;
285+
os << indent << "RAStoLPS: " << m_RAStoLPS << std::endl;
283286
}
284287

285288
void
@@ -446,14 +449,20 @@ MINCImageIO::ReadImageInformation()
446449

447450
this->SetNumberOfDimensions(spatial_dimension_count);
448451

449-
int numberOfComponents = 1;
450-
int usable_dimensions = 0;
452+
int numberOfComponents = 1;
453+
unsigned int usableDimensions = 0;
454+
455+
auto dir_cos = Matrix<double, 3, 3>::GetIdentity();
451456

452-
Matrix<double, 3, 3> dir_cos{};
453-
dir_cos.SetIdentity();
457+
// Conversion matrix for MINC PositiveCoordinateOrientation RAS (LeftToRight, PosteriorToAnterior, InferiorToSuperior)
458+
// to ITK PositiveCoordinateOrientation LPS (RightToLeft, AnteriorToPosterior, InferiorToSuperior)
459+
auto RAStofromLPS = Matrix<double, 3, 3>::GetIdentity();
460+
RAStofromLPS(0, 0) = -1.0;
461+
RAStofromLPS(1, 1) = -1.0;
462+
std::vector<double> dir_cos_temp(3);
454463

455464
Vector<double, 3> origin{};
456-
Vector<double, 3> o_origin{};
465+
Vector<double, 3> oOrigin{};
457466

458467
// minc api uses inverse order of dimensions , fastest varying are last
459468
Vector<double, 3> sep;
@@ -463,19 +472,19 @@ MINCImageIO::ReadImageInformation()
463472
{
464473
// MINC2: bad design!
465474
// micopy_dimension(hdim[m_MINCPImpl->m_DimensionIndices[i]],&apparent_dimension_order[usable_dimensions]);
466-
m_MINCPImpl->m_MincApparentDims[usable_dimensions] =
475+
m_MINCPImpl->m_MincApparentDims[usableDimensions] =
467476
m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[i]];
468477
// always use positive
469-
miset_dimension_apparent_voxel_order(m_MINCPImpl->m_MincApparentDims[usable_dimensions], MI_POSITIVE);
478+
miset_dimension_apparent_voxel_order(m_MINCPImpl->m_MincApparentDims[usableDimensions], MI_POSITIVE);
470479
misize_t _sz;
471-
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usable_dimensions], &_sz);
480+
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usableDimensions], &_sz);
472481

473482
double _sep;
474-
miget_dimension_separation(m_MINCPImpl->m_MincApparentDims[usable_dimensions], MI_ORDER_APPARENT, &_sep);
483+
miget_dimension_separation(m_MINCPImpl->m_MincApparentDims[usableDimensions], MI_ORDER_APPARENT, &_sep);
475484
std::vector<double> _dir(3);
476-
miget_dimension_cosines(m_MINCPImpl->m_MincApparentDims[usable_dimensions], &_dir[0]);
485+
miget_dimension_cosines(m_MINCPImpl->m_MincApparentDims[usableDimensions], &_dir[0]);
477486
double _start;
478-
miget_dimension_start(m_MINCPImpl->m_MincApparentDims[usable_dimensions], MI_ORDER_APPARENT, &_start);
487+
miget_dimension_start(m_MINCPImpl->m_MincApparentDims[usableDimensions], MI_ORDER_APPARENT, &_start);
479488

480489
for (int j = 0; j < 3; ++j)
481490
{
@@ -486,52 +495,61 @@ MINCImageIO::ReadImageInformation()
486495
sep[i - 1] = _sep;
487496

488497
this->SetDimensions(i - 1, static_cast<unsigned int>(_sz));
489-
this->SetDirection(i - 1, _dir);
490498
this->SetSpacing(i - 1, _sep);
491499

492-
++usable_dimensions;
500+
++usableDimensions;
501+
}
502+
}
503+
504+
505+
// Transform MINC PositiveCoordinateOrientation RAS coordinates to
506+
// internal ITK PositiveCoordinateOrientation LPS Coordinates
507+
if (this->m_RAStoLPS)
508+
dir_cos = RAStofromLPS * dir_cos;
509+
510+
// Transform origin coordinates
511+
oOrigin = dir_cos * origin;
512+
513+
for (int i = 0; i < spatial_dimension_count; ++i)
514+
{
515+
this->SetOrigin(i, oOrigin[i]);
516+
for (unsigned int j = 0; j < 3; j++)
517+
{
518+
dir_cos_temp[j] = dir_cos[j][i];
493519
}
520+
this->SetDirection(i, dir_cos_temp);
494521
}
495522

496523
if (m_MINCPImpl->m_DimensionIndices[0] != -1) // have vector dimension
497524
{
498525
// micopy_dimension(m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[0]],&apparent_dimension_order[usable_dimensions]);
499-
m_MINCPImpl->m_MincApparentDims[usable_dimensions] =
500-
m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[0]];
526+
m_MINCPImpl->m_MincApparentDims[usableDimensions] = m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[0]];
501527
// always use positive, vector dimension does not supposed to have notion of positive step size, so leaving as is
502528
// miset_dimension_apparent_voxel_order(m_MINCPImpl->m_MincApparentDims[usable_dimensions],MI_POSITIVE);
503529
misize_t _sz;
504-
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usable_dimensions], &_sz);
530+
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usableDimensions], &_sz);
505531
numberOfComponents = _sz;
506-
++usable_dimensions;
532+
++usableDimensions;
507533
}
508534

509535
if (m_MINCPImpl->m_DimensionIndices[4] != -1) // have time dimension
510536
{
511537
// micopy_dimension(hdim[m_MINCPImpl->m_DimensionIndices[4]],&apparent_dimension_order[usable_dimensions]);
512-
m_MINCPImpl->m_MincApparentDims[usable_dimensions] =
513-
m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[4]];
538+
m_MINCPImpl->m_MincApparentDims[usableDimensions] = m_MINCPImpl->m_MincFileDims[m_MINCPImpl->m_DimensionIndices[4]];
514539
// always use positive
515-
miset_dimension_apparent_voxel_order(m_MINCPImpl->m_MincApparentDims[usable_dimensions], MI_POSITIVE);
540+
miset_dimension_apparent_voxel_order(m_MINCPImpl->m_MincApparentDims[usableDimensions], MI_POSITIVE);
516541
misize_t _sz;
517-
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usable_dimensions], &_sz);
542+
miget_dimension_size(m_MINCPImpl->m_MincApparentDims[usableDimensions], &_sz);
518543
numberOfComponents = _sz;
519-
++usable_dimensions;
544+
++usableDimensions;
520545
}
521546

522547
// Set apparent dimension order to the MINC2 api
523-
if (miset_apparent_dimension_order(m_MINCPImpl->m_Volume, usable_dimensions, m_MINCPImpl->m_MincApparentDims) < 0)
548+
if (miset_apparent_dimension_order(m_MINCPImpl->m_Volume, usableDimensions, m_MINCPImpl->m_MincApparentDims) < 0)
524549
{
525550
itkExceptionMacro(" Can't set apparent dimension order!");
526551
}
527552

528-
o_origin = dir_cos * origin;
529-
530-
for (int i = 0; i < spatial_dimension_count; ++i)
531-
{
532-
this->SetOrigin(i, o_origin[i]);
533-
}
534-
535553
miclass_t volume_data_class;
536554

537555
if (miget_data_class(m_MINCPImpl->m_Volume, &volume_data_class) < 0)
@@ -660,6 +678,9 @@ MINCImageIO::ReadImageInformation()
660678
const std::string classname(GetNameOfClass());
661679
// EncapsulateMetaData<std::string>(thisDic,ITK_InputFilterName,
662680
// classname);
681+
// preserve information if the volume was PositiveCoordinateOrientation RAS to PositiveCoordinateOrientation LPS
682+
// converted
683+
EncapsulateMetaData<bool>(thisDic, "RAStoLPS", m_RAStoLPS);
663684

664685
// store history
665686
size_t minc_history_length = 0;
@@ -944,22 +965,34 @@ MINCImageIO::WriteImageInformation()
944965
}
945966

946967
// allocating dimensions
947-
vnl_matrix<double> dircosmatrix(nDims, nDims);
948-
dircosmatrix.set_identity();
968+
vnl_matrix<double> directionCosineMatrix(nDims, nDims);
969+
directionCosineMatrix.set_identity();
949970
vnl_vector<double> origin(nDims);
950971

972+
// MINC stores direction cosines in PositiveCoordinateOrientation RAS
973+
// need to convert to PositiveCoordinateOrientation LPS for ITK
974+
vnl_matrix<double> RAS_tofrom_LPS(nDims, nDims);
975+
RAS_tofrom_LPS.set_identity();
976+
RAS_tofrom_LPS(0, 0) = -1.0;
977+
RAS_tofrom_LPS(1, 1) = -1.0;
978+
951979
for (unsigned int i = 0; i < nDims; ++i)
952980
{
953981
for (unsigned int j = 0; j < nDims; ++j)
954982
{
955-
dircosmatrix[i][j] = this->GetDirection(i)[j];
983+
directionCosineMatrix[i][j] = this->GetDirection(i)[j];
956984
}
957985
origin[i] = this->GetOrigin(i);
958986
}
959987

960-
const vnl_matrix<double> inverseDirectionCosines{ vnl_matrix_inverse<double>(dircosmatrix).as_matrix() };
988+
const vnl_matrix<double> inverseDirectionCosines{ vnl_matrix_inverse<double>(directionCosineMatrix).as_matrix() };
961989
origin *= inverseDirectionCosines; // transform to minc convention
962990

991+
992+
// Convert ITK direction cosines from PositiveCoordinateOrientation LPS to PositiveCoordinateOrientation RAS
993+
if (this->m_RAStoLPS)
994+
directionCosineMatrix *= RAS_tofrom_LPS;
995+
963996
for (unsigned int i = 0; i < nDims; ++i)
964997
{
965998
const unsigned int j = i + (nComp > 1 ? 1 : 0);
@@ -968,7 +1001,7 @@ MINCImageIO::WriteImageInformation()
9681001
{
9691002
if (k < nDims)
9701003
{
971-
dir_cos[k] = dircosmatrix[i][k];
1004+
dir_cos[k] = directionCosineMatrix[i][k];
9721005
}
9731006
else
9741007
{
@@ -988,38 +1021,32 @@ MINCImageIO::WriteImageInformation()
9881021
{
9891022
case IOComponentEnum::UCHAR:
9901023
m_MINCPImpl->m_Volume_type = MI_TYPE_UBYTE;
991-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
9921024
break;
9931025
case IOComponentEnum::CHAR:
9941026
m_MINCPImpl->m_Volume_type = MI_TYPE_BYTE;
995-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
9961027
break;
9971028
case IOComponentEnum::USHORT:
9981029
m_MINCPImpl->m_Volume_type = MI_TYPE_USHORT;
999-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
10001030
break;
10011031
case IOComponentEnum::SHORT:
10021032
m_MINCPImpl->m_Volume_type = MI_TYPE_SHORT;
1003-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
10041033
break;
10051034
case IOComponentEnum::UINT:
10061035
m_MINCPImpl->m_Volume_type = MI_TYPE_UINT;
1007-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
10081036
break;
10091037
case IOComponentEnum::INT:
10101038
m_MINCPImpl->m_Volume_type = MI_TYPE_INT;
1011-
// m_MINCPImpl->m_Volume_class=MI_CLASS_INT;
10121039
break;
10131040
// case IOComponentEnum::ULONG://TODO: make sure we are cross-platform here!
10141041
// volume_data_type=MI_TYPE_ULONG;
10151042
// break;
10161043
// case IOComponentEnum::LONG://TODO: make sure we are cross-platform here!
10171044
// volume_data_type=MI_TYPE_LONG;
10181045
// break;
1019-
case IOComponentEnum::FLOAT: // TODO: make sure we are cross-platform here!
1046+
case IOComponentEnum::FLOAT:
10201047
m_MINCPImpl->m_Volume_type = MI_TYPE_FLOAT;
10211048
break;
1022-
case IOComponentEnum::DOUBLE: // TODO: make sure we are cross-platform here!
1049+
case IOComponentEnum::DOUBLE:
10231050
m_MINCPImpl->m_Volume_type = MI_TYPE_DOUBLE;
10241051
break;
10251052
default:
@@ -1059,7 +1086,6 @@ MINCImageIO::WriteImageInformation()
10591086
if (ExposeMetaData<std::string>(thisDic, "dimension_order", dimension_order))
10601087
{
10611088
// the format should be ((+|-)(X|Y|Z|V|T))*
1062-
// std::cout<<"Restoring original dimension order:"<<dimension_order<<std::endl;
10631089
if (dimension_order.length() == (minc_dimensions * 2))
10641090
{
10651091
dimorder_good = true;
@@ -1296,6 +1322,13 @@ MINCImageIO::WriteImageInformation()
12961322
// TODO: figure out what to do with it
12971323
}
12981324
}
1325+
1326+
// preserve information of MINC PositiveCoordinateOrientation RAS to ITK PositiveCoordinateOrientation LPS conversion
1327+
{
1328+
int tmp = (int)this->m_RAStoLPS;
1329+
miset_attr_values(m_MINCPImpl->m_Volume, MI_TYPE_INT, "itk", "RAStoLPS", 1, &tmp);
1330+
}
1331+
12991332
mifree_volume_props(hprops);
13001333
}
13011334

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
#ifndef itkMINCImageIOConfigurePrivate_h
19+
#define itkMINCImageIOConfigurePrivate_h
20+
21+
// This file is intended to hold preprocessor macros only used internally by
22+
// IO/MINC module and should not be installed.
23+
24+
// Convert from PositiveCoordinateOrientation RAS to PositiveCoordinateOrientation LPS by default
25+
#cmakedefine01 ITK_MINC_IO_RAS_TO_LPS
26+
27+
#endif // itkMINCImageIOConfigurePrivate_h

0 commit comments

Comments
 (0)