From 480eb5afe60fba34eaff7442678b343c4393ef18 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Mon, 6 Apr 2026 12:25:38 +0200 Subject: [PATCH 1/3] Fix docstrings and remove dead code across public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Systematic audit of all public method docstrings against their implementations, fixing mismatches and removing dead/broken code. Docstring fixes: - Fix `norm` type/default in render_shapes, render_points (was `bool | Normalize, default False`, now `Normalize | None`) - Fix `palette` type across all render methods (add `dict[str, str]`) - Remove false claim that palette defaults to lightgray when groups given - Fix `cmap` description: continuous only, not discrete (render_shapes, render_points) - Fix `scale` description in render_shapes: applies to polygons too - Fix `method` description: "or" not "and", add >10k threshold - Fix `show()` return type: `Axes | list[Axes] | None`, not `sd.SpatialData` - Document all 13 missing `show()` parameters - Fix `render_labels` return type (was `None`) - Fix `render_points` return saying "shapes" (copy-paste) - Fix `render_labels` broadcasting example referencing render_images - Add Notes section to render_images: RGB auto-detection, compositing, cmap broadcast - Add type annotations to colorbar/colorbar_params across all methods - Fix `make_palette_from_data` color column lookup description - Fix OutlineParams docstring (was "Cmap params") - Fix typo "precendence" → "precedence" Dead code removal: - Remove `share_extent` from `show()` (accepted but never used) - Remove `na_color` from `render_images()` (no use case for images) - Remove `**kwargs` from `render_images()` (broken: crashed or was silently dropped) - Remove dead `transfunc` from `LabelsRenderParams` (stored but never read) - Remove vestigial `LabelsRenderParams.outline` field - Remove vestigial `ShapesRenderParams.contour_px` field New behavior: - Warn when user requests `colorbar=True` for 5+ channel images Related: #463, #582 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/spatialdata_plot/pl/_palette.py | 5 +- src/spatialdata_plot/pl/basic.py | 214 ++++++++++++----------- src/spatialdata_plot/pl/render.py | 6 + src/spatialdata_plot/pl/render_params.py | 5 +- src/spatialdata_plot/pl/utils.py | 7 - 5 files changed, 125 insertions(+), 112 deletions(-) diff --git a/src/spatialdata_plot/pl/_palette.py b/src/spatialdata_plot/pl/_palette.py index 9f700181..05e2617b 100644 --- a/src/spatialdata_plot/pl/_palette.py +++ b/src/spatialdata_plot/pl/_palette.py @@ -594,8 +594,9 @@ def make_palette_from_data( element Name of a shapes or points element in *sdata*. color - Column name containing categorical labels (in the element itself - for points, or in the linked table for shapes/labels). + Column name containing categorical labels. The column is first + looked up directly on the element (both for shapes and points); + if not found there, it falls back to the linked AnnData table. palette Source colours. Accepts the same values as :func:`make_palette` (*None*, a list, a named palette, or a diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index 0a99f395..0b9a4a9c 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -220,10 +220,10 @@ def render_shapes( them. By default, non-matching elements are hidden. To show non-matching elements, set ``na_color`` explicitly. If element is None, broadcasting behaviour is attempted (use the same values for all elements). - palette : list[str] | str | None - Palette for discrete annotations. List of valid color names that should be used for the categories. Must - match the number of groups. If element is None, broadcasting behaviour is attempted (use the same values for - all elements). If groups is provided but not palette, palette is set to default "lightgray". + palette : dict[str, str] | list[str] | str | None + Palette for discrete annotations. Can be a dictionary mapping category names to colors, a list of valid + color names (must match the number of groups), a single named palette or matplotlib colormap name, or + ``None``. If element is None, broadcasting behaviour is attempted (use the same values for all elements). na_color : ColorLike | None, default "default" (gets set to "lightgray") Color to be used for NA values, if present. Can either be a named color ("red"), a hex representation ("#000000ff") or a list of floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). When None, the values @@ -245,17 +245,18 @@ def render_shapes( with outline_alpha=1.0 if outline_color does not imply an alpha. For two outlines, alpha values can be passed in a tuple of length 2. cmap : Colormap | str | None, optional - Colormap for discrete or continuous annotations using 'color', see :class:`matplotlib.colors.Colormap`. - norm : bool | Normalize, default False - Colormap normalization for continuous annotations. + Colormap for continuous annotations using 'color', see :class:`matplotlib.colors.Colormap`. + For categorical data, use ``palette`` instead. + norm : Normalize | None, optional + Colormap normalization for continuous annotations, see :class:`matplotlib.colors.Normalize`. scale : float | int, default 1.0 - Value to scale circles, if present. + Value to scale shapes (circles and polygons). method : str | None, optional - Whether to use 'matplotlib' and 'datashader'. When None, the method is - chosen based on the size of the data. - colorbar : - Whether to request a colorbar for continuous colors. Use "auto" (default) for automatic selection. - colorbar_params : + Whether to use ``'matplotlib'`` or ``'datashader'``. When ``None``, the method is + chosen automatically based on the size of the data (datashader for >10 000 elements). + colorbar : bool | str | None, default "auto" + Whether to request a colorbar for continuous colors. Use ``"auto"`` (default) for automatic selection. + colorbar_params : dict[str, object] | None Parameters forwarded to Matplotlib's colorbar alongside layout hints such as ``loc``, ``width``, ``pad``, and ``label``. table_name: str | None @@ -288,14 +289,13 @@ def render_shapes( ----- - Empty geometries will be removed at the time of plotting. - An `outline_width` of 0.0 leads to no border being plotted. - - When passing a color-like to 'color', this has precendence over the potential existence as a column name. + - When passing a color-like to 'color', this has precedence over the potential existence as a column name. Returns ------- sd.SpatialData - The modified SpatialData object with the rendered shapes. + A copy of the SpatialData object with the rendering parameters stored in its plotting tree. """ - # TODO add Normalize object in tutorial notebook and point to that notebook here if "vmin" in kwargs or "vmax" in kwargs: logger.warning("`vmin` and `vmax` are deprecated. Pass matplotlib `Normalize` object to norm instead.") params_dict = _validate_shape_render_params( @@ -396,10 +396,10 @@ def render_points( element : str | None, optional The name of the points element to render. If `None`, all points elements in the `SpatialData` object will be used. - color : str | None, optional + color : ColorLike | None, optional Can either be color-like (name of a color as string, e.g. "red", hex representation, e.g. "#000000" or "#000000ff", or an RGB(A) array as a tuple or list containing 3-4 floats within [0, 1]. If an alpha value is - indicated, the value of `fill_alpha` takes precedence if given) or a string representing a key in + indicated, the value of ``alpha`` takes precedence if given) or a string representing a key in :attr:`sdata.table.obs`. The latter can be used to color by categorical or continuous variables. If `element` is `None`, if possible the color will be broadcasted to all elements. For this, the table in which the color key is found must annotate the respective element (region must be set to the specific element). If @@ -412,27 +412,28 @@ def render_points( them. By default, non-matching points are filtered out entirely. To show non-matching points, set ``na_color`` explicitly. If element is None, broadcasting behaviour is attempted (use the same values for all elements). - palette : list[str] | str | None - Palette for discrete annotations. List of valid color names that should be used for the categories. Must - match the number of groups. If `element` is `None`, broadcasting behaviour is attempted (use the same values - for all elements). If groups is provided but not palette, palette is set to default "lightgray". + palette : dict[str, str] | list[str] | str | None + Palette for discrete annotations. Can be a dictionary mapping category names to colors, a list of valid + color names (must match the number of groups), a single named palette or matplotlib colormap name, or + ``None``. If `element` is `None`, broadcasting behaviour is attempted (use the same values for all + elements). na_color : ColorLike | None, default "default" (gets set to "lightgray") Color to be used for NA values, if present. Can either be a named color ("red"), a hex representation ("#000000ff") or a list of floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). When None, the values won't be shown. cmap : Colormap | str | None, optional - Colormap for discrete or continuous annotations using 'color', see :class:`matplotlib.colors.Colormap`. If + Colormap for continuous annotations using 'color', see :class:`matplotlib.colors.Colormap`. If no palette is given and `color` refers to a categorical, the colors are sampled from this colormap. - norm : bool | Normalize, default False - Colormap normalization for continuous annotations. + norm : Normalize | None, optional + Colormap normalization for continuous annotations, see :class:`matplotlib.colors.Normalize`. size : float | int, default 1.0 - Size of the points + Size of the points. method : str | None, optional - Whether to use 'matplotlib' and 'datashader'. When None, the method is - chosen based on the size of the data. - colorbar : - Whether to request a colorbar for continuous colors. Use "auto" (default) for automatic selection. - colorbar_params : + Whether to use ``'matplotlib'`` or ``'datashader'``. When ``None``, the method is + chosen automatically based on the size of the data (datashader for >10 000 elements). + colorbar : bool | str | None, default "auto" + Whether to request a colorbar for continuous colors. Use ``"auto"`` (default) for automatic selection. + colorbar_params : dict[str, object] | None Parameters forwarded to Matplotlib's colorbar alongside layout hints such as ``loc``, ``width``, ``pad``, and ``label``. table_name: str | None @@ -458,9 +459,8 @@ def render_points( Returns ------- sd.SpatialData - The modified SpatialData object with the rendered shapes. + A copy of the SpatialData object with the rendering parameters stored in its plotting tree. """ - # TODO add Normalize object in tutorial notebook and point to that notebook here if "vmin" in kwargs or "vmax" in kwargs: logger.warning("`vmin` and `vmax` are deprecated. Pass matplotlib `Normalize` object to norm instead.") params_dict = _validate_points_render_params( @@ -528,7 +528,6 @@ def render_images( channel: list[str] | list[int] | str | int | None = None, cmap: list[Colormap | str] | Colormap | str | None = None, norm: list[Normalize] | Normalize | None = None, - na_color: ColorLike | None = "default", palette: list[str] | str | None = None, alpha: float | int = 1.0, scale: str | None = None, @@ -536,7 +535,6 @@ def render_images( transfunc: Callable[[np.ndarray], np.ndarray] | list[Callable[[np.ndarray], np.ndarray]] | None = None, colorbar: bool | str | None = "auto", colorbar_params: dict[str, object] | None = None, - **kwargs: Any, ) -> sd.SpatialData: """ Render image elements in SpatialData. @@ -563,12 +561,9 @@ def render_images( A single :class:`~matplotlib.colors.Normalize` applies to all channels. A list of :class:`~matplotlib.colors.Normalize` objects applies per-channel (length must match the number of channels). - na_color : ColorLike | None, default "default" (gets set to "lightgray") - Color to be used for NAs values, if present. Can either be a named color ("red"), a hex representation - ("#000000ff") or a list of floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). When None, the values - won't be shown. palette : list[str] | str | None - Palette to color images. The number of palettes should be equal to the number of channels. + Palette to color images. Can be a single palette name (broadcast to all channels) or a list + matching the number of channels. alpha : float | int, default 1.0 Alpha value for the images. Must be a numeric between 0 and 1. scale : str | None @@ -608,31 +603,35 @@ def render_images( When combined with ``grayscale=True``, ``transfunc`` runs first and ``grayscale`` is applied to the result. - colorbar : - Whether to request a colorbar for continuous colors. Use "auto" (default) for automatic selection. - colorbar_params : + colorbar : bool | str | None, default "auto" + Whether to request a colorbar for continuous colors. Use ``"auto"`` (default) for automatic selection. + colorbar_params : dict[str, object] | None Parameters forwarded to Matplotlib's colorbar alongside layout hints such as ``loc``, ``width``, ``pad``, and ``label``. - kwargs - Additional arguments to be passed to cmap, norm, and other rendering functions. + + Notes + ----- + - **RGB(A) auto-detection**: when the channel names are exactly ``{r, g, b}`` or ``{r, g, b, a}`` + (case-insensitive) and no explicit ``cmap`` or ``palette`` is given, the image is rendered as + true-color RGB(A) without colormaps. + - **Multi-channel compositing**: when multiple channels are rendered with per-channel colormaps, + they are additively blended. Colormaps that go from a color to white (rather than to transparent) + will cause upper layers to occlude lower ones. + - A single ``cmap`` is automatically broadcast to all selected channels. Returns ------- sd.SpatialData - The SpatialData object with the rendered images. + A copy of the SpatialData object with the rendering parameters stored in its plotting tree. """ - # TODO add Normalize object in tutorial notebook and point to that notebook here if grayscale and palette is not None: raise ValueError("Cannot combine grayscale=True with palette.") - if "vmin" in kwargs or "vmax" in kwargs: - logger.warning("`vmin` and `vmax` are deprecated. Pass matplotlib `Normalize` object to norm instead.") params_dict = _validate_image_render_params( self._sdata, element=element, channel=channel, alpha=alpha, palette=palette, - na_color=na_color, cmap=cmap, norm=norm, scale=scale, @@ -668,7 +667,6 @@ def render_images( _prepare_cmap_norm( cmap=c, norm=n, - na_color=param_values["na_color"], ) for c, n in zip(effective_cmap, norms, strict=True) ] @@ -679,8 +677,6 @@ def render_images( cmap_params = _prepare_cmap_norm( cmap=scalar_cmap, norm=norm_scalar, - na_color=param_values["na_color"], - **kwargs, ) sdata.plotting_tree[f"{n_steps + 1}_render_images"] = ImageRenderParams( element=element, @@ -728,7 +724,7 @@ def render_labels( In case of no elements specified, "broadcasting" of parameters is applied. This means that for any particular SpatialElement, we validate whether a given parameter is valid. If not valid for a particular SpatialElement the specific parameter for that particular SpatialElement will be ignored. If you want to set specific parameters - for specific elements please chain the render functions: `pl.render_images(...).pl.render_images(...).pl.show()` + for specific elements please chain the render functions: `pl.render_labels(...).pl.render_labels(...).pl.show()` . Parameters @@ -746,10 +742,10 @@ def render_labels( groups : list[str] | str | None When using `color` and the key represents discrete labels, `groups` can be used to show only a subset of them. By default, non-matching labels are hidden. To show non-matching labels, set ``na_color`` explicitly. - palette : list[str] | str | None - Palette for discrete annotations. List of valid color names that should be used for the categories. Must - match the number of groups. The list can contain multiple palettes (one per group) to be visualized. If - groups is provided but not palette, palette is set to default "lightgray". + palette : dict[str, str] | list[str] | str | None + Palette for discrete annotations. Can be a dictionary mapping category names to colors, a list of valid + color names (must match the number of groups), a single named palette or matplotlib colormap name, or + ``None``. contour_px : int, default 3 Draw contour of specified width for each segment. If `None`, fills entire segment, see: func:`skimage.morphology.erosion`. @@ -779,9 +775,9 @@ def render_labels( (exception: a dpi is specified in `show()`. Then the image is rasterized to fit the canvas and dpi). 3) "full": render the full image without rasterization. In the case of a multiscale image, the scale with the highest resolution is selected. This can lead to long computing times for large images! - colorbar : - Whether to request a colorbar for continuous colors. Use "auto" (default) for automatic selection. - colorbar_params : + colorbar : bool | str | None, default "auto" + Whether to request a colorbar for continuous colors. Use ``"auto"`` (default) for automatic selection. + colorbar_params : dict[str, object] | None Parameters forwarded to Matplotlib's colorbar alongside layout hints such as ``loc``, ``width``, ``pad``, and ``label``. table_name: str | None @@ -798,9 +794,9 @@ def render_labels( Returns ------- - None + sd.SpatialData + A copy of the SpatialData object with the rendering parameters stored in its plotting tree. """ - # TODO add Normalize object in tutorial notebook and point to that notebook here if "vmin" in kwargs or "vmax" in kwargs: logger.warning("`vmin` and `vmax` are deprecated. Pass matplotlib `Normalize` object to norm instead.") params_dict = _validate_label_render_params( @@ -845,7 +841,6 @@ def render_labels( outline_alpha=param_values["outline_alpha"], outline_color=param_values["outline_color"], fill_alpha=param_values["fill_alpha"], - transfunc=kwargs.get("transfunc"), scale=param_values["scale"], table_name=param_values["table_name"], table_layer=param_values["table_layer"], @@ -875,54 +870,76 @@ def show( dpi: int | None = None, fig: Figure | None = None, title: list[str] | str | None = None, - share_extent: bool = True, pad_extent: int | float = 0, ax: list[Axes] | Axes | None = None, return_ax: bool = False, save: str | Path | None = None, show: bool | None = None, - ) -> sd.SpatialData: + ) -> Axes | list[Axes] | None: """ - Plot the images in the SpatialData object. + Execute the plotting tree and display the final figure. Parameters ---------- - coordinate_systems : - Name(s) of the coordinate system(s) to be plotted. If None, all coordinate systems are plotted. - If a coordinate system doesn't contain any relevant elements (as specified in the render_* calls), - it is automatically not plotted. - figsize : - Size of the figure (width, height) in inches. The size of the actual canvas may deviate from this, - depending on the dpi! In matplotlib, the actual figure size (in pixels) is dpi * figsize. - If None, the default of matlotlib is used (6.4, 4.8) - dpi : - Resolution of the plot in dots per inch (as in matplotlib). - If None, the default of matplotlib is used (100.0). - ax : - Matplotlib axes object to plot on. If None, a new figure is created. - Works only if there is one image in the SpatialData object. - ncols : - Number of columns in the figure. Default is 4. - return_ax : - Whether to return the axes object created. False by default. - show : - Whether to call ``plt.show()`` at the end. If ``None`` (default), the plot is shown - automatically when running in non-interactive mode (scripts) and suppressed in - interactive sessions (e.g. Jupyter). Set to ``False`` to prevent ``plt.show()`` - from being called, which is useful when you want to save or further modify the - figure after calling this method. - colorbar : - Global switch to enable/disable all colorbars. Per-layer settings are ignored when this is False. - colorbar_params : + coordinate_systems : list[str] | str | None + Name(s) of the coordinate system(s) to be plotted. If ``None``, all coordinate systems that contain + relevant elements (as specified in the ``render_*`` calls) are plotted automatically. + legend_fontsize : int | float | str | None + Font size for the legend text. Accepts numeric values or matplotlib font size strings + (e.g. ``"small"``, ``"large"``). + legend_fontweight : int | str, default "bold" + Font weight for the legend text (e.g. ``"bold"``, ``"normal"``). + legend_loc : str | None, default "right margin" + Location of the legend. Standard matplotlib legend locations (e.g. ``"upper left"``) or + ``"right margin"``, ``"left margin"``, ``"top margin"``, ``"bottom margin"`` to place + the legend outside the axes. + legend_fontoutline : int | None + Stroke width for a white outline around legend text, improving readability on busy plots. + na_in_legend : bool, default True + Whether to include NA / unmapped categories in the legend. + colorbar : bool, default True + Global switch to enable/disable all colorbars. Per-layer settings are ignored when this is ``False``. + colorbar_params : dict[str, object] | None Global overrides passed to colorbars for all axes. Accepts the same keys as per-layer ``colorbar_params`` (e.g., ``loc``, ``width``, ``pad``, ``label``). - title : - The title of the plot. If not provided the plot will have the name of the coordinate system as title. + wspace : float | None + Horizontal spacing between panels (passed to :class:`matplotlib.gridspec.GridSpec`). + hspace : float, default 0.25 + Vertical spacing between panels (passed to :class:`matplotlib.gridspec.GridSpec`). + ncols : int, default 4 + Number of columns in the multi-panel grid. + frameon : bool | None + Whether to draw the axes frame. If ``None``, the frame is hidden automatically for multi-panel plots. + figsize : tuple[float, float] | None + Size of the figure ``(width, height)`` in inches. The actual canvas size in pixels is + ``dpi * figsize``. If ``None``, the matplotlib default is used ``(6.4, 4.8)``. + dpi : int | None + Resolution of the plot in dots per inch. If ``None``, the matplotlib default is used ``(100.0)``. + fig : Figure | None + .. deprecated:: + Pass axes created from your figure via ``ax`` instead. + title : list[str] | str | None + Title(s) for the plot. A single string is applied to all panels; a list must match the number + of coordinate systems. If ``None``, each panel is titled with its coordinate system name. + pad_extent : int | float, default 0 + Padding added around the computed spatial extent on all sides. + ax : list[Axes] | Axes | None + Pre-existing matplotlib axes to plot on. Can be a single :class:`~matplotlib.axes.Axes` or a list + matching the number of coordinate systems. If ``None``, a new figure and axes are created. + return_ax : bool, default False + Whether to return the axes object(s) instead of ``None``. + save : str | Path | None + Path to save the figure to. If ``None``, the figure is not saved. + show : bool | None + Whether to call ``plt.show()`` at the end. If ``None`` (default), the plot is shown + automatically when running in non-interactive mode (scripts) and suppressed in + interactive sessions (e.g. Jupyter). When ``ax`` is provided by the user, defaults + to ``False`` to allow further modifications. Returns ------- - sd.SpatialData - A SpatialData object. + Axes | list[Axes] | None + The axes object(s) if ``return_ax=True``, otherwise ``None``. """ _log_context.set("show") # copy the SpatialData object so we don't modify the original @@ -950,7 +967,6 @@ def show( dpi, fig, title, - share_extent, pad_extent, ax, return_ax, diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index ca67d026..5a3f6bd9 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -1305,6 +1305,12 @@ def _render_images( # 2) Image has any number of channels but 1 else: + if n_channels >= 5 and render_params.colorbar is True: + logger.warning( + "Colorbars are not supported for multi-channel images with 5+ channels. " + "To view individual channel scales, render channels separately with " + "`channel=` and `colorbar=True`." + ) layers = {} for ch_idx, ch in enumerate(channels): layers[ch] = img.sel(c=ch).copy(deep=True).squeeze() diff --git a/src/spatialdata_plot/pl/render_params.py b/src/spatialdata_plot/pl/render_params.py index dffb97dd..4cbcae86 100644 --- a/src/spatialdata_plot/pl/render_params.py +++ b/src/spatialdata_plot/pl/render_params.py @@ -168,7 +168,7 @@ class FigParams: @dataclass class OutlineParams: - """Cmap params.""" + """Outline params.""" outer_outline_color: Color | None = None outer_outline_linewidth: float = 1.5 @@ -222,7 +222,6 @@ class ShapesRenderParams: color: Color | None = None col_for_color: str | None = None groups: str | list[str] | None = None - contour_px: int | None = None palette: ListedColormap | dict[str, str] | list[str] | None = None outline_alpha: tuple[float, float] = (1.0, 1.0) fill_alpha: float = 0.3 @@ -287,12 +286,10 @@ class LabelsRenderParams: col_for_color: str | None = None groups: str | list[str] | None = None contour_px: int | None = None - outline: bool = False palette: ListedColormap | dict[str, str] | list[str] | None = None outline_alpha: float = 1.0 outline_color: Color | None = None fill_alpha: float = 0.4 - transfunc: Callable[[float], float] | None = None scale: str | None = None table_name: str | None = None table_layer: str | None = None diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 7746c8af..60b7a787 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -2128,7 +2128,6 @@ def _validate_show_parameters( dpi: int | None, fig: Figure | None, title: list[str] | str | None, - share_extent: bool, pad_extent: int | float, ax: list[Axes] | Axes | None, return_ax: bool, @@ -2208,9 +2207,6 @@ def _validate_show_parameters( if title is not None and not isinstance(title, list | str): raise TypeError("Parameter 'title' must be a string or a list of strings.") - if not isinstance(share_extent, bool): - raise TypeError("Parameter 'share_extent' must be a boolean.") - if not isinstance(pad_extent, int | float): raise TypeError("Parameter 'pad_extent' must be numeric.") @@ -2870,7 +2866,6 @@ def _validate_image_render_params( channel: list[str] | list[int] | str | int | None, alpha: float | int | None, palette: list[str] | str | None, - na_color: ColorLike | None, cmap: list[Colormap | str] | Colormap | str | None, norm: list[Normalize] | Normalize | None, scale: str | None, @@ -2883,7 +2878,6 @@ def _validate_image_render_params( "channel": channel, "alpha": alpha, "palette": palette, - "na_color": na_color, "cmap": cmap, "norm": norm, "scale": scale, @@ -2941,7 +2935,6 @@ def _validate_image_render_params( f"({', '.join(str(c) for c in channels_to_use)})." ) element_params[el]["palette"] = palette - element_params[el]["na_color"] = param_dict["na_color"] cmap = param_dict["cmap"] if cmap is not None: From 90f86836b39996b695a5185d952eb2dc6b1a0f81 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Mon, 6 Apr 2026 12:31:47 +0200 Subject: [PATCH 2/3] Improve multi-channel colorbar info and fix dataclass docstrings - Change 5+ channel colorbar message to logger.info on auto mode only (explicit colorbar=True is not blocked) - Fix double periods in dataclass docstrings Co-Authored-By: Claude Opus 4.6 (1M context) --- src/spatialdata_plot/pl/render.py | 10 +++++----- src/spatialdata_plot/pl/render_params.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 5a3f6bd9..8cfb72e9 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -1305,11 +1305,11 @@ def _render_images( # 2) Image has any number of channels but 1 else: - if n_channels >= 5 and render_params.colorbar is True: - logger.warning( - "Colorbars are not supported for multi-channel images with 5+ channels. " - "To view individual channel scales, render channels separately with " - "`channel=` and `colorbar=True`." + if n_channels >= 5 and render_params.colorbar == "auto": + logger.info( + "Colorbars are not shown by default for images with 5+ channels. " + "To show individual channel colorbars, render channels separately " + "with `channel=` and `colorbar=True`." ) layers = {} for ch_idx, ch in enumerate(channels): diff --git a/src/spatialdata_plot/pl/render_params.py b/src/spatialdata_plot/pl/render_params.py index 4cbcae86..7cbc68f5 100644 --- a/src/spatialdata_plot/pl/render_params.py +++ b/src/spatialdata_plot/pl/render_params.py @@ -214,7 +214,7 @@ class ScalebarParams: @dataclass class ShapesRenderParams: - """Shapes render parameters..""" + """Shapes render parameters.""" cmap_params: CmapParams outline_params: OutlineParams @@ -239,7 +239,7 @@ class ShapesRenderParams: @dataclass class PointsRenderParams: - """Points render parameters..""" + """Points render parameters.""" cmap_params: CmapParams element: str @@ -261,7 +261,7 @@ class PointsRenderParams: @dataclass class ImageRenderParams: - """Image render parameters..""" + """Image render parameters.""" cmap_params: list[CmapParams] | CmapParams element: str @@ -278,7 +278,7 @@ class ImageRenderParams: @dataclass class LabelsRenderParams: - """Labels render parameters..""" + """Labels render parameters.""" cmap_params: CmapParams element: str From 964ae9a93652b9d47608174db90737e42ee4c66b Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Mon, 6 Apr 2026 12:33:04 +0200 Subject: [PATCH 3/3] Fix review findings: labels docstring consistency, dead na_color path - Align render_labels cmap/norm docstrings with shapes/points style - Remove misleading kwargs docstring entry from render_labels - Guard na_color processing in _type_check_params to skip images Co-Authored-By: Claude Opus 4.6 (1M context) --- src/spatialdata_plot/pl/basic.py | 9 ++++----- src/spatialdata_plot/pl/utils.py | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index 0b9a4a9c..be6cd741 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -749,9 +749,10 @@ def render_labels( contour_px : int, default 3 Draw contour of specified width for each segment. If `None`, fills entire segment, see: func:`skimage.morphology.erosion`. - cmap : Colormap | str | None - Colormap for continuous annotations, see :class:`matplotlib.colors.Colormap`. - norm : Normalize | None + cmap : Colormap | str | None, optional + Colormap for continuous annotations using 'color', see :class:`matplotlib.colors.Colormap`. + For categorical data, use ``palette`` instead. + norm : Normalize | None, optional Colormap normalization for continuous annotations, see :class:`matplotlib.colors.Normalize`. na_color : ColorLike | None, default "default" (gets set to "lightgray") Color to be used for NAs values, if present. Can either be a named color ("red"), a hex representation @@ -789,8 +790,6 @@ def render_labels( Column name in :attr:`sdata.table.var` to use for looking up ``color``. Use this when ``var_names`` are e.g. ENSEMBL IDs but you want to refer to genes by their symbols stored in another column of ``var``. Mimics scanpy's ``gene_symbols`` parameter. - kwargs - Additional arguments to be passed to cmap and norm. Returns ------- diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 60b7a787..cb1513cf 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -2436,8 +2436,9 @@ def _type_check_params(param_dict: dict[str, Any], element_type: str) -> dict[st else: raise TypeError("Parameter 'cmap' must be a string, a Colormap, or a list of these types.") - # validation happens within Color constructor - param_dict["na_color"] = Color(param_dict.get("na_color")) + # validation happens within Color constructor (images don't use na_color) + if "na_color" in param_dict: + param_dict["na_color"] = Color(param_dict.get("na_color")) norm = param_dict.get("norm") if norm is not None: