Skip to content

Commit 3bc8a0e

Browse files
timtreisclaude
andcommitted
Clean up outline_color integration and fix boundary rendering
- Simplify outline_color type for labels to ColorLike | None (no tuple) - Store as Color | None in LabelsRenderParams (consistent with shapes) - Add proper type annotation to _draw_labels - Simplify _map_color_seg boundary rendering: build clean RGBA image with outline_color on the eroded ring, transparent elsewhere - Remove duplicate matplotlib.colors import (mcolors → colors) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8e68afd commit 3bc8a0e

4 files changed

Lines changed: 15 additions & 34 deletions

File tree

src/spatialdata_plot/pl/basic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ def render_labels(
641641
na_color: ColorLike | None = "default",
642642
outline_alpha: float | int = 0.0,
643643
fill_alpha: float | int | None = None,
644-
outline_color: ColorLike | tuple[ColorLike] | None = None,
644+
outline_color: ColorLike | None = None,
645645
scale: str | None = None,
646646
colorbar: bool | str | None = "auto",
647647
colorbar_params: dict[str, object] | None = None,
@@ -696,7 +696,7 @@ def render_labels(
696696
outline_color : ColorLike | None
697697
Color of the outline of the labels. Can either be a named color ("red"), a hex representation
698698
("#000000") or a list of floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). If None, the outline
699-
color is set to "black".
699+
color defaults to "black".
700700
scale : str | None
701701
Influences the resolution of the rendering. Possibilities for setting this parameter:
702702
1) None (default). The image is rasterized to fit the canvas size. For multiscale images, the best scale

src/spatialdata_plot/pl/render.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1349,7 +1349,10 @@ def _render_labels(
13491349
assert color_source_vector is None
13501350

13511351
def _draw_labels(
1352-
seg_erosionpx: int | None, seg_boundaries: bool, alpha: float, outline_color=None
1352+
seg_erosionpx: int | None,
1353+
seg_boundaries: bool,
1354+
alpha: float,
1355+
outline_color: Color | None = None,
13531356
) -> matplotlib.image.AxesImage:
13541357
labels = _map_color_seg(
13551358
seg=label.values,

src/spatialdata_plot/pl/render_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class LabelsRenderParams:
285285
outline: bool = False
286286
palette: ListedColormap | list[str] | None = None
287287
outline_alpha: float = 1.0
288-
outline_color: ColorLike | tuple[ColorLike] | None = None
288+
outline_color: Color | None = None
289289
fill_alpha: float = 0.4
290290
transfunc: Callable[[float], float] | None = None
291291
scale: str | None = None

src/spatialdata_plot/pl/utils.py

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import dask
1414
import datashader as ds
1515
import matplotlib
16-
import matplotlib.colors as mcolors
1716
import matplotlib.patches as mpatches
1817
import matplotlib.path as mpath
1918
import matplotlib.pyplot as plt
@@ -1210,35 +1209,14 @@ def _map_color_seg(
12101209
)
12111210

12121211
if seg_boundaries:
1213-
if seg.shape[0] == 1:
1214-
seg = np.squeeze(seg, axis=0)
1215-
1216-
# Binary boundary mask
1217-
boundary_mask = seg.astype(bool)
1218-
1219-
# Ensure seg_im is float in 0-1 and has 3 channels
1220-
seg_float = seg_im.astype(float)
1221-
if seg_float.ndim == 2:
1222-
seg_float = np.stack([seg_float] * 3, axis=-1) # H x W x 3
1223-
1224-
# Add alpha channel from val_im (preserve original mask)
1225-
alpha_channel = (val_im > 0).astype(float)
1226-
seg_float = np.dstack((seg_float, alpha_channel)) # H x W x 4
1227-
1228-
# Convert outline_color to RGBA
1229-
if outline_color is None:
1230-
outline_rgba = (0, 0, 0, 1.0) # default black
1231-
elif isinstance(outline_color, str):
1232-
outline_rgba = mcolors.to_rgba(outline_color) # named color or hex string
1233-
else:
1234-
# assume it's your Color object
1235-
outline_rgba = mcolors.to_rgba(outline_color.get_hex_with_alpha())
1236-
1237-
# Apply outline color to boundary pixels, but keep original alpha from val_im
1238-
seg_float[boundary_mask, :3] = outline_rgba[:3] # RGB
1239-
seg_float[boundary_mask, 3] = alpha_channel[boundary_mask] * outline_rgba[3] # scale alpha
1212+
outline_rgba = colors.to_rgba(outline_color.get_hex_with_alpha() if outline_color is not None else "black")
12401213

1241-
return seg_float # H x W x 4, valid RGBA
1214+
# Build RGBA image: outline_color on the eroded ring, transparent elsewhere
1215+
outline_mask = val_im > 0
1216+
rgba = np.zeros((*val_im.shape, 4), dtype=float)
1217+
rgba[outline_mask, :3] = outline_rgba[:3]
1218+
rgba[outline_mask, 3] = outline_rgba[3]
1219+
return rgba
12421220

12431221
if len(val_im.shape) != len(seg_im.shape):
12441222
val_im = np.expand_dims((val_im > 0).astype(int), axis=-1)
@@ -2463,7 +2441,7 @@ def _validate_label_render_params(
24632441
na_color: ColorLike | None,
24642442
norm: Normalize | None,
24652443
outline_alpha: float | int,
2466-
outline_color: ColorLike | tuple[ColorLike] | None,
2444+
outline_color: ColorLike | None,
24672445
scale: str | None,
24682446
table_name: str | None,
24692447
table_layer: str | None,

0 commit comments

Comments
 (0)