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