Repository: InsightSoftwareConsortium/ITK (language: python)

Issue:
MetaDataDictionary::Print only prints [UNKNOWN_PRINT_CHARACTERISTICS]
### Description


<!-- Description of the bug. -->

For any MetaDataObject calling `obj->Print(std::cout)` produces "[UNKNOWN_PRINT_CHARACTERISTICS]" on the console. 

https://github.com/InsightSoftwareConsortium/ITK/blob/60469760ee13f48b265ca7424e3e9fd652672710/Modules/Core/Common/include/itkMetaDataObject.hxx#L63-L69

### Expected behavior

For `itk::Object` base objects the `Print` method should be used while those which support `operator<<`, that method should be used.

It is expected to be able to iterate over the metadata dictionary and print the key and value of all types and get descriptive output.


Discussion / hints:
This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.

Facing this as well...

By the way @blowekamp, it would be absolutely fantastic to transfer some of the SimpleITK great goodies on handling metadata dictionaries to ITK wrappings.

SimpleITK reference: https://simpleitk.readthedocs.io/en/master/link_DicomImagePrintTags_docs.html




Fix patch:
diff --git a/Modules/Core/Common/include/itkMetaDataObject.h b/Modules/Core/Common/include/itkMetaDataObject.h
index bbf2f7bd8cf..3b2c80117e2 100644
--- a/Modules/Core/Common/include/itkMetaDataObject.h
+++ b/Modules/Core/Common/include/itkMetaDataObject.h
@@ -58,15 +58,8 @@ namespace itk
  * and redefining the copy constructor and initializing constructor and the Get/Set functions
  * to work around those deficiencies.
  *
- * The behavior of the MetaDataObject<Type>::Print() function has many plausible
- * application dependent implementations.  The default implementation prints the
- * string "[UNKNOWN PRINT CHARACTERISTICS]" that works for all possible
- * MetaDataObject types.
- *
- * The application developer may overload the default implementation to provide
- * a specialized Print() characteristics to produce results desirable for their application.
- * A set of very crude Macros {NATIVE_TYPE_METADATAPRINT, ITK_OBJECT_TYPE_METADATAPRINT_1COMMA,
- * ITK_IMAGE_TYPE_METADATAPRINT  } are provided to facilitate a very simple implementation, and as an example.
+ * The default implementation prints uses the MetaDataObjectType's Print or operator<< if available. Otherwise, it
+ * prints string "[UNKNOWN PRINT CHARACTERISTICS]".
  *
  * \ingroup ITKCommon
  *
@@ -264,52 +257,6 @@ ExposeMetaData(const MetaDataDictionary & Dictionary, const std::string key, T &
 
 } // end namespace itk
 
-/**
- * \def ITK_NATIVE_TYPE_METADATAPRINT( TYPE_NAME )
- * \brief An ugly macro to facilitate creating a simple implementation of
- * the MetaDataObject<Type>::Print() function for types that
- * have operator<< defined.
- * \param TYPE_NAME the native type parameter type
- */
-#define ITK_NATIVE_TYPE_METADATAPRINT(TYPE_NAME)                      \
-  template <>                                                         \
-  void itk::MetaDataObject<TYPE_NAME>::Print(std::ostream & os) const \
-  {                                                                   \
-    os << this->m_MetaDataObjectValue << std::endl;                   \
-  }
-
-/**
- * \def ITK_OBJECT_TYPE_METADATAPRINT_1COMMA( TYPE_NAME_PART1, TYPE_NAME_PART2 )
- * \brief An ugly macro to facilitate creating a simple implementation of
- * the MetaDataObject< Type >::Print() function for
- * itk::Objects that have 1 comma in their type definition
- * \param TYPE_NAME_PART1
- * \param TYPE_NAME_PART2
- */
-#define ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(TYPE_NAME_PART1, TYPE_NAME_PART2)               \
-  template <>                                                                                \
-  void itk::MetaDataObject<TYPE_NAME_PART1, TYPE_NAME_PART2>::Print(std::ostream & os) const \
-  {                                                                                          \
-    this->m_MetaDataObjectValue->Print(os);                                                  \
-  }
-
-/**
- * \def ITK_IMAGE_TYPE_METADATAPRINT( STORAGE_TYPE )
- * An ugly macro to facilitate creating a simple implementation of
- * the MetaDataObject<Type>::Print() function for
- * itk::Image\<STORAGE_TYPE,[1-8]\>\::Pointer
- * \param STORAGE_TYPE The storage type of the image type to print.
- */
-#define ITK_IMAGE_TYPE_METADATAPRINT(STORAGE_TYPE)                           \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 1>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 2>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 3>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 4>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 5>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 6>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 7>::Pointer) \
-  ITK_OBJECT_TYPE_METADATAPRINT_1COMMA(itk::Image<STORAGE_TYPE, 8>::Pointer)
-
 #ifndef ITK_MANUAL_INSTANTIATION
 #  include "itkMetaDataObject.hxx"
 #endif
diff --git a/Modules/Core/Common/include/itkMetaDataObject.hxx b/Modules/Core/Common/include/itkMetaDataObject.hxx
index 23b92f234db..47c2d548956 100644
--- a/Modules/Core/Common/include/itkMetaDataObject.hxx
+++ b/Modules/Core/Common/include/itkMetaDataObject.hxx
@@ -59,11 +59,45 @@ MetaDataObject<MetaDataObjectType>::SetMetaDataObjectValue(const MetaDataObjectT
   Self::Assign(m_MetaDataObjectValue, newValue);
 }
 
+namespace
+{
+template <class T, class = void>
+struct has_Print : std::false_type
+{};
+
+template <class T>
+struct has_Print<T, std::void_t<decltype(std::declval<T>().Print(std::declval<std::ostream &>()))>> : std::true_type
+{};
+
+template <class T, class = void>
+struct has_output_operator : std::false_type
+{};
+
+template <class T>
+struct has_output_operator<T, std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
+  : std::true_type
+{};
+} // namespace
+
 template <typename MetaDataObjectType>
 void
 MetaDataObject<MetaDataObjectType>::Print(std::ostream & os) const
 {
-  Superclass::Print(os);
+  // future c++20 feature
+  // constexpr bool hasPrint = false; requires( const &MetaDataObjectType obj ) { obj.Print(os); };
+
+  if constexpr (has_Print<MetaDataObjectType>::value)
+  {
+    m_MetaDataObjectValue.Print(os);
+  }
+  else if constexpr (has_output_operator<MetaDataObjectType>::value)
+  {
+    os << m_MetaDataObjectValue;
+  }
+  else
+  {
+    Superclass::Print(os);
+  }
 }
 
 } // end namespace itk


Test patch:
diff --git a/Modules/Core/Common/test/itkMetaDataObjectTest.cxx b/Modules/Core/Common/test/itkMetaDataObjectTest.cxx
index 84e75b03c2b..35527a3dfb4 100644
--- a/Modules/Core/Common/test/itkMetaDataObjectTest.cxx
+++ b/Modules/Core/Common/test/itkMetaDataObjectTest.cxx
@@ -68,7 +68,12 @@ itkMetaDataObjectTest(int, char *[])
   result += testMetaData<float>(-24);
   result += testMetaData<double>(-24);
   result += testMetaData<std::string>("I T K");
-
+  result += testMetaData<std::vector<double>>({ 1.0, 2.0, 3.0 });
+  result += testMetaData<std::vector<std::vector<double>>>({ { 1.0, 2.0, 3.0 }, { 4.0, 5.0, 6.0 } });
+  result += testMetaData(itk::Array<char>(3, 'I'));
+  result += testMetaData<itk::Array<double>>(itk::Array<double>(3, 3.0));
+  result += testMetaData(itk::Matrix<float, 4, 4>());
+  result += testMetaData<itk::Matrix<double, 3, 3>>(itk::Matrix<double, 3, 3>::GetIdentity());
   using ImageType = itk::Image<unsigned short, 3>;
   ImageType::Pointer image = nullptr;
   result += testMetaData<ImageType::Pointer>(image);