Skip to content

Python image['spacing'] axis-order conflict between ITK (z,y,x) and SimpleITK (x,y,z) blocks generic interop (PR #6021) #6706

Description

@hjmjohnson

ITK and SimpleITK expose the same string keys (image['spacing'], ['origin'], ['direction']) with opposite axis-order conventions — ITK returns numpy (z,y,x) order, SimpleITK returns physical (x,y,z) order — so any generic reader that consumes these keys silently corrupts geometry. This blocks PR #6021 (SimpleITK↔ITK conversion) and cannot be fixed by picking one order, because both conventions have legitimate user expectations. Proposed fix: deprecate the ambiguous bare keys and add an explicit, order-annotated geometry protocol shared by both toolkits.

Problem detail and replication

For a 3-D image with spacing (1, 2, 3) in x,y,z (itk 5.4.6, SimpleITK 2.5.5):

Interface ['spacing'] returns Order
itk_image['spacing'] [3. 2. 1.] numpy (z,y,x) — flipped
sitk_image['spacing'] (1.0, 2.0, 3.0) ITK (x,y,z)
itk.dict_from_image(img)['spacing'] (1.0, 2.0, 3.0) ITK (x,y,z)
GetSpacing() (both toolkits) (1.0, 2.0, 3.0) ITK (x,y,z)
  • ITK is also internally inconsistent: dict(image)['spacing']itk.dict_from_image(image)['spacing'].
  • The flip is intentional (d8086fa, 2020) for consistency with np.array(image) [z,y,x] indexing, implemented in Wrapping/Generators/Python/PyBase/pyBase.i (keys/__getitem__/__setitem__).
  • The 'direction' flip (np.flip(matrix, axis=None) = D[::-1,::-1] = P·D·P) is internally correct: with origin/spacing/index all listed z,y,x, origin_zyx + D_zyx @ (spacing_zyx * index_zyx) reproduces flip(TransformIndexToPhysicalPoint(index)) to machine precision (verified, 200 random indices, non-axis-aligned 3-D rotation), and __setitem__ round-trips exactly. The corruption arises only cross-convention: passing itk_image['direction'] into an x,y,z consumer (e.g. SetDirection() or SimpleITK) yields a wrong matrix with no shape error.
  • Replication script: https://gist.github.com/hjmjohnson/799940ed88d2b9616239ca417f0e9986

Both expectations are individually reasonable: numpy-ecosystem users expect spacing[0] to match axis 0 of np.array(image); ITK/SimpleITK users expect physical x,y,z order matching GetSpacing(). The defect is that a bare string key carries no order semantics, so each community bound its own convention to the same name.

Proposed solution: explicit spatial-image protocol
  1. Define a small documented protocol both toolkits implement, with order-explicit names:
    • spacing_xyz, origin_xyz, direction_xyz — physical (ITK) order
    • spacing_zyx, origin_zyx, direction_zyx — numpy axis order; direction_zyx is well-defined as P·D·P (= np.flip(D, axis=None)), the correct change-of-basis when physical coordinates are also listed z,y,x (matches current image['direction'] behavior, verified against TransformIndexToPhysicalPoint).
    • Pixel access via shaped __array__ / GetArrayViewFromImage.
  2. Deprecate the bare 'spacing'/'origin'/'direction' keys in ITK's __getitem__/__setitem__/keys() with a DeprecationWarning pointing to the explicit names. Current behavior unchanged during the deprecation window.
  3. Coordinate with SimpleITK (Add .GetITKImage() method to sitk::Image class in Python SimpleITK/SimpleITK#2531) so SimpleITK 3.x adopts the same protocol before its conventions cement.
  4. Rebuild PR ENH: Add SimpleITK Image to ITK Image conversion support #6021's converter on the protocol (short-term unblock: GetSpacing()-family + GetArrayViewFromImage, which also fixes the np.array(sitk_image) flat-array failure on released SimpleITK).

Out of scope / unchanged: dict_from_image/image_from_dict (itk-wasm interchange format, ITK order, frozen), array_from_image pixel bridges (numpy order, correct), xarray bridge (named dims, already unambiguous).

Related: PR #6021 (blocked by this), SimpleITK/SimpleITK#2531, d8086fa.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:DesignImprovement in the design of a given area

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions