diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 62987ece..21b1349d 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -895,11 +895,17 @@ def _draw_centroids(xy: np.ndarray, radius: float | None = None) -> None: transformed_geometry = shapes["geometry"].transform( lambda x: (np.hstack([x, np.ones((x.shape[0], 1))]) @ tm.T)[:, :2] ) + transformed_geodataframe = gpd.GeoDataFrame( + data=shapes.drop("geometry", axis=1), + geometry=transformed_geometry, + ) + # Geometry is already in the target CS, so tag the wrapped element with an identity transform + # there. Depending on the pandas/geopandas version the constructor above may carry over the + # source element's transform attrs; drop them first so parse does not see conflicting ones. + transformed_geodataframe.attrs.pop("transform", None) transformed_element = ShapesModel.parse( - gpd.GeoDataFrame( - data=shapes.drop("geometry", axis=1), - geometry=transformed_geometry, - ) + transformed_geodataframe, + transformations={coordinate_system: Identity()}, ) if len(transformed_element) == 0: @@ -908,7 +914,7 @@ def _draw_centroids(xy: np.ndarray, radius: float | None = None) -> None: return plot_width, plot_height, x_ext, y_ext, factor = _get_extent_and_range_for_datashader_canvas( - transformed_element, "global", fig_params + transformed_element, coordinate_system, fig_params ) cvs = ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_ext, y_range=y_ext) diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index a84ebe42..896ab139 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -1331,10 +1331,10 @@ def test_plot_can_handle_non_numeric_radius_values(sdata_blobs: SpatialData): def test_groups_filtering_preserves_transformation(sdata_blobs: SpatialData): """Regression test for #420: groups filtering must not strip coordinate-system metadata. - Simulates the exact sequence that ``_render_shapes`` performs — + Simulates the sequence that ``_render_shapes`` performs — filter_by_coordinate_system -> groups boolean-index -> reset_index -> - re-assign to sdata_filt -> GeoDataFrame re-wrap — then asserts that - ``_prepare_transformation`` can still retrieve the correct transformation. + re-assign to sdata_filt — then asserts that ``_prepare_transformation`` + can still retrieve the correct transformation. """ from spatialdata_plot.pl._datashader import _prepare_transformation @@ -1354,8 +1354,6 @@ def test_groups_filtering_preserves_transformation(sdata_blobs: SpatialData): keep = shapes["cluster"] == "c1" shapes = shapes[keep].reset_index(drop=True) sdata_filt["blobs_polygons"] = shapes - # GeoDataFrame re-wrap strips .attrs (this is what _render_shapes does next) - shapes = gpd.GeoDataFrame(shapes, geometry="geometry") # sdata_filt's element must still carry the correct transformation trans, _ = _prepare_transformation(sdata_filt.shapes["blobs_polygons"], cs) @@ -1363,11 +1361,6 @@ def test_groups_filtering_preserves_transformation(sdata_blobs: SpatialData): np.testing.assert_allclose(matrix[0, 0], scale_factor, err_msg="x-scale lost after groups filtering") np.testing.assert_allclose(matrix[1, 1], scale_factor, err_msg="y-scale lost after groups filtering") - # The GeoDataFrame re-wrap strips attrs — reading the transform from - # the re-wrapped object must fail, proving why early capture matters. - with pytest.raises(AssertionError): - _prepare_transformation(shapes, cs) - def test_plot_can_handle_mixed_numeric_and_color_data(sdata_blobs: SpatialData): """Test that mixed numeric and color-like data raises a clear error."""