The unified fturader command handles both H&E brightfield and multiplex fluorescence
(CODEX / OME-TIFF) inputs through a single entry point.
Migration note: the older commands
fturader-infer(H&E only) andfturader-multiplex(multiplex only) are backward-compatibility aliases. They share the same core logic asfturaderand are deprecated; they will be removed in a future release.
usage: fturader [-h] --input PATH [--he {auto,true,false}] [--channel-names TXT]
[--pixel-size UM] [--region REGION] [--recipe RECIPE]
[--organ {kidney,largeintestine,lung,prostate,spleen}]
[--score-threshold T] [--min-mask-area PX2]
[--weights-root DIR] [--sam2-config SAM2_CONFIG]
[--finder-device DEV] [--sam-device DEV]
[--overlap-filter {NON_MAX_MERGE,NON_MAX_SUPPRESSION}]
[--overlap-metric {IOU,IOS}] [--iou-threshold T]
[--output-dir DIR] [--save-overlay] [--print-classification]
| Argument | Default | Description |
|---|---|---|
--input PATH |
required | Image file (TIFF / OME-TIFF / PNG / JPG) or a processed CODEX directory |
--he {auto,true,false} |
auto |
H&E handling. auto auto-detects (passthrough for ≤3-ch brightfield, synthesize for multichannel); true forces H&E / brightfield passthrough (no synthesis); false forces fluorescence synthesis even for a 3-channel image |
--channel-names TXT |
— | Path to channelnames.txt; required for non-OME multiplex TIFFs |
--pixel-size UM |
auto | Physical pixel size µm/px (read from OME-XML when available) |
--region REGION |
1 |
Region index for CODEX datasets |
--recipe RECIPE |
auto |
Pseudo-H&E recipe: auto | auto:mean | auto:max | ck | comma-separated list of eosin markers (e.g. Cytokeratin,Vimentin). v1 / v2 still work but are deprecated aliases. |
--organ |
largeintestine |
Target organ; selects the RF-DETR checkpoint and default thresholds. One of kidney / largeintestine / lung / prostate / spleen |
--score-threshold T |
0 (organ default) |
RF-DETR confidence threshold; 0 = use the organs.py organ default |
--min-mask-area PX2 |
0 (organ default) |
Minimum mask area px²; 0 = use the organs.py organ default |
--weights-root DIR |
~/.deepcell/models/ftusam_v0.1 |
Model-weights root directory |
--sam2-config |
configs/sam2.1/sam2.1_hiera_b+.yaml |
SAM2 config file |
--finder-device DEV |
cuda |
PyTorch device for RF-DETR (use CUDA_VISIBLE_DEVICES to pick the GPU) |
--sam-device DEV |
cuda |
PyTorch device for SAM2 |
--overlap-filter |
NON_MAX_MERGE |
Cross-tile merge strategy: NON_MAX_MERGE or NON_MAX_SUPPRESSION |
--overlap-metric |
IOS |
Overlap metric for cross-tile de-duplication: IOU or IOS |
--iou-threshold T |
0.5 |
IoU/IoS threshold for cross-tile merging |
--output-dir DIR |
required* | Output directory (*may be omitted with --print-classification) |
--save-overlay |
flag | Also write the exp4-style turbo filled-mask overlay (full image + short-side-2000px JPEG) |
--print-classification |
flag | Dry run: print channel classification and exit (no inference) |
Organ default thresholds (single source: src/fturader/organs.py):
| Organ | min_mask_area (px²) | score_threshold |
|---|---|---|
| prostate | 40 | 0.33 |
| largeintestine | 200 | 0.36 |
| lung | 1 000 | 0.25 |
| kidney | 5 000 | 0.25 |
| spleen | 10 000 | 0.38 |
Lung default raised in R3 (
score_threshold0.13 → 0.25,min_mask_area→ 1 000) for cleaner, faster output. To get fewer, cleaner detections on any organ, raise--score-thresholdand/or--min-mask-areaabove the organ default (0keeps the default).
fturader \
--input "/data/user-data/qli2/FTU/datasets/hubmap_dataset/\
Kidney-HandE-Stained-Microscopy(HBM269.FMTJ.275)-3a7480e53dc874ce2199cb73b31b4bdd/\
lab_processed/images/V12D05-071_XY02_K21-00202-1.ome.tiff" \
--he true \
--organ kidney \
--weights-root /home/qli2/fturader/model_weights \
--output-dir ./out_kidney/ \
--save-overlayThe HBM685 pipeline output is a single multichannel OME-TIFF whose OME-XML carries the
54 channel names and the pixel size, so no --channel-names / --pixel-size is needed.
fturader \
--input "/data/user-data/qli2/FTU/datasets/hubmap_dataset/\
LargeIntestine-ANNO_CODEX(HBM685.PCCJ.427)-c8fa615fb884c7b51d08c0cf35debfee/\
ometiff-pyramids/pipeline_output/expr/reg001_expr.ome.tif" \
--recipe auto \
--organ largeintestine \
--weights-root /home/qli2/fturader/model_weights \
--output-dir ./out_hbm685/ \
--save-overlayPointing --input at a processed CODEX directory triggers CodexDataset, which reads
Experiment.json / tileMap.txt / channelnames.txt itself. This path is supported
only for the verified HuBMAP reg{NNN}_X##_Y##.tif layout (see "Loading boundary"
below).
fturader \
--input "/data/user-data/qli2/FTU/datasets/hubmap_dataset/\
LargeIntestine-CODEX(HBM573.TTLG.748)-8eeca1a98041348bb14fd652b766661e/processed" \
--recipe ck \
--organ largeintestine \
--weights-root /home/qli2/fturader/model_weights \
--output-dir ./out_hbm573/ \
--save-overlayHBM288 caveat (not directly loadable via the CLI). HBM288 names its tiles
reg004_*while itstileMap.txtrecordsRegionNumber=1, so the default CODEX directory loader looks forreg001_*and fails. The CLI does not expose a--tile-patternoverride. Handle HBM288 in Python withTiledDataset.from_tiles(..., region=1, tile_pattern="reg004_X{x:02d}_Y{y:02d}.tif"), or pre-stitch it into one image and useSingleImageDataset.from_image.
fturader --input /path/to/stitched.ome.tif --print-classificationThe maintained Python loading contracts are two first-class entries by input shape:
SingleImageDataset.from_image (one assembled image + manual metadata; tile_grid= for a
big multi-z image processed block-wise) and TiledDataset.from_tiles (a grid of separate
tiles + geometry, modality-agnostic). CodexDataset is deprecated and delegates to the
from_tiles engine for the verified HuBMAP reg{NNN}_X##_Y##.tif layout.
The CLI exposes only the single---input path (one image, or a processed CODEX dir)
and intentionally has no --tile-pattern flag. Anything needing custom geometry —
HBM288's reg004 quirk, explicit tile positions, a custom reader_factory, or
tile_grid block-wise focus — is Python API only: use TiledDataset.from_tiles /
SingleImageDataset.from_image directly, or pre-stitch into one image first. See
docs/multiplex_pseudo_he.md.
Files written under --output-dir:
output_dir/
labels.tif (H, W) uint16/uint32 instance label map
detections.npz compact-RLE per-instance masks + scores
summary.json run metadata
overlay_full.jpg [--save-overlay] exp4-style full-resolution overlay
overlay_2000.jpg [--save-overlay] short-side-2000px version
- Shape
(H, W), dtypeuint16(N < 65535) oruint32. - Pixel value
0= background,1..N= instance index. - Overlapping instances: higher confidence painted last
(
overlap_strategy = "higher-confidence-on-top").
Always present:
| Key | Shape / dtype | Meaning |
|---|---|---|
xyxy |
(N, 4) float32 |
Bounding boxes (x1, y1, x2, y2) in original-image pixels |
confidence |
(N,) float32 |
Combined score √(rf_score × sam_score) (all-NaN when unset) |
label_id |
(N,) uint16/uint32 |
Pixel value in labels.tif (1-indexed); matches the labels.tif dtype |
Per-instance mask, compact-RLE encoding (written when compact_masks=True, the default):
| Key | Shape / dtype | Meaning |
|---|---|---|
mask_rle_flat |
(sum Lᵢ,) int32 |
All per-instance RLE runs concatenated |
mask_rle_lengths |
(N,) int32 |
Run-count Lᵢ per instance (splits mask_rle_flat) |
mask_crop_shapes |
(N, 2) int32 |
Per-instance crop size (h, w) |
mask_offsets |
(N, 2) int32 |
Per-instance crop top-left offset (x, y) |
mask_image_hw |
(2,) int32 |
Original image size (H, W) |
Optional (present only when the detections carry them — they do for normal inference):
| Key | Shape / dtype | Meaning |
|---|---|---|
rf_score |
(N,) float32 |
Raw RF-DETR confidence |
sam_score |
(N,) float32 |
SAM2 mask-quality score |
When the bundle was saved with compact_masks=False, the mask keys above are replaced by a
single mask_dense (N, H, W) uint8 array instead.
Format rationale (compact-RLE vs dense): see
multiplex_exp/2026-06-19/U2_detections_format_FINDINGS_CN.md.
| Field | Meaning |
|---|---|
organ |
Organ name |
pixel_size_um |
µm/px used for inference |
score_threshold |
Score threshold actually used |
min_mask_area |
Minimum mask area actually used |
weights |
Nested object {"rf_detr": <path>, "sam2": <path>} |
n_detections |
Number of detected FTUs |
label_dtype |
numpy dtype string of labels.tif |
overlap_strategy |
Mask-overlap strategy — exactly "higher-confidence-on-top" |
output_files |
List of files actually written |
input |
Absolute path of the input passed to the CLI |
region |
Region index used |
pseudo_he_report |
Effective pseudo-H&E report — the config actually used, mirroring the _refine_auto SNR selection (multiplex inputs only). The legacy fturader-multiplex CLI instead writes pseudo_he_config. |
profile |
Per-stage wall-clock timings (the CLI always runs infer_wsi(profile=True)) |
The profile block mirrors FTURader.last_profile (equivalently
segment_ftu(..., profile=True, return_report=True)["infer"]["profile"]). Keys:
scale_s, n_tiles, tile_forward_s (RF-DETR + SAM2 tile inference), assemble_s
(cross-tile concat), merge_s (the NMM/NMS dedup — timed directly by wrapping
sv.Detections.with_nmm), slicer_overhead_s, rescale_s, total_s, n_detections.
NMM is the bottleneck at high detection counts. Across R3 runs
tile_forward_s(inference) stays bounded at ~2–19 s, whilemerge_sscales with detection count and dominates: HBM573 6 det → 0.02 s; CODEX ~26 det → ~1.0 s; lung 6000²-px @ 0.25 ~219 det → ~27 s; HBM288 full 247 det → 99 s (≈80 % of a 124 s run). 8× more detections (26 → 209) → ~46× the merge time, inference roughly unchanged. Raising--score-threshold/--min-mask-areamakes runs both cleaner and faster.
from fturader.io import load_detections
# One call reloads an sv.Detections plus the summary.json metadata dict.
dets, meta = load_detections("./out_kidney/")
# dets: sv.Detections
# .xyxy (N, 4) float32 bounding boxes in original-image pixels
# .confidence (N,) float32 combined score (None when it was unset)
# .mask CompactMask (RLE crops) by default; dense (N, H, W) bool only
# when the bundle was saved with compact_masks=False
# .data {"label_id", "rf_score", "sam_score"} when present in the npz
#
# meta: dict = the summary.json contents (organ, pixel_size_um, n_detections, weights, …)
print(f"{len(dets)} FTUs, organ={meta['organ']}")
# The original image size is in the npz as mask_image_hw — summary.json has no image_shape.| Old command | Status | Equivalent |
|---|---|---|
fturader-infer |
deprecated (kept for compatibility) | fturader --he true --organ <organ> |
fturader-multiplex |
deprecated (kept for compatibility) | fturader --recipe <recipe> --organ <organ> |
fturader-infer additionally accepts the legacy WSI flags --tiled, --overlap-ratio,
--iou-threshold, --overlap-filter, and --overlap-metric so that old invocations keep
working (inference is always tiled, so --tiled is a no-op). The aliases share their core
logic with fturader (implemented in src/fturader/cli/_common.py).
- Pipeline internals (Beer-Lambert synthesis, channel classification, best-focus):
docs/multiplex_pseudo_he.md - End-to-end Python walkthrough:
notebooks/walkthrough_python.ipynb - Output-format comparison experiment:
multiplex_exp/2026-06-19/U2_detections_format_FINDINGS_CN.md