Skip to content

Commit 90b3bc6

Browse files
timtreisclaude
andcommitted
Address review: extract dtype helper, warn on ignored norm, fix signed int
- Extract _normalize_dtype_to_float helper to deduplicate dtype normalization between RGB and alpha channels - Warn when user-provided norm is silently ignored for RGB(A) images - Handle signed integer dtypes safely by clipping negative values after division by dtype max Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cc5929d commit 90b3bc6

1 file changed

Lines changed: 30 additions & 14 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,23 @@ def _render_points(
10181018
)
10191019

10201020

1021+
def _normalize_dtype_to_float(arr: np.ndarray) -> np.ndarray:
1022+
"""Normalize an integer or float array to float64 in [0, 1] for matplotlib.
1023+
1024+
- uint8 → divide by 255
1025+
- other int/uint dtypes → divide by dtype max
1026+
- float → clip to [0, 1]
1027+
"""
1028+
if arr.dtype == np.uint8:
1029+
return arr.astype(np.float64) / 255.0
1030+
if arr.dtype.kind == "u":
1031+
return arr.astype(np.float64) / np.iinfo(arr.dtype).max
1032+
if arr.dtype.kind == "i":
1033+
return np.clip(arr.astype(np.float64) / np.iinfo(arr.dtype).max, 0, 1)
1034+
result: np.ndarray = np.clip(arr, 0, 1).astype(np.float64)
1035+
return result
1036+
1037+
10211038
def _is_rgb_image(channel_coords: list[Any]) -> tuple[bool, bool]:
10221039
"""Check if channel coordinates indicate an RGB(A) image.
10231040
@@ -1112,28 +1129,27 @@ def _render_images(
11121129
isinstance(render_params.cmap_params, CmapParams) and not render_params.cmap_params.cmap_is_default
11131130
)
11141131
if is_rgb and palette is None and not got_multiple_cmaps and not has_explicit_cmap:
1132+
# Warn if user passed norm= that will be ignored in the RGB(A) path
1133+
if isinstance(render_params.cmap_params, CmapParams):
1134+
_norm = render_params.cmap_params.norm
1135+
if isinstance(_norm, Normalize) and (_norm.vmin is not None or _norm.vmax is not None):
1136+
logger.warning(
1137+
"Image detected as RGB(A) and will be rendered directly. "
1138+
"The 'norm' parameter is ignored for RGB images. "
1139+
"To apply normalization, specify a 'cmap' to use the multi-channel path."
1140+
)
1141+
11151142
coord_map = {str(c).lower(): c for c in channels}
11161143
ordered = [coord_map[ch] for ch in ("r", "g", "b")]
11171144
stacked = np.moveaxis(img.sel(c=ordered).values, 0, -1)
11181145

1119-
# Normalize to [0, 1] for matplotlib: uint8 → /255, other int dtypes → /max, float → clip
1120-
if stacked.dtype == np.uint8:
1121-
stacked = stacked.astype(np.float64) / 255.0
1122-
elif stacked.dtype.kind in ("u", "i"):
1123-
stacked = stacked.astype(np.float64) / np.iinfo(stacked.dtype).max
1124-
else:
1125-
stacked = np.clip(stacked, 0, 1)
1146+
# Normalize to [0, 1] for matplotlib
1147+
stacked = _normalize_dtype_to_float(stacked)
11261148

11271149
show_kwargs: dict[str, Any] = {"zorder": render_params.zorder}
11281150

11291151
if has_alpha and render_params.alpha == 1.0:
1130-
alpha_raw = img.sel(c=coord_map["a"]).values
1131-
if alpha_raw.dtype == np.uint8:
1132-
alpha_layer = alpha_raw.astype(np.float64) / 255.0
1133-
elif alpha_raw.dtype.kind in ("u", "i"):
1134-
alpha_layer = alpha_raw.astype(np.float64) / np.iinfo(alpha_raw.dtype).max
1135-
else:
1136-
alpha_layer = np.clip(alpha_raw.astype(np.float64), 0, 1)
1152+
alpha_layer = _normalize_dtype_to_float(img.sel(c=coord_map["a"]).values)
11371153
stacked = np.concatenate([stacked, alpha_layer[..., np.newaxis]], axis=-1)
11381154
else:
11391155
show_kwargs["alpha"] = render_params.alpha

0 commit comments

Comments
 (0)