Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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.