scverse / scverse/spatialdata

Image2DModel/Image3DModel.parse(DataArray) silently discards the channel names already present in the input

Open
#1,210 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug 🚨 element: images 🌌 models needs: triage priority: high
Dominant language
Python
Stars
394
Forks
95
Avg merge
4d 3h
Merged PRs (30d)
7

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

Image2DModel.parse(sdata["img"], scale_factors=[2, 2]) (or any re-parse of a DataArray that already has channel names) returns channels [0, 1, 2] instead of ['DAPI', 'CD3', 'CD8']. Internal callers are affected too: rasterize(points, value_key=<categorical>, return_single_channel=False) returns an image whose channels are [0, 1, 2] instead of the category names, so the category ↔ channel mapping is lost.

Severity (agent's assessment): high — silent metadata loss on a very common call (re-parsing an image to build a pyramid or rechunk)

Where: src/spatialdata/models/models.py::RasterSchema.parse (to_spatial_image(array_like=data, dims=cls.dims, c_coords=c_coords, **kwargs) with c_coords=None does not forward the existing c coordinate)

Expected behaviour

Existing channel names are preserved unless c_coords is given explicitly.

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",
# ]
# ///
"""Image2DModel.parse(DataArray with channel names) silently replaces the channel names with 0..n-1."""
import warnings
import numpy as np
import xarray as xr
from spatialdata.models import Image2DModel

warnings.simplefilter("ignore")
da = xr.DataArray(np.zeros((3, 4, 4)), dims=("c", "y", "x"), coords={"c": ["DAPI", "CD3", "CD8"]})
single = Image2DModel.parse(da)
multi = Image2DModel.parse(da, scale_factors=[2])
print("input channel names       :", da.coords["c"].values.tolist())
print("parsed (single scale)     :", single.coords["c"].values.tolist())
print("parsed (scale_factors=[2]):", multi["scale0"]["image"].coords["c"].values.tolist())
bug = single.coords["c"].values.tolist() != ["DAPI", "CD3", "CD8"]
print("VERDICT:", "BUG REPRODUCED" if bug else "NOT REPRODUCED")
Observed output
input channel names       : ['DAPI', 'CD3', 'CD8']
parsed (single scale)     : [0, 1, 2]
parsed (scale_factors=[2]): [0, 1, 2]
VERDICT: BUG REPRODUCED

Possible fix direction (unverified)

In RasterSchema.parse, when data is a DataArray with a c coordinate and c_coords is None, set c_coords = data.coords["c"].values.tolist() before to_spatial_image. In rasterize_shapes_points pass c_coords=agg.coords["c"].values for the count_cat branch.

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

#1136


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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/spatialdata/models/models.py, especially RasterSchema.parse, and run the provided repro.py with uv run to confirm the channel-coordinate loss. Also inspect rasterize_shapes_points and the related #1136 issue. Done means parsing a DataArray preserves existing channel names, including scaled parses and categorical rasterization, unless explicit c_coords are supplied.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.