Medical Imaging Interaction Toolkit  2018.4.99-389bf124
Medical Imaging Interaction Toolkit
mitkITKDICOMSeriesReaderHelper.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 //#define MBILOG_ENABLE_DEBUG
14 
15 #include <dcmtk/dcmdata/dcvrdt.h>
16 #include <dcmtk/ofstd/ofstd.h>
17 
18 #define BOOST_DATE_TIME_NO_LIB
19 //Prevent unnecessary/unwanted auto link in this compilation when activating boost libraries in the MITK superbuild
20 //It is necessary because BOOST_ALL_DYN_LINK overwrites BOOST_DATE_TIME_NO_LIB
21 #if defined(BOOST_ALL_DYN_LINK)
22  #undef BOOST_ALL_DYN_LINK
23 #endif
24 
25 #include <boost/date_time/posix_time/posix_time_types.hpp>
26 
29 
32 
33 #include "dcmtk/dcmdata/dcvrda.h"
34 
35 
39 
40 #define switch3DCase( IOType, T ) \
41  case IOType: \
42  return LoadDICOMByITK<T>( filenames, correctTilt, tiltInfo, io );
43 
44 bool mitk::ITKDICOMSeriesReaderHelper::CanHandleFile( const std::string& filename )
45 {
46  MITK_DEBUG << "ITKDICOMSeriesReaderHelper::CanHandleFile " << filename;
47  itk::GDCMImageIO::Pointer tester = itk::GDCMImageIO::New();
48  return tester->CanReadFile( filename.c_str() );
49 }
50 
52  bool correctTilt,
53  const GantryTiltInformation& tiltInfo )
54 {
55  if ( filenames.empty() )
56  {
58  << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic.";
59  return nullptr; // this is not actually an error but the result is very simple
60  }
61 
62  typedef itk::GDCMImageIO DcmIoType;
63  DcmIoType::Pointer io = DcmIoType::New();
64 
65  try
66  {
67  if ( io->CanReadFile( filenames.front().c_str() ) )
68  {
69  io->SetFileName( filenames.front().c_str() );
70  io->ReadImageInformation();
71 
72  if ( io->GetPixelType() == itk::ImageIOBase::SCALAR )
73  {
74  switch ( io->GetComponentType() )
75  {
76  switch3DCase(DcmIoType::UCHAR, unsigned char) switch3DCase(DcmIoType::CHAR, char) switch3DCase(
77  DcmIoType::USHORT, unsigned short) switch3DCase(DcmIoType::SHORT, short)
78  switch3DCase(DcmIoType::UINT, unsigned int) switch3DCase(DcmIoType::INT, int) switch3DCase(
79  DcmIoType::ULONG, long unsigned int) switch3DCase(DcmIoType::LONG, long int)
80  switch3DCase(DcmIoType::FLOAT, float) switch3DCase(DcmIoType::DOUBLE, double) default
81  : MITK_ERROR
82  << "Found unsupported DICOM scalar pixel type: (enum value) "
83  << io->GetComponentType();
84  }
85  }
86  else if ( io->GetPixelType() == itk::ImageIOBase::RGB )
87  {
88  switch ( io->GetComponentType() )
89  {
90  switch3DCase(DcmIoType::UCHAR, itk::RGBPixel<unsigned char>) switch3DCase(
91  DcmIoType::CHAR, itk::RGBPixel<char>) switch3DCase(DcmIoType::USHORT,
92  itk::RGBPixel<unsigned short>)
93  switch3DCase(DcmIoType::SHORT, itk::RGBPixel<short>) switch3DCase(
94  DcmIoType::UINT, itk::RGBPixel<unsigned int>) switch3DCase(DcmIoType::INT, itk::RGBPixel<int>)
95  switch3DCase(DcmIoType::ULONG, itk::RGBPixel<long unsigned int>)
96  switch3DCase(DcmIoType::LONG, itk::RGBPixel<long int>) switch3DCase(
97  DcmIoType::FLOAT, itk::RGBPixel<float>) switch3DCase(DcmIoType::DOUBLE,
98  itk::RGBPixel<double>) default
99  : MITK_ERROR
100  << "Found unsupported DICOM scalar pixel type: (enum value) "
101  << io->GetComponentType();
102  }
103  }
104 
105  MITK_ERROR << "Unsupported DICOM pixel type";
106  return nullptr;
107  }
108  }
109  catch ( const itk::MemoryAllocationError& e )
110  {
111  MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what();
112  }
113  catch ( const std::exception& e )
114  {
115  MITK_ERROR << "Error encountered when loading DICOM series:" << e.what();
116  }
117  catch ( ... )
118  {
119  MITK_ERROR << "Unspecified error encountered when loading DICOM series.";
120  }
121 
122  return nullptr;
123 }
124 
125 #define switch3DnTCase( IOType, T ) \
126  case IOType: \
127  return LoadDICOMByITK3DnT<T>( filenamesLists, correctTilt, tiltInfo, io );
128 
130  bool correctTilt,
131  const GantryTiltInformation& tiltInfo )
132 {
133  if ( filenamesLists.empty() || filenamesLists.front().empty() )
134  {
135  MITK_DEBUG
136  << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic.";
137  return nullptr; // this is not actually an error but the result is very simple
138  }
139 
140  typedef itk::GDCMImageIO DcmIoType;
141  DcmIoType::Pointer io = DcmIoType::New();
142 
143  try
144  {
145  if ( io->CanReadFile( filenamesLists.front().front().c_str() ) )
146  {
147  io->SetFileName( filenamesLists.front().front().c_str() );
148  io->ReadImageInformation();
149 
150  if ( io->GetPixelType() == itk::ImageIOBase::SCALAR )
151  {
152  switch ( io->GetComponentType() )
153  {
154  switch3DnTCase(DcmIoType::UCHAR, unsigned char) switch3DnTCase(DcmIoType::CHAR, char)
155  switch3DnTCase(DcmIoType::USHORT, unsigned short) switch3DnTCase(
156  DcmIoType::SHORT, short) switch3DnTCase(DcmIoType::UINT,
157  unsigned int) switch3DnTCase(DcmIoType::INT, int)
158  switch3DnTCase(DcmIoType::ULONG, long unsigned int) switch3DnTCase(DcmIoType::LONG, long int)
159  switch3DnTCase(DcmIoType::FLOAT, float) switch3DnTCase(DcmIoType::DOUBLE, double) default
160  : MITK_ERROR
161  << "Found unsupported DICOM scalar pixel type: (enum value) "
162  << io->GetComponentType();
163  }
164  }
165  else if ( io->GetPixelType() == itk::ImageIOBase::RGB )
166  {
167  switch ( io->GetComponentType() )
168  {
169  switch3DnTCase(DcmIoType::UCHAR, itk::RGBPixel<unsigned char>)
170  switch3DnTCase(DcmIoType::CHAR, itk::RGBPixel<char>) switch3DnTCase(
171  DcmIoType::USHORT, itk::RGBPixel<unsigned short>) switch3DnTCase(DcmIoType::SHORT,
172  itk::RGBPixel<short>)
173  switch3DnTCase(DcmIoType::UINT, itk::RGBPixel<unsigned int>) switch3DnTCase(
174  DcmIoType::INT, itk::RGBPixel<int>) switch3DnTCase(DcmIoType::ULONG,
175  itk::RGBPixel<long unsigned int>)
176  switch3DnTCase(DcmIoType::LONG, itk::RGBPixel<long int>) switch3DnTCase(
177  DcmIoType::FLOAT, itk::RGBPixel<float>) switch3DnTCase(DcmIoType::DOUBLE,
178  itk::RGBPixel<double>) default
179  : MITK_ERROR
180  << "Found unsupported DICOM scalar pixel type: (enum value) "
181  << io->GetComponentType();
182  }
183  }
184 
185  MITK_ERROR << "Unsupported DICOM pixel type";
186  return nullptr;
187  }
188  }
189  catch ( const itk::MemoryAllocationError& e )
190  {
191  MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what();
192  }
193  catch ( const std::exception& e )
194  {
195  MITK_ERROR << "Error encountered when loading DICOM series:" << e.what();
196  }
197  catch ( ... )
198  {
199  MITK_ERROR << "Unspecified error encountered when loading DICOM series.";
200  }
201 
202  return nullptr;
203 }
204 
205 bool ConvertDICOMDateTimeString( const std::string& dateString,
206  const std::string& timeString,
207  OFDateTime& time )
208 {
209  OFString content( timeString.c_str() );
210 
211  if ( !dateString.empty() )
212  {
213  content = OFString( dateString.c_str() ).append( content );
214  }
215  else
216  {
217  // This is a workaround for DICOM data that has an AquisitionTime but no AquisitionDate.
218  // In this case, we use the current date. That's not really nice, but is absolutely OK
219  // as we're only interested in the time anyways...
220  OFString currentDate;
221  DcmDate::getCurrentDate( currentDate );
222  content = currentDate.append( content );
223  }
224 
225  const OFCondition result = DcmDateTime::getOFDateTimeFromString( content, time );
226 
227  return result.good();
228 }
229 
230 boost::posix_time::ptime ConvertOFDateTimeToPTime( const OFDateTime& time )
231 {
232  const boost::gregorian::date boostDate(
233  time.getDate().getYear(), time.getDate().getMonth(), time.getDate().getDay() );
234 
235  const boost::posix_time::time_duration boostTime =
236  boost::posix_time::hours( time.getTime().getHour() )
237  + boost::posix_time::minutes( time.getTime().getMinute() )
238  + boost::posix_time::seconds( static_cast<int>(time.getTime().getSecond()) )
239  + boost::posix_time::milliseconds( time.getTime().getMilliSecond() );
240 
241  boost::posix_time::ptime result( boostDate, boostTime );
242 
243  return result;
244 }
245 
246 OFDateTime GetLowerDateTime( const OFDateTime& time1, const OFDateTime& time2 )
247 {
248  OFDateTime result = time1;
249 
250  if ( ( time2.getDate() < time1.getDate() )
251  || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() < time1.getTime() ) ) )
252  {
253  result = time2;
254  }
255 
256  return result;
257 }
258 
259 OFDateTime GetUpperDateTime( const OFDateTime& time1, const OFDateTime& time2 )
260 {
261  OFDateTime result = time1;
262 
263  if ( ( time2.getDate() > time1.getDate() )
264  || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() > time1.getTime() ) ) )
265  {
266  result = time2;
267  }
268 
269  return result;
270 }
271 
272 double ComputeMiliSecDuration( const OFDateTime& start, const OFDateTime& stop )
273 {
274  const boost::posix_time::ptime startTime = ConvertOFDateTimeToPTime( start );
275  const boost::posix_time::ptime stopTime = ConvertOFDateTimeToPTime( stop );
276 
277  ::boost::posix_time::time_duration duration = stopTime - startTime;
278 
279  return duration.total_milliseconds();
280 }
281 
282 bool mitk::ITKDICOMSeriesReaderHelper::ExtractDateTimeBoundsAndTriggerOfTimeStep(
283  const StringContainer& filenamesOfTimeStep, DateTimeBounds& bounds, TimeBounds& triggerBounds)
284 {
285  DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New();
286  filescanner->SetInputFiles(filenamesOfTimeStep);
287  filescanner->AddTag(AcquisitionDateTag);
288  filescanner->AddTag(AcquisitionTimeTag);
289  filescanner->AddTag(TriggerTimeTag);
290  filescanner->Scan();
291 
292  const DICOMDatasetAccessingImageFrameList frameList = filescanner->GetFrameInfoList();
293 
294  bool result = false;
295  bool firstAq = true;
296  bool firstTr = true;
297 
298  triggerBounds = TimeBounds(0.0);
299 
300  for (auto pos = frameList.cbegin(); pos != frameList.cend(); ++pos)
301  {
302  const std::string aqDateStr = (*pos)->GetTagValueAsString(AcquisitionDateTag).value;
303  const std::string aqTimeStr = (*pos)->GetTagValueAsString(AcquisitionTimeTag).value;
304  const std::string triggerTimeStr = (*pos)->GetTagValueAsString(TriggerTimeTag).value;
305 
306  OFDateTime aqDateTime;
307  const bool convertAqResult = ConvertDICOMDateTimeString(aqDateStr, aqTimeStr, aqDateTime);
308 
309  OFBool convertTriggerResult;
310  mitk::ScalarType triggerTime = OFStandard::atof(triggerTimeStr.c_str(), &convertTriggerResult);
311 
312  if (convertAqResult)
313  {
314  if (firstAq)
315  {
316  bounds[0] = aqDateTime;
317  bounds[1] = aqDateTime;
318  firstAq = false;
319  }
320  else
321  {
322  bounds[0] = GetLowerDateTime(bounds[0], aqDateTime);
323  bounds[1] = GetUpperDateTime(bounds[1], aqDateTime);
324  }
325  result = true;
326  }
327 
328  if (convertTriggerResult)
329  {
330  if (firstTr)
331  {
332  triggerBounds[0] = triggerTime;
333  triggerBounds[1] = triggerTime;
334  firstTr = false;
335  }
336  else
337  {
338  triggerBounds[0] = std::min(triggerBounds[0], triggerTime);
339  triggerBounds[1] = std::max(triggerBounds[1], triggerTime);
340  }
341  result = true;
342  }
343  }
344 
345  return result;
346 };
347 
348 bool mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeStep(
349  const StringContainer& filenamesOfTimeStep, TimeBounds& bounds, const OFDateTime& baselineDateTime )
350 {
351  DateTimeBounds aqDTBounds;
352  TimeBounds triggerBounds;
353 
354  bool result = ExtractDateTimeBoundsAndTriggerOfTimeStep(filenamesOfTimeStep, aqDTBounds, triggerBounds);
355 
356  mitk::ScalarType lowerBound = ComputeMiliSecDuration( baselineDateTime, aqDTBounds[0] );
357  mitk::ScalarType upperBound = ComputeMiliSecDuration( baselineDateTime, aqDTBounds[1] );
358  if ( lowerBound < mitk::eps || upperBound < mitk::eps )
359  {
360  lowerBound = triggerBounds[0];
361  upperBound = triggerBounds[1];
362  }
363 
364  bounds[0] = lowerBound;
365  bounds[1] = upperBound;
366 
367  return result;
368 };
369 
370 mitk::ITKDICOMSeriesReaderHelper::TimeBoundsList
371  mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeSteps(
372  const StringContainerList& filenamesOfTimeSteps )
373 {
374  TimeBoundsList result;
375 
376  OFDateTime baseLine;
377 
378  // extract the timebounds
379  DateTimeBounds baselineDateTimeBounds;
380  TimeBounds triggerBounds;
381  auto pos = filenamesOfTimeSteps.cbegin();
382  ExtractDateTimeBoundsAndTriggerOfTimeStep(*pos, baselineDateTimeBounds, triggerBounds);
383  baseLine = baselineDateTimeBounds[0];
384 
385  // timebounds for baseline is 0
386  TimeBounds bounds( 0.0 );
387  result.push_back( bounds );
388 
389 
390  // iterate over the remaining timesteps
391  for ( ++pos;
392  pos != filenamesOfTimeSteps.cend();
393  ++pos )
394  {
395  TimeBounds bounds( 0.0 );
396  TimeBounds dateTimeBounds;
397 
398  // extract the timebounds relative to the baseline
399  if ( ExtractTimeBoundsOfTimeStep( *pos, dateTimeBounds, baseLine ) )
400  {
401 
402  bounds[0] = dateTimeBounds[0];
403  bounds[1] = dateTimeBounds[1];
404  }
405 
406  result.push_back( bounds );
407  }
408 
409  return result;
410 };
411 
413  mitk::ITKDICOMSeriesReaderHelper::GenerateTimeGeometry( const BaseGeometry* templateGeometry,
414  const TimeBoundsList& boundsList )
415 {
416  TimeGeometry::Pointer timeGeometry;
417 
418  double check = 0.0;
419  const auto boundListSize = boundsList.size();
420  for ( std::size_t pos = 0; pos < boundListSize; ++pos )
421  {
422  check += boundsList[pos][0];
423  check += boundsList[pos][1];
424  }
425 
426  if ( check < mitk::eps )
427  { // if all bounds are zero we assume that the bounds could not be correctly determined
428  // and as a fallback generate a time geometry in the old mitk style
430  newTimeGeometry->Initialize( templateGeometry, boundListSize );
431  timeGeometry = newTimeGeometry.GetPointer();
432  }
433  else
434  {
436  newTimeGeometry->ClearAllGeometries();
437  newTimeGeometry->ReserveSpaceForGeometries( boundListSize );
438 
439  for ( std::size_t pos = 0; pos < boundListSize; ++pos )
440  {
441  TimeBounds bounds = boundsList[pos];
442  if ( pos + 1 < boundListSize )
443  { //Currently we do not explicitly support "gaps" in the time coverage
444  //thus we set the max time bound of a time step to the min time bound
445  //of its successor.
446  bounds[1] = boundsList[pos + 1][0];
447  }
448 
449  newTimeGeometry->AppendNewTimeStepClone(templateGeometry, bounds[0], bounds[1]);
450  }
451  timeGeometry = newTimeGeometry.GetPointer();
452  }
453 
454  return timeGeometry;
455 };
OFDateTime GetUpperDateTime(const OFDateTime &time1, const OFDateTime &time2)
itk::FixedArray< ScalarType, 2 > TimeBounds
Standard typedef for time-bounds.
#define MITK_ERROR
Definition: mitkLogMacros.h:20
static bool CanHandleFile(const std::string &filename)
double ScalarType
Representation of a DICOM tag.
Definition: mitkDICOMTag.h:32
Image::Pointer Load(const StringContainer &filenames, bool correctTilt, const GantryTiltInformation &tiltInfo)
#define MITK_DEBUG
Definition: mitkLogMacros.h:22
static Pointer New()
OFDateTime GetLowerDateTime(const OFDateTime &time1, const OFDateTime &time2)
Image::Pointer Load3DnT(const StringContainerList &filenamesLists, bool correctTilt, const GantryTiltInformation &tiltInfo)
#define switch3DnTCase(IOType, T)
bool ConvertDICOMDateTimeString(const std::string &dateString, const std::string &timeString, OFDateTime &time)
static T max(T x, T y)
Definition: svm.cpp:56
std::list< StringContainer > StringContainerList
itk::SmartPointer< Self > Pointer
static T min(T x, T y)
Definition: svm.cpp:53
std::vector< DICOMDatasetAccessingImageFrameInfo::Pointer > DICOMDatasetAccessingImageFrameList
Gantry tilt analysis result.
itk::SmartPointer< Self > Pointer
boost::posix_time::ptime ConvertOFDateTimeToPTime(const OFDateTime &time)
MITKCORE_EXPORT const ScalarType eps
double ComputeMiliSecDuration(const OFDateTime &start, const OFDateTime &stop)
#define switch3DCase(IOType, T)