Scale/Translation/Affine.__eq__ raise ValueError for a different number of axes (breaks aggregate on Xenium transcripts vs 2D cells)
Nobody has claimed this yet.
- 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 thespatialdatacode base. It has not been verified or triaged by a human yet; theneeds: triagelabel 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
Scale([2, 2, 1], axes=("x", "y", "z")) == Scale([2, 2], axes=("x", "y")) raises ValueError: operands could not be broadcast together with shapes (3,) (2,) instead of returning False; same for Translation and Affine. aggregate() compares the two transformations with == (by_transform == values_transform), so on the Xenium dataset from spatialdata-sandbox (xenium_rep1_io), where transcripts carry Scale (x, y, z) and cell boundaries Scale (x, y), the count-matrix aggregation crashes. spatialdata.testing.assert_elements_are_identical hits the same error after a write/read round trip of a raster whose Scale was defined on a subset of the axes.
Severity (agent's assessment): high — the canonical Xenium workflow aggregate(transcripts, by=cell_boundaries, value_key="feature_name", agg_func="count") crashes on real data
Where: src/spatialdata/transformations/transformations.py — Translation.__eq__, Scale.__eq__, Affine.__eq__ evaluate np.allclose(...) before comparing the axes
Expected behaviour
== returns False for transformations with different axes.
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",
# ]
# ///
"""Scale/Translation/Affine.__eq__ raise ValueError instead of returning False for a different number of axes."""
import warnings
import numpy as np
from spatialdata.transformations import Affine, Identity, Scale, Translation
warnings.simplefilter("ignore")
pairs = [
("Scale (x,y,z) vs Scale (x,y)", Scale([2, 2, 1], axes=("x", "y", "z")), Scale([2, 2], axes=("x", "y"))),
("Translation (x,y,z) vs (x,y)", Translation([1, 2, 3], axes=("x", "y", "z")), Translation([1, 2], axes=("x", "y"))),
("Affine 3D vs 2D", Affine(np.eye(4), input_axes=("x", "y", "z"), output_axes=("x", "y", "z")), Affine(np.eye(3), input_axes=("x", "y"), output_axes=("x", "y"))),
("Scale vs Identity (control)", Scale([2, 2], axes=("x", "y")), Identity()),
]
bug = False
for name, a, b in pairs:
try:
print(f"{name:32s}: a == b -> {a == b}")
except Exception as e: # noqa: BLE001
print(f"{name:32s}: {type(e).__name__}: {e}")
bug = True
print("VERDICT:", "BUG REPRODUCED" if bug else "NOT REPRODUCED")
# Real-data consequence: Xenium transcripts carry Scale(x, y, z) while cell boundaries carry Scale(x, y), so
# aggregate(transcripts, by=cell_boundaries, value_key="feature_name", agg_func="count") crashes in the
# `by_transform == values_transform` comparison inside aggregate().
Observed output
Scale (x,y,z) vs Scale (x,y) : ValueError: operands could not be broadcast together with shapes (3,) (2,)
Translation (x,y,z) vs (x,y) : ValueError: operands could not be broadcast together with shapes (3,) (2,)
Affine 3D vs 2D : ValueError: operands could not be broadcast together with shapes (4,4) (3,3)
Scale vs Identity (control) : a == b -> False
VERDICT: BUG REPRODUCED
Possible fix direction (unverified)
Compare axes (and matrix shapes) first and short-circuit, e.g. return isinstance(other, Scale) and self.axes == other.axes and np.allclose(self.scale, other.scale); same for Translation and Affine (input/output axes and matrix.shape). Add tests comparing 2D vs 3D transformations.
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
#1029
Automatically generated; discovered by an AI agent (Claude) and not yet reviewed by a human.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/spatialdata/transformations/transformations.py at Translation.eq, Scale.eq, and Affine.eq, then run the supplied repro.py to confirm the shape-mismatch failures. Compare the existing equality behavior with the stated expected results, add coverage for 2D-versus-3D transformations, and verify that the Xenium aggregation comparison no longer raises ValueError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- numpy, python
- Domain
- data
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100