1 /*===================================================================
3 The Medical Imaging Interaction Toolkit (MITK)
5 Copyright (c) German Cancer Research Center,
6 Division of Medical and Biological Informatics.
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 See LICENSE.txt or http://www.mitk.org for details.
15 ===================================================================*/
17 #ifndef MITKDICOMSERIESREADER_TXX_
18 #define MITKDICOMSERIESREADER_TXX_
20 #include <mitkDicomSeriesReader.h>
22 #include <itkImageSeriesReader.h>
23 #include <mitkProperties.h>
25 #include <itkAffineTransform.h>
26 #include <itkLinearInterpolateImageFunction.h>
27 #include <itkResampleImageFilter.h>
28 #include <itkTimeProbesCollectorBase.h>
32 #include <mitkImage.h>
36 template <typename PixelType>
37 Image::Pointer DicomSeriesReader::LoadDICOMByITK4D(std::list<StringContainer> &imageBlocks,
38 ImageBlockDescriptor imageBlockDescriptor,
40 const GantryTiltInformation &tiltInfo,
41 DcmIoType::Pointer &io,
42 CallbackCommand *command,
43 Image::Pointer preLoadedImageBlock)
45 mitk::Image::Pointer image = mitk::Image::New();
47 // It is 3D+t! Read it and store into mitk image
48 typedef itk::Image<PixelType, 4> ImageType;
49 typedef itk::ImageSeriesReader<ImageType> ReaderType;
51 typename ReaderType::Pointer reader = ReaderType::New();
53 reader->SetImageIO(io);
54 reader->ReverseOrderOff();
58 reader->AddObserver(itk::ProgressEvent(), command);
61 if (preLoadedImageBlock.IsNull())
63 reader->SetFileNames(imageBlocks.front());
67 typename ImageType::Pointer readVolume = reader->GetOutput();
68 // if we detected that the images are from a tilted gantry acquisition, we need to push some pixels into the right
72 readVolume = InPlaceFixUpTiltedGeometry(reader->GetOutput(), tiltInfo);
75 unsigned int volume_count = imageBlocks.size();
76 image->InitializeByItk(readVolume.GetPointer(), 1, volume_count);
77 image->SetImportVolume(readVolume->GetBufferPointer(), 0u);
79 FixSpacingInformation(image, imageBlockDescriptor);
83 StringContainer fakeList;
84 fakeList.push_back(imageBlocks.front().front());
85 reader->SetFileNames(fakeList); // only ONE first filename to get MetaDataDictionary
87 image = preLoadedImageBlock;
90 gdcm::Scanner scanner;
91 ScanForSliceInformation(imageBlockDescriptor.GetFilenames(), scanner);
92 CopyMetaDataToImageProperties(imageBlocks, scanner.GetMappings(), io, imageBlockDescriptor, image);
94 MITK_DEBUG << "Volume dimension: [" << image->GetDimension(0) << ", " << image->GetDimension(1) << ", "
95 << image->GetDimension(2) << ", " << image->GetDimension(3) << "]";
97 MITK_DEBUG << "Volume spacing: [" << image->GetGeometry()->GetSpacing()[0] << ", "
98 << image->GetGeometry()->GetSpacing()[1] << ", " << image->GetGeometry()->GetSpacing()[2] << "]";
100 if (preLoadedImageBlock.IsNull())
102 unsigned int act_volume = 1u;
103 for (auto df_it = ++imageBlocks.begin(); df_it != imageBlocks.end(); ++df_it)
105 reader->SetFileNames(*df_it);
107 typename ImageType::Pointer readVolume = reader->GetOutput();
111 readVolume = InPlaceFixUpTiltedGeometry(reader->GetOutput(), tiltInfo);
114 image->SetImportVolume(readVolume->GetBufferPointer(), act_volume++);
121 template <typename PixelType>
122 Image::Pointer DicomSeriesReader::LoadDICOMByITK(const StringContainer &filenames,
124 const GantryTiltInformation &tiltInfo,
125 DcmIoType::Pointer &io,
126 CallbackCommand *command,
127 Image::Pointer preLoadedImageBlock)
129 /******** Normal Case, 3D (also for GDCM < 2 usable) ***************/
130 mitk::Image::Pointer image = mitk::Image::New();
132 typedef itk::Image<PixelType, 3> ImageType;
133 typedef itk::ImageSeriesReader<ImageType> ReaderType;
135 io = DcmIoType::New();
136 typename ReaderType::Pointer reader = ReaderType::New();
138 reader->SetImageIO(io);
139 reader->ReverseOrderOff();
143 reader->AddObserver(itk::ProgressEvent(), command);
146 if (preLoadedImageBlock.IsNull())
148 reader->SetFileNames(filenames);
150 typename ImageType::Pointer readVolume = reader->GetOutput();
152 // if we detected that the images are from a tilted gantry acquisition, we need to push some pixels into the right
156 readVolume = InPlaceFixUpTiltedGeometry(reader->GetOutput(), tiltInfo);
159 image->InitializeByItk(readVolume.GetPointer());
160 image->SetImportVolume(readVolume->GetBufferPointer());
164 image = preLoadedImageBlock;
165 StringContainer fakeList;
166 fakeList.push_back(filenames.front());
167 reader->SetFileNames(fakeList); // we always need to load at least one file to get the MetaDataDictionary
171 MITK_DEBUG << "Volume dimension: [" << image->GetDimension(0) << ", " << image->GetDimension(1) << ", "
172 << image->GetDimension(2) << "]";
174 MITK_DEBUG << "Volume spacing: [" << image->GetGeometry()->GetSpacing()[0] << ", "
175 << image->GetGeometry()->GetSpacing()[1] << ", " << image->GetGeometry()->GetSpacing()[2] << "]";
180 template <typename ImageType>
181 typename ImageType::Pointer DicomSeriesReader::InPlaceFixUpTiltedGeometry(ImageType *input,
182 const GantryTiltInformation &tiltInfo)
184 typedef itk::ResampleImageFilter<ImageType, ImageType> ResampleFilterType;
185 typename ResampleFilterType::Pointer resampler = ResampleFilterType::New();
186 resampler->SetInput(input);
189 Transform for a point is
190 - transform from actual position to index coordinates
191 - apply a shear that undoes the gantry tilt
192 - transform back into world coordinates
194 Anybody who does this in a simpler way: don't forget to write up how and why your solution works
196 typedef itk::ScalableAffineTransform<double, ImageType::ImageDimension> TransformType;
197 typename TransformType::Pointer transformShear = TransformType::New();
200 - apply a shear and spacing correction to the image block that corrects the ITK reader's error
201 - ITK ignores the shear and loads slices into an orthogonal volume
202 - ITK calculates the spacing from the origin distance, which is more than the actual spacing with gantry tilt
205 - we have calculated some information in tiltInfo:
206 - the shift in Y direction that is added with each additional slice is the most important information
207 - the Y-shift is calculated in mm world coordinates
208 - we apply a shearing transformation to the ITK-read image volume
209 - to do this locally,
210 - we transform the image volume back to origin and "normal" orientation by applying the inverse of its
212 (this brings us into the image's "index coordinate" system)
213 - we apply a shear with the Y-shift factor put into a unit transform at row 1, col 2
214 - we transform the image volume back to its actual position (from index to world coordinates)
215 - we lastly apply modify the image spacing in z direction by replacing this number with the correctly calulcated
219 ScalarType factor = tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() / input->GetSpacing()[1];
220 // row 1, column 2 corrects shear in parallel to Y axis, proportional to distance in Z direction
221 transformShear->Shear(1, 2, factor);
223 typename TransformType::Pointer imageIndexToWorld = TransformType::New();
224 imageIndexToWorld->SetOffset(input->GetOrigin().GetVectorFromOrigin());
226 typename TransformType::MatrixType indexToWorldMatrix;
227 indexToWorldMatrix = input->GetDirection();
229 typename ImageType::DirectionType scale;
230 for (unsigned int i = 0; i < ImageType::ImageDimension; i++)
232 scale[i][i] = input->GetSpacing()[i];
234 indexToWorldMatrix *= scale;
236 imageIndexToWorld->SetMatrix(indexToWorldMatrix);
238 typename TransformType::Pointer imageWorldToIndex = TransformType::New();
239 imageIndexToWorld->GetInverse(imageWorldToIndex);
241 typename TransformType::Pointer gantryTiltCorrection = TransformType::New();
242 gantryTiltCorrection->Compose(imageWorldToIndex);
243 gantryTiltCorrection->Compose(transformShear);
244 gantryTiltCorrection->Compose(imageIndexToWorld);
246 resampler->SetTransform(gantryTiltCorrection);
248 typedef itk::LinearInterpolateImageFunction<ImageType, double> InterpolatorType;
249 typename InterpolatorType::Pointer interpolator = InterpolatorType::New();
250 resampler->SetInterpolator(interpolator);
252 This would be the right place to invent a meaningful value for positions outside of the image.
253 For CT, HU -1000 might be meaningful, but a general solution seems not possible. Even for CT,
254 -1000 would only look natural for many not all images.
257 // TODO use (0028,0120) Pixel Padding Value if present
258 resampler->SetDefaultPixelValue(itk::NumericTraits<typename ImageType::PixelType>::min());
260 // adjust size in Y direction! (maybe just transform the outer last pixel to see how much space we would need
262 resampler->SetOutputParametersFromImage(input); // we basically need the same image again, just sheared
264 // if tilt positive, then we need additional pixels BELOW origin, otherwise we need pixels behind the end of the
267 // in any case we need more size to accomodate shifted slices
268 typename ImageType::SizeType largerSize =
269 resampler->GetSize(); // now the resampler already holds the input image's size.
270 largerSize[1] += static_cast<typename ImageType::SizeType::SizeValueType>(
271 tiltInfo.GetTiltCorrectedAdditionalSize() / input->GetSpacing()[1] + 2.0);
272 resampler->SetSize(largerSize);
274 // in SOME cases this additional size is below/behind origin
275 if (tiltInfo.GetMatrixCoefficientForCorrectionInWorldCoordinates() > 0.0)
277 typename ImageType::DirectionType imageDirection = input->GetDirection();
279 yDirection[0] = imageDirection[0][1];
280 yDirection[1] = imageDirection[1][1];
281 yDirection[2] = imageDirection[2][1];
282 yDirection.Normalize();
284 typename ImageType::PointType shiftedOrigin;
285 shiftedOrigin = input->GetOrigin();
287 // add some pixels to make everything fit
288 shiftedOrigin[0] -= yDirection[0] * (tiltInfo.GetTiltCorrectedAdditionalSize() + 1.0 * input->GetSpacing()[1]);
289 shiftedOrigin[1] -= yDirection[1] * (tiltInfo.GetTiltCorrectedAdditionalSize() + 1.0 * input->GetSpacing()[1]);
290 shiftedOrigin[2] -= yDirection[2] * (tiltInfo.GetTiltCorrectedAdditionalSize() + 1.0 * input->GetSpacing()[1]);
292 resampler->SetOutputOrigin(shiftedOrigin);
296 typename ImageType::Pointer result = resampler->GetOutput();
298 // ImageSeriesReader calculates z spacing as the distance between the first two origins.
299 // This is not correct in case of gantry tilt, so we set our calculated spacing.
300 typename ImageType::SpacingType correctedSpacing = result->GetSpacing();
301 correctedSpacing[2] = tiltInfo.GetRealZSpacing();
302 result->SetSpacing(correctedSpacing);