Skip to content

render_labels(contour_px=1) produces no visible outline (1×1 erosion is identity) #631

@timtreis

Description

@timtreis

render_labels(contour_px=1) produces no visible outline (silently empty)

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


Problem

Calling render_labels with contour_px=1 (the minimum accepted value, and a natural first try for a thin outline) produces no visible outline. The labels canvas is entirely zeroed out, so the result is indistinguishable from fill_alpha=0 — but no error or warning is raised.

The root cause is in _draw_labels at utils.py:1301–1302:

footprint = footprint_rectangle((contour_px, contour_px))
eroded = erosion(seg_arr, footprint)
contour = seg_arr - eroded

For contour_px=1, footprint_rectangle((1, 1)) creates a 1×1 erosion footprint. A 1×1 erosion is the identity transformation — the eroded array equals the input. contour = seg_arr - eroded then equals zero everywhere, producing a blank canvas.

contour_px=1 is accepted without error, is the minimum non-zero integer, and is a natural choice for users who want the thinnest possible outline.


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 Labels2DModel
import spatialdata_plot

labels_data = np.zeros((50, 50), dtype=np.int32)
labels_data[5:20, 5:20] = 1
sdata = sd.SpatialData(labels={"lbl": Labels2DModel.parse(labels_data, dims=["y", "x"])})

for px in [1, 2, 3]:
    fig, ax = plt.subplots()
    sdata.pl.render_labels("lbl", fill_alpha=0, outline_alpha=1, contour_px=px).pl.show(ax=ax)
    arr = ax.images[0].get_array()
    non_zero = np.count_nonzero(arr)
    print(f"contour_px={px}: non-zero pixels = {non_zero}")
    plt.close(fig)

Expected behaviour

contour_px=1: non-zero pixels = <some positive number>  (thin outline visible)
contour_px=2: non-zero pixels = <some positive number>
contour_px=3: non-zero pixels = <some positive number>

Actual behaviour

contour_px=1: non-zero pixels = 0   ← silent empty outline
contour_px=2: non-zero pixels = 112
contour_px=3: non-zero pixels = 160

No error or warning for contour_px=1.


Fix sketch

Either:

  1. Raise a ValueError when contour_px=1 is provided: "contour_px=1 produces no visible outline (1×1 erosion is identity). Use contour_px >= 2."
  2. Use a cross-shaped structuring element for contour_px=1 instead of footprint_rectangle((1,1)), which would produce a 3×3 cross-shaped footprint and yield a visible 1-pixel outline
  3. Add a minimum effective footprint: clamp the footprint size to at least (2, 2) or use footprint_rectangle((contour_px + 1, contour_px + 1))

At minimum, the current silent failure should be replaced with a UserWarning.


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