scverse / scverse/spatialdata

aggregate(image, by=labels) silently ignores translations/offsets between the two rasters

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

Nobody has claimed this yet.

bug 🚨 element: images 🌌 element: labels 🏷️ method: aggregation 🔢 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

When the by and values transformations differ, aggregate calls transform() on both rasters; for rasters transform() applies only the linear part and keeps the translation in the element's transformation (documented behaviour of transform). _aggregate_image_by_labels then runs xrspatial.zonal_stats on the raw pixel grids, i.e. it assumes pixel (i, j) of the image and of the labels coincide. An image shifted by a Translation (e.g. a crop, or a query result) is therefore attributed to the wrong cells.

Severity (agent's assessment): high — wrong numbers, no warning

Where: src/spatialdata/_core/operations/aggregate.py::aggregate (~line 150-160) and _aggregate_image_by_labels

Expected behaviour

Either the offset is honoured (rasters aligned on a common grid before zonal_stats) or a clear NotImplementedError is raised when the affine matrices differ after the linear part has been applied.

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",
# ]
# ///
"""aggregate(image, by=labels) ignores the translation between the two rasters (silently wrong result)."""
import warnings
import numpy as np
from spatialdata import aggregate
from spatialdata.models import Image2DModel, Labels2DModel
from spatialdata.transformations import Identity, Translation

warnings.simplefilter("ignore")
# label 1 = top-left 2x2 block, label 2 = bottom-right 2x2 block
lab = Labels2DModel.parse(np.array([[1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 2, 2], [0, 0, 2, 2]], dtype=np.uint16))
arr = np.zeros((1, 4, 4))
arr[0, :2, :2] = 1.0  # ones in the top-left block (intrinsic coordinates)
results = {}
for name, t in [("identity", Identity()), ("translated by (2, 2)", Translation([2, 2], axes=("x", "y")))]:
    img = Image2DModel.parse(arr.copy(), transformations={"global": t})
    table = aggregate(values=img, by=lab, agg_func="sum")["table"]
    sums = dict(zip(table.obs.index.tolist(), table.X.toarray().ravel().tolist()))
    results[name] = sums
    print(f"image {name:22s}: sums per label {sums}")
print("expected for the translated image: {'1': 0.0, '2': 4.0} (the ones now sit on label 2)")
bug = results["translated by (2, 2)"].get("2") != 4.0
print("VERDICT:", "BUG REPRODUCED" if bug else "NOT REPRODUCED")
Observed output
image identity              : sums per label {'1': 4.0, '2': 0.0}
image translated by (2, 2)  : sums per label {'1': 4.0, '2': 0.0}
expected for the translated image: {'1': 0.0, '2': 4.0} (the ones now sit on label 2)
VERDICT: BUG REPRODUCED

Possible fix direction (unverified)

Align the rasters onto the target grid (reindex via the coordinates derived from the transformations, or rasterize() both onto the same grid) before calling zonal_stats; at minimum raise when the translations differ. Related: #950 reports an error for more complex transformations; this report is about the silent wrong result for translations.

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

#950


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 in src/spatialdata/_core/operations/aggregate.py, especially aggregate and _aggregate_image_by_labels, then run the supplied repro.py with uv run. Trace how raster transformations and translations are handled before zonal_stats. Done means translated rasters produce the expected label sums, or differing affine matrices raise the documented error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.