render_images(norm=LogNorm()) crashes when image contains zeros
Description
Calling render_images("img", norm=LogNorm()) on any image that contains zero-valued pixels crashes when drawing the colorbar. The same call with colorbar=False succeeds. colorbar="auto" is the default for norm-based image renders, so this crash is hit without any unusual arguments.
The error is ValueError: Invalid vmin or vmax from 15+ frames inside matplotlib's colorbar constructor. No spatialdata-plot frame appears in the traceback, making the failure opaque to users. The actual problem — LogNorm cannot represent zero — is straightforward to catch early.
Environment
spatialdata-plot: 0.3.4.dev (main, 5cfedc7)
spatialdata: 0.5.0
Python: 3.13
Minimal Reproducible Example
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import spatialdata as sd
from spatialdata.models import Image2DModel
from matplotlib.colors import LogNorm
import spatialdata_plot
img = np.array([[[0, 0.5, 1.0, 0.3, 0.8]] * 5] * 5, dtype=np.float32).T[:1]
sdata = sd.SpatialData(images={"img": Image2DModel.parse(img, c_coords=["DAPI"])})
# Works — zeros are masked, colorbar suppressed
fig, ax = plt.subplots()
sdata.pl.render_images("img", norm=LogNorm(), colorbar=False).pl.show(ax=ax)
# Crashes — colorbar="auto" is the default for norm-based renders
fig, ax = plt.subplots()
sdata.pl.render_images("img", norm=LogNorm()).pl.show(ax=ax)
Actual Output
ValueError: Invalid vmin or vmax
Full traceback is 15+ frames inside matplotlib's colorbar constructor (lib/matplotlib/colorbar.py). No spatialdata-plot frame visible. No mention of LogNorm, zeros, or how to work around the issue.
Expected Output
Either:
- Colorbar skipped with a
UserWarning: "Data contains zeros or negative values; colorbar suppressed for LogNorm. Use colorbar=False to silence this warning."
- Or a clear
ValueError raised at the point spatialdata-plot requests the colorbar: "Cannot draw colorbar for LogNorm when data contains zeros or negative values. Set colorbar=False or clip the data."
Fix Sketch
Option A (preferred): early guard in _draw_colorbar (basic.py ~line 1162)
Before calling fig.colorbar(...), check whether the norm is log-scale-sensitive and data contains non-positive values:
from matplotlib.colors import LogNorm, SymLogNorm
if isinstance(norm, (LogNorm, SymLogNorm)) and data.min() <= 0:
warnings.warn(
"Data contains zeros or negative values; colorbar suppressed for LogNorm. "
"Use colorbar=False to silence this warning.",
UserWarning,
stacklevel=3,
)
return
Option B: wrap in try/except with re-raise
try:
fig.colorbar(...)
except ValueError as e:
raise ValueError(
f"Failed to draw colorbar: {e}. "
"If using LogNorm, ensure data contains no zeros or negative values, "
"or set colorbar=False."
) from e
Option A provides a better user experience by surfacing the problem before entering matplotlib internals.
Triage tier: Tier 2
render_images(norm=LogNorm())crashes when image contains zerosDescription
Calling
render_images("img", norm=LogNorm())on any image that contains zero-valued pixels crashes when drawing the colorbar. The same call withcolorbar=Falsesucceeds.colorbar="auto"is the default for norm-based image renders, so this crash is hit without any unusual arguments.The error is
ValueError: Invalid vmin or vmaxfrom 15+ frames inside matplotlib's colorbar constructor. No spatialdata-plot frame appears in the traceback, making the failure opaque to users. The actual problem —LogNormcannot represent zero — is straightforward to catch early.Environment
Minimal Reproducible Example
Actual Output
Full traceback is 15+ frames inside matplotlib's colorbar constructor (
lib/matplotlib/colorbar.py). No spatialdata-plot frame visible. No mention ofLogNorm, zeros, or how to work around the issue.Expected Output
Either:
UserWarning:"Data contains zeros or negative values; colorbar suppressed for LogNorm. Use colorbar=False to silence this warning."ValueErrorraised at the point spatialdata-plot requests the colorbar:"Cannot draw colorbar for LogNorm when data contains zeros or negative values. Set colorbar=False or clip the data."Fix Sketch
Option A (preferred): early guard in
_draw_colorbar(basic.py~line 1162)Before calling
fig.colorbar(...), check whether the norm is log-scale-sensitive and data contains non-positive values:Option B: wrap in try/except with re-raise
Option A provides a better user experience by surfacing the problem before entering matplotlib internals.
Triage tier: Tier 2