Skip to content

Commit f8d0266

Browse files
timtreisclaude
andcommitted
Apply user-provided norm to RGB(A) images instead of ignoring it
When a user passes norm= with explicit vmin/vmax to an RGB(A) image, apply it per channel for contrast adjustment. Fall back to dtype-based normalization only when no explicit norm is provided. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 90b3bc6 commit f8d0266

2 files changed

Lines changed: 25 additions & 13 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,22 +1129,26 @@ def _render_images(
11291129
isinstance(render_params.cmap_params, CmapParams) and not render_params.cmap_params.cmap_is_default
11301130
)
11311131
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-
11421132
coord_map = {str(c).lower(): c for c in channels}
11431133
ordered = [coord_map[ch] for ch in ("r", "g", "b")]
1144-
stacked = np.moveaxis(img.sel(c=ordered).values, 0, -1)
11451134

1146-
# Normalize to [0, 1] for matplotlib
1147-
stacked = _normalize_dtype_to_float(stacked)
1135+
# Apply norm per channel if user provided one, otherwise normalize by dtype
1136+
user_norm = (
1137+
render_params.cmap_params.norm
1138+
if isinstance(render_params.cmap_params, CmapParams)
1139+
and isinstance(render_params.cmap_params.norm, Normalize)
1140+
and (render_params.cmap_params.norm.vmin is not None or render_params.cmap_params.norm.vmax is not None)
1141+
else None
1142+
)
1143+
1144+
if user_norm is not None:
1145+
rgb_layers = []
1146+
for ch in ordered:
1147+
ch_norm = copy(user_norm)
1148+
rgb_layers.append(np.clip(ch_norm(img.sel(c=ch).values).astype(np.float64), 0, 1))
1149+
stacked = np.stack(rgb_layers, axis=-1)
1150+
else:
1151+
stacked = _normalize_dtype_to_float(np.moveaxis(img.sel(c=ordered).values, 0, -1))
11481152

11491153
show_kwargs: dict[str, Any] = {"zorder": render_params.zorder}
11501154

tests/pl/test_render_images.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ def test_explicit_alpha_overrides_per_pixel(self):
246246
sdata.pl.render_images("img", alpha=0.3).pl.show(ax=ax)
247247
plt.close("all")
248248

249+
def test_norm_applied_to_rgba(self):
250+
"""User-provided norm should be applied per channel on RGB(A) images."""
251+
sdata = self._make_rgba_sdata(["r", "g", "b", "a"])
252+
fig, ax = plt.subplots()
253+
norm = Normalize(vmin=0.0, vmax=0.5, clip=True)
254+
sdata.pl.render_images("img", norm=norm).pl.show(ax=ax)
255+
plt.close("all")
256+
249257
def test_uint8_rgb_renders(self):
250258
"""uint8 RGB image should be normalized to [0, 1] and render correctly."""
251259
data = np.zeros((3, 50, 50), dtype=np.uint8)

0 commit comments

Comments
 (0)