Skip to content

Commit 994d52e

Browse files
timtreisclaude
andcommitted
Add info log for float auto-range, tests for edge cases
- Log when float RGB data outside [0, 1] triggers auto-ranging - Document _normalize_dtype_to_float as RGB-display-oriented - Add test for float RGB outside [0, 1] auto-ranging - Add test for explicit cmap overriding RGBA detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6f9953b commit 994d52e

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,9 @@ def _render_points(
10191019

10201020

10211021
def _normalize_dtype_to_float(arr: np.ndarray) -> np.ndarray:
1022-
"""Normalize an array to float64 in [0, 1] for matplotlib.
1022+
"""Normalize an array to float64 in [0, 1] for display with matplotlib.
1023+
1024+
Intended for RGB/RGBA image data where negative values are not meaningful.
10231025
10241026
- uint8 → divide by 255
10251027
- other unsigned int → divide by dtype max
@@ -1040,6 +1042,12 @@ def _normalize_dtype_to_float(arr: np.ndarray) -> np.ndarray:
10401042
return arr_f
10411043
if vmin == vmax:
10421044
return np.zeros_like(arr_f)
1045+
logger.info(
1046+
"Float RGB image has values outside [0, 1] (range [%.3f, %.3f]); "
1047+
"auto-ranging globally. Pass an explicit 'norm' to control contrast.",
1048+
vmin,
1049+
vmax,
1050+
)
10431051
result: np.ndarray = (arr_f - vmin) / (vmax - vmin)
10441052
return result
10451053

tests/pl/test_render_images.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,25 @@ def test_uint16_rgba_renders(self):
279279
sdata.pl.render_images("img").pl.show(ax=ax)
280280
plt.close("all")
281281

282+
def test_float_rgb_outside_01_auto_ranges(self):
283+
"""Float RGB image with values outside [0, 1] should auto-range globally."""
284+
data = np.zeros((3, 50, 50), dtype=np.float32)
285+
data[0] = 2000.0 # all channels have different magnitudes
286+
data[1] = 1000.0
287+
data[2] = 500.0
288+
img = Image2DModel.parse(data, dims=("c", "y", "x"), c_coords=["r", "g", "b"])
289+
sdata = SpatialData(images={"img": img})
290+
fig, ax = plt.subplots()
291+
sdata.pl.render_images("img").pl.show(ax=ax)
292+
plt.close("all")
293+
294+
def test_cmap_overrides_rgba_detection(self):
295+
"""Explicit single cmap should skip RGBA path and use multi-channel rendering."""
296+
sdata = self._make_rgba_sdata(["r", "g", "b", "a"])
297+
fig, ax = plt.subplots()
298+
sdata.pl.render_images("img", cmap="Reds").pl.show(ax=ax)
299+
plt.close("all")
300+
282301

283302
class TestMultiChannelClipping:
284303
"""Regression tests: multi-channel compositing should not produce clipping warnings."""

0 commit comments

Comments
 (0)