ImageTilesDataset: tile extent ignores the region's coordinate transformation, and the input circles' radii are modified in place
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
With circles of radius 2 and Scale([2, 2]) to global, the tile extent is 4.0 (intrinsic units) instead of 8.0 (units of the target coordinate system): extent is computed from the untransformed radius while the centroids are transformed, mixing units. Constructing two datasets with tile_scale=3 turns the radii [2, 2] of the element stored in the SpatialData object into [18, 18].
Severity (agent's assessment): high — wrong tile sizes whenever the region has a non-unit scale to the target coordinate system (e.g. Xenium µm→px), and the user's shapes element is corrupted
Where: src/spatialdata/dataloader/datasets.py::_get_tile_coords (the results of both transform(circles, ...) calls are discarded; circles.radius *= tile_scale / = tile_dim_in_units / 2 operate on the user's element because to_circles(GeoDataFrame) returns the input object for circles)
Expected behaviour
Tile extents expressed in the target coordinate system; the input SpatialData object is left unchanged.
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",
# "torch",
# ]
# ///
"""ImageTilesDataset: tile extent ignores the region's transformation and the input circles' radii are modified."""
import warnings
import numpy as np
import geopandas as gpd
from shapely.geometry import Point
from spatialdata import SpatialData
from spatialdata.dataloader import ImageTilesDataset
from spatialdata.models import Image2DModel, ShapesModel
from spatialdata.transformations import Identity, Scale
warnings.simplefilter("ignore")
def build(scale):
img = Image2DModel.parse(np.zeros((1, 100, 100), dtype=np.uint8), transformations={"global": Identity()})
circles = ShapesModel.parse(
gpd.GeoDataFrame({"geometry": [Point(10, 10), Point(30, 30)], "radius": [2.0, 2.0]}, index=[0, 1]),
transformations={"global": Scale([scale, scale], axes=("x", "y"))},
)
return SpatialData(images={"img": img}, shapes={"cells": circles})
bug = False
for scale in [1.0, 2.0]:
sdata = build(scale)
ds = ImageTilesDataset(sdata, regions_to_images={"cells": "img"}, regions_to_coordinate_systems={"cells": "global"}, tile_scale=1.0)
tc = ds.tiles_coords.iloc[0]
expected = 2 * 2.0 * scale
print(f"scale={scale}: tile centre in cs=({tc.x}, {tc.y}), extent={tc.extent} (expected {expected} = circle diameter in cs units)")
bug |= tc.extent != expected
sdata = build(1.0)
before = sdata["cells"].radius.tolist()
ImageTilesDataset(sdata, regions_to_images={"cells": "img"}, regions_to_coordinate_systems={"cells": "global"}, tile_scale=3.0)
ImageTilesDataset(sdata, regions_to_images={"cells": "img"}, regions_to_coordinate_systems={"cells": "global"}, tile_scale=3.0)
after = sdata["cells"].radius.tolist()
print(f"radius of the circles element before: {before}, after constructing two datasets with tile_scale=3: {after} (expected unchanged)")
bug |= before != after
print("VERDICT:", "BUG REPRODUCED" if bug else "NOT REPRODUCED")
Observed output
scale=1.0: tile centre in cs=(10.0, 10.0), extent=4.0 (expected 4.0 = circle diameter in cs units)
scale=2.0: tile centre in cs=(20.0, 20.0), extent=4.0 (expected 8.0 = circle diameter in cs units)
radius of the circles element before: [2.0, 2.0], after constructing two datasets with tile_scale=3: [18.0, 18.0] (expected unchanged)
VERDICT: BUG REPRODUCED
Possible fix direction (unverified)
Assign the result of transform() (circles = transform(circles, to_coordinate_system=cs)) on a copy of the circles (to_circles should return a copy for circle inputs), compute extent from the transformed radius, and drop or assign the unused intrinsic_of_element block. Add a test with a scaled transformation and a test asserting the input element is unchanged.
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).
Possibly related issues
#854
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
Run the provided repro.py first, then inspect src/spatialdata/dataloader/datasets.py::_get_tile_coords. Add coverage for a scaled coordinate transformation and for preserving the input circles' radii; done means tile extents use target-coordinate units and constructing datasets leaves the SpatialData element unchanged.
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
- 72/100