You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
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.
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.
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).
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):
['spacing']returnsitk_image['spacing'][3. 2. 1.]sitk_image['spacing'](1.0, 2.0, 3.0)itk.dict_from_image(img)['spacing'](1.0, 2.0, 3.0)GetSpacing()(both toolkits)(1.0, 2.0, 3.0)dict(image)['spacing']≠itk.dict_from_image(image)['spacing'].np.array(image)[z,y,x]indexing, implemented inWrapping/Generators/Python/PyBase/pyBase.i(keys/__getitem__/__setitem__).'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)reproducesflip(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: passingitk_image['direction']into an x,y,z consumer (e.g.SetDirection()or SimpleITK) yields a wrong matrix with no shape error.Both expectations are individually reasonable: numpy-ecosystem users expect
spacing[0]to match axis 0 ofnp.array(image); ITK/SimpleITK users expect physical x,y,z order matchingGetSpacing(). 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
spacing_xyz,origin_xyz,direction_xyz— physical (ITK) orderspacing_zyx,origin_zyx,direction_zyx— numpy axis order;direction_zyxis 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 currentimage['direction']behavior, verified againstTransformIndexToPhysicalPoint).__array__/GetArrayViewFromImage.'spacing'/'origin'/'direction'keys in ITK's__getitem__/__setitem__/keys()with aDeprecationWarningpointing to the explicit names. Current behavior unchanged during the deprecation window..GetITKImage()method to sitk::Image class in Python SimpleITK/SimpleITK#2531) so SimpleITK 3.x adopts the same protocol before its conventions cement.GetSpacing()-family +GetArrayViewFromImage, which also fixes thenp.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_imagepixel bridges (numpy order, correct), xarray bridge (named dims, already unambiguous).Related: PR #6021 (blocked by this), SimpleITK/SimpleITK#2531, d8086fa.