simpleitk_from_image / image_from_simpleitk (added in #6811) preserve pixel data, spacing,
direction, origin and the buffered start index across a round trip, but not the
LargestPossibleRegion. When an image's buffered region is smaller than its largest possible
region, the round trip silently returns an image whose largest possible region has been shrunk to
the buffered one.
This should land before 6.0.0 final. Both functions are new in #6811 and unreleased, so adding
a guard now costs nothing. After 6.0.0 ships, making these calls raise turns working user code into
throwing code — a behavior break of a released API.
Raised by @blowekamp on #6811:
Additionally if the buffered region is not the largest possible region, then in this current form
the round trip image has different region information.
Mechanism
SimpleITK images carry a single region: size, origin, spacing, direction, always starting at index
zero. ITK images carry three. The conversion can therefore represent at most one ITK region, and it
represents the buffered one.
In simpleitk_from_image (Wrapping/Generators/Python/itk/support/extras.py):
array = itk.array_from_image(image) # buffered data only -> SimpleITK size == buffered size
...
start_index = tuple(image.GetBufferedRegion().GetIndex()) # recorded as ITK_original_index
The largest possible region is never recorded. In image_from_simpleitk:
region = l_image.GetLargestPossibleRegion() # == the SimpleITK size, i.e. the buffered size
region.SetIndex(index)
l_image.SetRegions(region) # sets largest == buffered == requested
SetRegions assigns all three regions, so the restored image reports the buffered extent as its
largest possible region.
Demonstration
Running the same operations the two functions perform, on ITK 5.4.6:
SOURCE largest size : [10, 10, 10] index [0, 0, 0]
SOURCE buffered size: [4, 4, 4] index [2, 2, 2]
array_from_image shape (zyx): (4, 4, 4)
ROUNDTRIP largest size : [4, 4, 4] index [2, 2, 2]
ROUNDTRIP buffered size: [4, 4, 4] index [2, 2, 2]
Pixel values and physical locations survive; the largest possible region does not.
Why the frequency is low
array_from_image defaults to update=True, so it calls Update() on the image. For any image
attached to a pipeline that re-buffers to the requested region — normally the largest possible
region — and the mismatch is erased before it can be observed. The ordering in
simpleitk_from_image is also correct: start_index is read after the array extraction, so the
recorded index always matches the data actually copied.
The mismatch therefore survives only for a standalone image whose regions were set explicitly (no
source, so Update() is a no-op), a deliberately streamed pipeline with a smaller requested
region, or a reader configured with SetIORegion. Pixel values and physical locations are correct
in every case; only extent metadata is lost.
Proposed remedy
Detect the mismatch and raise rather than silently discard, per @hjmjohnson on #6811:
I had missed considering the Buffer != Largest case, and I think we should throw a Python
exception in that slightly unusual case for now (at least by default).
In simpleitk_from_image, compare GetBufferedRegion() with GetLargestPossibleRegion() and
raise a ValueError naming both extents when they differ. Failing loudly is preferable to
returning an image whose region information disagrees with its source.
An opt-out argument (accepting the buffered extent as the whole image) would cover the streaming
case for callers who know that is what they want. Recording the largest possible region as a second
metadata key and restoring it is a further option, but it makes the SimpleITK image's own size
disagree with the region it advertises, so it needs its own design discussion — as does
@blowekamp's separate question about _zyx keys on the SimpleITK side.
Scope
Wrapping/Generators/Python/itk/support/extras.py — simpleitk_from_image and
image_from_simpleitk. Follow-up to #6811; not a regression, as neither function existed before it.
simpleitk_from_image/image_from_simpleitk(added in #6811) preserve pixel data, spacing,direction, origin and the buffered start index across a round trip, but not the
LargestPossibleRegion. When an image's buffered region is smaller than its largest possibleregion, the round trip silently returns an image whose largest possible region has been shrunk to
the buffered one.
This should land before 6.0.0 final. Both functions are new in #6811 and unreleased, so adding
a guard now costs nothing. After 6.0.0 ships, making these calls raise turns working user code into
throwing code — a behavior break of a released API.
Raised by @blowekamp on #6811:
Mechanism
SimpleITK images carry a single region: size, origin, spacing, direction, always starting at index
zero. ITK images carry three. The conversion can therefore represent at most one ITK region, and it
represents the buffered one.
In
simpleitk_from_image(Wrapping/Generators/Python/itk/support/extras.py):The largest possible region is never recorded. In
image_from_simpleitk:SetRegionsassigns all three regions, so the restored image reports the buffered extent as itslargest possible region.
Demonstration
Running the same operations the two functions perform, on ITK 5.4.6:
Pixel values and physical locations survive; the largest possible region does not.
Why the frequency is low
array_from_imagedefaults toupdate=True, so it callsUpdate()on the image. For any imageattached to a pipeline that re-buffers to the requested region — normally the largest possible
region — and the mismatch is erased before it can be observed. The ordering in
simpleitk_from_imageis also correct:start_indexis read after the array extraction, so therecorded index always matches the data actually copied.
The mismatch therefore survives only for a standalone image whose regions were set explicitly (no
source, so
Update()is a no-op), a deliberately streamed pipeline with a smaller requestedregion, or a reader configured with
SetIORegion. Pixel values and physical locations are correctin every case; only extent metadata is lost.
Proposed remedy
Detect the mismatch and raise rather than silently discard, per @hjmjohnson on #6811:
In
simpleitk_from_image, compareGetBufferedRegion()withGetLargestPossibleRegion()andraise a
ValueErrornaming both extents when they differ. Failing loudly is preferable toreturning an image whose region information disagrees with its source.
An opt-out argument (accepting the buffered extent as the whole image) would cover the streaming
case for callers who know that is what they want. Recording the largest possible region as a second
metadata key and restoring it is a further option, but it makes the SimpleITK image's own size
disagree with the region it advertises, so it needs its own design discussion — as does
@blowekamp's separate question about
_zyxkeys on the SimpleITK side.Scope
Wrapping/Generators/Python/itk/support/extras.py—simpleitk_from_imageandimage_from_simpleitk. Follow-up to #6811; not a regression, as neither function existed before it.