Repository navigation
transform() of multiscale labels uses linear interpolation and invents label ids #1202
Description
Note
This whole message is AI-generated. The issue was automatically discovered and reported by an AI agent (Claude) during an autonomous bug hunt on the spatialdata code base. It has not been verified or triaged by a human yet; the needs: triage label is set so that a maintainer can confirm it. The reproduction script below was executed by the agent in an isolated environment (see Environment) and its output is pasted verbatim.
Summary
Rotating or scaling a multiscale labels element (DataTree) with transform() resamples it with dask_image.ndinterp.affine_transform's default order=1 (linear interpolation). New label ids that never existed in the input appear (blends of neighbouring ids) and match no table row. Single-scale labels are handled correctly (order=0). The same applies to transform_to_coordinate_system() and transform(sdata, ...). Side inconsistency: single-scale images are resampled with nearest neighbour while multiscale images are linearly interpolated.
Severity (agent's assessment): high — silent data corruption of segmentation masks
Where: src/spatialdata/_core/operations/transform.py, DataTree overload (kwargs = {"prefilter": False} for labels, no order=0, ~line 330); the DataArray overload correctly uses order=0
Expected behaviour
The set of label ids after the transformation is a subset of the input ids (nearest-neighbour resampling), for both single-scale and multiscale labels.
Reproduction
Save as repro.py and run uv run repro.py (the PEP 723 header pins spatialdata to the commit the bug was found on; replace the URL fragment with @main to test the current main branch).
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "spatialdata @ git+https://github.com/scverse/spatialdata.git@ccf1ea048d054b6624214bf618008a9f9ae223e0",
# ]
# ///
"""transform() of multiscale labels uses linear interpolation and invents label ids (single-scale is fine)."""
import warnings
import numpy as np
from spatialdata import transform
from spatialdata.models import Labels2DModel
from spatialdata.transformations import Affine, Scale, set_transformation
warnings.simplefilter("ignore")
arr = np.zeros((64, 64), dtype=np.uint16)
arr[:32, :] = 1
arr[32:, :] = 50
theta = np.deg2rad(30)
rotation = Affine(
np.array([[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1]]),
input_axes=("x", "y"),
output_axes=("x", "y"),
)
bug = False
cases = [
("single-scale labels, rotation", Labels2DModel.parse(arr.copy()), rotation),
("multiscale labels, rotation", Labels2DModel.parse(arr.copy(), scale_factors=[2]), rotation),
("multiscale labels, Scale(1.5)", Labels2DModel.parse(arr.copy(), scale_factors=[2]), Scale([1.5, 1.5], axes=("x", "y"))),
]
for name, labels, t in cases:
set_transformation(labels, t, "global")
out = transform(labels, to_coordinate_system="global")
data = out.data if not hasattr(out, "scale0") else out["scale0"]["image"].data
uniques = np.unique(np.asarray(data.compute())).tolist()
ok = set(uniques) <= {0, 1, 50}
print(f"{name:32s} -> unique ids: {uniques[:10]}{' ...' if len(uniques) > 10 else ''} ({'OK' if ok else 'INVENTED IDS'})")
bug |= not ok
print("VERDICT:", "BUG REPRODUCED" if bug else "NOT REPRODUCED")Observed output
single-scale labels, rotation -> unique ids: [0, 1, 50] (OK)
multiscale labels, rotation -> unique ids: [0, 1, 3, 4, 5, 6, 7, 8, 9, 10] ... (INVENTED IDS)
multiscale labels, Scale(1.5) -> unique ids: [0, 1, 17, 50] (INVENTED IDS)
VERDICT: BUG REPRODUCED
uv run --no-project --python 3.13 26.84s user 10.31s system 46% cpu 1:19.27 total
Possible fix direction (unverified)
Pass order=0 for labels in the DataTree branch (and decide one interpolation policy for images in both branches, ideally exposing order, see #725). Add a test asserting set(np.unique(out)) <= set(np.unique(in)) for labels after rotation/scaling.
Environment
uv run repro.py with the PEP 723 metadata in the script (fresh, isolated environment; spatialdata built from main @ ccf1ea0 (2026-08-28); Python 3.13, latest releases of the dependencies at run time: pandas 3.0, anndata 0.13, zarr 3.3, dask 2026.8, numpy 2.5, geopandas 1.1, shapely 2.1). macOS (arm64). Also reproduced in a second environment with pandas 2.3.3 / anndata 0.12.11 / numpy 2.4.4 / zarr 3.2.1.
Possibly related issues
Automatically generated; discovered by an AI agent (Claude) and not yet reviewed by a human.
Activity