From 9ea6b342f4e9bd4450a1cb76b9a7dd8506910a2f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 25 Jun 2026 17:26:08 +0200 Subject: [PATCH] =?UTF-8?q?refactor(napari):=20=E2=99=BB=EF=B8=8F=20use=20?= =?UTF-8?q?the=20glasbey=20package=20for=20label=20LUTs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the custom colorcet/CyclicLabelColormap helper; instead build the palette with glasbey.create_palette (the approach from napari's gallery) and hand the list straight to add_labels(colormap=...). Lightness biased up so colours read on the dark canvas. Toggle via the `glasbey` arg. Swap the napari extra dep colorcet -> glasbey. Co-Authored-By: Claude Opus 4.8 --- pyproject.toml | 4 +- src/patchworks/plugins/napari.py | 69 +++++++++----------------------- 2 files changed, 21 insertions(+), 52 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6e7f1f..4b90d0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,13 +63,13 @@ imaris = ["imaris-ims-file-reader"] # - ipykernel < 7: napari-console requires ipykernel < 7. # - lxml-html-clean: napari's notebook_display imports lxml.html.clean, split # into a separate package in lxml >= 5.2 (else ImportError on Viewer()). -# - colorcet: glasbey label LUTs for view_in_napari. +# - glasbey: distinct high-contrast label LUTs for view_in_napari. napari = [ "napari[all]>=0.5.5", "numpy<2.5", "ipykernel<7", "lxml-html-clean", - "colorcet", + "glasbey", ] # workflow runs the Snakemake pipeline (per-tile SLURM jobs across GPUs). workflow = ["snakemake>=8", "snakemake-executor-plugin-slurm"] diff --git a/src/patchworks/plugins/napari.py b/src/patchworks/plugins/napari.py index 6ad9795..f177034 100644 --- a/src/patchworks/plugins/napari.py +++ b/src/patchworks/plugins/napari.py @@ -191,48 +191,6 @@ def _resolve_labels( return arr.astype("int32") -def _label_colormap(name: str | None): - """Build a cyclic napari label colormap from a colorcet glasbey palette. - - Parameters - ---------- - name : str or None - A ``colorcet`` palette attribute, e.g. ``"glasbey_dark"`` (glasbey on a - dark background). ``None`` falls back to napari's default label colours. - - Returns - ------- - napari.utils.colormaps.CyclicLabelColormap or None - The colormap to pass to ``add_labels``, or ``None`` for the default. - """ - if not name: - return None - try: - import colorcet - except ImportError: - logger.warning( - "colorcet not installed; using napari's default label colours " - "(pip install colorcet, or it ships with patchworks[napari])." - ) - return None - - palette = getattr(colorcet, name, None) - if palette is None: - logger.warning("colorcet has no palette %r; using default colours.", name) - return None - - import numpy as np - from napari.utils.colormaps import CyclicLabelColormap - - def _hex_to_rgba(h: str): - h = h.lstrip("#") - return (int(h[0:2], 16) / 255, int(h[2:4], 16) / 255, - int(h[4:6], 16) / 255, 1.0) - - colors = np.array([_hex_to_rgba(c) for c in palette], dtype=float) - return CyclicLabelColormap(colors=colors) - - def view_in_napari( image: Union[da.Array, str, Path], labels: Union[da.Array, str, Path, None] = None, @@ -241,7 +199,7 @@ def view_in_napari( labels_component: str = "labels", image_name: str = "image", labels_name: str = "labels", - label_colormap: str | None = "glasbey_dark", + glasbey: bool = True, show: bool = True, **add_image_kwargs: Any, ): @@ -266,11 +224,10 @@ def view_in_napari( matching ``tile_process``'s ``output_component``). image_name, labels_name : str, optional Layer names shown in napari. - label_colormap : str or None, optional - ``colorcet`` palette for the label LUT; default ``"glasbey_dark"`` - (glasbey on a dark background — many distinct, high-contrast colours). - Any colorcet name works (e.g. ``"glasbey_light"``); ``None`` uses - napari's default label colours. Needs ``colorcet`` (ships with + glasbey : bool, optional + Colour the labels with a glasbey palette (many distinct, high-contrast + colours, tuned to read on the dark canvas) instead of napari's default. + Default ``True``. Needs the ``glasbey`` package (ships with ``patchworks[napari]``). show : bool, optional Start the napari event loop (blocking). Set ``False`` in scripts/tests @@ -299,8 +256,20 @@ def view_in_napari( **add_image_kwargs, ) - cmap = _label_colormap(label_colormap) - label_kwargs = {"colormap": cmap} if cmap is not None else {} + label_kwargs: dict[str, Any] = {} + if glasbey: + try: + import glasbey as _glasbey + + # bias lighter so colours read on napari's dark canvas + label_kwargs["colormap"] = _glasbey.create_palette( + 256, lightness_bounds=(40, 100) + ) + except ImportError: + logger.warning( + "glasbey not installed; using napari's default label colours " + "(pip install glasbey, or it ships with patchworks[napari])." + ) if labels is not None: lab = _resolve_labels(labels, labels_component)