Skip to content

Commit dc490c9

Browse files
timtreisclaude
andcommitted
Clip multi-channel composited images to [0, 1] to prevent matplotlib warnings
The shared Normalize instance auto-ranges on the first channel, so subsequent channels with different value ranges can exceed [0, 1]. This causes matplotlib's "Clipping input data to the valid range for imshow with RGB data" warning. Added np.clip(array, 0, 1) before _ax_show_and_transform in all multi-channel compositing paths (2A, 2B, 2C, 2D). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 68abcb7 commit dc490c9

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ def _render_images(
12291229
)
12301230

12311231
_ax_show_and_transform(
1232-
stacked,
1232+
np.clip(stacked, 0, 1),
12331233
trans_data,
12341234
ax,
12351235
render_params.alpha,
@@ -1291,7 +1291,7 @@ def _render_images(
12911291
) # TODO: update when pca is added as strategy
12921292

12931293
_ax_show_and_transform(
1294-
colored,
1294+
np.clip(colored, 0, 1),
12951295
trans_data,
12961296
ax,
12971297
render_params.alpha,
@@ -1308,7 +1308,7 @@ def _render_images(
13081308
colored = colored[:, :, :3]
13091309

13101310
_ax_show_and_transform(
1311-
colored,
1311+
np.clip(colored, 0, 1),
13121312
trans_data,
13131313
ax,
13141314
render_params.alpha,
@@ -1327,7 +1327,7 @@ def _render_images(
13271327
colored = colored[:, :, :3]
13281328

13291329
_ax_show_and_transform(
1330-
colored,
1330+
np.clip(colored, 0, 1),
13311331
trans_data,
13321332
ax,
13331333
render_params.alpha,

tests/pl/test_render_images.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,47 @@ def test_uint16_rgba_renders(self):
270270
fig, ax = plt.subplots()
271271
sdata.pl.render_images("img").pl.show(ax=ax)
272272
plt.close("all")
273+
274+
275+
class TestMultiChannelClipping:
276+
"""Regression tests: multi-channel compositing should not produce clipping warnings."""
277+
278+
def test_no_clipping_warning_3channel_different_ranges(self):
279+
"""3-channel image with different value ranges per channel should not clip."""
280+
import warnings
281+
282+
rng = np.random.default_rng(42)
283+
data = np.zeros((3, 50, 50), dtype=np.float32)
284+
data[0] = rng.uniform(0.0, 0.5, (50, 50))
285+
data[1] = rng.uniform(0.0, 1.0, (50, 50))
286+
data[2] = rng.uniform(0.0, 1.5, (50, 50))
287+
img = Image2DModel.parse(data, dims=("c", "y", "x"), c_coords=[0, 1, 2])
288+
sdata = SpatialData(images={"img": img})
289+
290+
with warnings.catch_warnings(record=True) as w:
291+
warnings.simplefilter("always")
292+
fig, ax = plt.subplots()
293+
sdata.pl.render_images("img").pl.show(ax=ax)
294+
plt.close("all")
295+
clip_warns = [x for x in w if "Clipping input data" in str(x.message)]
296+
assert len(clip_warns) == 0, f"Got unexpected clipping warning: {clip_warns[0].message}"
297+
298+
def test_no_clipping_warning_palette_compositing(self):
299+
"""Palette compositing should not produce clipping warnings."""
300+
import warnings
301+
302+
rng = np.random.default_rng(42)
303+
data = np.zeros((3, 50, 50), dtype=np.float32)
304+
data[0] = rng.uniform(0.0, 0.5, (50, 50))
305+
data[1] = rng.uniform(0.0, 1.0, (50, 50))
306+
data[2] = rng.uniform(0.0, 1.5, (50, 50))
307+
img = Image2DModel.parse(data, dims=("c", "y", "x"), c_coords=[0, 1, 2])
308+
sdata = SpatialData(images={"img": img})
309+
310+
with warnings.catch_warnings(record=True) as w:
311+
warnings.simplefilter("always")
312+
fig, ax = plt.subplots()
313+
sdata.pl.render_images("img", palette=["red", "green", "blue"]).pl.show(ax=ax)
314+
plt.close("all")
315+
clip_warns = [x for x in w if "Clipping input data" in str(x.message)]
316+
assert len(clip_warns) == 0, f"Got unexpected clipping warning: {clip_warns[0].message}"

0 commit comments

Comments
 (0)