COMP: Fix cast filter proxy pixel writes (supersedes #6898) - #6901
hjmjohnson wants to merge 1 commit into
Conversation
|
b50acb5 to
be90fa6
Compare
be90fa6 to
d0b8af9
Compare
|
Two force-pushes: the first ( |
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>
d0b8af9 to
ebe1845
Compare
N-Dekker
left a comment
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.)
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 ITK/Modules/Core/Common/include/itkImageBufferRange.h Lines 80 to 87 in 84bb830 Maybe this check on AccessorFunctorType in ImageBufferRange is too strict? AccessorFunctorType should be a template instantiation of What if SupportsDirectPixelAccess just checks that 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! |
|
Update: I'm preparing a PR to let ImageBufferRange and ImageRegionRange do direct pixel access for subclasses of
|
| // 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> |
There was a problem hiding this comment.
- This comment ("Accesses pixels through a proxy...") should be removed or adjusted when we merge ENH: Let Range classes do direct pixel access to itk::Image subclasses #6905
| itk::ImageRegionConstIterator<OutputImageType> castedImageIterator( | ||
| castImageFilter->GetOutput(), castImageFilter->GetOutput()->GetLargestPossibleRegion()); | ||
| itk::ImageRegionConstIterator<FloatVectorImageType> originalImageIterator(image, image->GetLargestPossibleRegion()); |
There was a problem hiding this comment.
[nitpick] Please replace these two GetLargestPossibleRegion() calls here with use of the local variable region.
| // Create a 1x3 image of 2D vectors | ||
| auto image = FloatVectorImageType::New(); | ||
|
|
||
| constexpr itk::Size<2> size{ { 1, 3 } }; |
There was a problem hiding this comment.
[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:
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>; |
There was a problem hiding this comment.
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]));
}
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. |
|
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. |
Supersedes #6898. Same fix, plus the regression test @N-Dekker asked for and the root cause @blowekamp asked about: any subclass of
itk::Image— includingotb::Image— triggers this, becauseAccessorFunctorTypeis inherited fromitk::Imagerather than redeclared forSelf.Root cause, verified empirically
itk::ImagedeclaresAccessorFunctorType = DefaultPixelAccessorFunctor<Self>, whereSelfis fixed toImage<TPixel, VDim>itself. A subclass that doesn't redeclare this typedef inherits the base class's version, soImageBufferRange'sSupportsDirectPixelAccesscheck — which compares that typedef againstDefaultPixelAccessorFunctor<TImage>for the actual image type — fails for any such subclass.*outputItthen returns aPixelProxyvalue instead of a real reference, and the unfixed code's non-const reference bind fails to compile for a fixed-size, non-VariableLengthVectoroutput pixel type.otb::Imageis exactly this: a plain, unmodifieditk::Imagesubclass. Confirmed by compiling a minimal local reproducer (a trivialitk::Imagesubclass, 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'sotb::VectorImage<float,2>→otb::Image<itk::Vector<double,2>,2>cast inotbStereoRectificationGridGenerator.cxx.Test added
TestVectorImageCastToImageSubclass()initkCastImageFilterTest.cxx, modeled on the existingTestVectorImageCast2. Casts aVectorImage<float,2>to a localImageSubclass<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.