Skip to content

COMP: Fix cast filter proxy pixel writes (supersedes #6898) - #6901

Closed
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:comp-castimagefilter-imagesubclass-proxy
Closed

hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:comp-castimagefilter-imagesubclass-proxy

Conversation

@hjmjohnson

Copy link
Copy Markdown
Member

Supersedes #6898. Same fix, plus the regression test @N-Dekker asked for and the root cause @blowekamp asked about: any subclass of itk::Image — including otb::Image — triggers this, because AccessorFunctorType is inherited from itk::Image rather than redeclared for Self.

Root cause, verified empirically

itk::Image declares AccessorFunctorType = DefaultPixelAccessorFunctor<Self>, where Self is fixed to Image<TPixel, VDim> itself. A subclass that doesn't redeclare this typedef inherits the base class's version, so ImageBufferRange's SupportsDirectPixelAccess check — which compares that typedef against DefaultPixelAccessorFunctor<TImage> for the actual image type — fails for any such subclass. *outputIt then returns a PixelProxy value instead of a real reference, and the unfixed code's non-const reference bind fails to compile for a fixed-size, non-VariableLengthVector output pixel type.

otb::Image is exactly this: a plain, unmodified itk::Image subclass. Confirmed by compiling a minimal local reproducer (a trivial itk::Image subclass, no OTB dependency) against the pre-fix header and getting the identical compiler error reported in #6898, then confirming it compiles clean with the fix restored.

Answering @blowekamp's question directly: the triggering template parameters are itk::CastImageFilter<itk::VectorImage<float,2>, AnySubclassOf<itk::Image<itk::Vector<double,2>,2>>> — matching OTB's otb::VectorImage<float,2> → otb::Image<itk::Vector<double,2>,2> cast in otbStereoRectificationGridGenerator.cxx.

Test added

TestVectorImageCastToImageSubclass() in itkCastImageFilterTest.cxx, modeled on the existing TestVectorImageCast2. Casts a VectorImage<float,2> to a local ImageSubclass<Vector<double,2>,2> and verifies the round-tripped pixel values, not just that it compiles. Passes locally; fails to compile against the pre-fix header with the exact error from #6898.

@github-actions github-actions Bot added type:Compiler Compiler support or related warnings type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Filtering Issues affecting the Filtering module labels Sep 24, 2026
@hjmjohnson
hjmjohnson marked this pull request as ready for review September 24, 2026 14:31
@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

Not ready to merge: the destination-pixel copy needs correction, and the repository’s comment requirement must be met.

Findings

  1. P1 Uninitialized destination pixel copy ▶
  2. P2 Shorten test comments ▶
Summary

The PR adds pixel write-back for output-image proxies and tests casting vector-image pixels into an image subclass. The new path copies an uninitialized destination pixel before replacing its components, and the new test comments exceed the repository’s prose limit. Both need correction before merging.

Reviews (1) · Last reviewed commit: "COMP: Fix cast filter proxy pixel writes"

Comment thread Modules/Filtering/ImageFilterBase/include/itkCastImageFilter.hxx Outdated
Comment thread Modules/Filtering/ImageFilterBase/test/itkCastImageFilterTest.cxx Outdated

@dzenanz dzenanz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mostly looks good.

Comment thread Modules/Filtering/ImageFilterBase/test/itkCastImageFilterTest.cxx Outdated
Comment thread Modules/Filtering/ImageFilterBase/test/itkCastImageFilterTest.cxx
@hjmjohnson
hjmjohnson force-pushed the comp-castimagefilter-imagesubclass-proxy branch from b50acb5 to be90fa6 Compare September 24, 2026 15:40
@github-actions github-actions Bot added the type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots label Sep 24, 2026
@hjmjohnson
hjmjohnson force-pushed the comp-castimagefilter-imagesubclass-proxy branch from be90fa6 to d0b8af9 Compare September 24, 2026 15:41
@hjmjohnson

Copy link
Copy Markdown
Member Author

Two force-pushes: the first (b50acb500b9 → be90fa639e6) is the GTest conversion requested above, content only; the second (be90fa639e6 → d0b8af9b2b7) is a plain rebase on main with no content change (git range-diff reports =).

Handle output iterators that return pixel proxies instead of lvalue
references by copying the pixel, updating its components, and assigning
it back through the iterator.

Any subclass of itk::Image inherits AccessorFunctorType from itk::Image
itself rather than redeclaring it for Self, so ImageBufferRange treats the
subclass as not supporting direct pixel access and dereferences through a
proxy. A fixed-size, non-VariableLengthVector output pixel type then hits
this cast filter's reference-binding path and fails to compile:

```
error: non-const lvalue reference to type 'itk::Vector<double, 2>' cannot
bind to an initializer list temporary
    std::conditional_t<isVariableLengthVector, OutputPixelType, OutputPixelType &> outputPixel{ *outputIt };
```

Reported against otb::Image, itself an unmodified itk::Image subclass;
reproduced here with a local subclass, no external dependency needed.

Co-Authored-By: Nicklas Larsson <14186207+nilason@users.noreply.github.com>
@hjmjohnson
hjmjohnson force-pushed the comp-castimagefilter-imagesubclass-proxy branch from d0b8af9 to ebe1845 Compare September 24, 2026 15:50

@N-Dekker N-Dekker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for making it a GTest! 👍 May I still have a look before it is merged? (Just pressing Request changes to get a little bit more time!)

else
{
outputPixel[k] = static_cast<OutputPixelValueType>(inputPixel[k]);
OutputPixelType outputPixel;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just for my understanding, the original PR did OutputPixelType outputPixel{ *outputIt }; here. Was the part { *outputIt } intentionally removed?

Probably OK, just wondering if it was intended. (It might make it harder to share code between the if and the else clause.)

@N-Dekker

N-Dekker commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

the root cause @blowekamp asked about: any subclass of itk::Image — including otb::Image — triggers this, because AccessorFunctorType is inherited from itk::Image rather than redeclared for Self.

Ah, I see now, the range uses a proxy when it cannot use direct pixel access, based on:

  static constexpr bool SupportsDirectPixelAccess =
    std::is_same_v<PixelType, InternalPixelType> &&
    std::is_same_v<typename TImage::AccessorType, DefaultPixelAccessor<PixelType>> &&
    std::is_same_v<AccessorFunctorType, DefaultPixelAccessorFunctor<std::remove_const_t<TImage>>>;

At

// Tells whether or not this range supports direct pixel access. If it does,
// iterator::operator*() returns a reference to the internally stored pixel,
// otherwise iterator::operator*() returns a proxy, which internally uses the
// AccessorFunctor of the image to access the pixel indirectly.
static constexpr bool SupportsDirectPixelAccess =
std::is_same_v<PixelType, InternalPixelType> &&
std::is_same_v<typename TImage::AccessorType, DefaultPixelAccessor<PixelType>> &&
std::is_same_v<AccessorFunctorType, DefaultPixelAccessorFunctor<std::remove_const_t<TImage>>>;

Maybe this check on AccessorFunctorType in ImageBufferRange is too strict? AccessorFunctorType should be a template instantiation of DefaultPixelAccessorFunctor<T>, but maybe we should not require that it is exactly DefaultPixelAccessorFunctor<std::remove_const_t<TImage>>?

What if SupportsDirectPixelAccess just checks that AccessorFunctorType == DefaultPixelAccessorFunctor<AccessorFunctorType::ImageType> ?


Just checked: it appears that the changes in itkCastImageFilter.hxx are not necessary, in order to make the test pass!!! It is necessary though to fix SupportsDirectPixelAccess in itkImageBufferRange.h, as follows:

  static constexpr bool SupportsDirectPixelAccess =
    std::is_same_v<PixelType, InternalPixelType> &&
    std::is_same_v<typename TImage::AccessorType, DefaultPixelAccessor<PixelType>> &&
    std::is_same_v<AccessorFunctorType, DefaultPixelAccessorFunctor<typename AccessorFunctorType::ImageType>>;

That will make the new test pass successfully, even with the old itkCastImageFilter.hxx!

@N-Dekker

N-Dekker commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Update: I'm preparing a PR to let ImageBufferRange and ImageRegionRange do direct pixel access for subclasses of itk::Image. This should solve the compile errors at OrfeoToolBox, reported from by @nilason at #6898, and make it faster!


Comment on lines +29 to +31
// Accesses pixels through a proxy, not a reference, like otb::Image (itk.org/issue/6898).
template <typename TPixel, unsigned int VImageDimension>
class ImageSubclass : public itk::Image<TPixel, VImageDimension>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment on lines +73 to +75
itk::ImageRegionConstIterator<OutputImageType> castedImageIterator(
castImageFilter->GetOutput(), castImageFilter->GetOutput()->GetLargestPossibleRegion());
itk::ImageRegionConstIterator<FloatVectorImageType> originalImageIterator(image, image->GetLargestPossibleRegion());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nitpick] Please replace these two GetLargestPossibleRegion() calls here with use of the local variable region.

Comment on lines +53 to +56
// Create a 1x3 image of 2D vectors
auto image = FloatVectorImageType::New();

constexpr itk::Size<2> size{ { 1, 3 } };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nitpick] While I like to see small images being used in tests, an 1x3 image seems a bit too exotic to me. Some filters don't even support images smaller than 4x4:

<< " is less than 4. This filter requires a minimum of four pixels along the dimension to be processed.");

So I would prefer an image size of 4x5.

// This casts a VectorImage<float, 2> to an image-subclass Image<Vector<double, 2>, 2>
TEST(CastImageFilter, CastToImageSubclassWithoutDirectPixelAccess)
{
using OutputImageType = ImageSubclass<itk::Vector<double, 2>, 2>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For code readability, I would suggest to add a named constant for the vector length:

    constexpr unsigned int vectorLength{ 2 };
    using OutputImageType = ImageSubclass<itk::Vector<double, vectorLength>, 2>

vectorLength can then also be used to specify the size of VariableLengthVector (further below here). Assuming that it is essential that VariableLengthVector and itk::Vector have the same vector length, of course! Is that assumption correct?

vectorLength can then also be used to check the vector elements in a for loop, as follows:

    for( unsigned int i{}; i < vectorLength; ++i )
    {
      EXPECT_EQ(castedImageIterator.Get()[i], static_cast<double>(originalImageIterator.Get()[i]));
    }

@blowekamp

Copy link
Copy Markdown
Member

See #6905
·N-Dekker for potentially a better fix a the root of the issue.

@N-Dekker

Copy link
Copy Markdown
Contributor

See #6905 ·N-Dekker for potentially a better fix a the root of the issue.

Thanks @blowekamp ! Note that even if my PR #6905 fixes the problem for subclasses of itk::Image (as I think it does), it may still be useful to have a unit test for ImageSubclass as output of CastImageFilter, like the one presented here by Hans (@hjmjohnson).

I wonder if there are still other use cases that would motivate the proposed modification of CastImageFilter 🤷 I hope not, because I feel that the proposed extra code is a bit complicated.

@hjmjohnson

Copy link
Copy Markdown
Member Author

I am reading the comment from N-Dekker, that his PR makes this one unnecessary. I could be wrong; perhaps #6905 complements this one? In any case. I'm closing this issue as is. Feel free to take tests or other elements, or take over this PR if there are components that are useful.

@hjmjohnson hjmjohnson closed this Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Filtering Issues affecting the Filtering module type:Compiler Compiler support or related warnings type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots 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.

5 participants