Skip to content

ENH: Add SimpleITK <-> ITK image conversion to the Python interface (supersedes #6021) - #6811

Draft
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:enh-image-from-simpleitk
Draft

ENH: Add SimpleITK <-> ITK image conversion to the Python interface (supersedes #6021)#6811
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:enh-image-from-simpleitk

Conversation

@hjmjohnson

Copy link
Copy Markdown
Member

Adds itk.image_from_simpleitk() and itk.simpleitk_from_image(), and teaches the filter decorator to accept a SimpleITK image wherever it accepts a NumPy array. Supersedes #6021 (@blowekamp), which stalled on the axis-order conflict that #6710 has since resolved.

No new dependency. SimpleITK is imported lazily inside the functions, so ITK builds and imports without it. The in-tree test uses a duck-typed stub and never imports SimpleITK — SimpleITK's superbuild builds ITK, so a test requiring it would close a cycle in the build graph, and SimpleITK is not present in ITK CI.

Related Status
Original PR this supersedes #6021
Axis-order conflict #6706
Order-explicit keys this builds on #6710
SimpleITK-side dual (open question) SimpleITK#2531
Why the bare spacing key cannot be used

image['spacing'] means (z, y, x) on an itk.Image and (x, y, z) on a SimpleITK Image. A converter reading it from one and writing it to the other silently reverses the spacing — the conflict reported in #6706, and what blocked #6021.

Geometry is therefore read through the order-explicit spacing_xyz / origin_xyz / direction_xyz keys from #6710, falling back to GetSpacing() / GetOrigin() / GetDirection(). Both are unambiguous.

Note that SimpleITK exposes no order-explicit keys today, so the accessor fallback carries every conversion; the key path is forward-looking, and starts working by itself if SimpleITK#2531 lands. A test asserts the two bare-key conventions still disagree, so this fails loudly rather than drifting if either toolkit changes.

Concerns from #6021, and how each is resolved
Concern Raised by Resolution
P1 — fallback never read GetSpacing()/GetOrigin()/GetDirection(); SimpleITK exposes methods, not dict keys. Vector images treated as scalar, geometry left at ITK defaults greptile _spatial_from_order_explicit() tries the _xyz key, then the accessor. Components come from GetNumberOfComponentsPerPixel(); dim from GetDimension(), not array.ndim
Missing image buffer start index @hjmjohnson (CHANGES_REQUESTED) A non-zero buffered-region index is carried as ITK_original_index and restored by the inverse. SimpleITK images always start at 0, so it cannot be represented directly
Store ImageRegion.m_Index as metadata @hjmjohnson / @blowekamp Implemented as above. The earlier "not needed" applied to sitk→itk, where SimpleITK enforces a zero index; this PR adds the itk→sitk direction, where it is needed

Metadata: every key is copied in both directions, and ITK_-prefixed entries are guaranteed to survive — a conversion failure on one of those raises rather than being silently dropped.

Testing

macOS 15 arm64, Release.

  • PythonSimpleITKProtocolTestPassed via ctest against this commit. Runs with SimpleITK present and with it blocked, confirming no hard dependency.
  • Full ITK Python suite — 167/176. The 9 failures are pre-existing: the identical 9 test numbers fail with pristine upstream extras.py/helpers.py. Causes are a broken VTK @rpath and a wrapping predating 002eebce7c4, neither related to this change.
  • Cross-toolkit round trip — 16/16 with ITK 6.0.0 and SimpleITK 3.0.0b1 built against that same ITK, in one interpreter. Covers geometry, vector, 2-D, pixels, metadata, index round trip, and physical-point agreement between the toolkits (which catches a transposed direction matrix that element-wise comparison misses). That suite lives outside both projects so neither gains a dependency.
  • pre-commit run --all-files passes on the branch tip.

Not validated on Linux or Windows.

@github-actions github-actions Bot added type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Enhancement Improvement of existing methods or implementation area:Python wrapping Python bindings for a class type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct labels Aug 27, 2026
@hjmjohnson

Copy link
Copy Markdown
Member Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change introduces ITK/SimpleITK image conversion and allows Python filter functions to accept SimpleITK images. Runtime conversion checks reproduced two data-preservation failures in Wrapping/Generators/Python/itk/support/extras.py: nonzero ITK region indices move pixels to incorrect physical coordinates in the SimpleITK output, and stale reserved index metadata can replace the actual buffered-region index during a round trip. These issues should be fixed before merging.

Confidence Score: 3/5

Not safe to merge until nonzero-index geometry and reserved index metadata are preserved correctly.

Both reported failures were reproduced with real ITK and SimpleITK conversions against the changed source. One test measured a physical-coordinate mismatch for a nonzero buffered-region index, and the other completed a round trip that restored a conflicting stale index value.

Files Needing Attention: Wrapping/Generators/Python/itk/support/extras.py needs changes to establish the output origin from the buffered-region start and to prevent ITK_original_index metadata from overriding the current region index.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex ran the executable for the nonzero-index ITK to SimpleITK physical-point experiment and reviewed the initial outputs.
  • T-Rex reproduced the focused scenario for conflicting and malformed original-index metadata and observed the overwritten index and a ValueError in the runtime output.
  • T-Rex produced a proof for a posted P1 finding with no artifacts attached.
  • T-Rex validated the geometry semantics by adjusting the SimpleITK origin to the ITK physical point at the buffered-region start, after which the physical points matched.
  • T-Rex executed the exact original-index runtime command and confirmed a successful run with exit code 0, observed that buffering preserved (7, 11) when the disputed operations were reordered, and saw a ValueError for the non-integer metadata value.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. General comment

    P1 ITK-to-SimpleITK conversion loses physical coordinates for nonzero buffered indices

    • Bug
      • simpleitk_from_image copies an ITK image's pixel buffer into a zero-indexed SimpleITK image but preserves the ITK origin. Consequently, the original buffered-start pixel moves to SimpleITK index zero while its physical coordinate changes.
    • Cause
      • The converter records GetBufferedRegion().GetIndex() as metadata but does not incorporate that index into the output origin. For the output array, SimpleITK index zero semantically corresponds to the ITK buffered-region start, not ITK index zero.
    • Fix
      • Before calling SetOrigin, set the SimpleITK origin to image.TransformIndexToPhysicalPoint(image.GetBufferedRegion().GetIndex()) (with the existing spacing and direction copied unchanged). Preserve the metadata only for inverse conversion if needed.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Existing ITK_original_index metadata replaces the buffered-region index

    • Bug
      • simpleitk_from_image records the actual non-zero buffered-region index, but its subsequent metadata-copy loop overwrites that reserved key. A real round trip of an image indexed (7, 11) with metadata ITK_original_index='1 2' restores (1, 2) instead.
    • Cause
      • The reserved index metadata is written at lines 891-895 before lines 897-900 copy all metadata entries without excluding ITK_original_index.
    • Fix
      • Copy user metadata before setting ITK_original_index, or skip that reserved key in the metadata-copy loop; validate the parsed metadata dimensions in image_from_simpleitk if malformed external metadata should produce a controlled error.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "ENH: Add SimpleITK <-> ITK image convers..." | Re-trigger Greptile

Comment thread Wrapping/Generators/Python/itk/support/extras.py Outdated
Comment thread Wrapping/Generators/Python/itk/support/extras.py
itk.image_from_simpleitk() and itk.simpleitk_from_image() convert between the
two toolkits, and the filter decorator accepts a SimpleITK image wherever it
accepts a NumPy array.

Geometry uses the order-explicit spatial keys from InsightSoftwareConsortium#6710, falling back to the
Get*() accessors. The bare 'spacing' key means (z,y,x) on an itk.Image and
(x,y,z) on a SimpleITK Image, so reading it would reverse the spacing (InsightSoftwareConsortium#6706).

SimpleITK images start at index 0, so the origin moves to the first stored
voxel and the index is carried as ITK_original_index; the inverse restores
both, keeping the pixels in the same physical location.

Supersedes InsightSoftwareConsortium#6021.

Co-Authored-By: Bradley Lowekamp <321061+blowekamp@users.noreply.github.com>
Co-Authored-By: Matt McCormick <25432+thewtex@users.noreply.github.com>
Co-Authored-By: Dzenan Zukic <1792121+dzenanz@users.noreply.github.com>
@hjmjohnson
hjmjohnson force-pushed the enh-image-from-simpleitk branch from 769fa94 to 46afb95 Compare August 28, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Python wrapping Python bindings for a class type:Enhancement Improvement of existing methods or implementation 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.

1 participant