Skip to content

SimpleITK conversion silently drops LargestPossibleRegion when the buffered region is smaller #6826

Description

@hjmjohnson

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.pysimpleitk_from_image and
image_from_simpleitk. Follow-up to #6811; not a regression, as neither function existed before it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions