Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkDataStorageCompare.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 
17 #include "mitkDataStorageCompare.h"
18 #include "mitkBaseDataCompare.h"
19 
20 #include "mitkBaseRenderer.h"
21 #include "mitkMapper.h"
22 
23 #include "usGetModuleContext.h"
24 #include "usLDAPFilter.h"
25 #include "usModuleContext.h"
26 
28  const mitk::DataStorage *test,
29  Tests flags,
30  double eps)
31  : m_Eps(eps),
32  m_TestAspects(flags),
33  m_ReferenceDS(reference),
34  m_TestDS(test),
35  m_HierarchyPassed(true),
36  m_DataPassed(true),
37  m_PropertiesPassed(true),
38  m_MappersPassed(true),
39  m_InteractorsPassed(true),
40  m_AspectsFailed(0)
41 {
43 }
44 
46 {
47  return Compare(true);
48 }
49 
51 {
52  DescribeHierarchyOfNodes(m_ReferenceDS, m_RefNodesByHierarchy);
53  DescribeHierarchyOfNodes(m_TestDS, m_TestNodesByHierarchy);
54 
55  m_HierarchyPassed = true;
56  m_DataPassed = true;
57  m_PropertiesPassed = true;
58  m_MappersPassed = true;
59  m_InteractorsPassed = true;
60  m_AspectsFailed = 0;
61 
62  if (m_TestAspects & CMP_Hierarchy)
63  m_HierarchyPassed = CompareHierarchy(verbose);
64 
65  if (m_TestAspects != CMP_Nothing)
66  CompareDataNodes(verbose);
67 
68  if ((m_TestAspects & CMP_Data) && !m_DataPassed)
69  ++m_AspectsFailed;
70  if ((m_TestAspects & CMP_Properties) && !m_PropertiesPassed)
71  ++m_AspectsFailed;
72  if ((m_TestAspects & CMP_Mappers) && !m_MappersPassed)
73  ++m_AspectsFailed;
74  if ((m_TestAspects & CMP_Interactors) && !m_InteractorsPassed)
75  ++m_AspectsFailed;
76 
77  if (verbose)
78  Report();
79 
80  return m_AspectsFailed == 0;
81 }
82 
84 {
85  MITK_INFO << "Comparison results:";
86  MITK_INFO << " Hierarchy comparison: "
87  << (m_TestAspects & CMP_Hierarchy ? (m_HierarchyPassed ? "pass" : "fail") : "skipped");
88  MITK_INFO << " Data comparison: "
89  << (m_TestAspects & CMP_Data ? (m_DataPassed ? "pass" : "fail") : "skipped");
90  MITK_INFO << " Properties comparison: "
91  << (m_TestAspects & CMP_Properties ? (m_PropertiesPassed ? "pass" : "fail") : "skipped");
92  MITK_INFO << " Mappers comparison: "
93  << (m_TestAspects & CMP_Mappers ? (m_MappersPassed ? "pass" : "fail") : "skipped");
94  MITK_INFO << " Interactors comparison: "
95  << (m_TestAspects & CMP_Interactors ? (m_InteractorsPassed ? "pass" : "fail") : "skipped");
96 
97  if (m_AspectsFailed == 0)
98  MITK_INFO << " Summary: ALL PASSED";
99  else
100  MITK_INFO << " Summary: " << m_AspectsFailed << " failures";
101 }
102 
103 void mitk::DataStorageCompare::DescribeHierarchyOfNodes(DataStorage::ConstPointer storage,
104  HierarchyDescriptorMap &result)
105 {
106  result.clear();
107  if (storage.IsNull())
108  return;
109 
110  mitk::DataStorage::SetOfObjects::ConstPointer allNodes = storage->GetAll();
111  for (auto node : *allNodes)
112  {
113  std::string descriptor = GenerateHierarchyDescriptor(node, storage);
114  result.insert(std::make_pair(descriptor, node));
115  }
116 }
117 
118 std::string mitk::DataStorageCompare::GenerateNodeDescriptor(mitk::DataNode::Pointer node)
119 {
120  if (node.IsNull())
121  return "nullptr";
122 
123  std::string thisDataDescriptor = "nullptr";
124  mitk::BaseData *data = node->GetData();
125  if (data != nullptr)
126  thisDataDescriptor = data->GetNameOfClass();
127 
128  std::string thisNodeName = node->GetName();
129 
130  std::string thisNodesDescriptor = std::string("_") + thisDataDescriptor + "_(" + thisNodeName + ")";
131  return thisNodesDescriptor;
132 }
133 
134 std::string mitk::DataStorageCompare::GenerateHierarchyDescriptor(mitk::DataNode::Pointer node,
136 {
137  std::string thisNodesDescriptor = GenerateNodeDescriptor(node);
139  storage->GetSources(node, nullptr, true); // direct sources without filter
140 
141  // construct descriptors for parents
142  std::vector<std::string> parentDescriptors;
143 
144  for (auto parent : *parents)
145  parentDescriptors.push_back(GenerateHierarchyDescriptor(parent, storage));
146 
147  // sort descriptors (we don't want to rely on potentially random order of parents)
148  std::sort(parentDescriptors.begin(), parentDescriptors.end());
149 
150  // construct a string from all sorted parent descriptors
151  if (!parentDescriptors.empty())
152  {
153  thisNodesDescriptor += " <(";
154  for (auto descriptor : parentDescriptors)
155  {
156  if (descriptor != parentDescriptors.front()) // join by '+'
157  {
158  thisNodesDescriptor += " + ";
159  }
160  thisNodesDescriptor += descriptor;
161  }
162  thisNodesDescriptor += ")";
163  }
164 
165  return thisNodesDescriptor;
166 }
167 
168 bool mitk::DataStorageCompare::CompareHierarchy(bool verbose)
169 {
170  int numberOfMisMatches = 0;
171 
172  // check for each reference storage entry
173  // if it can be found in test storage with
174  // an identical hierarchy descriptor.
175  // Compare just counts because there might be
176  // multiple nodes that have the same name / type / etc.
177  for (auto entry : m_RefNodesByHierarchy)
178  {
179  const std::string &key = entry.first;
180  const mitk::DataNode::Pointer &node = entry.second;
181 
182  unsigned int timesInReference = m_RefNodesByHierarchy.count(key);
183  unsigned int timesInTest = m_TestNodesByHierarchy.count(key);
184 
185  if (timesInTest != timesInReference)
186  {
187  ++numberOfMisMatches;
188  if (verbose)
189  {
190  MITK_WARN << "### Hierarchy mismatch problem";
191  MITK_WARN << " Reference storage has " << timesInReference << " node(s), test storage " << timesInTest;
192  MITK_WARN << " Node name '" << node->GetName() << "'";
193  MITK_WARN << " Reference hierarchy descriptor: " << key;
194  }
195  }
196  }
197 
198  // test also keys that are _only_ in test!
199  for (auto entry : m_TestNodesByHierarchy)
200  {
201  const std::string &key = entry.first;
202  const mitk::DataNode::Pointer &node = entry.second;
203 
204  unsigned int timesInReference = m_RefNodesByHierarchy.count(key);
205  unsigned int timesInTest = m_TestNodesByHierarchy.count(key);
206 
207  // we already tested all items in reference storage.
208  // Here we want to test additional items in test storage.
209  if (timesInTest > timesInReference)
210  {
211  ++numberOfMisMatches;
212 
213  if (verbose)
214  {
215  MITK_WARN << "### Hierarchy mismatch problem";
216  MITK_WARN << " Test storage has more nodes (" << timesInReference << ") than reference storage ("
217  << timesInTest << ")";
218  MITK_WARN << " Node name '" << node->GetName() << "'";
219  MITK_WARN << " Reference hierarchy descriptor: " << key;
220  }
221  }
222  }
223 
224  // for debug purposes we provide a dump of the test storage
225  // in error cases. This can be compared to the test case
226  // by a programmer.
227  if (verbose && numberOfMisMatches > 0)
228  {
229  MITK_WARN << "Dumping test storage because there were errors:";
230  for (auto entry : m_TestNodesByHierarchy)
231  {
232  const std::string &key = entry.first;
233  const mitk::DataNode::Pointer &node = entry.second;
234  MITK_WARN << " Test node '" << node->GetName() << "', hierarchy : " << key;
235  }
236  }
237 
238  return numberOfMisMatches == 0;
239 }
240 
241 bool mitk::DataStorageCompare::AreNodesEqual(const mitk::DataNode *reference, const mitk::DataNode *test, bool verbose)
242 {
243  if (reference == nullptr && test == nullptr)
244  return true;
245 
246  if (reference == nullptr && test != nullptr)
247  {
248  if (verbose)
249  MITK_WARN << " Reference node is nullptr, test node is not (type " << test->GetNameOfClass() << ")";
250  return false;
251  }
252 
253  if (reference != nullptr && test == nullptr)
254  {
255  if (verbose)
256  MITK_WARN << " Test node is nullptr, reference node is not (type " << reference->GetNameOfClass() << ")";
257  return false;
258  }
259 
260  if (m_TestAspects & CMP_Data)
261  m_DataPassed &= IsDataEqual(reference->GetData(), test->GetData(), verbose);
262 
263  if (m_TestAspects & CMP_Properties)
264  m_PropertiesPassed &= ArePropertyListsEqual(*reference, *test, verbose);
265 
266  if (m_TestAspects & CMP_Mappers)
267  m_MappersPassed &= AreMappersEqual(*reference, *test, verbose);
268 
269  // .. add interactors/mappers
270 
271  // two real nodes, need to really compare
272  return m_AspectsFailed == 0;
273 }
274 
275 bool mitk::DataStorageCompare::IsDataEqual(const mitk::BaseData *reference, const mitk::BaseData *test, bool verbose)
276 {
277  // early-out for nullptrs
278  if (reference == nullptr && test == nullptr)
279  return true;
280 
281  if (reference == nullptr && test != nullptr)
282  {
283  if (verbose)
284  MITK_WARN << " Reference data is nullptr, test data is not (type " << test->GetNameOfClass() << ")";
285  return false;
286  }
287 
288  if (reference != nullptr && test == nullptr)
289  {
290  if (verbose)
291  MITK_WARN << " Test data is nullptr, reference data is not (type " << reference->GetNameOfClass() << ")";
292  return false;
293  }
294 
295  // two real BaseData objects, need to really compare
296  if (reference->GetNameOfClass() != test->GetNameOfClass())
297  {
298  if (verbose)
299  MITK_WARN << " Mismatch: Reference data is '" << reference->GetNameOfClass() << "', "
300  << "test data is '" << test->GetNameOfClass() << "'";
301  return false;
302  }
303  try
304  {
305  std::string ldapFilter = std::string("(basedata=") + reference->GetNameOfClass() + "*)";
306  std::vector<us::ServiceReference<BaseDataCompare>> comparators =
307  us::GetModuleContext()->GetServiceReferences<BaseDataCompare>(ldapFilter);
308  if (comparators.empty())
309  {
310  // bad, no comparator found, cannot compare
311  MITK_ERROR << "Comparison error: no comparator for objects of type '" << reference->GetNameOfClass() << "'";
312  return false;
313  }
314  else if (comparators.size() > 1)
315  {
316  MITK_WARN << "Comparison warning: multiple comparisons possible for objects of type '"
317  << reference->GetNameOfClass() << "'. Using just one.";
318  // bad, multiple comparators, need to add ranking or something
319  }
320 
321  BaseDataCompare *comparator = us::GetModuleContext()->GetService<BaseDataCompare>(comparators.front());
322  if (!comparator)
323  {
324  MITK_ERROR << "Service lookup error, cannot get comparator for class " << reference->GetNameOfClass();
325  }
326 
327  return comparator->AreEqual(reference, test, m_Eps, verbose);
328  }
329  catch (std::exception &e)
330  {
331  MITK_ERROR << "Exception during comparison: " << e.what();
332  return false;
333  }
334 }
335 
336 bool mitk::DataStorageCompare::ArePropertyListsEqual(const mitk::DataNode &reference,
337  const mitk::DataNode &test,
338  bool verbose)
339 {
340  DataNode::PropertyListKeyNames refListNames = reference.GetPropertyListNames();
342  // add the empty names to treat all lists equally
343  refListNames.push_back("");
344  testListNames.push_back("");
345 
346  // verify that list names are identical
347  bool error = false;
348  if (refListNames.size() != testListNames.size())
349  {
350  for (auto name : refListNames)
351  if (std::find(testListNames.begin(), testListNames.end(), name) == testListNames.end())
352  {
353  MITK_WARN << "Propertylist '" << name << "' from reference node (" << reference.GetName()
354  << ") not found in test node.";
355  error = true;
356  }
357 
358  for (auto name : testListNames)
359  if (std::find(refListNames.begin(), refListNames.end(), name) == refListNames.end())
360  {
361  MITK_WARN << "Propertylist '" << name << "' did not exist in reference node (" << reference.GetName()
362  << "), but is present in test node.";
363  error = true;
364  }
365 
366  if (error)
367  return false;
368  }
369 
370  // compare each list
371  for (auto name : refListNames)
372  {
373  if (!ArePropertyListsEqual(*(reference.GetPropertyList(name)), *(test.GetPropertyList(name)), verbose))
374  {
375  MITK_WARN << "Property mismatch while comparing propertylist '" << name << "'. See messages above.";
376  error = true;
377  }
378  }
379 
380  return !error;
381 }
382 
383 bool mitk::DataStorageCompare::ArePropertyListsEqual(const mitk::PropertyList &reference,
384  const mitk::PropertyList &test,
385  bool verbose)
386 {
387  const mitk::PropertyList::PropertyMap *refMap = reference.GetMap();
388 
389  bool error = false;
390 
391  for (auto refEntry : *refMap)
392  {
393  std::string propertyKey = refEntry.first;
394  BaseProperty::Pointer refProperty = refEntry.second;
395  BaseProperty::Pointer testProperty = test.GetProperty(propertyKey);
396 
397  if (testProperty.IsNull())
398  {
399  if (verbose)
400  MITK_WARN << "Property '" << propertyKey << "' not found in test, only in reference.";
401  error = true;
402  }
403  else
404  {
405  if (!(*refProperty == *testProperty))
406  {
407  if (verbose)
408  {
409  MITK_WARN << "Property '" << propertyKey << "' does not match original.";
410  MITK_WARN << "Reference was: " << refProperty->GetValueAsString();
411  MITK_WARN << "Test was:" << testProperty->GetValueAsString();
412  }
413  error = true;
414  }
415  }
416  }
417 
418  return !error;
419 }
420 
421 bool mitk::DataStorageCompare::AreMappersEqual(const mitk::DataNode &reference,
422  const mitk::DataNode &test,
423  bool verbose)
424 {
425  bool error = false;
426 
427  mitk::Mapper *refMapper2D = reference.GetMapper(mitk::BaseRenderer::Standard2D);
429 
430  if (refMapper2D == nullptr && testMapper2D == nullptr)
431  {
432  ; // ok
433  }
434  else if (refMapper2D != nullptr && testMapper2D == nullptr)
435  {
436  if (verbose)
437  {
438  MITK_WARN << "Mapper for 2D was '" << refMapper2D->GetNameOfClass() << "' in reference, is 'nullptr"
439  << "' in test (DataNode '" << reference.GetName() << "')";
440  }
441  error = true;
442  }
443  else if (refMapper2D == nullptr && testMapper2D != nullptr)
444  {
445  if (verbose)
446  {
447  MITK_WARN << "Mapper for 2D was 'nullptr"
448  << "' in reference, is '" << testMapper2D->GetNameOfClass() << "' in test (DataNode '"
449  << reference.GetName() << "')";
450  }
451  error = true;
452  } // else both are valid pointers, we just compare the type
453  else if (refMapper2D->GetNameOfClass() != testMapper2D->GetNameOfClass())
454  {
455  if (verbose)
456  {
457  MITK_WARN << "Mapper for 2D was '" << refMapper2D->GetNameOfClass() << "' in reference, is '"
458  << testMapper2D->GetNameOfClass() << "' in test (DataNode '" << reference.GetName() << "')";
459  }
460  error = true;
461  }
462 
463  mitk::Mapper *refMapper3D = reference.GetMapper(mitk::BaseRenderer::Standard3D);
465 
466  if (refMapper3D == nullptr && testMapper3D == nullptr)
467  {
468  ; // ok
469  }
470  else if (refMapper3D != nullptr && testMapper3D == nullptr)
471  {
472  if (verbose)
473  {
474  MITK_WARN << "Mapper for 3D was '" << refMapper3D->GetNameOfClass() << "' in reference, is 'nullptr"
475  << "' in test (DataNode '" << reference.GetName() << "')";
476  }
477  error = true;
478  }
479  else if (refMapper3D == nullptr && testMapper3D != nullptr)
480  {
481  if (verbose)
482  {
483  MITK_WARN << "Mapper for 3D was 'nullptr"
484  << "' in reference, is '" << testMapper3D->GetNameOfClass() << "' in test (DataNode '"
485  << reference.GetName() << "')";
486  }
487  error = true;
488  } // else both are valid pointers, we just compare the type
489  else if (refMapper3D->GetNameOfClass() != testMapper3D->GetNameOfClass())
490  {
491  if (verbose)
492  {
493  MITK_WARN << "Mapper for 3D was '" << refMapper3D->GetNameOfClass() << "' in reference, is '"
494  << testMapper3D->GetNameOfClass() << "' in test (DataNode '" << reference.GetName() << "')";
495  }
496  error = true;
497  }
498 
499  return !error;
500 }
501 
502 bool mitk::DataStorageCompare::CompareDataNodes(bool verbose)
503 {
504  int numberOfMisMatches = 0;
505 
506  for (auto entry : m_RefNodesByHierarchy)
507  {
508  const std::string &key = entry.first;
509  const mitk::DataNode::Pointer &refNode = entry.second;
510 
511  unsigned int timesInReference = m_RefNodesByHierarchy.count(key);
512  unsigned int timesInTest = m_TestNodesByHierarchy.count(key);
513 
514  if (timesInReference == 1 && timesInTest == 1)
515  {
516  // go on an compare those two
517  auto testEntry = m_TestNodesByHierarchy.find(key);
518  mitk::DataNode::Pointer testNode = testEntry->second;
519  if (!AreNodesEqual(refNode, testNode, verbose))
520  {
521  ++numberOfMisMatches;
522  if (verbose)
523  {
524  MITK_WARN << "### DataNode mismatch problem";
525  MITK_WARN << " Node '" << key << "' did not compare to equal (see warnings above).";
526  }
527  }
528  }
529  else
530  {
531  ++numberOfMisMatches;
532  if (verbose)
533  {
534  MITK_WARN << "### DataNode mismatch problem";
535  MITK_WARN << " Reference storage has " << timesInReference << " node(s), test storage " << timesInTest;
536  MITK_WARN << " This does not match or we don't know how to figure out comparison partners";
537  }
538  }
539  }
540 
541  return numberOfMisMatches == 0;
542 }
mitk::PropertyList * GetPropertyList(const mitk::BaseRenderer *renderer=nullptr) const
Get the PropertyList of the renderer. If renderer is NULL, the BaseRenderer-independent PropertyList ...
mitk::BaseProperty * GetProperty(const std::string &propertyKey) const
Get a property by its name.
Data management class that handles 'was created by' relations.
#define MITK_INFO
Definition: mitkLogMacros.h:22
std::vector< MapOfPropertyLists::key_type > PropertyListKeyNames
Definition: mitkDataNode.h:72
Base of all data objects.
Definition: mitkBaseData.h:39
#define MITK_ERROR
Definition: mitkLogMacros.h:24
bool CompareVerbose()
Shorthand for Compare(true).
Follow Up Storage - Class to facilitate loading/accessing structured follow-up data.
Definition: testcase.h:32
bool GetName(std::string &nodeName, const mitk::BaseRenderer *renderer=nullptr, const char *propertyKey="name") const
Convenience access method for accessing the name of an object (instance of StringProperty with proper...
Definition: mitkDataNode.h:366
BaseData * GetData() const
Get the data object (instance of BaseData, e.g., an Image) managed by this DataNode.
itk::SmartPointer< Self > Pointer
Key-value list holding instances of BaseProperty.
DataStorageCompare(const DataStorage *reference, const DataStorage *test, Tests flags=CMP_All, double eps=mitk::eps)
Constructor taking reference and test DataStorage.
Base class of all mappers, Vtk as well as OpenGL mappers.
Definition: mitkMapper.h:54
void * GetService(const ServiceReferenceBase &reference)
itk::SmartPointer< const Self > ConstPointer
PropertyListKeyNames GetPropertyListNames() const
The "names" used for (renderer-specific) PropertyLists in GetPropertyList(string).
#define MITK_WARN
Definition: mitkLogMacros.h:23
std::map< std::string, BaseProperty::Pointer > PropertyMap
bool Compare(bool verbose=false)
Execute the comparison.
std::vector< ServiceReferenceU > GetServiceReferences(const std::string &clazz, const std::string &filter=std::string())
static void RegisterCoreEquals()
Register core type comparators that come with mitk::Equal() functions.
mitk::Mapper * GetMapper(MapperSlotId id) const
Tests
Flag describing the aspects of comparing two DataStorages.
MITKCORE_EXPORT const ScalarType eps
static ModuleContext * GetModuleContext()
Returns the module context of the calling module.
void Report()
Prints a small summary of what tests have been executed and which ones failed or passed.
Class for nodes of the DataTree.
Definition: mitkDataNode.h:66
const PropertyMap * GetMap() const