Skip to content

transform() of multiscale labels uses linear interpolation and invents label ids #1202

Open

Description

@LucaMarconato

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

#725, #1070


Automatically generated; discovered by an AI agent (Claude) and not yet reviewed by a human.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions