diff --git a/Modules/Core/Common/include/itkPrintHelper.h b/Modules/Core/Common/include/itkPrintHelper.h index daca58011935..a93e2ac77e78 100644 --- a/Modules/Core/Common/include/itkPrintHelper.h +++ b/Modules/Core/Common/include/itkPrintHelper.h @@ -30,6 +30,26 @@ namespace itk::print_helper { +// Forward declarations so the per-container bodies below see all overloads at +// definition time. Required for nested cases like vector>, where the +// recursive `os << *it` is parsed before the list overload would otherwise be +// declared, and ADL on std container types never reaches itk::print_helper. +template +std::ostream & +operator<<(std::ostream & os, const std::vector & v); + +template +std::ostream & +operator<<(std::ostream & os, const std::list & l); + +template +std::ostream & +operator<<(std::ostream & os, const std::array & container); + +template >> +std::ostream & +operator<<(std::ostream & os, const T (&arr)[VLength]); + template std::ostream & operator<<(std::ostream & os, const std::vector & v) @@ -40,7 +60,13 @@ operator<<(std::ostream & os, const std::vector & v) } os << '['; - std::copy(v.begin(), v.end() - 1, std::ostream_iterator(os, ", ")); + // Manual loop so that an unqualified `os << *it` resolves overloads in + // itk::print_helper for nested containers; std::ostream_iterator inserts + // from inside namespace std and would not see them. + for (auto it = v.begin(); it != std::prev(v.end()); ++it) + { + os << *it << ", "; + } return os << v.back() << ']'; } @@ -54,7 +80,10 @@ operator<<(std::ostream & os, const std::list & l) } os << '['; - std::copy(l.begin(), std::prev(l.end()), std::ostream_iterator(os, ", ")); + for (auto it = l.begin(); it != std::prev(l.end()); ++it) + { + os << *it << ", "; + } return os << l.back() << ']'; } @@ -69,13 +98,16 @@ operator<<(std::ostream & os, [[maybe_unused]] const std::array & co else { os << '('; - std::copy(container.cbegin(), std::prev(container.cend()), std::ostream_iterator(os, ", ")); + for (auto it = container.cbegin(); it != std::prev(container.cend()); ++it) + { + os << *it << ", "; + } return os << container.back() << ')'; } } // Stream insertion operator for C-style arrays, excluding character arrays (strings) -template >> +template std::ostream & operator<<(std::ostream & os, const T (&arr)[VLength]) { diff --git a/Modules/Core/Common/src/itkMetaDataObject.cxx b/Modules/Core/Common/src/itkMetaDataObject.cxx index 130b5d225428..131665cf7a58 100644 --- a/Modules/Core/Common/src/itkMetaDataObject.cxx +++ b/Modules/Core/Common/src/itkMetaDataObject.cxx @@ -16,6 +16,21 @@ * *=========================================================================*/ #define ITK_TEMPLATE_EXPLICIT_MetaDataObject + + +#include "itkPrintHelper.h" +namespace itk +{ +// Bring print_helper's operator<< overloads for std::vector, +// std::list, and std::array into scope so the explicit +// MetaDataObject<...>::Print() instantiations below can resolve the +// streaming call. The 'extern template' declarations in +// itkMetaDataObject.h prevent these specializations from being +// re-instantiated in client TUs, so the using-directive's effect on +// has_output_operator<> trait dispatch stays local to this single +// translation unit. +using namespace print_helper; +} // namespace itk #include "itkMetaDataObject.h" namespace itk diff --git a/Modules/Core/Common/test/itkPrintHelperGTest.cxx b/Modules/Core/Common/test/itkPrintHelperGTest.cxx index 8bf29df88e65..cfc60a080c9f 100644 --- a/Modules/Core/Common/test/itkPrintHelperGTest.cxx +++ b/Modules/Core/Common/test/itkPrintHelperGTest.cxx @@ -19,6 +19,7 @@ #include "itkPrintHelper.h" #include "itkOffset.h" #include "gtest/gtest.h" +#include #include #include #include @@ -89,3 +90,43 @@ TEST(PrintHelper, VectorOfOffsets) oss << v; EXPECT_EQ(oss.str(), "[[1, 2], [3, 4]]"); } + +// Recursive case: vector>. Verifies the inner operator<< is +// reachable from the outer template body via in-namespace lookup, which +// requires the manual loop to replace std::ostream_iterator (whose +// insertion happens inside namespace std and bypasses ADL into print_helper). +TEST(PrintHelper, VectorOfVector) +{ + using namespace itk::print_helper; + std::vector> v{ { 1, 2, 3 }, { 4, 5, 6 } }; + std::ostringstream oss; + oss << v; + EXPECT_EQ(oss.str(), "[[1, 2, 3], [4, 5, 6]]"); +} + +TEST(PrintHelper, VectorOfEmptyVector) +{ + using namespace itk::print_helper; + std::vector> v{ {}, { 7 } }; + std::ostringstream oss; + oss << v; + EXPECT_EQ(oss.str(), "[[], [7]]"); +} + +TEST(PrintHelper, VectorOfList) +{ + using namespace itk::print_helper; + std::vector> v{ { 1, 2 }, { 3, 4, 5 } }; + std::ostringstream oss; + oss << v; + EXPECT_EQ(oss.str(), "[[1, 2], [3, 4, 5]]"); +} + +TEST(PrintHelper, ArrayOfVector) +{ + using namespace itk::print_helper; + std::array, 2> a{ std::vector{ 1, 2 }, std::vector{ 3 } }; + std::ostringstream oss; + oss << a; + EXPECT_EQ(oss.str(), "([1, 2], [3])"); +} diff --git a/Modules/Filtering/ImageGrid/test/itkResampleImageFilterGTest.cxx b/Modules/Filtering/ImageGrid/test/itkResampleImageFilterGTest.cxx index d56c051c6179..b44c7e900bf3 100644 --- a/Modules/Filtering/ImageGrid/test/itkResampleImageFilterGTest.cxx +++ b/Modules/Filtering/ImageGrid/test/itkResampleImageFilterGTest.cxx @@ -19,7 +19,13 @@ // The header file to be tested: #include "itkResampleImageFilter.h" +#include "itkAffineTransform.h" +#include "itkCastImageFilter.h" +#include "itkGaussianInterpolateImageFunction.h" #include "itkImage.h" +#include "itkImageRegionIterator.h" +#include "itkImageRegionIteratorWithIndex.h" +#include "itkStreamingImageFilter.h" // Google Test header file: #include @@ -172,3 +178,66 @@ TEST(ResampleImageFilter, ThrowsOnIncompleteConfiguration) { Expect_ResampleImageFilter_thows_on_incomplete_configuration(128.0); } + + +// Streaming a Gaussian-interpolated resample must reproduce the non-streamed result (issue #1011). +TEST(ResampleImageFilter, StreamingMatchesNonStreamedWithGaussianInterpolator) +{ + constexpr unsigned int Dimension{ 3 }; + using PixelType = float; + using ImageType = itk::Image; + + constexpr unsigned int kNumberOfStreamDivisions{ 8 }; + + const auto input = ImageType::New(); + const auto size = ImageType::SizeType::Filled(32); + input->SetRegions(size); + input->Allocate(); + for (itk::ImageRegionIteratorWithIndex it(input, input->GetLargestPossibleRegion()); !it.IsAtEnd(); ++it) + { + const auto idx = it.GetIndex(); + it.Set(static_cast(idx[0] + idx[1] + idx[2])); + } + + const auto transform = itk::AffineTransform::New(); + transform->Scale(0.9); + + using InterpolatorType = itk::GaussianInterpolateImageFunction; + const auto interpolator = InterpolatorType::New(); + auto sigma = itk::MakeFilled(1.0); + interpolator->SetSigma(sigma); + interpolator->SetAlpha(1.0); + + // CastImageFilter is a streaming-aware no-op; it produces a per-chunk BufferedRegion + // upstream of the resampler, which is required to exercise the interpolator bug. + const auto upstream = itk::CastImageFilter::New(); + upstream->SetInput(input); + + const auto resampler = itk::ResampleImageFilter::New(); + resampler->SetInput(upstream->GetOutput()); + resampler->SetTransform(transform); + resampler->SetInterpolator(interpolator); + resampler->SetSize(size); + + const auto streamer = itk::StreamingImageFilter::New(); + streamer->SetInput(resampler->GetOutput()); + + streamer->SetNumberOfStreamDivisions(1); + ASSERT_NO_THROW(streamer->UpdateLargestPossibleRegion()); + const ImageType::Pointer unstreamed = streamer->GetOutput(); + unstreamed->DisconnectPipeline(); + + input->Modified(); + streamer->SetNumberOfStreamDivisions(kNumberOfStreamDivisions); + ASSERT_NO_THROW(streamer->UpdateLargestPossibleRegion()); + const ImageType::Pointer streamed = streamer->GetOutput(); + streamed->DisconnectPipeline(); + + itk::ImageRegionIterator itU(unstreamed, unstreamed->GetLargestPossibleRegion()); + itk::ImageRegionIterator itS(streamed, streamed->GetLargestPossibleRegion()); + for (; !itU.IsAtEnd() && !itS.IsAtEnd(); ++itU, ++itS) + { + EXPECT_FLOAT_EQ(itU.Get(), itS.Get()); + } + EXPECT_EQ(itU.IsAtEnd(), itS.IsAtEnd()); +} diff --git a/Modules/IO/GDCM/src/itkGDCMImageIO.cxx b/Modules/IO/GDCM/src/itkGDCMImageIO.cxx index 6bb0d527ab82..7efa688a1677 100644 --- a/Modules/IO/GDCM/src/itkGDCMImageIO.cxx +++ b/Modules/IO/GDCM/src/itkGDCMImageIO.cxx @@ -765,7 +765,8 @@ GDCMImageIO::InternalReadImageInformation() // Process binary field and encode them as mime64: only when we do not know // of any better // representation. VR::US is binary, but user want ASCII representation. - if (vr & (gdcm::VR::OB | gdcm::VR::OF | gdcm::VR::OW | gdcm::VR::SQ | gdcm::VR::UN)) + if (vr & (gdcm::VR::OB | gdcm::VR::OD | gdcm::VR::OF | gdcm::VR::OL | gdcm::VR::OV | gdcm::VR::OW | gdcm::VR::SQ | + gdcm::VR::UN)) { // itkAssertInDebugAndIgnoreInReleaseMacro( vr & gdcm::VR::VRBINARY ); /* @@ -894,9 +895,8 @@ GDCMImageIO::Write(const void * buffer) { // How did we reach here ? } - else if (vrtype & (gdcm::VR::OB | gdcm::VR::OF | gdcm::VR::OW /*| - gdcm::VR::SQ*/ - | gdcm::VR::UN)) + else if (vrtype & + (gdcm::VR::OB | gdcm::VR::OD | gdcm::VR::OF | gdcm::VR::OL | gdcm::VR::OV | gdcm::VR::OW | gdcm::VR::UN)) { // Custom VR::VRBINARY // convert value from Base64