Skip to content

Commit 0ba9197

Browse files
committed
BUG: Fix case where DICOM series order has negative spacing
If the spacing between slices is negative, then direction cosine matrix was not correct with reguards to the sign.
1 parent c69a657 commit 0ba9197

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

Modules/IO/GDCM/test/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,10 @@ addcompliancetest(raw-YBR_FULL_422)
439439
addcompliancetest(RLE-RGB)
440440
addcompliancetest(HTJ2K-YBR_ICT Lily_full.mha)
441441
addcompliancetest(HTJ2K-YBR_RCT Lily_full.mha)
442+
443+
444+
set(ITKGDCMImageIOGTests itkGDCMImageIOGTest.cxx
445+
)
446+
creategoogletestdriver(ITKGDCMImageIO "${ITKIOGDCM-Test_LIBRARIES}" "${ITKGDCMImageIOGTests}")
447+
448+
target_compile_definitions(ITKGDCMImageIOGTestDriver PRIVATE "-DITK_TEST_OUTPUT_DIR=${ITK_TEST_OUTPUT_DIR}")
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
19+
#include "itkImage.h"
20+
#include "itkImageFileWriter.h"
21+
#include "itkGDCMImageIO.h"
22+
#include "itkMetaDataObject.h"
23+
#include "itkGTest.h"
24+
#include "itksys/SystemTools.hxx"
25+
#include "itkImageSeriesReader.h"
26+
#include <string>
27+
#include <vector>
28+
29+
30+
#define _STRING(s) #s
31+
#define TOSTRING(s) std::string(_STRING(s))
32+
33+
namespace
34+
{
35+
36+
struct ITKGDCMImageIO : public ::testing::Test
37+
{
38+
void
39+
SetUp() override
40+
{
41+
itksys::SystemTools::MakeDirectory(m_TempDir);
42+
}
43+
44+
void
45+
TearDown() override
46+
{
47+
itksys::SystemTools::RemoveADirectory(m_TempDir);
48+
}
49+
50+
std::string m_TempDir = TOSTRING(ITK_TEST_OUTPUT_DIR) + "/ITKGDCMImageIO";
51+
};
52+
53+
} // namespace
54+
55+
TEST_F(ITKGDCMImageIO, ReadSlicesNegativeSpacing)
56+
{
57+
// Tests the case where the slices are written with negative space between slices,
58+
// and the reader should be able to read them back correctly with negative in the direction cosine matrix.
59+
60+
61+
using PixelType = uint16_t;
62+
constexpr unsigned int Dimension = 3;
63+
using ImageType = itk::Image<PixelType, Dimension>;
64+
using SliceType = itk::Image<PixelType, 2>;
65+
66+
// Create a 3D image of size [2,2,3] and fill with 100
67+
ImageType::SizeType size = { { 2, 2, 3 } };
68+
ImageType::RegionType region;
69+
region.SetSize(size);
70+
auto image = ImageType::New();
71+
image->SetRegions(region);
72+
image->Allocate();
73+
image->FillBuffer(100);
74+
75+
// DICOM meta-data values
76+
const std::vector<std::string> positions = { "-216.500\\-216.500\\70.000",
77+
"-216.500\\-216.500\\-187.500",
78+
"-216.500\\-216.500\\-445.000" };
79+
const std::string orientation = "1.000000\\0.000000\\0.000000\\0.000000\\1.000000\\0.000000";
80+
const std::string pixelSpacing = "1.1\\1.2";
81+
82+
std::string seriesDir = m_TempDir + "/" + ::testing::UnitTest::GetInstance()->current_test_info()->name();
83+
itksys::SystemTools::MakeDirectory(seriesDir);
84+
85+
using WriterType = itk::ImageFileWriter<SliceType>;
86+
using ImageIOType = itk::GDCMImageIO;
87+
88+
for (unsigned int i = 0; i < size[2]; ++i)
89+
{
90+
// Extract 2D slice
91+
SliceType::RegionType sliceRegion;
92+
SliceType::SizeType sliceSize = { { size[0], size[1] } };
93+
SliceType::IndexType sliceIndex = { { 0, 0 } };
94+
sliceRegion.SetSize(sliceSize);
95+
sliceRegion.SetIndex(sliceIndex);
96+
97+
SliceType::Pointer slice = SliceType::New();
98+
slice->SetRegions(sliceRegion);
99+
slice->Allocate();
100+
101+
slice->FillBuffer(5 + i);
102+
103+
// Set image spacing
104+
ImageType::SpacingType spacing{ { 1.1, 1.2, 257.5 } };
105+
image->SetSpacing(spacing);
106+
107+
108+
// Set required DICOM tags
109+
itk::MetaDataDictionary & dict = slice->GetMetaDataDictionary();
110+
itk::EncapsulateMetaData<std::string>(dict, "0020|0032", positions[i]);
111+
itk::EncapsulateMetaData<std::string>(dict, "0020|0037", orientation);
112+
itk::EncapsulateMetaData<std::string>(dict, "0028|0030", pixelSpacing);
113+
114+
// Write the slice
115+
auto writer = WriterType::New();
116+
auto gdcmIO = ImageIOType::New();
117+
std::ostringstream filename;
118+
filename << seriesDir << "/slice_" << (i + 1) << ".dcm";
119+
writer->SetFileName(filename.str());
120+
writer->SetInput(slice);
121+
writer->SetImageIO(gdcmIO);
122+
ASSERT_NO_THROW(writer->Update());
123+
}
124+
125+
std::vector<std::string> fileNames;
126+
for (unsigned int i = 0; i < size[2]; ++i)
127+
{
128+
std::ostringstream filename;
129+
filename << seriesDir << "/slice_" << (i + 1) << ".dcm";
130+
fileNames.push_back(filename.str());
131+
}
132+
133+
// Reader the slices back
134+
using SeriesReaderType = itk::ImageSeriesReader<ImageType>;
135+
auto seriesReader = SeriesReaderType::New();
136+
seriesReader->SetFileNames(fileNames);
137+
auto gdcmIO = ImageIOType::New();
138+
seriesReader->SetImageIO(gdcmIO);
139+
ASSERT_NO_THROW(seriesReader->Update());
140+
auto outputImage = seriesReader->GetOutput();
141+
142+
// Check the spacing, origin, and direction
143+
ImageType::SpacingType spacing = outputImage->GetSpacing();
144+
ImageType::PointType origin = outputImage->GetOrigin();
145+
ImageType::DirectionType direction = outputImage->GetDirection();
146+
ImageType::DirectionType expectedDirection;
147+
expectedDirection.SetIdentity();
148+
expectedDirection[2][2] = -1.0; // Set the z-direction to -1.0
149+
ImageType::PointType expectedOrigin{ { -216.5, -216.5, 70.0 } };
150+
ImageType::SpacingType expectedSpacing{ { 1.1, 1.2, 257.5 } };
151+
152+
std::cout << "Spacing: " << spacing << std::endl;
153+
154+
EXPECT_EQ(spacing, expectedSpacing);
155+
EXPECT_EQ(origin, expectedOrigin);
156+
EXPECT_EQ(direction, expectedDirection);
157+
}

Modules/IO/ImageBase/include/itkImageSeriesReader.hxx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,14 @@ ImageSeriesReader<TOutputImage>::GenerateOutputInformation()
184184
{
185185
spacing[this->m_NumberOfDimensionsInImage] = dirNnorm / (numberOfFiles - 1);
186186
this->m_SpacingDefined = true;
187-
if (!m_ForceOrthogonalDirection)
187+
if (m_ForceOrthogonalDirection)
188+
{
189+
for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j)
190+
{
191+
direction[j][this->m_NumberOfDimensionsInImage] *= std::signbit(dirN[j]) ? -1.0 : 1.0;
192+
}
193+
}
194+
else
188195
{
189196
for (unsigned int j = 0; j < TOutputImage::ImageDimension; ++j)
190197
{

0 commit comments

Comments
 (0)