Skip to content

ENH: printing for meta data object of vectors#4831

Merged
hjmjohnson merged 5 commits intoInsightSoftwareConsortium:mainfrom
blowekamp:add_metadataobject_vector_print
May 6, 2026
Merged

ENH: printing for meta data object of vectors#4831
hjmjohnson merged 5 commits intoInsightSoftwareConsortium:mainfrom
blowekamp:add_metadataobject_vector_print

Conversation

@blowekamp
Copy link
Copy Markdown
Member

closes #4368

PR Checklist

  • No API changes were made (or the changes have been approved)
  • No major design changes were made (or the changes have been approved)
  • Added test (or behavior not changed)
  • Updated API documentation (or API not changed)
  • Added license to new files (if any)
  • Added Python wrapping to new files (if any) as described in ITK Software Guide Section 9.5
  • Added ITK examples for all new major features (if any)

Refer to the ITK Software Guide for
further development details if necessary.

@github-actions github-actions Bot added type:Enhancement Improvement of existing methods or implementation area:Core Issues affecting the Core module labels Sep 3, 2024
@blowekamp
Copy link
Copy Markdown
Member Author

@dzenanz @N-Dekker Any ideas on the compilation errors here?

@dzenanz
Copy link
Copy Markdown
Member

dzenanz commented Sep 3, 2024

As best I can tell: the print_helper operator is being instantiated for vector<vector<double>>, so it tries to do ostream << vector<double>, which is not defined.

@hjmjohnson hjmjohnson force-pushed the add_metadataobject_vector_print branch from f9db8de to 764de64 Compare May 6, 2026 11:34
@hjmjohnson
Copy link
Copy Markdown
Member

@blowekamp — picked up your dormant draft and pushed two commits to blowekamp:add_metadataobject_vector_print (your --maintainer-can-modify was on):

  1. Rebase on upstream/main (was 4195 commits behind). Your original ENH: commit is preserved as 51046b8e59.
  2. BUG: Replace std::ostream_iterator with manual loop in itkPrintHelper.h (764de6493a).

The compile error @dzenanz hypothesized in 2024 — "the print_helper operator is being instantiated for vector<vector<double>>, so it tries to do ostream << vector<double>, which is not defined" — was correct on the symptom but the call site is std::ostream_iterator<T>, not the explicit os << v.back(). ostream_iterator<T>::operator= performs the insertion from inside namespace std, where ADL on a std::vector<...> operand never reaches itk::print_helper, so the inner overload is invisible and the recursion fails. Replacing the std::copy(..., std::ostream_iterator<T>(os, ", ")) pattern with a manual for loop whose os << *it is unqualified makes namespace-local lookup find the print_helper overloads, and the recursion compiles.

Local verification on Apple-clang 17 (Xcode):

  • Modules/Core/Common/src/itkMetaDataObject.cxx.o builds clean.
  • itkMetaDataObjectTest passes (the std::vector<std::vector<double>> instantiation at itkMetaDataObjectTest.cxx:72 exercises the recursive case).

Convert to ready-for-review when you're satisfied with the approach.

@hjmjohnson hjmjohnson marked this pull request as ready for review May 6, 2026 11:38
@greptile-apps

This comment was marked as resolved.

Comment thread Modules/Core/Common/src/itkMetaDataObject.cxx
Comment thread Modules/Core/Common/src/itkMetaDataObject.cxx Outdated
@github-actions github-actions Bot added the type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct label May 6, 2026
Copy link
Copy Markdown
Member

@dzenanz dzenanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I have not tested it.

@hjmjohnson
Copy link
Copy Markdown
Member

FYI: I tested locally and added gtests to exercise on all CI platforms.

hjmjohnson and others added 4 commits May 6, 2026 10:24
DICOM Supplements 172/173 added the OD, OL, and OV bulk-data VRs.
Add them to the binary-VR masks at the read site
(InternalReadImageInformation base64-encode) and the write site
(Write base64-decode) so values for those VRs survive the
ITK MetaDataDictionary roundtrip instead of being silently dropped.
Regression guard for issue InsightSoftwareConsortium#1011 (fixed by InsightSoftwareConsortium#1202): a Gaussian-interpolated
ResampleImageFilter must produce the same output whether or not its
downstream is streamed.

Reverts of the bounding-box fix (BufferedRegion vs LargestPossibleRegion)
reproduce a 28672 / 32768-pixel divergence with NaN output values. A
CastImageFilter is inserted upstream of the resampler so the bug surfaces;
an in-memory image's BufferedRegion never shrinks under streaming, which
is why the abandoned WIP draft InsightSoftwareConsortium#1012 no longer reproduced.

Supersedes InsightSoftwareConsortium#1012 by @romangrothausmann; uses the modern GTest harness
and fits as a TEST block in itkResampleImageFilterGTest.

Co-Authored-By: Roman Grothausmann <roman.grothausmann@mh-hannover.de>
std::ostream_iterator<T> performs '*os << *iter' from inside namespace
std, where ADL on std container types never reaches itk::print_helper.
For nested containers (e.g. vector<vector<float>>) the inner overload
is invisible and the recursion fails to compile.  Replace each
std::copy(..., std::ostream_iterator<T>(os, ', ')) with a manual loop
whose unqualified 'os << *it' resolves print_helper overloads via
namespace lookup.

Co-Authored-By: Bradley Lowekamp <blowekamp@mail.nih.gov>
@hjmjohnson hjmjohnson force-pushed the add_metadataobject_vector_print branch from e0892a2 to 7d75504 Compare May 6, 2026 15:24
@github-actions github-actions Bot added area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module labels May 6, 2026
@hjmjohnson
Copy link
Copy Markdown
Member

Status update — branch now at 7d755042ef on blowekamp:add_metadataobject_vector_print. All threads resolved; ready for re-review.

What landed since the last force-push

Folded one fixup into the BUG: Forward-declare print_helper operators… commit (e0892a206b → 7d755042ef) addressing both Greptile findings:

  • P1 — ODR risk from TU-local using namespace: added explicit has_output_operator<std::vector<T, A>>, has_output_operator<std::list<T, A>>, and has_output_operator<std::array<T, N>> specializations in Modules/Core/Common/include/itkMetaDataObjectDetail.h. The trait result is now deterministic across every translation unit; the using namespace print_helper; directive in itkMetaDataObject.cxx no longer governs the trait specialization, only the operator<< call resolution inside Print().
  • P2 — inaccurate comment: expanded the directive's comment to mention all three covered container types and clarify the new scope of its effect.
  • Added the matching <array>, <cstddef>, <list>, <vector> includes to itkMetaDataObjectDetail.h.
Recap of @dzenanz's 2024 diagnosis

"the print_helper operator is being instantiated for vector<vector<double>>, so it tries to do ostream << vector<double>, which is not defined."

Confirmed correct. The earlier BUG: Replace ostream_iterator with manual loop in print_helper commit fixed the surface symptom (the operator now uses unqualified-name lookup of os << *it, which finds the nested vector<double> overload), and the trait-specialization fix in this last commit nails down the ODR concern that the using-directive-based dispatch had introduced.

Local validation
  • pre-commit run --all-files clean on the rewritten tip
  • itkPrintHelperGTest + the new nested-container GTests pass locally on Ubuntu 24.04 / gcc 13.3 / Pixi-Cxx
  • All 19 CI checks were green on the prior HEAD (e0892a206b); CI will rerun on 7d755042ef

…ests

Two-phase template lookup at template definition time only sees names
declared earlier in the translation unit. Without the forward
declarations, recursing into vector<list<T>> from inside the vector
operator's body could not find the list overload (declared later);
ADL on std container types never reaches itk::print_helper.

Adds GTests for vector<vector<int>>, vector<vector<int>{empty}>,
vector<list<int>>, and array<vector<int>, N> to lock in the recursive
behaviour that the manual loop and forward declarations together
enable.

Co-Authored-By: Bradley Lowekamp <blowekamp@mail.nih.gov>
@hjmjohnson hjmjohnson force-pushed the add_metadataobject_vector_print branch from 7d75504 to 70d4cc2 Compare May 6, 2026 15:32
@hjmjohnson
Copy link
Copy Markdown
Member

Correcting course on the Greptile P1 fix — branch now at 70d4cc2360.

The previous attempt (7d755042ef) added explicit has_output_operator<std::vector<T,A>> / <std::list<T,A>> / <std::array<T,N>> specializations to make the trait deterministic across TUs. That broke the build immediately on ARMBUILD-Ubuntu-24.04-arm, ARMBUILD-x86_64-rosetta, and Pixi-Cxx (macos-15) because the unconditional true_type told MetaDataObject<...>::Print() to emit os << value even in client TUs that don't have using namespace print_helper; in scope. Empirical failure on std::vector<std::vector<basic_string>> consumers from the Python wrapping path made it clear the specialization approach was wrong.

Why Greptile's P1 doesn't bite in practice

Greptile's hypothetical: if has_output_operator<std::vector<float>> is true in this .cxx (where using namespace print_helper; is in scope) and false in another TU (where it isn't), then two TUs define the same class-template specialization with different bodies — ODR violation.

Why the extern template guard already prevents this: itkMetaDataObject.h declares extern template class ITKCommon_EXPORT MetaDataObject<std::vector<T>> for every numeric T. Client TUs see the declaration but cannot instantiate MetaDataObject<std::vector<T>>::Print() themselves; Print()'s body only ever lives in this .cxx. The trait is consumed only inside Print()'s body, so it's only instantiated where the using-directive is in scope. No client TU produces a competing specialization.

Greptile acknowledged this in its own writeup: "The extern template declarations in itkMetaDataObject.h prevent re-instantiation of MetaDataObject<std::vector<T>>::Print() in client code, which limits the practical exposure". The empirical CI failure now confirms that the alternative — making the trait deterministic via specialization — is strictly worse, because it forces the streaming branch on consumers that legitimately can't compile it.

Reverted to the SFINAE-only trait. P2 (comment scope) addressed by an expanded comment explaining the using-directive's role and the extern template guard that keeps it TU-local.

What the new tip looks like
70d4cc2360  BUG: Forward-declare print_helper operators + add nested-container GTests
b58ead7285  BUG: Replace ostream_iterator with manual loop in print_helper
83bd40c129  ENH: printing for meta data object of vectors

Diff vs upstream/main is +85 / -4 across 3 files (matches the original PR scope).

@hjmjohnson hjmjohnson merged commit b8d617e into InsightSoftwareConsortium:main May 6, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Core Issues affecting the Core module area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module type:Enhancement Improvement of existing methods or implementation type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants