agentsclimarketplace

Bio spatial transcriptomics image analysis skills image analysis

Skill bg-szy/TOP-SKILLS/skills/awesome-skills/bio-spatial-transcriptomics-image-analysis__skills-image-analysis

Segments cells/nuclei and extracts image features from imaging spatial transcriptomics (Xenium, MERFISH/MERSCOPE, CosMx) and H&E/IF tissue images using Cellpose, StarDist, Baysor, and Squidpy. Use when choosing a segmentation strategy (DAPI nucleus + expansion vs membrane-stain whole-cell vs transcript-aware Baysor/proseg vs segmentation-free SSAM) given the available stain; judging whether transcript spillover is fabricating false co-expression and short-range cell-cell signal; and deciding whether the derived cell-by-gene matrix is trustworthy before downstream typing, DE, or ligand-receptor analysis.From its SKILL.md

Install
npx -y skills add bg-szy/TOP-SKILLS --skill bio-spatial-transcriptomics-image-analysis__skills-image-analysis

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

18.4 KB, ~4.4k tokens by cl100k_base, as published. Nobody here has run it

Version Compatibility

Reference examples tested with: squidpy 1.7+, scanpy 1.10+, scikit-image 0.22+, numpy 1.26+, pandas 2.2+, cellpose 4.0+ (CLI tools: Baysor 0.6+, proseg 1.0+)

Before using code patterns, verify installed versions match. If versions differ:

  • Python: pip show <package> then help(module.function) to check signatures

If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.

Image Analysis for Spatial Transcriptomics

"Segment cells from my imaging data" -> Draw cell/nucleus boundaries on an image (or on the transcript point cloud) and assign each molecule to one cell, producing a cell-by-gene matrix.

  • Python image-based: cellpose.models.CellposeModel().eval(), StarDist for nuclei, squidpy.im.segment() (watershed baseline)
  • CLI transcript-based: Baysor, proseg run on the molecule table (x, y, gene)

"Extract image features for my spots" -> Summarize pixel intensity/texture under each Visium spot (a DIFFERENT operation from segmentation -- no cell boundaries are drawn).

  • Python: squidpy.im.calculate_image_features() (summary, histogram, texture/GLCM)

Keep these two operations distinct. squidpy.im feature extraction (texture/summary on H&E) describes the image patch under a spot for domain detection. Segmentation manufactures the cells themselves. This skill leads with segmentation because that is the dominant error source; feature extraction is a downstream convenience.

Governing Principle

In imaging spatial data the cell is a segmentation HYPOTHESIS, not an observation. The raw data are images plus a table of decoded molecules with x, y (and sometimes z); there is no native cell. A cell-by-gene matrix exists only AFTER an algorithm draws boundaries and assigns each molecule to one cell or to background. Every row of that matrix is produced by the segmentation step, which is the dominant, irreducible upstream error source for every imaging platform -- it confounds typing, DE, and communication downstream (Mitchel et al. 2026 Nat Genet 58:434, who find segmentation errors "dominate the results" for context-dependent DE and ligand-receptor inference).

Segmentation fails three ways, and each fabricates a specific downstream lie:

  • Over-segmentation splits one cell into many -> inflated cell count; fragments look low-quality and get filtered or mis-typed.
  • Under-segmentation merges neighbors into a spatial DOUBLET whose profile is a mixture of two types. Unlike droplet doublets these are spatially structured -- adjacent types merge preferentially -- so standard doublet detectors (Scrublet, DoubletFinder) MISS them, because the synthetic doublets those tools simulate are random pairs, not neighbors.
  • Transcript mis-assignment / spillover: a molecule of cell A is assigned to neighbor B (diffusion, optical PSF bleed, z-collapse, boundary error). Spillover is DISTANCE-DEPENDENT and strongest between adjacent heterotypic cells, so it manufactures spatially-structured contamination, not uniform noise.

The cascade is worst for exactly the analyses people prize. A T cell abutting epithelium picks up keratin spillover -> false marker co-expression -> a spurious "transitional/hybrid" state that is pure artifact; rare cells in dense parenchyma (TILs, neutrophils) are swamped by neighbor spillover and lost. Crucially, distance-dependent spillover FABRICATES the short-range co-localization that ligand-receptor and cell-cell communication tools detect -- so an L-R "hit" between two touching types can be a pure segmentation artifact (a circularity; see spatial-communication). Treat the derived matrix as PROVISIONAL and run a contamination QC step before trusting any single-cell-resolution claim.

The decision: choose by available signal, not by reflex

The first question is not "which tool" but "what boundary signal do I have?" A nuclear stain says where the nucleus is; a membrane/boundary stain says where the cell ENDS. Whole-cell segmentation is boundary-finding, and DAPI carries no boundary information -- so DAPI-only whole-cell is always inference (expansion). Adding a membrane/boundary stain converts that inference into measurement, and is the single highest-leverage change available -- it beats swapping algorithms on DAPI-only data.

ToolClassInput signalBest whenFails / weak when
StarDistimage, star-convex polygonsDAPI, 1 channelround, crowded NUCLEI; fastnon-convex shapes (whole cells, neurons) cannot be represented; not a whole-cell tool
Cellposeimage, DL generalist1-2 ch (nucleus +/- membrane)generalist; 2-channel nucleus+membrane = true whole-cell; retrainableno transcript-only mode; over/under-segments on DAPI-only without a membrane channel
Watershed (squidpy.im.segment)classic floodingDAPI seeds + intensityfast baseline; seeded splitting of touching nucleiover-segments textured nuclei; seed/threshold-sensitive; no shape prior
Mesmer / DeepCellimage, DL whole-cell (TissueNet)2 ch: nuclear + membraneany platform WITH a membrane stain (also CODEX/MIBI/IMC); human-level whole-cellneeds a membrane channel; DAPI-only -> nuclear only
Baysortranscript MRF+EM, optional priormolecule table (+ optional DAPI)refining/replacing image segmentation by transcriptional composition; recovers cells images miss; runs with or without a priorsparse/low-plex panel + no prior -> unstable; compute-heavy
prosegtranscript, cell-simulation membershipmolecule tabletranscript-only whole-cell WITHOUT a membrane stain; fast; recovers hard immune cellssparse-panel limits of all transcript-only methods; newer/less battle-tested
SSAM / ClusterMapsegmentation-FREE molecule densitymolecule tablecell-type/domain MAPPING when boundaries are hopeless; recovers low-density typesproduce NO cell objects -> no per-cell composition, counts, or neighbor graph

The ladder of trust runs: DAPI-only StarDist/Cellpose-nuclei (accept nuclear sensitivity loss or expansion bias) < membrane-stain whole-cell Mesmer or 2-channel Cellpose < transcript-aware Baysor/proseg (molecules, not a fixed radius, set boundaries) < segmentation-free SSAM/ClusterMap (best mapping, but the cell unit is lost -- no per-cell matrix, neighbor graph, or QC). Methods evolve fast here; verify current best practice against the latest benchmarks before committing.

Nucleus-only and the expansion trap

Nuclear (DAPI) segmentation is robust because nuclei are round, separated, and high-contrast -- but the nucleus holds only a minority of mRNA, so cytoplasmic transcripts fall OUTSIDE the mask and are discarded (large sensitivity loss) or must be reassigned. The cheap substitute is nucleus expansion: dilate each nuclear mask by a fixed radius until it hits a neighbor. This assumes round, equal-sized, isotropically-arranged cells -- false for almost all tissue. In dense tissue expanded disks collide and partition intercellular space by a Voronoi-like rule unrelated to true membranes (the worst region for spillover); elongated or large-cytoplasm cells (neurons, muscle, glia, macrophages) are badly served -- a fixed disk captures none of their projections and steals neighbors' transcripts. No single radius is correct for a heterogeneous tissue. Expansion is a baseline, not a solution.

Xenium makes this concrete: XOA v1.0-1.9 used DAPI + 15 um nucleus expansion; v2.0+ cut the default to 5 um "for improved accuracy" -- an admission that 15 um over-assigned in dense tissue. The vendor changed the answer, so do not treat any expansion radius as ground truth. Note that Cellpose on Xenium is a community path via Xenium Ranger import-segmentation, NOT the built-in XOA default -- do not conflate them.

Segment nuclei from a DAPI/IF image

Goal: Produce instance masks (one integer label per cell) from a nuclear-stain image as the starting cell hypotheses.

Approach: Run Cellpose's generalist model; in v4 (Cellpose-SAM) there is one model, channels are no longer an input, and diameter is optional because the model is size-invariant. With a membrane channel available, pass it as a second channel for true whole-cell masks instead of nuclei + expansion.

from cellpose import models

model = models.CellposeModel(gpu=False)            # v4 Cellpose-SAM single generalist model; v3 used models.Cellpose(model_type='nuclei')
masks, flows, styles = model.eval(dapi_image, diameter=None)   # v4 returns 3 values + drops channels=; v3 returned masks, flows, styles, diams and took channels=[0,0]
# masks: integer label image; 0 = background, 1..N = cells. This is a HYPOTHESIS, not ground truth.
n_cells = int(masks.max())

DAPI-only masks are nuclei. Approximating whole cells without a membrane stain requires expansion (round-cell bias above) or a transcript-aware method. With a membrane/boundary channel, stacking [nucleus, membrane] and passing both lets Cellpose-SAM use the first channels in any order -- that converts boundary inference into measurement.

Re-segment from the transcript table (membrane-free whole-cell)

Goal: Recover cells that image-based nuclear segmentation drops (small, irregular, immune) by letting transcript composition and density define boundaries.

Approach: Run Baysor or proseg on the per-molecule table (x, y, gene). These are CLI tools; the molecule table is the source of truth and the only object that permits re-segmentation. Optionally seed Baysor with the vendor nuclear masks as a prior.

# Baysor: molecule-table segmentation; -m = min transcripts/cell, -s = expected cell scale (um), :gene names the gene column
baysor run -x x_location -y y_location -g feature_name -m 30 -s 10 \
  --prior-segmentation-confidence 0.5 transcripts.csv nucleus_id

# proseg: transcript-only whole-cell, reads Xenium/CosMx/MERSCOPE molecule tables directly
proseg --xenium transcripts.csv.gz --output-counts counts.csv.gz --output-cell-polygons cells.geojson

Baysor and proseg output per-molecule cell assignments -> rebuild a cell-by-gene matrix from those. Where the panel is sparse and no membrane stain exists, all transcript-only methods become unstable -- check cell-yield and size distributions against the image before trusting them.

QC the segmentation before trusting the matrix

Goal: Detect the segmentation failure modes (over/under-segmentation, spillover) BEFORE they propagate into typing and communication results.

Approach: Treat the matrix as provisional. Inspect cell-size and transcripts-per-cell distributions (bimodality flags merged doublets or fragments), check for impossible co-expression of mutually exclusive lineage markers (a spillover signature), and where possible run a dedicated contamination tool.

import numpy as np

counts = np.asarray(adata.X.sum(axis=1)).ravel()       # transcripts per cell
area = adata.obs['cell_area'].to_numpy()               # from the segmentation polygons
# Over-segmentation: a spike of tiny, low-count fragments. Under-segmentation: a tail of huge, high-count "cells".
print('transcripts/cell pct [5,50,95]:', np.percentile(counts, [5, 50, 95]))
print('cell area pct [5,50,95]:', np.percentile(area, [5, 50, 95]))

# Spillover signature: cells co-expressing markers of two mutually exclusive lineages (e.g. epithelial KRT + T-cell CD3).
# Distance-dependent -> these false double-positives concentrate at heterotypic boundaries.
epi = np.asarray(adata[:, 'EPCAM'].X).ravel() > 0
tcell = np.asarray(adata[:, 'CD3E'].X).ravel() > 0
print('suspicious EPCAM+CD3E+ cells:', int((epi & tcell).sum()))

Dedicated correction/QC tools target the distance-dependent contamination directly: SPLIT and neighborhood factorization (Mitchel et al. 2026) for contamination, FastReseg for transcript-based re-segmentation (CosMx), and ovrlpy for vertical/z-collapse doublets. SOPA runs Cellpose and Baysor on the same data with patch-based conflict resolution. Run a contamination step before any rare-state, hybrid-state, or short-range L-R claim.

Extract image features per spot (NOT segmentation)

Goal: Summarize the tissue image under each Visium spot (intensity, texture) to augment expression-based spatial-domain detection.

Approach: Wrap the image in a Squidpy ImageContainer and call calculate_image_features. This draws no cell boundaries -- it describes the pixel patch under each spot. Pass layer='image' explicitly if a segmentation layer already exists on the container.

import squidpy as sq

img = sq.datasets.visium_hne_image_crop()              # ImageContainer; pair with the matching adata
adata = sq.datasets.visium_hne_adata_crop()
sq.im.calculate_image_features(adata, img, layer='image', features=['summary', 'texture'],
                               key_added='img_features', n_jobs=1, show_progress_bar=False)
# texture = GLCM (contrast, homogeneity, correlation, ASM); summary = per-channel intensity stats
feats = adata.obsm['img_features']                     # rows = spots, columns = features

Watershed via sq.im.segment(img, layer='image', method='watershed') is a fast classical baseline that over-segments H&E; use it for a quick look, not for production cell calling. Morphology per mask comes from skimage.measure.regionprops_table (area, eccentricity, solidity).

Common Errors

SymptomCauseFix
Many tiny, low-count "cells"Over-segmentation split single cellsLower model sensitivity / raise min_size; check cell-area histogram for a fragment spike; prefer a learned model over watershed
Cluster of cells co-expressing exclusive lineage markers (KRT + CD3)Transcript spillover fabricating co-expression at heterotypic boundariesRun a contamination QC (SPLIT, neighborhood factorization); re-segment with a membrane stain or Baysor/proseg; do not interpret as a "hybrid state"
A short-range ligand-receptor hit between two touching typesDistance-dependent spillover manufactures the short-range co-localization (circularity)Validate against segmentation quality; constrain L-R by distance; treat as hypothesis (see spatial-communication)
Standard doublet detector finds almost nothing, yet merged cells existSpatial doublets are neighbor merges, not random pairs the detector simulatesInspect transcripts/cell and area tails; re-segment; do not rely on Scrublet/DoubletFinder for imaging merges
Cytoplasmic markers nearly absent from every cellNucleus-only mask discarded cytoplasmic mRNAExpand the mask, add a membrane stain, or use a transcript-aware method
Sharp drop in transcripts/cell after a vendor software updateXenium expansion default cut 15 um -> 5 um (v2.0)Expected; the smaller radius assigns fewer (and fewer mis-assigned) transcripts -- re-run downstream, do not "fix"
model.eval returns 3 values but code unpacks 4Cellpose v4 dropped diams and the channels= argumentUnpack masks, flows, styles; remove channels=; diameter is optional in v4
Unable to determine which layer to useA segmentation layer was added, so the container has >1 layerPass layer='image' to calculate_image_features / segment
Transcript-only segmentation yields implausible cell shapes/yieldSparse/low-plex panel with no prior -> Baysor/proseg unstableAdd a nuclear prior; compare yield + size to the image; fall back to image segmentation

Related Skills

  • spatial-transcriptomics/spatial-preprocessing - QC floors and non-gene-count normalization for the post-segmentation matrix
  • spatial-transcriptomics/spatial-communication - ligand-receptor inference, where segmentation spillover fabricates short-range signal (the circularity)
  • spatial-transcriptomics/spatial-proteomics - whole-cell segmentation on membrane markers for CODEX/IMC/MIBI (Mesmer/DeepCell)
  • imaging-mass-cytometry/cell-segmentation - segmentation for multiplexed-imaging proteomics
  • spatial-transcriptomics/spatial-data-io - load the molecule table (the only object that permits re-segmentation) and the derived matrix

References

  • Stringer C, Wang T, Michaelos M, Pachitariu M (2021) Cellpose: a generalist algorithm for cellular segmentation. Nature Methods 18:100-106. DOI 10.1038/s41592-020-01018-x
  • Pachitariu M, Stringer C (2022) Cellpose 2.0: how to train your own model. Nature Methods 19:1634-1641. DOI 10.1038/s41592-022-01663-4
  • Schmidt U, Weigert M, Broaddus C, Myers G (2018) Cell Detection with Star-Convex Polygons (StarDist). MICCAI 2018, LNCS 11071:265-273. DOI 10.1007/978-3-030-00934-2_30
  • Greenwald NF, Miller G, Moen E, et al. (2022) Whole-cell segmentation of tissue images with human-level performance using large-scale data annotation and deep learning (Mesmer/DeepCell). Nature Biotechnology 40:555-565. DOI 10.1038/s41587-021-01094-0
  • Petukhov V, Xu RJ, Soldatov RA, et al. (2022) Cell segmentation in imaging-based spatial transcriptomics (Baysor). Nature Biotechnology 40:345-354. DOI 10.1038/s41587-021-01044-w
  • Jones DC, Elz AE, Hadadianpour A, et al. (2025) Cell simulation as cell segmentation (proseg). Nature Methods 22:1331-1342. DOI 10.1038/s41592-025-02697-0
  • Park J, Choi W, Tiesmeyer S, et al. (2021) Cell segmentation-free inference of cell types from in situ transcriptomics data (SSAM). Nature Communications 12:3545. DOI 10.1038/s41467-021-23807-4
  • Mitchel J, Gao T, Petukhov V, et al. (2026) Impact and correction of segmentation errors in spatial transcriptomics. Nature Genetics 58:434-444. DOI 10.1038/s41588-025-02497-4
  • Palla G, Spitzer H, Klein M, et al. (2022) Squidpy: a scalable framework for spatial omics analysis. Nature Methods 19:171-178. DOI 10.1038/s41592-021-01358-2
  • Janesick A, Shelansky R, Gottscho AD, et al. (2023) High resolution mapping of the tumor microenvironment using integrated single-cell, spatial and in situ analysis (Xenium). Nature Communications 14:8353. DOI 10.1038/s41467-023-43458-x

What ships with it: 1 file

5.2 KB alongside SKILL.md

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.