Medical Imaging Interaction Toolkit  2018.4.99-3e3f1a6e
Medical Imaging Interaction Toolkit
mitkTestDICOMLoading.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 //#define MBILOG_ENABLE_DEBUG
13 
14 #include "mitkTestDICOMLoading.h"
15 
16 #include <stack>
17 
19 :m_PreviousCLocale(nullptr)
20 {
21 }
22 
23 void mitk::TestDICOMLoading::SetDefaultLocale()
24 {
25  // remember old locale only once
26  if (m_PreviousCLocale == nullptr)
27  {
28  m_PreviousCLocale = setlocale(LC_NUMERIC, nullptr);
29 
30  // set to "C"
31  setlocale(LC_NUMERIC, "C");
32 
33  m_PreviousCppLocale = std::cin.getloc();
34 
35  std::locale l( "C" );
36  std::cin.imbue(l);
37  std::cout.imbue(l);
38  }
39 }
40 
41 void mitk::TestDICOMLoading::ResetUserLocale()
42 {
43  if (m_PreviousCLocale)
44  {
45  setlocale(LC_NUMERIC, m_PreviousCLocale);
46 
47  std::cin.imbue(m_PreviousCppLocale);
48  std::cout.imbue(m_PreviousCppLocale);
49 
50  m_PreviousCLocale = nullptr;
51  }
52 }
53 
54 
55 
58 ::LoadFiles( const StringList& files )
59 {
60  for (auto iter = files.begin();
61  iter != files.end();
62  ++iter)
63  {
64  MITK_DEBUG << "File " << *iter;
65  }
66 
67  ImageList result;
68 
69  ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader();
70  reader->SetInputFiles( files );
71  reader->AnalyzeInputFiles();
72  reader->PrintOutputs(std::cout,true);
73  reader->LoadImages();
74 
75  unsigned int numberOfImages = reader->GetNumberOfOutputs();
76  for (unsigned imageIndex = 0; imageIndex < numberOfImages; ++imageIndex)
77  {
78  const DICOMImageBlockDescriptor& block = reader->GetOutput(imageIndex);
79  result.push_back( block.GetMitkImage() );
80  }
81 
82  return result;
83 }
84 
85 mitk::ClassicDICOMSeriesReader::Pointer
86 mitk::TestDICOMLoading
87 ::BuildDICOMReader()
88 {
89  ClassicDICOMSeriesReader::Pointer reader = ClassicDICOMSeriesReader::New();
90  reader->SetFixTiltByShearing(true);
91  return reader;
92 }
93 
97 {
99 
100  DICOMImageFrameList framelist;
101  for (auto iter = files.begin();
102  iter != files.end();
103  ++iter)
104  {
105  framelist.push_back( DICOMImageFrameInfo::New(*iter) );
106  }
107 
108  block.SetImageFrameList( framelist );
109 
110  block.SetTagCache( tagCache );
111  block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices
112  return block.GetMitkImage();
113 }
114 
115 
119 {
120  ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader();
121  reader->SetInputFiles( files );
122  reader->AnalyzeInputFiles(); // This just creates a "tag cache and a nice DICOMImageBlockDescriptor.
123  // Both of these could also be produced in a different way. The only
124  // important thing is, that the DICOMImageBlockDescriptor knows a
125  // tag-cache object when PropertyDecorateCachedMitkImageForImageBlockDescriptor
126  // is called.
127 
128  if ( reader->GetNumberOfOutputs() != 1 )
129  {
130  MITK_ERROR << "Reader produce " << reader->GetNumberOfOutputs() << " images instead of 1 expected..";
131  return nullptr;
132  }
133 
134  DICOMImageBlockDescriptor block = reader->GetOutput(0); // creates a block copy
135  block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices
136  return block.GetMitkImage();
137 }
138 
139 std::string
140 mitk::TestDICOMLoading::ComponentTypeToString(int type)
141 {
142  if (type == itk::ImageIOBase::UCHAR)
143  return "UCHAR";
144  else if (type == itk::ImageIOBase::CHAR)
145  return "CHAR";
146  else if (type == itk::ImageIOBase::USHORT)
147  return "USHORT";
148  else if (type == itk::ImageIOBase::SHORT)
149  return "SHORT";
150  else if (type == itk::ImageIOBase::UINT)
151  return "UINT";
152  else if (type == itk::ImageIOBase::INT)
153  return "INT";
154  else if (type == itk::ImageIOBase::ULONG)
155  return "ULONG";
156  else if (type == itk::ImageIOBase::LONG)
157  return "LONG";
158  else if (type == itk::ImageIOBase::FLOAT)
159  return "FLOAT";
160  else if (type == itk::ImageIOBase::DOUBLE)
161  return "DOUBLE";
162  else
163  return "UNKNOWN";
164 }
165 
166 
167 // add a line to stringstream result (see DumpImageInformation
168 #define DumpLine(field, data) DumpILine(0, field, data)
169 
170 // add an indented(!) line to stringstream result (see DumpImageInformation
171 #define DumpILine(indent, field, data) \
172 { \
173  std::string DumpLine_INDENT; DumpLine_INDENT.resize(indent, ' ' ); \
174  result << DumpLine_INDENT << field << ": " << data << "\n"; \
175 }
176 
177 std::string
179 {
180  std::stringstream result;
181 
182  if (image == nullptr) return result.str();
183 
184  SetDefaultLocale();
185 
186  // basic image data
187  DumpLine( "Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()) );
188  DumpLine( "BitsPerPixel", image->GetPixelType().GetBpe() );
189  DumpLine( "Dimension", image->GetDimension() );
190 
191  result << "Dimensions: ";
192  for (unsigned int dim = 0; dim < image->GetDimension(); ++dim)
193  result << image->GetDimension(dim) << " ";
194  result << "\n";
195 
196  // geometry data
197  result << "Geometry: \n";
198  const TimeGeometry* timeGeometry = image->GetTimeGeometry();
199  BaseGeometry* geometry = timeGeometry->GetGeometryForTimeStep(0);
200  if (geometry)
201  {
202  AffineTransform3D* transform = geometry->GetIndexToWorldTransform();
203  if (transform)
204  {
205  result << " " << "Matrix: ";
206  const AffineTransform3D::MatrixType& matrix = transform->GetMatrix();
207  for (unsigned int i = 0; i < 3; ++i)
208  for (unsigned int j = 0; j < 3; ++j)
209  result << matrix[i][j] << " ";
210  result << "\n";
211 
212  result << " " << "Offset: ";
213  const AffineTransform3D::OutputVectorType& offset = transform->GetOffset();
214  for (unsigned int i = 0; i < 3; ++i)
215  result << offset[i] << " ";
216  result << "\n";
217 
218  result << " " << "Center: ";
219  const AffineTransform3D::InputPointType& center = transform->GetCenter();
220  for (unsigned int i = 0; i < 3; ++i)
221  result << center[i] << " ";
222  result << "\n";
223 
224  result << " " << "Translation: ";
225  const AffineTransform3D::OutputVectorType& translation = transform->GetTranslation();
226  for (unsigned int i = 0; i < 3; ++i)
227  result << translation[i] << " ";
228  result << "\n";
229 
230  result << " " << "Scale: ";
231  const double* scale = transform->GetScale();
232  for (unsigned int i = 0; i < 3; ++i)
233  result << scale[i] << " ";
234  result << "\n";
235 
236  result << " " << "Origin: ";
237  const Point3D& origin = geometry->GetOrigin();
238  for (unsigned int i = 0; i < 3; ++i)
239  result << origin[i] << " ";
240  result << "\n";
241 
242  result << " " << "Spacing: ";
243  const Vector3D& spacing = geometry->GetSpacing();
244  for (unsigned int i = 0; i < 3; ++i)
245  result << spacing[i] << " ";
246  result << "\n";
247 
248  result << " " << "TimeBounds: ";
249  const TimeBounds timeBounds = timeGeometry->GetTimeBounds();
250  for (unsigned int i = 0; i < 2; ++i)
251  result << timeBounds[i] << " ";
252  result << "\n";
253 
254 
255  }
256  }
257 
258  ResetUserLocale();
259 
260  return result.str();
261 }
262 
263 std::string
264 mitk::TestDICOMLoading::trim(const std::string& pString,
265  const std::string& pWhitespace)
266 {
267  const size_t beginStr = pString.find_first_not_of(pWhitespace);
268  if (beginStr == std::string::npos)
269  {
270  // no content
271  return "";
272  }
273 
274  const size_t endStr = pString.find_last_not_of(pWhitespace);
275  const size_t range = endStr - beginStr + 1;
276 
277  return pString.substr(beginStr, range);
278 }
279 
280 std::string
281 mitk::TestDICOMLoading::reduce(const std::string& pString,
282  const std::string& pFill,
283  const std::string& pWhitespace)
284 {
285  // trim first
286  std::string result(trim(pString, pWhitespace));
287 
288  // replace sub ranges
289  size_t beginSpace = result.find_first_of(pWhitespace);
290  while (beginSpace != std::string::npos)
291  {
292  const size_t endSpace =
293  result.find_first_not_of(pWhitespace, beginSpace);
294  const size_t range = endSpace - beginSpace;
295 
296  result.replace(beginSpace, range, pFill);
297 
298  const size_t newStart = beginSpace + pFill.length();
299  beginSpace = result.find_first_of(pWhitespace, newStart);
300  }
301 
302  return result;
303 }
304 
305 
306 bool
307 mitk::TestDICOMLoading::CompareSpacedValueFields( const std::string& reference,
308  const std::string& test,
309  double /*eps*/ )
310 {
311  bool result(true);
312 
313  // tokenize string, compare each token, if possible by float comparison
314  std::stringstream referenceStream(reduce(reference));
315  std::stringstream testStream(reduce(test));
316 
317  std::string refToken;
318  std::string testToken;
319  while ( std::getline( referenceStream, refToken, ' ' ) &&
320  std::getline ( testStream, testToken, ' ' ) )
321  {
322  float refNumber;
323  float testNumber;
324  if ( this->StringToNumber(refToken, refNumber) )
325  {
326  if ( this->StringToNumber(testToken, testNumber) )
327  {
328  // print-out compared tokens if DEBUG output allowed
329  MITK_DEBUG << "Reference Token '" << refToken << "'" << " value " << refNumber
330  << ", test Token '" << testToken << "'" << " value " << testNumber;
331 
332  bool old_result = result;
333 
334  result &= ( std::abs(refNumber - testNumber) < 0.0001f /*mitk::eps*/ );
335  // log the token/number which causes the test to fail
336  if( old_result != result)
337  {
338  MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'" << " value " << refNumber
339  << ", test Token '" << testToken << "'" << " value " << testNumber;
340 
341  MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << std::abs(refNumber - testNumber) << " EPS: " << 0.0001f; //mitk::eps;
342  }
343  }
344  else
345  {
346  MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'";
347  }
348  }
349  else
350  {
351  MITK_DEBUG << "Token '" << refToken << "'" << " handled as string";
352  result &= refToken == testToken;
353  }
354  }
355 
356  if ( std::getline( referenceStream, refToken, ' ' ) )
357  {
358  MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference << "', test '" << test << "'";
359  result = false;
360  }
361  else if ( std::getline( testStream, testToken, ' ' ) )
362  {
363  MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference << "', test '" << test << "'";
364  result = false;
365  }
366 
367  return result;
368 }
369 
370 bool
372  const std::string& testDump )
373 {
374  KeyValueMap reference = ParseDump(referenceDump);
375  KeyValueMap test = ParseDump(testDump);
376 
377  bool testResult(true);
378 
379  // verify all expected values
380  for (KeyValueMap::const_iterator refIter = reference.begin();
381  refIter != reference.end();
382  ++refIter)
383  {
384  const std::string& refKey = refIter->first;
385  const std::string& refValue = refIter->second;
386 
387  if ( test.find(refKey) != test.end() )
388  {
389  const std::string& testValue = test[refKey];
390 
391  bool thisTestResult = CompareSpacedValueFields( refValue, testValue );
392  testResult &= thisTestResult;
393 
394  MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult?"YES":"NO");
395  }
396  else
397  {
398  MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "')." ;
399  MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update your test data.";
400  return false;
401  }
402  }
403 
404  // now check test dump does not contain any additional keys
405  for (KeyValueMap::const_iterator testIter = test.begin();
406  testIter != test.end();
407  ++testIter)
408  {
409  const std::string& key = testIter->first;
410  const std::string& value = testIter->second;
411 
412  if ( reference.find(key) == reference.end() )
413  {
414  MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "')." ;
415  MITK_ERROR << "This key is not expected. Most probably you need to update your test data.";
416  return false;
417  }
418  }
419 
420  return testResult;
421 }
422 
423 mitk::TestDICOMLoading::KeyValueMap
424 mitk::TestDICOMLoading::ParseDump( const std::string& dump )
425 {
426  KeyValueMap parsedResult;
427 
428  std::string shredder(dump);
429 
430  std::stack<std::string> surroundingKeys;
431 
432  std::stack<std::string::size_type> expectedIndents;
433  expectedIndents.push(0);
434 
435  while (true)
436  {
437  std::string::size_type newLinePos = shredder.find( '\n' );
438  if (newLinePos == std::string::npos || newLinePos == 0) break;
439 
440  std::string line = shredder.substr( 0, newLinePos );
441  shredder = shredder.erase( 0, newLinePos+1 );
442 
443  std::string::size_type keyPosition = line.find_first_not_of( ' ' );
444  std::string::size_type colonPosition = line.find( ':' );
445 
446  std::string key = line.substr(keyPosition, colonPosition - keyPosition);
447  std::string::size_type firstSpacePosition = key.find_first_of(" ");
448  if (firstSpacePosition != std::string::npos)
449  {
450  key.erase(firstSpacePosition);
451  }
452 
453  if ( keyPosition > expectedIndents.top() )
454  {
455  // more indent than before
456  expectedIndents.push(keyPosition);
457  }
458  else if (keyPosition == expectedIndents.top() )
459  {
460  if (!surroundingKeys.empty())
461  {
462  surroundingKeys.pop(); // last of same length
463  }
464  }
465  else
466  {
467  // less indent than before
468  do expectedIndents.pop();
469  while (expectedIndents.top() != keyPosition); // unwind until current indent is found
470  }
471 
472  if (!surroundingKeys.empty())
473  {
474  key = surroundingKeys.top() + "." + key; // construct current key name
475  }
476 
477  surroundingKeys.push(key); // this is the new embracing key
478 
479  std::string value = line.substr(colonPosition+1);
480 
481  MITK_DEBUG << " Key: '" << key << "' value '" << value << "'" ;
482 
483  parsedResult[key] = value; // store parsing result
484  }
485 
486  return parsedResult;
487 }
488 
static Pointer New()
static char * line
Definition: svm.cpp:2870
itk::FixedArray< ScalarType, 2 > TimeBounds
Standard typedef for time-bounds.
const mitk::PixelType GetPixelType(int n=0) const
Returns the PixelType of channel n.
Definition: mitkImage.cpp:101
#define MITK_ERROR
Definition: mitkLogMacros.h:20
Follow Up Storage - Class to facilitate loading/accessing structured follow-up data.
Definition: testcase.h:28
#define MITK_DEBUG
Definition: mitkLogMacros.h:22
Image::Pointer GetMitkImage() const
the 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList
std::string DumpImageInformation(const Image *image)
Dump relevant image information for later comparison.
void SetImageFrameList(const DICOMImageFrameList &framelist)
List of frames that constitute the mitk::Image (DICOMImageFrames)
vcl_size_t GetBpe() const
Get the number of bits per element (of an element)
void SetMitkImage(Image::Pointer image)
The 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList.
std::list< Image::Pointer > ImageList
int GetComponentType() const
Get the component type (the scalar (!) type). Each element may contain m_NumberOfComponents (more tha...
static Vector3D offset
const mitk::TimeGeometry * GetTimeGeometry() const
Return the TimeGeometry of the data as const pointer.
Definition: mitkBaseData.h:61
virtual TimeBounds GetTimeBounds() const =0
Get the time bounds (in ms)
unsigned int GetDimension() const
Get dimension of the image.
Definition: mitkImage.cpp:106
std::vector< DICOMImageFrameInfo::Pointer > DICOMImageFrameList
Image class for storing images.
Definition: mitkImage.h:72
Output descriptor for DICOMFileReader.
void SetTagCache(DICOMTagCache *privateCache)
#define DumpLine(field, data)
itk::AffineGeometryFrame< ScalarType, 3 >::TransformType AffineTransform3D
mitk::Image::Pointer image
const Point3D GetOrigin() const
Get the origin, e.g. the upper-left corner of the plane.
std::vector< std::string > StringList
const mitk::Vector3D GetSpacing() const
Get the spacing (size of a pixel).
virtual BaseGeometry::Pointer GetGeometryForTimeStep(TimeStepType timeStep) const =0
Returns the geometry which corresponds to the given time step.
Image::Pointer DecorateVerifyCachedImage(const StringList &files, mitk::Image::Pointer cachedImage)
ImageList LoadFiles(const StringList &files)
BaseGeometry Describes the geometry of a data object.
mitk::AffineTransform3D * GetIndexToWorldTransform()
Get the transformation used to convert from index to world coordinates.
bool CompareImageInformationDumps(const std::string &reference, const std::string &test)
Compare two image information dumps.