Skip to content

Commit 46b80ce

Browse files
timtreisclaude
andcommitted
Fix render_shapes losing transformation after groups filtering (#420)
Move _prepare_transformation() call to before the groups filtering block so the coordinate-system transformation is captured while the element's metadata is still intact. The GeoDataFrame re-wrap that follows groups filtering strips .attrs, which would lose the transformation if read afterwards. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6b2c484 commit 46b80ce

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ def _render_shapes(
350350

351351
shapes = sdata_filt[element]
352352

353+
# Capture the transformation *before* any groups filtering that may strip
354+
# coordinate-system metadata from the element (see #420, #447).
355+
trans, trans_data = _prepare_transformation(sdata_filt.shapes[element], coordinate_system)
356+
353357
# get color vector (categorical or continuous)
354358
color_source_vector, color_vector, _ = _set_color_source_vec(
355359
sdata=sdata_filt,
@@ -425,9 +429,6 @@ def _render_shapes(
425429
# necessary in case different shapes elements are annotated with one table
426430
color_source_vector = color_source_vector.remove_unused_categories()
427431

428-
# Apply the transformation to the PatchCollection's paths
429-
trans, trans_data = _prepare_transformation(sdata_filt.shapes[element], coordinate_system)
430-
431432
shapes = gpd.GeoDataFrame(shapes, geometry="geometry")
432433
# convert shapes if necessary
433434
if render_params.shape is not None:

tests/pl/test_render_shapes.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,51 @@ def test_plot_can_handle_non_numeric_radius_values(sdata_blobs: SpatialData):
10671067
sdata_blobs.pl.render_shapes(element="blobs_circles", color="red").pl.show()
10681068

10691069

1070+
def test_groups_filtering_preserves_transformation(sdata_blobs: SpatialData):
1071+
"""Regression test for #420: groups filtering must not strip coordinate-system metadata.
1072+
1073+
Simulates the exact sequence that ``_render_shapes`` performs —
1074+
``filter_by_coordinate_system`` ➜ groups boolean-index ➜ ``reset_index`` ➜
1075+
re-assign to ``sdata_filt`` — then asserts that ``_prepare_transformation``
1076+
can still retrieve the non-global transformation with the correct scale.
1077+
"""
1078+
from spatialdata.transformations import set_transformation
1079+
1080+
from spatialdata_plot.pl.utils import _prepare_transformation
1081+
1082+
scale_factor = 2.5
1083+
cs = "not_global"
1084+
set_transformation(
1085+
sdata_blobs["blobs_polygons"],
1086+
transformation={cs: Scale([scale_factor, scale_factor], axes=("x", "y"))},
1087+
set_all=True,
1088+
)
1089+
sdata_blobs.shapes["blobs_polygons"]["cluster"] = pd.Categorical(["c1", "c2", "c1", "c2", "c1"])
1090+
1091+
sdata_filt = sdata_blobs.filter_by_coordinate_system(coordinate_system=cs, filter_tables=False)
1092+
1093+
# --- replicate the groups-filtering path from _render_shapes (lines 382-389) ---
1094+
shapes = sdata_filt.shapes["blobs_polygons"]
1095+
keep = shapes["cluster"] == "c1"
1096+
shapes = shapes[keep].reset_index(drop=True)
1097+
sdata_filt["blobs_polygons"] = shapes
1098+
# also replicate the GeoDataFrame re-wrap that follows (line 432), which strips .attrs
1099+
shapes = gpd.GeoDataFrame(shapes, geometry="geometry")
1100+
1101+
# The sdata_filt element must still carry the correct transformation
1102+
# (this is where _render_shapes reads the transform after the fix).
1103+
trans, _ = _prepare_transformation(sdata_filt.shapes["blobs_polygons"], cs)
1104+
matrix = trans.get_matrix()
1105+
np.testing.assert_allclose(matrix[0, 0], scale_factor, err_msg="x-scale lost after groups filtering")
1106+
np.testing.assert_allclose(matrix[1, 1], scale_factor, err_msg="y-scale lost after groups filtering")
1107+
1108+
# The GeoDataFrame re-wrap (which _render_shapes does right after) strips
1109+
# attrs — prove that reading the transform from *that* object would fail,
1110+
# demonstrating why early capture matters.
1111+
with pytest.raises((KeyError, AssertionError)):
1112+
_prepare_transformation(shapes, cs)
1113+
1114+
10701115
def test_plot_can_handle_mixed_numeric_and_color_data(sdata_blobs: SpatialData):
10711116
"""Test that mixed numeric and color-like data raises a clear error."""
10721117
sdata_blobs["table"].obs["region"] = pd.Categorical(["blobs_circles"] * sdata_blobs["table"].n_obs)

0 commit comments

Comments
 (0)