diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 230066f8..94f530ff 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -2189,9 +2189,15 @@ def _render_labels( is_label=True, ) - # Label dtype is validated upstream: spatialdata rejects non-integer label rasters at the model - # boundary (parse / SpatialData construction / __setitem__), so a validly built element always - # reaches here with an integer dtype. No local guard needed (see #606, resolved upstream). + # spatialdata >= 0.8 rejects non-integer label rasters at the model boundary, but the library + # still supports spatialdata >= 0.3, where float labels parse fine and would otherwise crash + # deep in skimage with a cryptic TypeError (#606). Keep a clear render-time guard for that range. + if np.issubdtype(label.dtype, np.floating): + raise ValueError( + f"Label element '{element}' has dtype {label.dtype}. Label arrays must use an " + f"integer dtype (e.g. int32 or uint16). Cast before plotting, e.g.:\n" + f" sdata['{element}'] = sdata['{element}'].astype('int32')" + ) # rasterize spatial image if necessary to speed up performance if rasterize: diff --git a/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png b/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png index 77d1ac75..ff728798 100644 Binary files a/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png and b/tests/_images/Shapes_can_plot_queried_with_annotation_despite_random_shuffling.png differ diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 447b9e02..725ab23a 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -719,6 +719,24 @@ def test_render_labels_lognorm_with_zeros_does_not_crash(sdata_blobs: SpatialDat plt.close(fig) +@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64]) +def test_render_labels_rejects_float_dtype(dtype): + # Regression test for #606: float-dtype labels must raise a clear "integer dtype" ValueError, + # not a cryptic skimage TypeError. spatialdata>=0.8 enforces this at model validation + # (parse/construction); on older spatialdata (still supported, >=0.3) our render-time guard is + # the safety net. The supported stack must reject them at one of those layers either way. + arr = np.zeros((20, 20), dtype=dtype) + arr[3:8, 3:8] = 1 + arr[12:17, 12:17] = 2 + fig, ax = plt.subplots() + try: + with pytest.raises(ValueError, match="integer dtype"): + sdata = SpatialData(labels={"lbl": Labels2DModel.parse(arr, dims=["y", "x"])}) + sdata.pl.render_labels("lbl").pl.show(ax=ax) + finally: + plt.close(fig) + + def test_render_labels_rejects_background_instance_id_in_table(): # Regression test for #607: table row with instance_id=0 (background) # used to crash with obnscure error. diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 9041c902..a84ebe42 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -564,6 +564,12 @@ def test_plot_can_plot_queried_with_annotation_despite_random_shuffling(self, sd filter_table=True, ) + # spatialdata's query returns the cropped geometries in a version-dependent order (0.8's + # relational-query refactor reorders them), which flips the draw/z-order of the overlapping + # circles and so the rendered image. Sort by index for a deterministic draw order, so the + # baseline matches across the supported spatialdata range. + sdata_cropped["blobs_circles"] = sdata_cropped["blobs_circles"].sort_index() + sdata_cropped.pl.render_shapes("blobs_circles", color="annotation").pl.show() def test_plot_can_color_two_shapes_elements_by_annotation(self, sdata_blobs: SpatialData):