From 098f68a27c658def59eee52c562915c733f2c622 Mon Sep 17 00:00:00 2001 From: xuefei-wang Date: Thu, 6 Aug 2026 15:25:32 -0700 Subject: [PATCH 1/2] docs(tutorial): run the whole pipeline at build, and fail loudly on an empty mask The tutorial is the only end-to-end check that the pieces of this package work together, so every step should actually execute: raw image -> cellSAM segmentation -> cell-type prediction -> visualization. Two things had drifted from that: - The napari screenshots were replaced with prose ("Static documentation builds do not execute or embed GUI screenshots"), so the rendered docs no longer showed the segmentation or the cell-type layers. Restore the two hide-cell `nim.screenshot` cells and their `` embeds, matching the tutorial as of 616d4b5. `docs/_static/_generated` is already gitignored, so the images stay build artifacts. - A user reported "the cell type prediction result appears empty". The only post-segmentation check verified the mask *shape*, and a degenerate all-background mask has the correct shape, so it passed; predict() then returned [] and the DataFrame rendered empty with nothing to explain it. Turn the shape check into a hard assert, add a cell-count assert, and report the number of cells detected, so a failed segmentation stops the tutorial at the point of failure. Supersedes #54, which took the opposite approach (defaulting to the archive's precomputed mask); per review, the computation must not be replaced. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FwCG7rydRT4EWWhuerxuuT --- docs/site/tutorial.md | 61 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/site/tutorial.md b/docs/site/tutorial.md index dfa7a19..0c92c01 100644 --- a/docs/site/tutorial.md +++ b/docs/site/tutorial.md @@ -208,10 +208,20 @@ mask = cellsam_pipeline( ) ``` +Before moving on, sanity-check the segmentation. The mask must have the same +`H, W` dimensions as the input image **and** actually contain cells: a +degenerate, all-background mask has the correct shape but silently produces an +*empty* cell-type prediction downstream, so check both. + ```{code-cell} ipython3 -# Sanity check: the segmentation mask should have the same H, W dimensions as -# the input image -mask.shape == img.shape[1:] +assert mask.shape == img.shape[1:], "mask shape does not match the image H, W" +assert mask.max() > 0, ( + "segmentation produced no cells - try a different `membrane_channel` " + "(see the note above) and confirm cellSAM is running as expected" +) + +# Number of cells detected +int(mask.max()) ``` Let's perform a bit of post-processing to ensure that the segmentation mask @@ -247,8 +257,26 @@ mask_lyr = nim.add_labels(mask, name="CellSAM segmentation") mask_lyr.contour = 3 # Relatively thick borders for static viz ``` -The image and segmentation layers appear directly in the interactive Napari -viewer. Static documentation builds do not execute or embed GUI screenshots. +```{code-cell} ipython3 +:tags: [hide-cell] + +# For static rendering - can safely be ignored if running notebook interactively +from pathlib import Path + +screenshot_path = Path("../_static/_generated") +screenshot_path.mkdir(parents=True, exist_ok=True) +nim.screenshot( + path=screenshot_path / "napari_img_and_segmentation.png", + canvas_only=False, +); +``` + +
+ Napari window of multiplexed image and computed segmentation mask +
### Cell-type inference with `deepcell-types` @@ -401,9 +429,26 @@ for k, l in labels_by_celltype.items(): ) ``` -When running interactively, the new label layers appear directly in the Napari -viewer and can be toggled independently. Static documentation builds do not -execute or embed GUI screenshots. +```{code-cell} ipython3 +:tags: [hide-cell] + +# For static rendering - can safely be ignored if running notebook interactively +from pathlib import Path + +screenshot_path = Path("../_static/_generated") +screenshot_path.mkdir(parents=True, exist_ok=True) +nim.screenshot( + path=screenshot_path / "napari_celltype_layers.png", + canvas_only=False, +); +``` + +
+ Napari window of multiplexed image with celltype predictions +
[hubmap-data-portal]: https://portal.hubmapconsortium.org/search/datasets [zarr]: https://zarr.readthedocs.io/en/stable/ From 47160ac29d8758e7a483fcdee78f96837734cc84 Mon Sep 17 00:00:00 2001 From: xuefei-wang Date: Thu, 6 Aug 2026 22:08:59 -0700 Subject: [PATCH 2/2] docs(tutorial): count distinct labels, not the max label ID cellSAM's segmentation labels are not contiguous, so `int(mask.max())` under "Number of cells detected" reported the largest label ID rather than the cell count. The rendered page then contradicted itself: the guard cell showed 4644 while "Total number of cells" showed 1743 after relabel_sequential -- and 1743 is what predict() returns. Count distinct nonzero labels instead. Verified with a full docs build running real cellSAM segmentation (not the archive's precomputed mask, whose labels are already contiguous, which is why this only surfaces on a from-scratch run): guard cell, cell-type total, and the prediction DataFrame now all report 1743. The empty-mask assertion is unchanged. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BHtuHjUExCH8bvCowX7GJ3 --- docs/site/tutorial.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/site/tutorial.md b/docs/site/tutorial.md index 0c92c01..cd82b5e 100644 --- a/docs/site/tutorial.md +++ b/docs/site/tutorial.md @@ -220,8 +220,9 @@ assert mask.max() > 0, ( "(see the note above) and confirm cellSAM is running as expected" ) -# Number of cells detected -int(mask.max()) +# Number of cells detected. cellSAM's label IDs are not necessarily +# contiguous, so count the distinct labels rather than taking the max. +int((np.unique(mask) > 0).sum()) ``` Let's perform a bit of post-processing to ensure that the segmentation mask