Skip to content

NaN pixels in multi-channel images silently render black instead of na_color #628

@timtreis

Description

@timtreis

NaN pixels in multi-channel images silently render black instead of na_color

Environment: spatialdata-plot 0.3.4.dev (main, commit 5cfedc7), Python 3.13


Problem

NaN handling is inconsistent between single-channel and multi-channel image rendering:

  • Single-channel: NaN pixels are handled by the colormap (cmap.set_bad(na_color)), so they display as na_color.
  • Multi-channel: NaN pixels are silently converted to 0 by Normalize()(layer) at render.py:1425 before additive compositing. They appear black, regardless of any na_color setting. No warning is emitted.

NaN values are common in real spatial transcriptomics data: stitched images, padded crops, masked tissue regions, and segmentation-derived images all produce NaN pixels. The inconsistency between one-channel and multi-channel paths means the same data is rendered differently depending on whether the user selects a single channel or all channels.


Minimal reproducible example

import matplotlib; matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import dask; dask.config.set({"dataframe.query-planning": False})
import spatialdata as sd
from spatialdata.models import Image2DModel
import spatialdata_plot

H, W = 8, 8
gradient = np.linspace(0, 200, H*W).reshape(H, W).astype(np.float32)
nan_gradient = gradient.copy()
nan_gradient[0:3, 0:3] = np.nan

# Single-channel: NaN region shows na_color (handled by colormap set_bad)
img_1ch = Image2DModel.parse(np.stack([nan_gradient]),
                              dims=["c","y","x"], c_coords=["ch0"])

# Multi-channel: NaN region becomes black, no warning
img_2ch = Image2DModel.parse(np.stack([nan_gradient, gradient]),
                              dims=["c","y","x"], c_coords=["ch0","ch1"])

for name, sdata in [
    ("1ch", sd.SpatialData(images={"img": img_1ch})),
    ("2ch", sd.SpatialData(images={"img": img_2ch})),
]:
    fig, ax = plt.subplots()
    sdata.pl.render_images("img").pl.show(ax=ax)
    arr = np.array(ax.images[0].get_array())
    print(f"{name}: NaN_pixel_value={arr[0,0]}")
    plt.close(fig)

Expected behaviour

NaN pixels in multi-channel images should either:

  • Render with na_color (matching single-channel behaviour)
  • Or emit a UserWarning noting that NaN values will appear black

Actual behaviour

1ch: NaN_pixel_value=nan        ← correct: propagates, colormap handles na_color
2ch: NaN_pixel_value=[0. 0. 0.] ← incorrect: silently black, no warning

No warning is raised in either case.


Fix sketch

In the multi-channel compositing path (render.py:1413–1425):

  1. Before normalizing, record a combined NaN mask: nan_mask = np.isnan(layer).any(axis=0)
  2. After compositing all channels, set pixels where nan_mask is True to na_color
  3. Emit logger.warning(f"Channel data contains NaN pixels; they will appear as na_color in the composite.") if any NaN was found

This matches the existing single-channel behaviour where cmap.set_bad(na_color) handles NaN pixels.


Triage tier: Tier 3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions