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
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.