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