Repository navigation
Python xarray conversion: CI never runs test_xarray.py; follow-ups to #6937 #6950
Description
#6937 fixes image_from_xarray() assigning origin and spacing to the wrong axes
for 4D images, a bug present since at least ITK 5.3. It survived because ITK's CI
never executes test_xarray.py. This issue lists the CI fix first, then the
remaining test and code follow-ups, each with explicit instructions.
1. Make CI run the xarray tests (highest value)
Two independent gates turn the test into a silent pass in every CI build:
- Install xarray in the Python pipelines. Add
xarrayto the pip line in
Testing/ContinuousIntegration/AzurePipelinesLinuxPython.yml(line 80),
AzurePipelinesMacOSPython.yml(line 83), and
AzurePipelinesWindowsPython.yml(line 62). Today the test hits its
except ImportError:branch, prints "xarray not imported. Skipping xarray
conversion tests", and exits 0. - Report the skip instead of passing. In
Wrapping/Generators/Python/Tests/CMakeLists.txt(testPythonXarrayTest,
line 223), add
set_tests_properties(PythonXarrayTest PROPERTIES SKIP_REGULAR_EXPRESSION "xarray not imported")
so CDash shows the test as skipped, not passed, wherever xarray is absent. - Wrap 4D images in at least one Python CI build. The 4D block in
test_xarray.py(guarded by"(<itkCType unsigned char>, 4)" in itk.Image.GetTypesAsList(), line 136) only runs when 4D images are
wrapped. Source builds default toITK_WRAP_IMAGE_DIMS=2;3
(Wrapping/WrappingOptions.cmake, line 241). Set
ITK_WRAP_IMAGE_DIMS:STRING=2;3;4in one Python dashboard configuration
(Linux is the cheapest). The PyPI wheels already wrap 4D. - Verify on the CI log that
PythonXarrayTestexecutes the 4D asserts.
2. Tests to add in Wrapping/Generators/Python/Tests/test_xarray.py
All inside the existing 4D block:
- 4D round trip (fails before BUG: Fix axis order in image_from_xarray origin/spacing #6937, passes after; verified locally on
the ITK 5.4.7 wheel):
python rt = itk.image_from_xarray(itk.xarray_from_image(image)) assert np.allclose(rt.GetSpacing(), image.GetSpacing()) assert np.allclose(rt.GetOrigin(), image.GetOrigin()) - 4D input stored in non-canonical order: a DataArray with
dims=["x", "y", "z", "t"]and distinct per-axis coords must give spacing
(sx, sy, sz, st)and origin(ox, oy, oz, ot). - 4D vector image (
dims=["t", "z", "y", "x", "c"]), guarded by the
matching wrapped type, to cover the"c"path.
3. Code follow-ups in Wrapping/Generators/Python/itk/support/xarray.py
- Non-suffix dimension sets raise
ValueError.ordered_dims = ("t", "z", "y", "x")[-image_dimension:](line 115) assumes the present dims
are a suffix of(t, z, y, x).dims=("t", "y", "x"),("t", "x"), or
("z", "x")fail withValueError: tuple.index(x): x not in tuple. Either
support them with
ordered_dims = tuple(d for d in ("t", "z", "y", "x") if d in data_array.dims)
plus a test, or raise a clearValueErrorthat names the supported
combinations. - Remove the dead sort. After BUG: Fix axis order in image_from_xarray origin/spacing #6937,
image_dimsis used only for its
length. Replace lines 112 to 114 with a direct count, for example
image_dimension = len({"t", "z", "y", "x"}.intersection(data_array.dims)). - Remove the duplicate
directionassignment inxarray_from_image
(lines 66 and 85 compute the same value). - Size-1 axes lose their origin.
if coords.shape[0] > 1:(line 133)
skips both origin and spacing for a single-sample axis, so a single time
point gets origin 0. Set the origin whenever coords exist and keep the
spacing default for size-1 axes.
Evidence (local verification of #6937)
Isolated venv, ITK 5.4.7 wheel (wraps 4D unsigned char), xarray 2026.9.0. The
wheel's image_from_xarray is identical to the code #6937 changed; the "after"
column adds only #6937's one-line change.
| Check | Before #6937 | After #6937 |
|---|---|---|
#6937's test_xarray.py |
fails at the new spacing assert | passes |
| 4D round trip image to xarray to image | spacing (2,5,4,3), origin (100,30,20,10) |
preserved |
4D DataArray in x,y,z,t order |
scrambled | correct |
3D (z,y,x) with coords |
correct | unchanged |
dims (t,y,x) |
ValueError |
ValueError |
xarray_from_image always names a 4D image's fourth axis t, so before #6937
every 4D image round-tripped through xarray lost its geometry, not only
hand-built DataArrays.
The CI pip lines install numpy, dask, distributed, and lxml, but no
xarray; no pixi environment or workflow installs it either.
Activity
BUG: Preserve singleton xarray coordinate origins