Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions Modules/Core/Common/include/itkPrintHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<list<T>>, 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 <typename T>
std::ostream &
operator<<(std::ostream & os, const std::vector<T> & v);

template <typename T>
std::ostream &
operator<<(std::ostream & os, const std::list<T> & l);

template <typename T, size_t VLength>
std::ostream &
operator<<(std::ostream & os, const std::array<T, VLength> & container);

template <typename T, size_t VLength, typename = std::enable_if_t<!std::is_same_v<T, char>>>
std::ostream &
operator<<(std::ostream & os, const T (&arr)[VLength]);

template <typename T>
std::ostream &
operator<<(std::ostream & os, const std::vector<T> & v)
Expand All @@ -40,7 +60,13 @@ operator<<(std::ostream & os, const std::vector<T> & v)
}

os << '[';
std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(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() << ']';
}

Expand All @@ -54,7 +80,10 @@ operator<<(std::ostream & os, const std::list<T> & l)
}

os << '[';
std::copy(l.begin(), std::prev(l.end()), std::ostream_iterator<T>(os, ", "));
for (auto it = l.begin(); it != std::prev(l.end()); ++it)
{
os << *it << ", ";
}
return os << l.back() << ']';
}

Expand All @@ -69,13 +98,16 @@ operator<<(std::ostream & os, [[maybe_unused]] const std::array<T, VLength> & co
else
{
os << '(';
std::copy(container.cbegin(), std::prev(container.cend()), std::ostream_iterator<T>(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 <typename T, size_t VLength, typename = std::enable_if_t<!std::is_same_v<T, char>>>
template <typename T, size_t VLength, typename>
std::ostream &
operator<<(std::ostream & os, const T (&arr)[VLength])
{
Expand Down
15 changes: 15 additions & 0 deletions Modules/Core/Common/src/itkMetaDataObject.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
*
*=========================================================================*/
#define ITK_TEMPLATE_EXPLICIT_MetaDataObject


#include "itkPrintHelper.h"
namespace itk
{
// Bring print_helper's operator<< overloads for std::vector<T>,
// std::list<T>, and std::array<T, N> 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
Comment thread
hjmjohnson marked this conversation as resolved.
#include "itkMetaDataObject.h"

namespace itk
Expand Down
41 changes: 41 additions & 0 deletions Modules/Core/Common/test/itkPrintHelperGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "itkPrintHelper.h"
#include "itkOffset.h"
#include "gtest/gtest.h"
#include <array>
#include <sstream>
#include <vector>
#include <list>
Expand Down Expand Up @@ -89,3 +90,43 @@ TEST(PrintHelper, VectorOfOffsets)
oss << v;
EXPECT_EQ(oss.str(), "[[1, 2], [3, 4]]");
}

// Recursive case: vector<vector<T>>. 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<std::vector<int>> 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<std::vector<int>> v{ {}, { 7 } };
std::ostringstream oss;
oss << v;
EXPECT_EQ(oss.str(), "[[], [7]]");
}

TEST(PrintHelper, VectorOfList)
{
using namespace itk::print_helper;
std::vector<std::list<int>> 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<std::vector<int>, 2> a{ std::vector<int>{ 1, 2 }, std::vector<int>{ 3 } };
std::ostringstream oss;
oss << a;
EXPECT_EQ(oss.str(), "([1, 2], [3])");
}
69 changes: 69 additions & 0 deletions Modules/Filtering/ImageGrid/test/itkResampleImageFilterGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <gtest/gtest.h>
Expand Down Expand Up @@ -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<PixelType, Dimension>;

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<ImageType> it(input, input->GetLargestPossibleRegion()); !it.IsAtEnd(); ++it)
{
const auto idx = it.GetIndex();
it.Set(static_cast<PixelType>(idx[0] + idx[1] + idx[2]));
}

const auto transform = itk::AffineTransform<double, Dimension>::New();
transform->Scale(0.9);

using InterpolatorType = itk::GaussianInterpolateImageFunction<ImageType, double>;
const auto interpolator = InterpolatorType::New();
auto sigma = itk::MakeFilled<InterpolatorType::ArrayType>(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<ImageType, ImageType>::New();
upstream->SetInput(input);

const auto resampler = itk::ResampleImageFilter<ImageType, ImageType>::New();
resampler->SetInput(upstream->GetOutput());
resampler->SetTransform(transform);
resampler->SetInterpolator(interpolator);
resampler->SetSize(size);

const auto streamer = itk::StreamingImageFilter<ImageType, ImageType>::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<ImageType> itU(unstreamed, unstreamed->GetLargestPossibleRegion());
itk::ImageRegionIterator<ImageType> itS(streamed, streamed->GetLargestPossibleRegion());
for (; !itU.IsAtEnd() && !itS.IsAtEnd(); ++itU, ++itS)
{
EXPECT_FLOAT_EQ(itU.Get(), itS.Get());
}
EXPECT_EQ(itU.IsAtEnd(), itS.IsAtEnd());
}
8 changes: 4 additions & 4 deletions Modules/IO/GDCM/src/itkGDCMImageIO.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
/*
Expand Down Expand Up @@ -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
Expand Down
Loading