Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkTestDCMLoading.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 <limits>
19 
20 #include "mitkImage.h"
21 #include "mitkTestDCMLoading.h"
22 #include <stack>
23 
24 mitk::TestDCMLoading::TestDCMLoading() : m_PreviousCLocale(nullptr)
25 {
26 }
27 
28 void mitk::TestDCMLoading::SetDefaultLocale()
29 {
30  // remember old locale only once
31  if (m_PreviousCLocale == nullptr)
32  {
33  m_PreviousCLocale = setlocale(LC_NUMERIC, nullptr);
34 
35  // set to "C"
36  setlocale(LC_NUMERIC, "C");
37 
38  m_PreviousCppLocale = std::cin.getloc();
39 
40  std::locale l("C");
41  std::cin.imbue(l);
42  std::cout.imbue(l);
43  }
44 }
45 
46 void mitk::TestDCMLoading::ResetUserLocale()
47 {
48  if (m_PreviousCLocale)
49  {
50  setlocale(LC_NUMERIC, m_PreviousCLocale);
51 
52  std::cin.imbue(m_PreviousCppLocale);
53  std::cout.imbue(m_PreviousCppLocale);
54 
55  m_PreviousCLocale = nullptr;
56  }
57 }
58 
60  itk::SmartPointer<Image> preLoadedVolume)
61 {
62  for (auto iter = files.begin(); iter != files.end(); ++iter)
63  {
64  MITK_DEBUG << "File " << *iter;
65  }
66 
67  ImageList result;
68 
70 
71  // TODO sort series UIDs, implementation of map iterator might differ on different platforms (or verify this is a
72  // standard topic??)
73  for (DicomSeriesReader::FileNamesGrouping::const_iterator seriesIter = seriesInFiles.begin();
74  seriesIter != seriesInFiles.end();
75  ++seriesIter)
76  {
77  StringContainer files = seriesIter->second.GetFilenames();
78 
80  files, true, true, true, nullptr, preLoadedVolume); // true, true, true ist just a copy of the default values
81 
82  if (node.IsNotNull())
83  {
84  Image::Pointer image = dynamic_cast<mitk::Image *>(node->GetData());
85 
86  result.push_back(image);
87  }
88  else
89  {
90  }
91  }
92 
93  return result;
94 }
95 
96 std::string mitk::TestDCMLoading::ComponentTypeToString(int type)
97 {
98  if (type == itk::ImageIOBase::UCHAR)
99  return "UCHAR";
100  else if (type == itk::ImageIOBase::CHAR)
101  return "CHAR";
102  else if (type == itk::ImageIOBase::USHORT)
103  return "USHORT";
104  else if (type == itk::ImageIOBase::SHORT)
105  return "SHORT";
106  else if (type == itk::ImageIOBase::UINT)
107  return "UINT";
108  else if (type == itk::ImageIOBase::INT)
109  return "INT";
110  else if (type == itk::ImageIOBase::ULONG)
111  return "ULONG";
112  else if (type == itk::ImageIOBase::LONG)
113  return "LONG";
114  else if (type == itk::ImageIOBase::FLOAT)
115  return "FLOAT";
116  else if (type == itk::ImageIOBase::DOUBLE)
117  return "DOUBLE";
118  else
119  return "UNKNOWN";
120 }
121 
122 // add a line to stringstream result (see DumpImageInformation
123 #define DumpLine(field, data) DumpILine(0, field, data)
124 
125 // add an indented(!) line to stringstream result (see DumpImageInformation
126 #define DumpILine(indent, field, data) \
127  \
128 { \
129  std::string DumpLine_INDENT; \
130  DumpLine_INDENT.resize(indent, ' '); \
131  result << DumpLine_INDENT << field << ": " << data << "\n"; \
132  \
133 }
134 
136 {
137  std::stringstream result;
138 
139  if (image == nullptr)
140  return result.str();
141 
142  SetDefaultLocale();
143 
144  // basic image data
145  DumpLine("Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()));
146  DumpLine("BitsPerPixel", image->GetPixelType().GetBpe());
147  DumpLine("Dimension", image->GetDimension());
148 
149  result << "Dimensions: ";
150  for (unsigned int dim = 0; dim < image->GetDimension(); ++dim)
151  result << image->GetDimension(dim) << " ";
152  result << "\n";
153 
154  // geometry data
155  result << "Geometry: \n";
156  BaseGeometry *geometry = image->GetGeometry();
157  if (geometry)
158  {
159  AffineTransform3D *transform = geometry->GetIndexToWorldTransform();
160  if (transform)
161  {
162  result << " "
163  << "Matrix: ";
164  const AffineTransform3D::MatrixType &matrix = transform->GetMatrix();
165  for (unsigned int i = 0; i < 3; ++i)
166  for (unsigned int j = 0; j < 3; ++j)
167  result << matrix[i][j] << " ";
168  result << "\n";
169 
170  result << " "
171  << "Offset: ";
172  const AffineTransform3D::OutputVectorType &offset = transform->GetOffset();
173  for (unsigned int i = 0; i < 3; ++i)
174  result << offset[i] << " ";
175  result << "\n";
176 
177  result << " "
178  << "Center: ";
179  const AffineTransform3D::InputPointType &center = transform->GetCenter();
180  for (unsigned int i = 0; i < 3; ++i)
181  result << center[i] << " ";
182  result << "\n";
183 
184  result << " "
185  << "Translation: ";
186  const AffineTransform3D::OutputVectorType &translation = transform->GetTranslation();
187  for (unsigned int i = 0; i < 3; ++i)
188  result << translation[i] << " ";
189  result << "\n";
190 
191  result << " "
192  << "Scale: ";
193  const double *scale = transform->GetScale();
194  for (unsigned int i = 0; i < 3; ++i)
195  result << scale[i] << " ";
196  result << "\n";
197 
198  result << " "
199  << "Origin: ";
200  const Point3D &origin = geometry->GetOrigin();
201  for (unsigned int i = 0; i < 3; ++i)
202  result << origin[i] << " ";
203  result << "\n";
204 
205  result << " "
206  << "Spacing: ";
207  const Vector3D &spacing = geometry->GetSpacing();
208  for (unsigned int i = 0; i < 3; ++i)
209  result << spacing[i] << " ";
210  result << "\n";
211 
212  result << " "
213  << "TimeBounds: ";
214  const TimeBounds timeBounds = image->GetTimeGeometry()->GetTimeBounds();
215  for (unsigned int i = 0; i < 2; ++i)
216  result << timeBounds[i] << " ";
217  result << "\n";
218  }
219  }
220 
221  ResetUserLocale();
222 
223  return result.str();
224 }
225 
226 std::string mitk::TestDCMLoading::trim(const std::string &pString, const std::string &pWhitespace)
227 {
228  const size_t beginStr = pString.find_first_not_of(pWhitespace);
229  if (beginStr == std::string::npos)
230  {
231  // no content
232  return "";
233  }
234 
235  const size_t endStr = pString.find_last_not_of(pWhitespace);
236  const size_t range = endStr - beginStr + 1;
237 
238  return pString.substr(beginStr, range);
239 }
240 
241 std::string mitk::TestDCMLoading::reduce(const std::string &pString,
242  const std::string &pFill,
243  const std::string &pWhitespace)
244 {
245  // trim first
246  std::string result(trim(pString, pWhitespace));
247 
248  // replace sub ranges
249  size_t beginSpace = result.find_first_of(pWhitespace);
250  while (beginSpace != std::string::npos)
251  {
252  const size_t endSpace = result.find_first_not_of(pWhitespace, beginSpace);
253  const size_t range = endSpace - beginSpace;
254 
255  result.replace(beginSpace, range, pFill);
256 
257  const size_t newStart = beginSpace + pFill.length();
258  beginSpace = result.find_first_of(pWhitespace, newStart);
259  }
260 
261  return result;
262 }
263 
264 bool mitk::TestDCMLoading::CompareSpacedValueFields(const std::string &reference,
265  const std::string &test,
266  double /*eps*/)
267 {
268  bool result(true);
269 
270  // tokenize string, compare each token, if possible by float comparison
271  std::stringstream referenceStream(reduce(reference));
272  std::stringstream testStream(reduce(test));
273 
274  std::string refToken;
275  std::string testToken;
276  while (std::getline(referenceStream, refToken, ' ') && std::getline(testStream, testToken, ' '))
277  {
278  float refNumber;
279  float testNumber;
280  if (this->StringToNumber(refToken, refNumber))
281  {
282  if (this->StringToNumber(testToken, testNumber))
283  {
284  // print-out compared tokens if DEBUG output allowed
285  MITK_DEBUG << "Reference Token '" << refToken << "'"
286  << " value " << refNumber << ", test Token '" << testToken << "'"
287  << " value " << testNumber;
288 
289  bool old_result = result;
290 
291  result &= (fabs(refNumber - testNumber) < 0.0001 /*mitk::eps*/);
292  // log the token/number which causes the test to fail
293  if (old_result != result)
294  {
295  MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'"
296  << " value " << refNumber << ", test Token '" << testToken << "'"
297  << " value " << testNumber;
298 
299  MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << fabs(refNumber - testNumber)
300  << " EPS: " << 0.0001; // mitk::eps;
301  }
302  }
303  else
304  {
305  MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'";
306  }
307  }
308  else
309  {
310  MITK_DEBUG << "Token '" << refToken << "'"
311  << " handled as string";
312  result &= refToken == testToken;
313  }
314  }
315 
316  if (std::getline(referenceStream, refToken, ' '))
317  {
318  MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference
319  << "', test '" << test << "'";
320  result = false;
321  }
322  else if (std::getline(testStream, testToken, ' '))
323  {
324  MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference
325  << "', test '" << test << "'";
326  result = false;
327  }
328 
329  return result;
330 }
331 
332 bool mitk::TestDCMLoading::CompareImageInformationDumps(const std::string &referenceDump, const std::string &testDump)
333 {
334  KeyValueMap reference = ParseDump(referenceDump);
335  KeyValueMap test = ParseDump(testDump);
336 
337  bool testResult(true);
338 
339  // verify all expected values
340  for (KeyValueMap::const_iterator refIter = reference.begin(); refIter != reference.end(); ++refIter)
341  {
342  const std::string &refKey = refIter->first;
343  const std::string &refValue = refIter->second;
344 
345  if (test.find(refKey) != test.end())
346  {
347  const std::string &testValue = test[refKey];
348 
349  bool thisTestResult = CompareSpacedValueFields(refValue, testValue);
350  testResult &= thisTestResult;
351 
352  MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO");
353  }
354  else
355  {
356  MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "').";
357  MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update "
358  "your test data.";
359  return false;
360  }
361  }
362 
363  // now check test dump does not contain any additional keys
364  for (KeyValueMap::const_iterator testIter = test.begin(); testIter != test.end(); ++testIter)
365  {
366  const std::string &key = testIter->first;
367  const std::string &value = testIter->second;
368 
369  if (reference.find(key) == reference.end())
370  {
371  MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "').";
372  MITK_ERROR << "This key is not expected. Most probably you need to update your test data.";
373  return false;
374  }
375  }
376 
377  return testResult;
378 }
379 
380 mitk::TestDCMLoading::KeyValueMap mitk::TestDCMLoading::ParseDump(const std::string &dump)
381 {
382  KeyValueMap parsedResult;
383 
384  std::string shredder(dump);
385 
386  std::stack<std::string> surroundingKeys;
387 
388  std::stack<std::string::size_type> expectedIndents;
389  expectedIndents.push(0);
390 
391  while (true)
392  {
393  std::string::size_type newLinePos = shredder.find('\n');
394  if (newLinePos == std::string::npos || newLinePos == 0)
395  break;
396 
397  std::string line = shredder.substr(0, newLinePos);
398  shredder = shredder.erase(0, newLinePos + 1);
399 
400  std::string::size_type keyPosition = line.find_first_not_of(' ');
401  std::string::size_type colonPosition = line.find(':');
402 
403  std::string key = line.substr(keyPosition, colonPosition - keyPosition);
404  std::string::size_type firstSpacePosition = key.find_first_of(" ");
405  if (firstSpacePosition != std::string::npos)
406  {
407  key.erase(firstSpacePosition);
408  }
409 
410  if (keyPosition > expectedIndents.top())
411  {
412  // more indent than before
413  expectedIndents.push(keyPosition);
414  }
415  else if (keyPosition == expectedIndents.top())
416  {
417  if (!surroundingKeys.empty())
418  {
419  surroundingKeys.pop(); // last of same length
420  }
421  }
422  else
423  {
424  // less indent than before
425  do
426  expectedIndents.pop();
427  while (expectedIndents.top() != keyPosition); // unwind until current indent is found
428  }
429 
430  if (!surroundingKeys.empty())
431  {
432  key = surroundingKeys.top() + "." + key; // construct current key name
433  }
434 
435  surroundingKeys.push(key); // this is the new embracing key
436 
437  std::string value = line.substr(colonPosition + 1);
438 
439  MITK_DEBUG << " Key: '" << key << "' value '" << value << "'";
440 
441  parsedResult[key] = value; // store parsing result
442  }
443 
444  return parsedResult;
445 }
const Point3D GetOrigin() const
Get the origin, e.g. the upper-left corner of the plane.
static char * line
Definition: svm.cpp:2884
itk::FixedArray< ScalarType, 2 > TimeBounds
Standard typedef for time-bounds.
#define MITK_ERROR
Definition: mitkLogMacros.h:24
std::string DumpImageInformation(const Image *image)
Dump relevant image information for later comparison.
Follow Up Storage - Class to facilitate loading/accessing structured follow-up data.
Definition: testcase.h:32
#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)
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
DicomSeriesReader::StringContainer StringContainer
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)
static FileNamesGrouping GetSeries(const std::string &dir, bool groupImagesWithGantryTilt, const StringContainer &restrictions=StringContainer())
see other GetSeries().
Image class for storing images.
Definition: mitkImage.h:76
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
bool CompareImageInformationDumps(const std::string &reference, const std::string &test)
Compare two image information dumps.
std::map< std::string, ImageBlockDescriptor > FileNamesGrouping
std::list< itk::SmartPointer< Image > > ImageList
#define DumpLine(field, data)
unsigned int GetDimension() const
Get dimension of the image.
Definition: mitkImage.cpp:110
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
ImageList LoadFiles(const StringContainer &files, itk::SmartPointer< Image > preLoadedVolume=nullptr)
BaseGeometry Describes the geometry of a data object.
mitk::AffineTransform3D * GetIndexToWorldTransform()
Get the transformation used to convert from index to world coordinates.