Skip to content

Commit 43a0124

Browse files
authored
fix(labels): restore render float-dtype guard for spatialdata <0.8 (#751)
1 parent c6054fb commit 43a0124

4 files changed

Lines changed: 33 additions & 3 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,9 +2189,15 @@ def _render_labels(
21892189
is_label=True,
21902190
)
21912191

2192-
# Label dtype is validated upstream: spatialdata rejects non-integer label rasters at the model
2193-
# boundary (parse / SpatialData construction / __setitem__), so a validly built element always
2194-
# reaches here with an integer dtype. No local guard needed (see #606, resolved upstream).
2192+
# spatialdata >= 0.8 rejects non-integer label rasters at the model boundary, but the library
2193+
# still supports spatialdata >= 0.3, where float labels parse fine and would otherwise crash
2194+
# deep in skimage with a cryptic TypeError (#606). Keep a clear render-time guard for that range.
2195+
if np.issubdtype(label.dtype, np.floating):
2196+
raise ValueError(
2197+
f"Label element '{element}' has dtype {label.dtype}. Label arrays must use an "
2198+
f"integer dtype (e.g. int32 or uint16). Cast before plotting, e.g.:\n"
2199+
f" sdata['{element}'] = sdata['{element}'].astype('int32')"
2200+
)
21952201

21962202
# rasterize spatial image if necessary to speed up performance
21972203
if rasterize:
12 Bytes
Loading

tests/pl/test_render_labels.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,24 @@ def test_render_labels_lognorm_with_zeros_does_not_crash(sdata_blobs: SpatialDat
719719
plt.close(fig)
720720

721721

722+
@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64])
723+
def test_render_labels_rejects_float_dtype(dtype):
724+
# Regression test for #606: float-dtype labels must raise a clear "integer dtype" ValueError,
725+
# not a cryptic skimage TypeError. spatialdata>=0.8 enforces this at model validation
726+
# (parse/construction); on older spatialdata (still supported, >=0.3) our render-time guard is
727+
# the safety net. The supported stack must reject them at one of those layers either way.
728+
arr = np.zeros((20, 20), dtype=dtype)
729+
arr[3:8, 3:8] = 1
730+
arr[12:17, 12:17] = 2
731+
fig, ax = plt.subplots()
732+
try:
733+
with pytest.raises(ValueError, match="integer dtype"):
734+
sdata = SpatialData(labels={"lbl": Labels2DModel.parse(arr, dims=["y", "x"])})
735+
sdata.pl.render_labels("lbl").pl.show(ax=ax)
736+
finally:
737+
plt.close(fig)
738+
739+
722740
def test_render_labels_rejects_background_instance_id_in_table():
723741
# Regression test for #607: table row with instance_id=0 (background)
724742
# used to crash with obnscure error.

tests/pl/test_render_shapes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,12 @@ def test_plot_can_plot_queried_with_annotation_despite_random_shuffling(self, sd
564564
filter_table=True,
565565
)
566566

567+
# spatialdata's query returns the cropped geometries in a version-dependent order (0.8's
568+
# relational-query refactor reorders them), which flips the draw/z-order of the overlapping
569+
# circles and so the rendered image. Sort by index for a deterministic draw order, so the
570+
# baseline matches across the supported spatialdata range.
571+
sdata_cropped["blobs_circles"] = sdata_cropped["blobs_circles"].sort_index()
572+
567573
sdata_cropped.pl.render_shapes("blobs_circles", color="annotation").pl.show()
568574

569575
def test_plot_can_color_two_shapes_elements_by_annotation(self, sdata_blobs: SpatialData):

0 commit comments

Comments
 (0)