Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkDicomSeriesReader.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,
6 Division of Medical and Biological Informatics.
7 All rights reserved.
8 
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE.
12 
13 See LICENSE.txt or http://www.mitk.org for details.
14 
15 ===================================================================*/
16 
17 // uncomment for learning more about the internal sorting mechanisms
18 //#define MBILOG_ENABLE_DEBUG
19 
20 #include <mitkDicomSeriesReader.h>
21 #include <mitkImage.h>
22 #include <mitkImageCast.h>
23 #include <mitkLocaleSwitch.h>
24 
25 #include <itkGDCMSeriesFileNames.h>
26 
27 #include <gdcmAttribute.h>
28 #include <gdcmDirectory.h>
29 #include <gdcmPixmapReader.h>
30 #include <gdcmRAWCodec.h>
31 #include <gdcmScanner.h>
32 #include <gdcmSorter.h>
33 #include <gdcmStringFilter.h>
34 #include <gdcmUIDs.h>
35 
36 #include "mitkProperties.h"
37 
38 namespace mitk
39 {
41  {
42  switch (enumValue)
43  {
45  return "Supported";
47  return "PartlySupported";
49  return "Implemented";
51  return "Unsupported";
52  default:
53  return "<unknown value of enum ReaderImplementationLevel>";
54  };
55  }
56 
58  {
59  switch (enumValue)
60  {
62  return "In Patient";
64  return "At Detector";
66  return "Unknown spacing";
67  default:
68  return "<unknown value of enum PixelSpacingInterpretation>";
69  };
70  }
71 
73  {
74  static bool initialized = false;
75  static TagToPropertyMapType dictionary;
76  if (!initialized)
77  {
78  /*
79  Selection criteria:
80  - no sequences because we cannot represent that
81  - nothing animal related (specied, breed registration number), MITK focusses on human medical image processing.
82  - only general attributes so far
83 
84  When extending this, we should make use of a real dictionary (GDCM/DCMTK and lookup the tag names there)
85  */
86 
87  // Patient module
88  dictionary["0010|0010"] = "dicom.patient.PatientsName";
89  dictionary["0010|0020"] = "dicom.patient.PatientID";
90  dictionary["0010|0030"] = "dicom.patient.PatientsBirthDate";
91  dictionary["0010|0040"] = "dicom.patient.PatientsSex";
92  dictionary["0010|0032"] = "dicom.patient.PatientsBirthTime";
93  dictionary["0010|1000"] = "dicom.patient.OtherPatientIDs";
94  dictionary["0010|1001"] = "dicom.patient.OtherPatientNames";
95  dictionary["0010|2160"] = "dicom.patient.EthnicGroup";
96  dictionary["0010|4000"] = "dicom.patient.PatientComments";
97  dictionary["0012|0062"] = "dicom.patient.PatientIdentityRemoved";
98  dictionary["0012|0063"] = "dicom.patient.DeIdentificationMethod";
99 
100  // General Study module
101  dictionary["0020|000d"] = "dicom.study.StudyInstanceUID";
102  dictionary["0008|0020"] = "dicom.study.StudyDate";
103  dictionary["0008|0030"] = "dicom.study.StudyTime";
104  dictionary["0008|0090"] = "dicom.study.ReferringPhysiciansName";
105  dictionary["0020|0010"] = "dicom.study.StudyID";
106  dictionary["0008|0050"] = "dicom.study.AccessionNumber";
107  dictionary["0008|1030"] = "dicom.study.StudyDescription";
108  dictionary["0008|1048"] = "dicom.study.PhysiciansOfRecord";
109  dictionary["0008|1060"] = "dicom.study.NameOfPhysicianReadingStudy";
110 
111  // General Series module
112  dictionary["0008|0060"] = "dicom.series.Modality";
113  dictionary["0020|000e"] = "dicom.series.SeriesInstanceUID";
114  dictionary["0020|0011"] = "dicom.series.SeriesNumber";
115  dictionary["0020|0060"] = "dicom.series.Laterality";
116  dictionary["0008|0021"] = "dicom.series.SeriesDate";
117  dictionary["0008|0031"] = "dicom.series.SeriesTime";
118  dictionary["0008|1050"] = "dicom.series.PerformingPhysiciansName";
119  dictionary["0018|1030"] = "dicom.series.ProtocolName";
120  dictionary["0008|103e"] = "dicom.series.SeriesDescription";
121  dictionary["0008|1070"] = "dicom.series.OperatorsName";
122  dictionary["0018|0015"] = "dicom.series.BodyPartExamined";
123  dictionary["0018|5100"] = "dicom.series.PatientPosition";
124  dictionary["0028|0108"] = "dicom.series.SmallestPixelValueInSeries";
125  dictionary["0028|0109"] = "dicom.series.LargestPixelValueInSeries";
126 
127  // VOI LUT module
128  dictionary["0028|1050"] = "dicom.voilut.WindowCenter";
129  dictionary["0028|1051"] = "dicom.voilut.WindowWidth";
130  dictionary["0028|1055"] = "dicom.voilut.WindowCenterAndWidthExplanation";
131 
132  // Image Pixel module
133  dictionary["0028|0004"] = "dicom.pixel.PhotometricInterpretation";
134  dictionary["0028|0010"] = "dicom.pixel.Rows";
135  dictionary["0028|0011"] = "dicom.pixel.Columns";
136 
137  // Image Plane module
138  dictionary["0028|0030"] = "dicom.PixelSpacing";
139  dictionary["0018|1164"] = "dicom.ImagerPixelSpacing";
140 
141  initialized = true;
142  }
143 
144  return dictionary;
145  }
146 
147  DataNode::Pointer DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames,
148  bool sort,
149  bool check_4d,
150  bool correctTilt,
151  UpdateCallBackMethod callback,
152  Image::Pointer preLoadedImageBlock)
153  {
155 
157  filenames, *node, sort, check_4d, correctTilt, callback, preLoadedImageBlock))
158  {
159  if (filenames.empty())
160  {
161  return nullptr;
162  }
163 
164  return node;
165  }
166  else
167  {
168  return nullptr;
169  }
170  }
171 
173  DataNode &node,
174  bool sort,
175  bool check_4d,
176  bool correctTilt,
177  UpdateCallBackMethod callback,
178  itk::SmartPointer<Image> preLoadedImageBlock)
179  {
180  if (filenames.empty())
181  {
182  MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic.";
183  node.SetData(nullptr);
184  return true; // this is not actually an error but the result is very simple
185  }
186 
188 
189  try
190  {
191  if (io->CanReadFile(filenames.front().c_str()))
192  {
193  io->SetFileName(filenames.front().c_str());
194  io->ReadImageInformation();
195 
196  if (io->GetPixelType() == itk::ImageIOBase::SCALAR || io->GetPixelType() == itk::ImageIOBase::RGB)
197  {
198  LoadDicom(filenames, node, sort, check_4d, correctTilt, callback, preLoadedImageBlock);
199  }
200 
201  if (node.GetData())
202  {
203  return true;
204  }
205  }
206  }
207  catch (itk::MemoryAllocationError &e)
208  {
209  MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what();
210  }
211  catch (std::exception &e)
212  {
213  MITK_ERROR << "Error encountered when loading DICOM series:" << e.what();
214  }
215  catch (...)
216  {
217  MITK_ERROR << "Unspecified error encountered when loading DICOM series.";
218  }
219 
220  return false;
221  }
222 
223  bool DicomSeriesReader::IsDicom(const std::string &filename)
224  {
226 
227  return io->CanReadFile(filename.c_str());
228  }
229 
231  {
233 
234  if (io->CanReadFile(filename.c_str()))
235  {
236  // Look at header Tag 3001,0010 if it is "Philips3D"
237  gdcm::Reader reader;
238  reader.SetFileName(filename.c_str());
239  reader.Read();
240  gdcm::DataSet &data_set = reader.GetFile().GetDataSet();
241  gdcm::StringFilter sf;
242  sf.SetFile(reader.GetFile());
243 
244  if (data_set.FindDataElement(gdcm::Tag(0x3001, 0x0010)) &&
245  (sf.ToString(gdcm::Tag(0x3001, 0x0010)) == "Philips3D "))
246  {
247  return true;
248  }
249  }
250  return false;
251  }
252 
254  {
255  // Now get PhilipsSpecific Tags
256 
257  gdcm::PixmapReader reader;
258  reader.SetFileName(filename.c_str());
259  reader.Read();
260  gdcm::DataSet &data_set = reader.GetFile().GetDataSet();
261  gdcm::StringFilter sf;
262  sf.SetFile(reader.GetFile());
263 
264  gdcm::Attribute<0x0028, 0x0011> dimTagX; // coloumns || sagittal
265  gdcm::Attribute<0x3001, 0x1001, gdcm::VR::UL, gdcm::VM::VM1>
266  dimTagZ; // I have no idea what is VM1. // (Philips specific) // axial
267  gdcm::Attribute<0x0028, 0x0010> dimTagY; // rows || coronal
268  gdcm::Attribute<0x0028, 0x0008> dimTagT; // how many frames
269  gdcm::Attribute<0x0018, 0x602c> spaceTagX; // Spacing in X , unit is "physicalTagx" (usually centimeter)
270  gdcm::Attribute<0x0018, 0x602e> spaceTagY;
271  gdcm::Attribute<0x3001, 0x1003, gdcm::VR::FD, gdcm::VM::VM1> spaceTagZ; // (Philips specific)
272  gdcm::Attribute<0x0018, 0x6024> physicalTagX; // if 3, then spacing params are centimeter
273  gdcm::Attribute<0x0018, 0x6026> physicalTagY;
274  gdcm::Attribute<0x3001, 0x1002, gdcm::VR::US, gdcm::VM::VM1> physicalTagZ; // (Philips specific)
275 
276  dimTagX.Set(data_set);
277  dimTagY.Set(data_set);
278  dimTagZ.Set(data_set);
279  dimTagT.Set(data_set);
280  spaceTagX.Set(data_set);
281  spaceTagY.Set(data_set);
282  spaceTagZ.Set(data_set);
283  physicalTagX.Set(data_set);
284  physicalTagY.Set(data_set);
285  physicalTagZ.Set(data_set);
286 
287  unsigned int dimX = dimTagX.GetValue(), dimY = dimTagY.GetValue(), dimZ = dimTagZ.GetValue(),
288  dimT = dimTagT.GetValue(), physicalX = physicalTagX.GetValue(), physicalY = physicalTagY.GetValue(),
289  physicalZ = physicalTagZ.GetValue();
290 
291  float spaceX = spaceTagX.GetValue(), spaceY = spaceTagY.GetValue(), spaceZ = spaceTagZ.GetValue();
292 
293  if (physicalX == 3) // spacing parameter in cm, have to convert it to mm.
294  spaceX = spaceX * 10;
295 
296  if (physicalY == 3) // spacing parameter in cm, have to convert it to mm.
297  spaceY = spaceY * 10;
298 
299  if (physicalZ == 3) // spacing parameter in cm, have to convert it to mm.
300  spaceZ = spaceZ * 10;
301 
302  // Ok, got all necessary Tags!
303  // Now read Pixeldata (7fe0,0010) X x Y x Z x T Elements
304 
305  const gdcm::Pixmap &pixels = reader.GetPixmap();
306  gdcm::RAWCodec codec;
307 
308  codec.SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2);
309  codec.SetPixelFormat(pixels.GetPixelFormat());
310  codec.SetPlanarConfiguration(0);
311 
312  gdcm::DataElement out;
313  codec.Decode(data_set.GetDataElement(gdcm::Tag(0x7fe0, 0x0010)), out);
314 
315  const gdcm::ByteValue *bv = out.GetByteValue();
316  const char *new_pixels = bv->GetPointer();
317 
318  // Create MITK Image + Geometry
319  typedef itk::Image<unsigned char, 4>
320  ImageType; // Pixeltype might be different sometimes? Maybe read it out from header
321  ImageType::RegionType myRegion;
322  ImageType::SizeType mySize;
323  ImageType::IndexType myIndex;
324  ImageType::SpacingType mySpacing;
325  ImageType::Pointer imageItk = ImageType::New();
326 
327  mySpacing[0] = spaceX;
328  mySpacing[1] = spaceY;
329  mySpacing[2] = spaceZ;
330  mySpacing[3] = 1;
331  myIndex[0] = 0;
332  myIndex[1] = 0;
333  myIndex[2] = 0;
334  myIndex[3] = 0;
335  mySize[0] = dimX;
336  mySize[1] = dimY;
337  mySize[2] = dimZ;
338  mySize[3] = dimT;
339  myRegion.SetSize(mySize);
340  myRegion.SetIndex(myIndex);
341  imageItk->SetSpacing(mySpacing);
342  imageItk->SetRegions(myRegion);
343  imageItk->Allocate();
344  imageItk->FillBuffer(0);
345 
346  itk::ImageRegionIterator<ImageType> iterator(imageItk, imageItk->GetLargestPossibleRegion());
347  iterator.GoToBegin();
348  unsigned long pixCount = 0;
349  unsigned long planeSize = dimX * dimY;
350  unsigned long planeCount = 0;
351  unsigned long timeCount = 0;
352  unsigned long numberOfSlices = dimZ;
353 
354  while (!iterator.IsAtEnd())
355  {
356  unsigned long adressedPixel =
357  pixCount +
358  (numberOfSlices - 1 - planeCount) * planeSize // add offset to adress the first pixel of current plane
359  + timeCount * numberOfSlices * planeSize; // add time offset
360 
361  iterator.Set(new_pixels[adressedPixel]);
362  pixCount++;
363  ++iterator;
364 
365  if (pixCount == planeSize)
366  {
367  pixCount = 0;
368  planeCount++;
369  }
370  if (planeCount == numberOfSlices)
371  {
372  planeCount = 0;
373  timeCount++;
374  }
375  if (timeCount == dimT)
376  {
377  break;
378  }
379  }
380  mitk::CastToMitkImage(imageItk, output_image);
381  return true; // actually never returns false yet.. but exception possible
382  }
383 
384  std::string DicomSeriesReader::ConstCharStarToString(const char *s) { return s ? std::string(s) : std::string(); }
385  bool DicomSeriesReader::DICOMStringToSpacing(const std::string &s, ScalarType &spacingX, ScalarType &spacingY)
386  {
387  bool successful = false;
388 
389  std::istringstream spacingReader(s);
390  std::string spacing;
391  if (std::getline(spacingReader, spacing, '\\'))
392  {
393  spacingY = atof(spacing.c_str());
394 
395  if (std::getline(spacingReader, spacing, '\\'))
396  {
397  spacingX = atof(spacing.c_str());
398 
399  successful = true;
400  }
401  }
402 
403  return successful;
404  }
405 
406  Point3D DicomSeriesReader::DICOMStringToPoint3D(const std::string &s, bool &successful)
407  {
408  Point3D p;
409  successful = true;
410 
411  std::istringstream originReader(s);
412  std::string coordinate;
413  unsigned int dim(0);
414  while (std::getline(originReader, coordinate, '\\') && dim < 3)
415  {
416  p[dim++] = atof(coordinate.c_str());
417  }
418 
419  if (dim && dim != 3)
420  {
421  successful = false;
422  MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim
423  << " instead of 3 values.";
424  }
425  else if (dim == 0)
426  {
427  successful = false;
428  p.Fill(0.0); // assume default (0,0,0)
429  }
430 
431  return p;
432  }
433 
435  Vector3D &right,
436  Vector3D &up,
437  bool &successful)
438  {
439  successful = true;
440 
441  std::istringstream orientationReader(s);
442  std::string coordinate;
443  unsigned int dim(0);
444  while (std::getline(orientationReader, coordinate, '\\') && dim < 6)
445  {
446  if (dim < 3)
447  {
448  right[dim++] = atof(coordinate.c_str());
449  }
450  else
451  {
452  up[dim++ - 3] = atof(coordinate.c_str());
453  }
454  }
455 
456  if (dim && dim != 6)
457  {
458  successful = false;
459  MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim
460  << " instead of 6 values.";
461  }
462  else if (dim == 0)
463  {
464  // fill with defaults
465  right.Fill(0.0);
466  right[0] = 1.0;
467 
468  up.Fill(0.0);
469  up[1] = 1.0;
470 
471  successful = false;
472  }
473  }
474 
476  const StringContainer &files, bool groupImagesWithGantryTilt, const gdcm::Scanner::MappingType &tagValueMappings_)
477  {
478  // result.first = files that fit ITK's assumption
479  // result.second = files that do not fit, should be run through
480  // AnalyzeFileForITKImageSeriesReaderSpacingAssumption()
481  // again
483 
484  // we const_cast here, because I could not use a map.at(), which would make the code much more readable
485  gdcm::Scanner::MappingType &tagValueMappings = const_cast<gdcm::Scanner::MappingType &>(tagValueMappings_);
486  const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient)
487  const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation
488  const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt
489 
490  Vector3D fromFirstToSecondOrigin;
491  fromFirstToSecondOrigin.Fill(0.0);
492  bool fromFirstToSecondOriginInitialized(false);
493  Point3D thisOrigin;
494  thisOrigin.Fill(0.0f);
495  Point3D lastOrigin;
496  lastOrigin.Fill(0.0f);
497  Point3D lastDifferentOrigin;
498  lastDifferentOrigin.Fill(0.0f);
499 
500  bool lastOriginInitialized(false);
501 
502  MITK_DEBUG << "--------------------------------------------------------------------------------";
503  MITK_DEBUG << "Analyzing files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: "
504  << groupImagesWithGantryTilt << ")";
505  unsigned int fileIndex(0);
506  for (auto fileIter = files.begin(); fileIter != files.end(); ++fileIter, ++fileIndex)
507  {
508  bool fileFitsIntoPattern(false);
509  std::string thisOriginString;
510  // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that
511  thisOriginString = ConstCharStarToString(tagValueMappings[fileIter->c_str()][tagImagePositionPatient]);
512 
513  if (thisOriginString.empty())
514  {
515  // don't let such files be in a common group. Everything without position information will be loaded as a single
516  // slice:
517  // with standard DICOM files this can happen to: CR, DX, SC
518  MITK_DEBUG << " ==> Sort away " << *fileIter
519  << " for later analysis (no position information)"; // we already have one occupying this position
520 
521  if (result.GetBlockFilenames().empty()) // nothing WITH position information yet
522  {
523  // ==> this is a group of its own, stop processing, come back later
524  result.AddFileToSortedBlock(*fileIter);
525 
526  StringContainer remainingFiles;
527  remainingFiles.insert(remainingFiles.end(), fileIter + 1, files.end());
528  result.AddFilesToUnsortedBlock(remainingFiles);
529 
530  fileFitsIntoPattern = false;
531  break; // no files anymore
532  }
533  else
534  {
535  // ==> this does not match, consider later
536  result.AddFileToUnsortedBlock(*fileIter);
537  fileFitsIntoPattern = false;
538  continue; // next file
539  }
540  }
541 
542  bool ignoredConversionError(-42); // hard to get here, no graceful way to react
543  thisOrigin = DICOMStringToPoint3D(thisOriginString, ignoredConversionError);
544 
545  MITK_DEBUG << " " << fileIndex << " " << *fileIter << " at "
546  /* << thisOriginString */
547  << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")";
548 
549  if (lastOriginInitialized && (thisOrigin == lastOrigin))
550  {
551  MITK_DEBUG << " ==> Sort away " << *fileIter
552  << " for separate time step"; // we already have one occupying this position
553  result.AddFileToUnsortedBlock(*fileIter);
554  fileFitsIntoPattern = false;
555  }
556  else
557  {
558  if (!fromFirstToSecondOriginInitialized &&
559  lastOriginInitialized) // calculate vector as soon as possible when we get a new position
560  {
561  fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin;
562  fromFirstToSecondOriginInitialized = true;
563 
564  // Here we calculate if this slice and the previous one are well aligned,
565  // i.e. we test if the previous origin is on a line through the current
566  // origin, directed into the normal direction of the current slice.
567 
568  // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry,
569  // which cannot be simply loaded into a single mitk::Image at the moment.
570  // For this case, we flag this finding in the result and DicomSeriesReader
571  // can correct for that later.
572 
573  Vector3D right;
574  right.Fill(0.0);
575  Vector3D up;
576  right.Fill(0.0); // might be down as well, but it is just a name at this point
578  tagValueMappings[fileIter->c_str()][tagImageOrientation], right, up, ignoredConversionError);
579 
580  GantryTiltInformation tiltInfo(lastDifferentOrigin, thisOrigin, right, up, 1);
581 
582  if (tiltInfo.IsSheared()) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt
583  {
584  /* optimistic approach, accepting gantry tilt: save file for later, check all further files */
585 
586  // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there
587  // is
588  // no third to verify our tilting assumption.
589  // later with a third being available, we must check if the initial tilting vector is still valid. if yes,
590  // continue.
591  // if NO, we need to split the already sorted part (result.first) and the currently analyzed file
592  // (*fileIter)
593 
594  // tell apart gantry tilt from overall skewedness
595  // sort out irregularly sheared slices, that IS NOT tilting
596 
597  if (groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt())
598  {
599  // check if this is at least roughly the same angle as recorded in DICOM tags
600  if (tagValueMappings[fileIter->c_str()].find(tagGantryTilt) != tagValueMappings[fileIter->c_str()].end())
601  {
602  // read value, compare to calculated angle
603  std::string tiltStr = ConstCharStarToString(tagValueMappings[fileIter->c_str()][tagGantryTilt]);
604  double angle = atof(tiltStr.c_str());
605 
606  MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value "
607  << tiltInfo.GetTiltAngleInDegrees();
608  // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing
609  // serious)
610  // TODO TODO TODO when angle -27 and tiltangle 63, this will never trigger the if-clause... useless
611  // check
612  // in this case! old bug..?!
613  if (fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25)
614  {
615  result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis
616  fileFitsIntoPattern = false;
617  }
618  else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this
619  // is
620  // fine
621  {
622  result.FlagGantryTilt();
623  result.AddFileToSortedBlock(*fileIter); // this file is good for current block
624  fileFitsIntoPattern = true;
625  }
626  }
627  else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we
628  // are
629  // right)
630  {
631  result.FlagGantryTilt();
632  result.AddFileToSortedBlock(*fileIter); // this file is good for current block
633  fileFitsIntoPattern = true;
634  }
635  }
636  else // caller does not want tilt compensation OR shearing is more complicated than tilt
637  {
638  result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis
639  fileFitsIntoPattern = false;
640  }
641  }
642  else // not sheared
643  {
644  result.AddFileToSortedBlock(*fileIter); // this file is good for current block
645  fileFitsIntoPattern = true;
646  }
647  }
648  else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices
649  {
650  Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin;
651 
652  Vector3D originError = assumedOrigin - thisOrigin;
653  double norm = originError.GetNorm();
654  double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction
655 
656  if (norm > toleratedError)
657  {
658  MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed "
659  << toleratedError << ").";
660  MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << ","
661  << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << ","
662  << thisOrigin[2] << ")";
663  MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis";
664 
665  // At this point we know we deviated from the expectation of ITK's ImageSeriesReader
666  // We split the input file list at this point, i.e. all files up to this one (excluding it)
667  // are returned as group 1, the remaining files (including the faulty one) are group 2
668 
669  /* Optimistic approach: check if any of the remaining slices fits in */
670  result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis
671  fileFitsIntoPattern = false;
672  }
673  else
674  {
675  result.AddFileToSortedBlock(*fileIter); // this file is good for current block
676  fileFitsIntoPattern = true;
677  }
678  }
679  else // this should be the very first slice
680  {
681  result.AddFileToSortedBlock(*fileIter); // this file is good for current block
682  fileFitsIntoPattern = true;
683  }
684  }
685 
686  // record current origin for reference in later iterations
687  if (!lastOriginInitialized || (fileFitsIntoPattern && (thisOrigin != lastOrigin)))
688  {
689  lastDifferentOrigin = thisOrigin;
690  }
691 
692  lastOrigin = thisOrigin;
693  lastOriginInitialized = true;
694  }
695 
696  if (result.ContainsGantryTilt())
697  {
698  // check here how many files were grouped.
699  // IF it was only two files AND we assume tiltedness (e.g. save "distance")
700  // THEN we would want to also split the two previous files (simple) because
701  // we don't have any reason to assume they belong together
702 
703  if (result.GetBlockFilenames().size() == 2)
704  {
705  result.UndoPrematureGrouping();
706  }
707  }
708 
709  return result;
710  }
711 
713  bool groupImagesWithGantryTilt,
714  const StringContainer &restrictions)
715  {
716  return GetSeries(files, true, groupImagesWithGantryTilt, restrictions);
717  }
718 
720  bool sortTo3DPlust,
721  bool groupImagesWithGantryTilt,
722  const StringContainer & /*restrictions*/)
723  {
734  // use GDCM directly, itk::GDCMSeriesFileNames does not work with GDCM 2
735 
736  // PART I: scan files for sorting relevant DICOM tags,
737  // separate images that differ in any of those
738  // attributes (they cannot possibly form a 3D block)
739 
740  // scan for relevant tags in dicom files
741  gdcm::Scanner scanner;
742  const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID
743  scanner.AddTag(tagSOPClassUID);
744 
745  const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID
746  scanner.AddTag(tagSeriesInstanceUID);
747 
748  const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation
749  scanner.AddTag(tagImageOrientation);
750 
751  const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing
752  scanner.AddTag(tagPixelSpacing);
753 
754  const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing
755  scanner.AddTag(tagImagerPixelSpacing);
756 
757  const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness
758  scanner.AddTag(tagSliceThickness);
759 
760  const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows
761  scanner.AddTag(tagNumberOfRows);
762 
763  const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols
764  scanner.AddTag(tagNumberOfColumns);
765 
766  const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt
767  scanner.AddTag(tagGantryTilt);
768 
769  const gdcm::Tag tagModality(0x0008, 0x0060); // modality
770  scanner.AddTag(tagModality);
771 
772  const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames
773  scanner.AddTag(tagNumberOfFrames);
774 
775  // additional tags read in this scan to allow later analysis
776  // THESE tag are not used for initial separating of files
777  const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient)
778  scanner.AddTag(tagImagePositionPatient);
779 
780  // TODO add further restrictions from arguments (when anybody asks for it)
781 
782  FileNamesGrouping result;
783 
784  // let GDCM scan files
785  if (!scanner.Scan(files))
786  {
787  MITK_ERROR << "gdcm::Scanner failed when scanning " << files.size() << " input files.";
788  return result;
789  }
790 
791  // assign files IDs that will separate them for loading into image blocks
792  for (auto fileIter = scanner.Begin(); fileIter != scanner.End(); ++fileIter)
793  {
794  if (std::string(fileIter->first).empty())
795  continue; // TODO understand why Scanner has empty string entries
796  if (std::string(fileIter->first) == std::string("DICOMDIR"))
797  continue;
798 
799  /* sort out multi-frame
800  if ( scanner.GetValue( fileIter->first , tagNumberOfFrames ) )
801  {
802  MITK_INFO << "Ignoring " << fileIter->first << " because we cannot handle multi-frame images.";
803  continue;
804  }
805  */
806 
807  // we const_cast here, because I could not use a map.at() function in CreateMoreUniqueSeriesIdentifier.
808  // doing the same thing with find would make the code less readable. Since we forget the Scanner results
809  // anyway after this function, we can simply tolerate empty map entries introduced by bad operator[] access
810  std::string moreUniqueSeriesId =
811  CreateMoreUniqueSeriesIdentifier(const_cast<gdcm::Scanner::TagToValue &>(fileIter->second));
812  result[moreUniqueSeriesId].AddFile(fileIter->first);
813  }
814 
815  // PART II: sort slices spatially (or at least consistently if this is NOT possible, see method)
816 
817  for (FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter)
818  {
819  try
820  {
821  result[groupIter->first] =
822  ImageBlockDescriptor(SortSeriesSlices(groupIter->second.GetFilenames())); // sort each slice group spatially
823  }
824  catch (...)
825  {
826  MITK_ERROR << "Caught something.";
827  }
828  }
829 
830  // PART III: analyze pre-sorted images for valid blocks (i.e. blocks of equal z-spacing),
831  // separate into multiple blocks if necessary.
832  //
833  // Analysis performs the following steps:
834  // * imitate itk::ImageSeriesReader: use the distance between the first two images as z-spacing
835  // * check what images actually fulfill ITK's z-spacing assumption
836  // * separate all images that fail the test into new blocks, re-iterate analysis for these blocks
837  // * this includes images which DO NOT PROVIDE spatial information, i.e. all images w/o
838  // ImagePositionPatient will be loaded separately
839 
840  FileNamesGrouping groupsOf3DPlusTBlocks; // final result of this function
841  for (FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter)
842  {
843  FileNamesGrouping groupsOf3DBlocks; // intermediate result for only this group(!)
844  StringContainer filesStillToAnalyze = groupIter->second.GetFilenames();
845  std::string groupUID = groupIter->first;
846  unsigned int subgroup(0);
847  MITK_DEBUG << "Analyze group " << groupUID << " of " << groupIter->second.GetFilenames().size() << " files";
848 
849  while (!filesStillToAnalyze.empty()) // repeat until all files are grouped somehow
850  {
852  filesStillToAnalyze, groupImagesWithGantryTilt, scanner.GetMappings());
853 
854  // enhance the UID for additional groups
855  std::stringstream newGroupUID;
856  newGroupUID << groupUID << '.' << subgroup;
857 
858  ImageBlockDescriptor thisBlock(analysisResult.GetBlockFilenames());
859 
860  std::string firstFileInBlock = thisBlock.GetFilenames().front();
861 
862  thisBlock.SetImageBlockUID(newGroupUID.str());
863  thisBlock.SetSeriesInstanceUID(
864  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagSeriesInstanceUID)));
865  thisBlock.SetHasGantryTiltCorrected(analysisResult.ContainsGantryTilt());
866  thisBlock.SetSOPClassUID(
867  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagSOPClassUID)));
868  thisBlock.SetNumberOfFrames(
869  ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagNumberOfFrames)));
870  thisBlock.SetModality(
871  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagModality)));
872  thisBlock.SetPixelSpacingInformation(
873  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagPixelSpacing)),
874  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagImagerPixelSpacing)));
875  thisBlock.SetHasMultipleTimePoints(false);
876 
877  groupsOf3DBlocks[newGroupUID.str()] = thisBlock;
878 
879  // MITK_DEBUG << "Result: sorted 3D group " << newGroupUID.str() << " with " << groupsOf3DBlocks[
880  // newGroupUID.str() ].GetFilenames().size() << " files";
881  MITK_DEBUG << "Result: sorted 3D group with " << groupsOf3DBlocks[newGroupUID.str()].GetFilenames().size()
882  << " files";
883  StringContainer debugOutputFiles = analysisResult.GetBlockFilenames();
884  for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter)
885  MITK_DEBUG << " IN " << *siter;
886 
887  ++subgroup;
888 
889  filesStillToAnalyze = analysisResult.GetUnsortedFilenames(); // remember what needs further analysis
890  for (StringContainer::const_iterator siter = filesStillToAnalyze.begin(); siter != filesStillToAnalyze.end();
891  ++siter)
892  MITK_DEBUG << " OUT " << *siter;
893  }
894 
895  // end of grouping, now post-process groups
896 
897  // PART IV: attempt to group blocks to 3D+t blocks if requested
898  // inspect entries of groupsOf3DBlocks
899  // - if number of files is identical to previous entry, collect for 3D+t block
900  // - as soon as number of files changes from previous entry, record collected blocks as 3D+t block,
901  // start
902  // a new one, continue
903 
904  // decide whether or not to group 3D blocks into 3D+t blocks where possible
905  if (!sortTo3DPlust)
906  {
907  // copy 3D blocks to output
908  groupsOf3DPlusTBlocks.insert(groupsOf3DBlocks.begin(), groupsOf3DBlocks.end());
909  }
910  else
911  {
912  // sort 3D+t (as described in "PART IV")
913 
914  MITK_DEBUG << "================================================================================";
915  MITK_DEBUG << "3D+t analysis:";
916 
917  unsigned int numberOfFilesInPreviousBlock(0);
918  std::string previousBlockKey;
919 
920  for (FileNamesGrouping::const_iterator block3DIter = groupsOf3DBlocks.begin();
921  block3DIter != groupsOf3DBlocks.end();
922  ++block3DIter)
923  {
924  unsigned int numberOfFilesInThisBlock = block3DIter->second.GetFilenames().size();
925  std::string thisBlockKey = block3DIter->first;
926 
927  if (numberOfFilesInPreviousBlock == 0)
928  {
929  numberOfFilesInPreviousBlock = numberOfFilesInThisBlock;
930  groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second;
931  MITK_DEBUG << " 3D+t group " << thisBlockKey;
932  previousBlockKey = thisBlockKey;
933  }
934  else
935  {
936  bool identicalOrigins;
937  try
938  {
939  // check whether this and the previous block share a comon origin
940  // TODO should be safe, but a little try/catch or other error handling wouldn't hurt
941 
942  const char *origin_value = scanner.GetValue(groupsOf3DBlocks[thisBlockKey].GetFilenames().front().c_str(),
943  tagImagePositionPatient),
944  *previous_origin_value = scanner.GetValue(
945  groupsOf3DBlocks[previousBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient),
946  *destination_value = scanner.GetValue(
947  groupsOf3DBlocks[thisBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient),
948  *previous_destination_value = scanner.GetValue(
949  groupsOf3DBlocks[previousBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient);
950 
951  if (!origin_value || !previous_origin_value || !destination_value || !previous_destination_value)
952  {
953  identicalOrigins = false;
954  }
955  else
956  {
957  std::string thisOriginString = ConstCharStarToString(origin_value);
958  std::string previousOriginString = ConstCharStarToString(previous_origin_value);
959 
960  // also compare last origin, because this might differ if z-spacing is different
961  std::string thisDestinationString = ConstCharStarToString(destination_value);
962  std::string previousDestinationString = ConstCharStarToString(previous_destination_value);
963 
964  identicalOrigins =
965  ((thisOriginString == previousOriginString) && (thisDestinationString == previousDestinationString));
966  }
967  }
968  catch (...)
969  {
970  identicalOrigins = false;
971  }
972 
973  if (identicalOrigins && (numberOfFilesInPreviousBlock == numberOfFilesInThisBlock))
974  {
975  // group with previous block
976  groupsOf3DPlusTBlocks[previousBlockKey].AddFiles(block3DIter->second.GetFilenames());
977  groupsOf3DPlusTBlocks[previousBlockKey].SetHasMultipleTimePoints(true);
978  MITK_DEBUG << " --> group enhanced with another timestep";
979  }
980  else
981  {
982  // start a new block
983  groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second;
984  int numberOfTimeSteps =
985  groupsOf3DPlusTBlocks[previousBlockKey].GetFilenames().size() / numberOfFilesInPreviousBlock;
986  MITK_DEBUG << " ==> group closed with " << numberOfTimeSteps << " time steps";
987  previousBlockKey = thisBlockKey;
988  MITK_DEBUG << " 3D+t group " << thisBlockKey << " started";
989  }
990  }
991 
992  numberOfFilesInPreviousBlock = numberOfFilesInThisBlock;
993  }
994  }
995  }
996 
997  MITK_DEBUG << "================================================================================";
998  MITK_DEBUG << "Summary: ";
999  for (FileNamesGrouping::const_iterator groupIter = groupsOf3DPlusTBlocks.begin();
1000  groupIter != groupsOf3DPlusTBlocks.end();
1001  ++groupIter)
1002  {
1003  ImageBlockDescriptor block = groupIter->second;
1004  MITK_DEBUG << " " << block.GetFilenames().size() << " '" << block.GetModality() << "' images ("
1005  << block.GetSOPClassUIDAsString() << ") in volume " << block.GetImageBlockUID();
1006  MITK_DEBUG << " (gantry tilt : " << (block.HasGantryTiltCorrected() ? "Yes" : "No") << "; "
1007  "pixel spacing : "
1009  "3D+t: "
1010  << (block.HasMultipleTimePoints() ? "Yes" : "No") << "; "
1011  "reader support: "
1013  StringContainer debugOutputFiles = block.GetFilenames();
1014  for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter)
1015  MITK_DEBUG << " F " << *siter;
1016  }
1017  MITK_DEBUG << "================================================================================";
1018 
1019  return groupsOf3DPlusTBlocks;
1020  }
1021 
1023  bool groupImagesWithGantryTilt,
1024  const StringContainer &restrictions)
1025  {
1026  gdcm::Directory directoryLister;
1027  directoryLister.Load(dir.c_str(), false); // non-recursive
1028  return GetSeries(directoryLister.GetFilenames(), groupImagesWithGantryTilt, restrictions);
1029  }
1030 
1031  std::string DicomSeriesReader::CreateSeriesIdentifierPart(gdcm::Scanner::TagToValue &tagValueMap,
1032  const gdcm::Tag &tag)
1033  {
1034  std::string result;
1035  try
1036  {
1037  result = IDifyTagValue(tagValueMap[tag] ? tagValueMap[tag] : std::string(""));
1038  }
1039  catch (std::exception &)
1040  {
1041  // we are happy with even nothing, this will just group images of a series
1042  // MITK_WARN << "Could not access tag " << tag << ": " << e.what();
1043  }
1044 
1045  return result;
1046  }
1047 
1048  std::string DicomSeriesReader::CreateMoreUniqueSeriesIdentifier(gdcm::Scanner::TagToValue &tagValueMap)
1049  {
1050  const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID
1051  const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation
1052  const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing
1053  const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing
1054  const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness
1055  const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows
1056  const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols
1057  const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames
1058 
1059  const char *tagSeriesInstanceUid = tagValueMap[tagSeriesInstanceUID];
1060  if (!tagSeriesInstanceUid)
1061  {
1062  mitkThrow() << "CreateMoreUniqueSeriesIdentifier() could not access series instance UID. Something is seriously "
1063  "wrong with this image, so stopping here.";
1064  }
1065  std::string constructedID = tagSeriesInstanceUid;
1066 
1067  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfRows);
1068  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfColumns);
1069  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagPixelSpacing);
1070  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagImagerPixelSpacing);
1071  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagSliceThickness);
1072  constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfFrames);
1073 
1074  // be a bit tolerant for orienatation, let only the first few digits matter
1075  // (http://bugs.mitk.org/show_bug.cgi?id=12263)
1076  // NOT constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImageOrientation );
1077  if (tagValueMap.find(tagImageOrientation) != tagValueMap.end())
1078  {
1079  bool conversionError(false);
1080  Vector3D right;
1081  right.Fill(0.0);
1082  Vector3D up;
1083  right.Fill(0.0);
1084  DICOMStringToOrientationVectors(tagValueMap[tagImageOrientation], right, up, conversionError);
1085  // string newstring sprintf(simplifiedOrientationString, "%.3f\\%.3f\\%.3f\\%.3f\\%.3f\\%.3f", right[0],
1086  // right[1],
1087  // right[2], up[0], up[1], up[2]);
1088  std::ostringstream ss;
1089  ss.setf(std::ios::fixed, std::ios::floatfield);
1090  ss.precision(5);
1091  ss << right[0] << "\\" << right[1] << "\\" << right[2] << "\\" << up[0] << "\\" << up[1] << "\\" << up[2];
1092  std::string simplifiedOrientationString(ss.str());
1093 
1094  constructedID += IDifyTagValue(simplifiedOrientationString);
1095  }
1096 
1097  constructedID.resize(constructedID.length() - 1); // cut of trailing '.'
1098 
1099  return constructedID;
1100  }
1101 
1102  std::string DicomSeriesReader::IDifyTagValue(const std::string &value)
1103  {
1104  std::string IDifiedValue(value);
1105  if (value.empty())
1106  throw std::logic_error("IDifyTagValue() illegaly called with empty tag value");
1107 
1108  // Eliminate non-alnum characters, including whitespace...
1109  // that may have been introduced by concats.
1110  for (std::size_t i = 0; i < IDifiedValue.size(); i++)
1111  {
1112  while (i < IDifiedValue.size() &&
1113  !(IDifiedValue[i] == '.' || (IDifiedValue[i] >= 'a' && IDifiedValue[i] <= 'z') ||
1114  (IDifiedValue[i] >= '0' && IDifiedValue[i] <= '9') ||
1115  (IDifiedValue[i] >= 'A' && IDifiedValue[i] <= 'Z')))
1116  {
1117  IDifiedValue.erase(i, 1);
1118  }
1119  }
1120 
1121  IDifiedValue += ".";
1122  return IDifiedValue;
1123  }
1124 
1126  const std::string &series_uid,
1127  bool groupImagesWithGantryTilt,
1128  const StringContainer &restrictions)
1129  {
1130  FileNamesGrouping allSeries = GetSeries(dir, groupImagesWithGantryTilt, restrictions);
1131  StringContainer resultingFileList;
1132 
1133  for (FileNamesGrouping::const_iterator idIter = allSeries.begin(); idIter != allSeries.end(); ++idIter)
1134  {
1135  if (idIter->first.find(series_uid) == 0) // this ID starts with given series_uid
1136  {
1137  return idIter->second.GetFilenames();
1138  }
1139  }
1140 
1141  return resultingFileList;
1142  }
1143 
1145  {
1146  /* we CAN expect a group of equal
1147  - series instance uid
1148  - image orientation
1149  - pixel spacing
1150  - imager pixel spacing
1151  - slice thickness
1152  - number of rows/columns
1153 
1154  (each piece of information except the rows/columns might be missing)
1155 
1156  sorting with GdcmSortFunction tries its best by sorting by spatial position
1157  and more hints (acquisition number, acquisition time, trigger time) but will
1158  always produce a sorting by falling back to SOP Instance UID.
1159  */
1160  gdcm::Sorter sorter;
1161 
1162  sorter.SetSortFunction(DicomSeriesReader::GdcmSortFunction);
1163  try
1164  {
1165  if (sorter.Sort(unsortedFilenames))
1166  {
1167  return sorter.GetFilenames();
1168  }
1169  else
1170  {
1171  MITK_WARN << "Sorting error. Leaving series unsorted.";
1172  return unsortedFilenames;
1173  }
1174  }
1175  catch (std::logic_error &)
1176  {
1177  MITK_WARN << "Sorting error. Leaving series unsorted.";
1178  return unsortedFilenames;
1179  }
1180  }
1181 
1182  bool DicomSeriesReader::GdcmSortFunction(const gdcm::DataSet &ds1, const gdcm::DataSet &ds2)
1183  {
1184  // This method MUST accept missing location and position information (and all else, too)
1185  // because we cannot rely on anything
1186  // (restriction on the sentence before: we have to provide consistent sorting, so we
1187  // rely on the minimum information all DICOM files need to provide: SOP Instance UID)
1188 
1189  /* we CAN expect a group of equal
1190  - series instance uid
1191  - image orientation
1192  - pixel spacing
1193  - imager pixel spacing
1194  - slice thickness
1195  - number of rows/columns
1196  */
1197  static const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient)
1198  static const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation
1199 
1200  // see if we have Image Position and Orientation
1201  if (ds1.FindDataElement(tagImagePositionPatient) && ds1.FindDataElement(tagImageOrientation) &&
1202  ds2.FindDataElement(tagImagePositionPatient) && ds2.FindDataElement(tagImageOrientation))
1203  {
1204  gdcm::Attribute<0x0020, 0x0032> image_pos1; // Image Position (Patient)
1205  gdcm::Attribute<0x0020, 0x0037> image_orientation1; // Image Orientation (Patient)
1206 
1207  image_pos1.Set(ds1);
1208  image_orientation1.Set(ds1);
1209 
1210  gdcm::Attribute<0x0020, 0x0032> image_pos2;
1211  gdcm::Attribute<0x0020, 0x0037> image_orientation2;
1212 
1213  image_pos2.Set(ds2);
1214  image_orientation2.Set(ds2);
1215 
1216  /*
1217  we tolerate very small differences in image orientation, since we got to know about
1218  acquisitions where these values change across a single series (7th decimal digit)
1219  (http://bugs.mitk.org/show_bug.cgi?id=12263)
1220 
1221  still, we want to check if our assumption of 'almost equal' orientations is valid
1222  */
1223  for (unsigned int dim = 0; dim < 6; ++dim)
1224  {
1225  if (fabs(image_orientation2[dim] - image_orientation1[dim]) > 0.0001)
1226  {
1227  MITK_ERROR << "Dicom images have different orientations.";
1228  throw std::logic_error(
1229  "Dicom images have different orientations. Call GetSeries() first to separate images.");
1230  }
1231  }
1232 
1233  double normal[3];
1234 
1235  normal[0] = image_orientation1[1] * image_orientation1[5] - image_orientation1[2] * image_orientation1[4];
1236  normal[1] = image_orientation1[2] * image_orientation1[3] - image_orientation1[0] * image_orientation1[5];
1237  normal[2] = image_orientation1[0] * image_orientation1[4] - image_orientation1[1] * image_orientation1[3];
1238 
1239  double dist1 = 0.0, dist2 = 0.0;
1240 
1241  // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes
1242  for (unsigned char i = 0u; i < 3u; ++i)
1243  {
1244  dist1 += normal[i] * image_pos1[i];
1245  dist2 += normal[i] * image_pos2[i];
1246  }
1247 
1248  // if we can sort by just comparing the distance, we do exactly that
1249  if (fabs(dist1 - dist2) >= mitk::eps)
1250  {
1251  // default: compare position
1252  return dist1 < dist2;
1253  }
1254  else // we need to check more properties to distinguish slices
1255  {
1256  // try to sort by Acquisition Number
1257  static const gdcm::Tag tagAcquisitionNumber(0x0020, 0x0012);
1258  if (ds1.FindDataElement(tagAcquisitionNumber) && ds2.FindDataElement(tagAcquisitionNumber))
1259  {
1260  gdcm::Attribute<0x0020, 0x0012> acquisition_number1; // Acquisition number
1261  gdcm::Attribute<0x0020, 0x0012> acquisition_number2;
1262 
1263  acquisition_number1.Set(ds1);
1264  acquisition_number2.Set(ds2);
1265 
1266  if (acquisition_number1 != acquisition_number2)
1267  {
1268  return acquisition_number1 < acquisition_number2;
1269  }
1270  else // neither position nor acquisition number are good for sorting, so check more
1271  {
1272  // try to sort by Acquisition Time
1273  static const gdcm::Tag tagAcquisitionTime(0x0008, 0x0032);
1274  if (ds1.FindDataElement(tagAcquisitionTime) && ds2.FindDataElement(tagAcquisitionTime))
1275  {
1276  gdcm::Attribute<0x0008, 0x0032> acquisition_time1; // Acquisition time
1277  gdcm::Attribute<0x0008, 0x0032> acquisition_time2;
1278 
1279  acquisition_time1.Set(ds1);
1280  acquisition_time2.Set(ds2);
1281 
1282  if (acquisition_time1 != acquisition_time2)
1283  {
1284  return acquisition_time1 < acquisition_time2;
1285  }
1286  else // we gave up on image position, acquisition number and acquisition time now
1287  {
1288  // let's try trigger time
1289  static const gdcm::Tag tagTriggerTime(0x0018, 0x1060);
1290  if (ds1.FindDataElement(tagTriggerTime) && ds2.FindDataElement(tagTriggerTime))
1291  {
1292  gdcm::Attribute<0x0018, 0x1060> trigger_time1; // Trigger time
1293  gdcm::Attribute<0x0018, 0x1060> trigger_time2;
1294 
1295  trigger_time1.Set(ds1);
1296  trigger_time2.Set(ds2);
1297 
1298  if (trigger_time1 != trigger_time2)
1299  {
1300  return trigger_time1 < trigger_time2;
1301  }
1302  // ELSE!
1303  // for this and many previous ifs we fall through if nothing lets us sort
1304  } // .
1305  } // .
1306  } // .
1307  }
1308  }
1309  }
1310  } // .
1311 
1312  // LAST RESORT: all valuable information for sorting is missing.
1313  // Sort by some meaningless but unique identifiers to satisfy the sort function
1314  static const gdcm::Tag tagSOPInstanceUID(0x0008, 0x0018);
1315  if (ds1.FindDataElement(tagSOPInstanceUID) && ds2.FindDataElement(tagSOPInstanceUID))
1316  {
1317  MITK_DEBUG
1318  << "Dicom images are missing attributes for a meaningful sorting, falling back to SOP instance UID comparison.";
1319  gdcm::Attribute<0x0008, 0x0018> SOPInstanceUID1; // SOP instance UID is mandatory and unique
1320  gdcm::Attribute<0x0008, 0x0018> SOPInstanceUID2;
1321 
1322  SOPInstanceUID1.Set(ds1);
1323  SOPInstanceUID2.Set(ds2);
1324 
1325  return SOPInstanceUID1 < SOPInstanceUID2;
1326  }
1327  else
1328  {
1329  // no DICOM file should really come down here, this should only be reached with unskillful and unlucky
1330  // manipulation
1331  // of files
1332  std::string error_message("Malformed DICOM images, which do not even contain a SOP Instance UID.");
1333  MITK_ERROR << error_message;
1334  throw std::logic_error(error_message);
1335  }
1336  }
1337 
1339  {
1340  std::stringstream configuration;
1341  configuration << "MITK_USE_GDCMIO: ";
1342  configuration << "true";
1343  configuration << "\n";
1344 
1345  configuration << "GDCM_VERSION: ";
1346 #ifdef GDCM_MAJOR_VERSION
1347  configuration << GDCM_VERSION;
1348 #endif
1349  // configuration << "\n";
1350 
1351  return configuration.str();
1352  }
1353 
1355  const gdcm::Scanner::MappingType &tagValueMappings_,
1356  DcmIoType *io,
1357  const ImageBlockDescriptor &blockInfo,
1358  Image *image)
1359  {
1360  std::list<StringContainer> imageBlock;
1361  imageBlock.push_back(filenames);
1362  CopyMetaDataToImageProperties(imageBlock, tagValueMappings_, io, blockInfo, image);
1363  }
1364 
1365  void DicomSeriesReader::CopyMetaDataToImageProperties(std::list<StringContainer> imageBlock,
1366  const gdcm::Scanner::MappingType &tagValueMappings_,
1367  DcmIoType *io,
1368  const ImageBlockDescriptor &blockInfo,
1369  Image *image)
1370  {
1371  if (!io || !image)
1372  return;
1373 
1374  StringLookupTable filesForSlices;
1375  StringLookupTable sliceLocationForSlices;
1376  StringLookupTable instanceNumberForSlices;
1377  StringLookupTable SOPInstanceNumberForSlices;
1378 
1379  gdcm::Scanner::MappingType &tagValueMappings = const_cast<gdcm::Scanner::MappingType &>(tagValueMappings_);
1380 
1381  // DICOM tags which should be added to the image properties
1382  const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location
1383 
1384  const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number
1385 
1386  const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number
1387  unsigned int timeStep(0);
1388 
1389  std::string propertyKeySliceLocation = "dicom.image.0020.1041";
1390  std::string propertyKeyInstanceNumber = "dicom.image.0020.0013";
1391  std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018";
1392 
1393  // tags for each image
1394  for (auto i = imageBlock.begin(); i != imageBlock.end(); i++, timeStep++)
1395  {
1396  const StringContainer &files = (*i);
1397  unsigned int slice(0);
1398  for (auto fIter = files.begin(); fIter != files.end(); ++fIter, ++slice)
1399  {
1400  filesForSlices.SetTableValue(slice, *fIter);
1401  gdcm::Scanner::TagToValue tagValueMapForFile = tagValueMappings[fIter->c_str()];
1402  if (tagValueMapForFile.find(tagSliceLocation) != tagValueMapForFile.end())
1403  sliceLocationForSlices.SetTableValue(slice, tagValueMapForFile[tagSliceLocation]);
1404  if (tagValueMapForFile.find(tagInstanceNumber) != tagValueMapForFile.end())
1405  instanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagInstanceNumber]);
1406  if (tagValueMapForFile.find(tagSOPInstanceNumber) != tagValueMapForFile.end())
1407  SOPInstanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagSOPInstanceNumber]);
1408  }
1409 
1410  image->SetProperty("files", StringLookupTableProperty::New(filesForSlices));
1411 
1412  // If more than one time step add postfix ".t" + timestep
1413  if (timeStep != 0)
1414  {
1415  std::ostringstream postfix;
1416  postfix << ".t" << timeStep;
1417 
1418  propertyKeySliceLocation.append(postfix.str());
1419  propertyKeyInstanceNumber.append(postfix.str());
1420  propertyKeySOPInstanceNumber.append(postfix.str());
1421  }
1422  image->SetProperty(propertyKeySliceLocation.c_str(), StringLookupTableProperty::New(sliceLocationForSlices));
1423  image->SetProperty(propertyKeyInstanceNumber.c_str(), StringLookupTableProperty::New(instanceNumberForSlices));
1424  image->SetProperty(propertyKeySOPInstanceNumber.c_str(),
1425  StringLookupTableProperty::New(SOPInstanceNumberForSlices));
1426  }
1427 
1428  // Copy tags for series, study, patient level (leave interpretation to application).
1429  // These properties will be copied to the DataNode by DicomSeriesReader.
1430 
1431  // tags for the series (we just use the one that ITK copied to its dictionary (proably that of the last slice)
1432  const itk::MetaDataDictionary &dict = io->GetMetaDataDictionary();
1434 
1435  auto dictIter = dict.Begin();
1436  while (dictIter != dict.End())
1437  {
1438  // MITK_DEBUG << "Key " << dictIter->first;
1439  std::string value;
1440  if (itk::ExposeMetaData<std::string>(dict, dictIter->first, value))
1441  {
1442  // MITK_DEBUG << "Value " << value;
1443 
1444  auto valuePosition = propertyLookup.find(dictIter->first);
1445  if (valuePosition != propertyLookup.end())
1446  {
1447  std::string propertyKey = valuePosition->second;
1448  // MITK_DEBUG << "--> " << propertyKey;
1449 
1450  image->SetProperty(propertyKey.c_str(), StringProperty::New(value));
1451  }
1452  }
1453  else
1454  {
1455  MITK_WARN << "Tag " << dictIter->first << " not read as string as expected. Ignoring...";
1456  }
1457  ++dictIter;
1458  }
1459 
1460  // copy imageblockdescriptor as properties
1461  image->SetProperty("dicomseriesreader.SOPClass", StringProperty::New(blockInfo.GetSOPClassUIDAsString()));
1462  image->SetProperty(
1463  "dicomseriesreader.ReaderImplementationLevelString",
1465  image->SetProperty("dicomseriesreader.ReaderImplementationLevel",
1467  image->SetProperty("dicomseriesreader.PixelSpacingInterpretationString",
1469  image->SetProperty("dicomseriesreader.PixelSpacingInterpretation",
1471  image->SetProperty("dicomseriesreader.MultiFrameImage", BoolProperty::New(blockInfo.IsMultiFrameImage()));
1472  image->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New(blockInfo.HasGantryTiltCorrected()));
1473  image->SetProperty("dicomseriesreader.3D+t", BoolProperty::New(blockInfo.HasMultipleTimePoints()));
1474  }
1475 
1477  {
1478  // spacing provided by ITK/GDCM
1479  Vector3D imageSpacing = image->GetGeometry()->GetSpacing();
1480  ScalarType imageSpacingX = imageSpacing[0];
1481  ScalarType imageSpacingY = imageSpacing[1];
1482 
1483  // spacing as desired by MITK (preference for "in patient", else "on detector", or "1.0/1.0")
1484  ScalarType desiredSpacingX = imageSpacingX;
1485  ScalarType desiredSpacingY = imageSpacingY;
1486  imageBlockDescriptor.GetDesiredMITKImagePixelSpacing(desiredSpacingX, desiredSpacingY);
1487 
1488  MITK_DEBUG << "Loaded spacing: " << imageSpacingX << "/" << imageSpacingY;
1489  MITK_DEBUG << "Corrected spacing: " << desiredSpacingX << "/" << desiredSpacingY;
1490 
1491  imageSpacing[0] = desiredSpacingX;
1492  imageSpacing[1] = desiredSpacingY;
1493  image->GetGeometry()->SetSpacing(imageSpacing);
1494  }
1495 
1497  DataNode &node,
1498  bool sort,
1499  bool load4D,
1500  bool correctTilt,
1501  UpdateCallBackMethod callback,
1502  Image::Pointer preLoadedImageBlock)
1503  {
1504  mitk::LocaleSwitch localeSwitch("C");
1505  std::locale previousCppLocale(std::cin.getloc());
1506  std::locale l("C");
1507  std::cin.imbue(l);
1508 
1509  ImageBlockDescriptor imageBlockDescriptor;
1510 
1511  const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient)
1512  const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation
1513  const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID
1514  const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID
1515  const gdcm::Tag tagModality(0x0008, 0x0060); // modality
1516  const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing
1517  const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing
1518  const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames
1519 
1520  try
1521  {
1522  Image::Pointer image = preLoadedImageBlock.IsNull() ? Image::New() : preLoadedImageBlock;
1523  CallbackCommand *command = callback ? new CallbackCommand(callback) : nullptr;
1524  bool initialize_node = false;
1525 
1526  /* special case for Philips 3D+t ultrasound images */
1527  if (DicomSeriesReader::IsPhilips3DDicom(filenames.front().c_str()))
1528  {
1529  // TODO what about imageBlockDescriptor?
1530  // TODO what about preLoadedImageBlock?
1531  ReadPhilips3DDicom(filenames.front().c_str(), image);
1532  initialize_node = true;
1533  }
1534  else
1535  {
1536  /* default case: assume "normal" image blocks, possibly 3D+t */
1537  bool canLoadAs4D(true);
1538  gdcm::Scanner scanner;
1539  ScanForSliceInformation(filenames, scanner);
1540 
1541  // need non-const access for map
1542  gdcm::Scanner::MappingType &tagValueMappings = const_cast<gdcm::Scanner::MappingType &>(scanner.GetMappings());
1543 
1544  std::list<StringContainer> imageBlocks =
1545  SortIntoBlocksFor3DplusT(filenames, tagValueMappings, sort, canLoadAs4D);
1546  unsigned int volume_count = imageBlocks.size();
1547 
1548  imageBlockDescriptor.SetSeriesInstanceUID(
1549  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagSeriesInstanceUID)));
1550  imageBlockDescriptor.SetSOPClassUID(
1551  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagSOPClassUID)));
1552  imageBlockDescriptor.SetModality(
1553  DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagModality)));
1554  imageBlockDescriptor.SetNumberOfFrames(
1555  ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagNumberOfFrames)));
1556  imageBlockDescriptor.SetPixelSpacingInformation(
1557  ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagPixelSpacing)),
1558  ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagImagerPixelSpacing)));
1559 
1560  GantryTiltInformation tiltInfo;
1561 
1562  // check possibility of a single slice with many timesteps. In this case, don't check for tilt, no second slice
1563  // possible
1564  if (!imageBlocks.empty() && imageBlocks.front().size() > 1 && correctTilt)
1565  {
1566  // check tiltedness here, potentially fixup ITK's loading result by shifting slice contents
1567  // check first and last position slice from tags, make some calculations to detect tilt
1568 
1569  std::string firstFilename(imageBlocks.front().front());
1570  // calculate from first and last slice to minimize rounding errors
1571  std::string secondFilename(imageBlocks.front().back());
1572 
1573  std::string imagePosition1(
1574  ConstCharStarToString(tagValueMappings[firstFilename.c_str()][tagImagePositionPatient]));
1575  std::string imageOrientation(
1576  ConstCharStarToString(tagValueMappings[firstFilename.c_str()][tagImageOrientation]));
1577  std::string imagePosition2(
1578  ConstCharStarToString(tagValueMappings[secondFilename.c_str()][tagImagePositionPatient]));
1579 
1580  bool ignoredConversionError(-42); // hard to get here, no graceful way to react
1581  Point3D origin1(DICOMStringToPoint3D(imagePosition1, ignoredConversionError));
1582  Point3D origin2(DICOMStringToPoint3D(imagePosition2, ignoredConversionError));
1583 
1584  Vector3D right;
1585  right.Fill(0.0);
1586  Vector3D up;
1587  right.Fill(0.0); // might be down as well, but it is just a name at this point
1588  DICOMStringToOrientationVectors(imageOrientation, right, up, ignoredConversionError);
1589 
1590  tiltInfo = GantryTiltInformation(origin1, origin2, right, up, filenames.size() - 1);
1591  correctTilt = tiltInfo.IsSheared() && tiltInfo.IsRegularGantryTilt();
1592  }
1593  else
1594  {
1595  correctTilt = false; // we CANNOT do that
1596  }
1597 
1598  imageBlockDescriptor.SetHasGantryTiltCorrected(correctTilt);
1599 
1600  if (volume_count == 1 || !canLoadAs4D || !load4D)
1601  {
1602  DcmIoType::Pointer io;
1603  image = MultiplexLoadDICOMByITK(
1604  imageBlocks.front(), correctTilt, tiltInfo, io, command, preLoadedImageBlock); // load first 3D block
1605 
1606  imageBlockDescriptor.AddFiles(imageBlocks.front()); // only the first part is loaded
1607  imageBlockDescriptor.SetHasMultipleTimePoints(false);
1608 
1609  FixSpacingInformation(image, imageBlockDescriptor);
1610  CopyMetaDataToImageProperties(imageBlocks.front(), scanner.GetMappings(), io, imageBlockDescriptor, image);
1611 
1612  initialize_node = true;
1613  }
1614  else if (volume_count > 1)
1615  {
1616  imageBlockDescriptor.AddFiles(filenames); // all is loaded
1617  imageBlockDescriptor.SetHasMultipleTimePoints(true);
1618 
1619  DcmIoType::Pointer io;
1620  image = MultiplexLoadDICOMByITK4D(
1621  imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock);
1622 
1623  initialize_node = true;
1624  }
1625  }
1626 
1627  if (initialize_node)
1628  {
1629  // forward some image properties to node
1630  node.GetPropertyList()->ConcatenatePropertyList(image->GetPropertyList(), true);
1631 
1632  std::string patientName = "NoName";
1633  if (node.GetProperty("dicom.patient.PatientsName"))
1634  patientName = node.GetProperty("dicom.patient.PatientsName")->GetValueAsString();
1635 
1636  node.SetData(image);
1637  node.SetName(patientName);
1638  std::cin.imbue(previousCppLocale);
1639  }
1640 
1641  MITK_DEBUG << "--------------------------------------------------------------------------------";
1642  MITK_DEBUG << "DICOM files loaded (from series UID " << imageBlockDescriptor.GetSeriesInstanceUID() << "):";
1643  MITK_DEBUG << " " << imageBlockDescriptor.GetFilenames().size() << " '" << imageBlockDescriptor.GetModality()
1644  << "' files (" << imageBlockDescriptor.GetSOPClassUIDAsString() << ") loaded into 1 mitk::Image";
1645  MITK_DEBUG << " multi-frame: " << (imageBlockDescriptor.IsMultiFrameImage() ? "Yes" : "No");
1646  MITK_DEBUG << " reader support: "
1648  MITK_DEBUG << " pixel spacing type: "
1649  << PixelSpacingInterpretationToString(imageBlockDescriptor.GetPixelSpacingType()) << " "
1650  << image->GetGeometry()->GetSpacing()[0] << "/" << image->GetGeometry()->GetSpacing()[0];
1651  MITK_DEBUG << " gantry tilt corrected: " << (imageBlockDescriptor.HasGantryTiltCorrected() ? "Yes" : "No");
1652  MITK_DEBUG << " 3D+t: " << (imageBlockDescriptor.HasMultipleTimePoints() ? "Yes" : "No");
1653  MITK_DEBUG << "--------------------------------------------------------------------------------";
1654  }
1655  catch (std::exception &e)
1656  {
1657  // reset locale then throw up
1658  std::cin.imbue(previousCppLocale);
1659 
1660  MITK_DEBUG << "Caught exception in DicomSeriesReader::LoadDicom";
1661 
1662  throw e;
1663  }
1664  }
1665 
1666  void DicomSeriesReader::ScanForSliceInformation(const StringContainer &filenames, gdcm::Scanner &scanner)
1667  {
1668  const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image position (Patient)
1669  scanner.AddTag(tagImagePositionPatient);
1670 
1671  const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID
1672  scanner.AddTag(tagSeriesInstanceUID);
1673 
1674  const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image orientation
1675  scanner.AddTag(tagImageOrientation);
1676 
1677  const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location
1678  scanner.AddTag(tagSliceLocation);
1679 
1680  const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number
1681  scanner.AddTag(tagInstanceNumber);
1682 
1683  const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number
1684  scanner.AddTag(tagSOPInstanceNumber);
1685 
1686  const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // Pixel Spacing
1687  scanner.AddTag(tagPixelSpacing);
1688 
1689  const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // Imager Pixel Spacing
1690  scanner.AddTag(tagImagerPixelSpacing);
1691 
1692  const gdcm::Tag tagModality(0x0008, 0x0060); // Modality
1693  scanner.AddTag(tagModality);
1694 
1695  const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP Class UID
1696  scanner.AddTag(tagSOPClassUID);
1697 
1698  const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames
1699  scanner.AddTag(tagNumberOfFrames);
1700 
1701  scanner.Scan(filenames); // make available image information for each file
1702  }
1703 
1704  std::list<DicomSeriesReader::StringContainer> DicomSeriesReader::SortIntoBlocksFor3DplusT(
1705  const StringContainer &presortedFilenames,
1706  const gdcm::Scanner::MappingType &tagValueMappings,
1707  bool /*sort*/,
1708  bool &canLoadAs4D)
1709  {
1710  std::list<StringContainer> imageBlocks;
1711 
1712  // ignore sort request, because most likely re-sorting is now needed due to changes in GetSeries(bug #8022)
1713  StringContainer sorted_filenames = DicomSeriesReader::SortSeriesSlices(presortedFilenames);
1714 
1715  std::string firstPosition;
1716  unsigned int numberOfBlocks(0); // number of 3D image blocks
1717 
1718  static const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image position (Patient)
1719  const gdcm::Tag tagModality(0x0008, 0x0060);
1720 
1721  // loop files to determine number of image blocks
1722  for (StringContainer::const_iterator fileIter = sorted_filenames.begin(); fileIter != sorted_filenames.end();
1723  ++fileIter)
1724  {
1725  gdcm::Scanner::TagToValue tagToValueMap = tagValueMappings.find(fileIter->c_str())->second;
1726 
1727  if (tagToValueMap.find(tagImagePositionPatient) == tagToValueMap.end())
1728  {
1729  const std::string &modality = tagToValueMap.find(tagModality)->second;
1730  if (modality.compare("RTIMAGE ") == 0 || modality.compare("RTIMAGE") == 0)
1731  {
1732  MITK_WARN << "Modality " << modality << " is not fully supported yet.";
1733  numberOfBlocks = 1;
1734  break;
1735  }
1736  else
1737  {
1738  // we expect to get images w/ missing position information ONLY as separated blocks.
1739  assert(presortedFilenames.size() == 1);
1740  numberOfBlocks = 1;
1741  break;
1742  }
1743  }
1744 
1745  std::string position = tagToValueMap.find(tagImagePositionPatient)->second;
1746  MITK_DEBUG << " " << *fileIter << " at " << position;
1747  if (firstPosition.empty())
1748  {
1749  firstPosition = position;
1750  }
1751 
1752  if (position == firstPosition)
1753  {
1754  ++numberOfBlocks;
1755  }
1756  else
1757  {
1758  break; // enough information to know the number of image blocks
1759  }
1760  }
1761 
1762  MITK_DEBUG << " ==> Assuming " << numberOfBlocks << " time steps";
1763 
1764  if (numberOfBlocks == 0)
1765  return imageBlocks; // only possible if called with no files
1766 
1767  // loop files to sort them into image blocks
1768  unsigned int numberOfExpectedSlices(0);
1769  for (unsigned int block = 0; block < numberOfBlocks; ++block)
1770  {
1771  StringContainer filesOfCurrentBlock;
1772 
1773  for (StringContainer::const_iterator fileIter = sorted_filenames.begin() + block;
1774  fileIter != sorted_filenames.end();
1775  // fileIter += numberOfBlocks) // TODO shouldn't this work? give invalid iterators on first attempts
1776  )
1777  {
1778  filesOfCurrentBlock.push_back(*fileIter);
1779  for (unsigned int b = 0; b < numberOfBlocks; ++b)
1780  {
1781  if (fileIter != sorted_filenames.end())
1782  ++fileIter;
1783  }
1784  }
1785 
1786  imageBlocks.push_back(filesOfCurrentBlock);
1787 
1788  if (block == 0)
1789  {
1790  numberOfExpectedSlices = filesOfCurrentBlock.size();
1791  }
1792  else
1793  {
1794  if (filesOfCurrentBlock.size() != numberOfExpectedSlices)
1795  {
1796  MITK_WARN
1797  << "DicomSeriesReader expected " << numberOfBlocks << " image blocks of " << numberOfExpectedSlices
1798  << " images each. Block " << block << " got " << filesOfCurrentBlock.size()
1799  << " instead. Cannot load this as 3D+t"; // TODO implement recovery (load as many slices 3D+t as much
1800  // as possible)
1801  canLoadAs4D = false;
1802  }
1803  }
1804  }
1805 
1806  return imageBlocks;
1807  }
1808 
1810  bool correctTilt,
1811  const GantryTiltInformation &tiltInfo,
1812  DcmIoType::Pointer &io,
1813  CallbackCommand *command,
1814  Image::Pointer preLoadedImageBlock)
1815  {
1816  io = DcmIoType::New();
1817  io->SetFileName(filenames.front().c_str());
1818  io->ReadImageInformation();
1819 
1820  if (io->GetPixelType() == itk::ImageIOBase::SCALAR)
1821  {
1822  return MultiplexLoadDICOMByITKScalar(filenames, correctTilt, tiltInfo, io, command, preLoadedImageBlock);
1823  }
1824  else if (io->GetPixelType() == itk::ImageIOBase::RGB)
1825  {
1826  return MultiplexLoadDICOMByITKRGBPixel(filenames, correctTilt, tiltInfo, io, command, preLoadedImageBlock);
1827  }
1828  else
1829  {
1830  return nullptr;
1831  }
1832  }
1833 
1834  Image::Pointer DicomSeriesReader::MultiplexLoadDICOMByITK4D(std::list<StringContainer> &imageBlocks,
1835  ImageBlockDescriptor imageBlockDescriptor,
1836  bool correctTilt,
1837  const GantryTiltInformation &tiltInfo,
1838  DcmIoType::Pointer &io,
1839  CallbackCommand *command,
1840  Image::Pointer preLoadedImageBlock)
1841  {
1842  io = DcmIoType::New();
1843  io->SetFileName(imageBlocks.front().front().c_str());
1844  io->ReadImageInformation();
1845 
1846  if (io->GetPixelType() == itk::ImageIOBase::SCALAR)
1847  {
1849  imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock);
1850  }
1851  else if (io->GetPixelType() == itk::ImageIOBase::RGB)
1852  {
1854  imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock);
1855  }
1856  else
1857  {
1858  return nullptr;
1859  }
1860  }
1861 } // end namespace mitk
PixelSpacingInterpretation
How the mitk::Image spacing should be interpreted.
mitk::PropertyList * GetPropertyList(const mitk::BaseRenderer *renderer=nullptr) const
Get the PropertyList of the renderer. If renderer is NULL, the BaseRenderer-independent PropertyList ...
StringContainer GetBlockFilenames()
Grouping result, all same origin-to-origin distance w/o gaps.
std::map< std::string, std::string > TagToPropertyMapType
Maps DICOM tags to MITK properties.
StringContainer GetFilenames() const
List of files in this group.
itk::SmartPointer< Self > Pointer
static itk::SmartPointer< Image > MultiplexLoadDICOMByITK4DScalar(std::list< StringContainer > &imageBlocks, ImageBlockDescriptor imageBlockDescriptor, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
static bool DICOMStringToSpacing(const std::string &s, ScalarType &spacingX, ScalarType &spacingY)
Safely convert a string into pixel spacing x and y.
void SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing=false)
Set the spacing (m_Spacing).
Return type of GetSeries, describes a logical group of files.
void SetTableValue(IdentifierType id, ValueType value)
bool HasGantryTiltCorrected() const
Whether or not the block contains a gantry tilt which will be "corrected" during loading.
static Point3D DICOMStringToPoint3D(const std::string &s, bool &successful)
Convert DICOM string describing a point to Point3D.
std::string GetImageBlockUID() const
A unique ID describing this bloc (enhanced Series Instance UID).
static void ScanForSliceInformation(const StringContainer &filenames, gdcm::Scanner &scanner)
Scan for slice image information.
#define MITK_ERROR
Definition: mitkLogMacros.h:24
double ScalarType
static void FixSpacingInformation(Image *image, const ImageBlockDescriptor &imageBlockDescriptor)
void UndoPrematureGrouping()
Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption.
static std::string GetConfigurationString()
Provide combination of preprocessor defines that was active during compilation.
virtual void SetData(mitk::BaseData *baseData)
Set the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
std::vector< std::string > StringContainer
Lists of filenames.
static bool ReadPhilips3DDicom(const std::string &filename, itk::SmartPointer< Image > output_image)
Read a Philips3D ultrasound DICOM file and put into an mitk::Image.
#define MITK_DEBUG
Definition: mitkLogMacros.h:26
static DataNode::Pointer LoadDicomSeries(const StringContainer &filenames, bool sort=true, bool load4D=true, bool correctGantryTilt=true, UpdateCallBackMethod callback=nullptr, itk::SmartPointer< Image > preLoadedImageBlock=nullptr)
DataCollection - Class to facilitate loading/accessing structured data.
const mitk::Vector3D GetSpacing() const
Get the spacing (size of a pixel).
void ConcatenatePropertyList(PropertyList *pList, bool replace=false)
Set a property object in the list/map by reference.
BaseData * GetData() const
Get the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
static itk::SmartPointer< Image > MultiplexLoadDICOMByITKRGBPixel(const StringContainer &, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
std::string GetModality() const
Series Modality (CT, MR, etc.)
mitk::BaseProperty * GetProperty(const char *propertyKey, const mitk::BaseRenderer *renderer=nullptr) const
Get the property (instance of BaseProperty) with key propertyKey from the PropertyList of the rendere...
StringContainer GetUnsortedFilenames()
Remaining files, which could not be grouped.
static std::string CreateSeriesIdentifierPart(gdcm::Scanner::TagToValue &tagValueMap, const gdcm::Tag &tag)
Helper for CreateMoreUniqueSeriesIdentifier.
static std::string ReaderImplementationLevelToString(const ReaderImplementationLevel &enumValue)
static itk::SmartPointer< Image > MultiplexLoadDICOMByITK4DRGBPixel(std::list< StringContainer > &imageBlocks, ImageBlockDescriptor imageBlockDescriptor, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
std::string GetSOPClassUIDAsString() const
SOP Class UID as readable string (Computed Tomography Image Storage, Secondary Capture Image Storage...
static std::string ConstCharStarToString(const char *s)
Safely convert const char* to std::string.
loader code is implemented but not accompanied by tests
static Pointer New()
static std::string PixelSpacingInterpretationToString(const PixelSpacingInterpretation &enumValue)
static std::string IDifyTagValue(const std::string &value)
Helper for CreateMoreUniqueSeriesIdentifier.
void SetProperty(const char *propertyKey, BaseProperty *property)
map::core::discrete::Elements< 3 >::InternalImageType ImageType
#define MITK_WARN
Definition: mitkLogMacros.h:23
static void LoadDicom(const StringContainer &filenames, DataNode &node, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback, itk::SmartPointer< Image > preLoadedImageBlock)
Performs actual loading of a series and creates an image having the specified pixel type...
Convenience class to temporarily change the current locale.
static std::list< StringContainer > SortIntoBlocksFor3DplusT(const StringContainer &presortedFilenames, const gdcm::Scanner::MappingType &tagValueMappings_, bool sort, bool &canLoadAs4D)
Sort files into time step blocks of a 3D+t image.
bool IsSheared() const
Whether the slices were sheared.
static Pointer New()
static const std::string filename
virtual std::string GetValueAsString() const
#define mitkThrow()
static FileNamesGrouping GetSeries(const std::string &dir, bool groupImagesWithGantryTilt, const StringContainer &restrictions=StringContainer())
see other GetSeries().
static void DICOMStringToOrientationVectors(const std::string &s, Vector3D &right, Vector3D &up, bool &successful)
Convert DICOM string describing a point two Vector3D.
void AddFileToSortedBlock(const std::string &filename)
Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
Image class for storing images.
Definition: mitkImage.h:76
virtual void SetName(const char *name)
Extra convenience access method to set the name of an object.
Definition: mitkDataNode.h:395
std::string GetSeriesInstanceUID() const
The Series Instance UID.
bool ContainsGantryTilt()
Wheter or not the grouped result contain a gantry tilt.
Progress callback for DicomSeriesReader.
static bool IsPhilips3DDicom(const std::string &filename)
Checks if a specific file is a Philips3D ultrasound DICOM file.
static itk::SmartPointer< Image > MultiplexLoadDICOMByITKScalar(const StringContainer &, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
static bool IsDicom(const std::string &filename)
Checks if a specific file contains DICOM data.
static Pointer New()
bool IsMultiFrameImage() const
Multi-frame image(s) or not.
Return type of DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption.
void CastToMitkImage(const itk::SmartPointer< ItkOutputImageType > &itkimage, itk::SmartPointer< mitk::Image > &mitkoutputimage)
Cast an itk::Image (with a specific type) to an mitk::Image.
Definition: mitkImageCast.h:78
static void CopyMetaDataToImageProperties(StringContainer filenames, const gdcm::Scanner::MappingType &tagValueMappings_, DcmIoType *io, const ImageBlockDescriptor &blockInfo, Image *image)
Copy information about files and DICOM tags from ITK's MetaDataDictionary and from the list of input ...
static itk::SmartPointer< Image > MultiplexLoadDICOMByITK4D(std::list< StringContainer > &imageBlocks, ImageBlockDescriptor imageBlockDescriptor, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
ReaderImplementationLevel GetReaderImplementationLevel() const
Confidence of the reader that this block can be read successfully.
void FlagGantryTilt()
Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
MITKCORE_EXPORT const ScalarType eps
static const TagToPropertyMapType & GetDICOMTagsToMITKPropertyMap()
Map between DICOM tags and MITK properties.
void AddFileToUnsortedBlock(const std::string &filename)
Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
std::map< std::string, ImageBlockDescriptor > FileNamesGrouping
mitk::BaseGeometry * GetGeometry(int t=0) const
Return the geometry, which is a TimeGeometry, of the data as non-const pointer.
Definition: mitkBaseData.h:129
PixelSpacingInterpretation GetPixelSpacingType() const
How the mitk::Image spacing can meaningfully be interpreted.
static std::string CreateMoreUniqueSeriesIdentifier(gdcm::Scanner::TagToValue &tagValueMap)
Construct a UID that takes into account sorting criteria from GetSeries().
static Pointer New()
static itk::SmartPointer< Image > MultiplexLoadDICOMByITK(const StringContainer &, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, itk::SmartPointer< Image > preLoadedImageBlock)
static StringContainer SortSeriesSlices(const StringContainer &unsortedFilenames)
Sort a set of file names in an order that is meaningful for loading them into an mitk::Image.
ReaderImplementationLevel
Describes how well the reader is tested for a certain file type.
bool IsRegularGantryTilt() const
Whether the shearing is a gantry tilt or more complicated.
static bool GdcmSortFunction(const gdcm::DataSet &ds1, const gdcm::DataSet &ds2)
Defines spatial sorting for sorting by GDCM 2.
Class for nodes of the DataTree.
Definition: mitkDataNode.h:66
static SliceGroupingAnalysisResult AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const StringContainer &files, bool groupsOfSimilarImages, const gdcm::Scanner::MappingType &tagValueMappings_)
Ensure an equal z-spacing for a group of files.
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.