Preprocessing¶
Preprocessing covers tissue segmentation, tile extraction, and geometry resolution. In practice, spacing is the primary scale-selection knob because it controls the biological context seen by the encoder.
The main configuration object is soma.config.PreprocessingConfig.
- class soma.config.PreprocessingConfig(backend='auto', requested_tile_size_px=None, requested_spacing_um=None, requested_region_size_px=None, region_tile_multiple=None, read_tile_size_px=None, read_region_size_px=None, tissue_method=None, min_coverage=<factory>, overlap=0.0, seg_downsample=64, sam2_device='cpu', sam2_num_workers=None, tolerance=0.05, ref_tile_size_px=None, a_t=4, tissue_mask_tissue_value=1, dense_window_size=None, dense_window_overlap=0.0, feature_kind=None, attention=<factory>, preview=<factory>, masks=None, sampling=None)¶
Bases:
objectWhole-slide preprocessing, tiling, and geometry settings.
The preprocessing backend controls tissue segmentation and tile extraction.
requested_spacing_umandrequested_tile_size_pxare the primary scale-selection knobs.requested_region_size_pxandregion_tile_multipledescribe HIPT-style hierarchical region geometry.sam2_deviceandsam2_num_workerstune SAM2 tissue-segmentation execution when the backend supports that path.
Key knobs¶
Field |
Meaning |
Typical use |
|---|---|---|
|
Tile size requested from the tiler |
Match encoder expectations |
|
Microns per pixel for tiling |
Coarse vs fine biological context |
|
Region size for hierarchical pipelines |
HIPT-style runs |
|
Tile count per hierarchical region |
Alternative to |
|
Tissue segmentation method: |
Leave empty/unused when dataset rows provide pre-computed tissue masks |
|
Device used for SAM2 tissue segmentation |
Set explicitly when running SAM2 on GPU |
|
Cap on concurrent SAM2 tissue-segmentation workers |
Reduce GPU memory pressure on smaller cards |
|
Minimum tissue fraction to keep a tile (masks-shaped map; |
Adjust only if tissue masks are too loose or too strict |
Segmentation slide-manifest sampling¶
For the segmentation slide-manifest path (whole slides + annotation masks), the
annotation masks and ROI sampling blocks nest under preprocessing
because annotation-based tile selection is a preprocessing/tiling concern (this
mirrors slide2vec’s own PreprocessingConfig):
preprocessing:
requested_tile_size_px: 512
requested_spacing_um: 0.5
masks:
pixel_mapping: {background: 0, tumor: 1}
min_coverage: {tumor: 0.5}
sampling:
output_mode: merged
strategy: joint
The presence of preprocessing.masks selects the slide-manifest input mode
(slides + annotation masks → soma-sampled ROIs → dense grids → segmentation head).
See soma.config.MasksConfig and soma.config.SamplingConfig.
Annotation vocabulary: background-present vs background-absent¶
pixel_mapping is the dataset’s own raw-pixel vocabulary. No reserved label name is
required — soma keeps only structural validation (non-empty mapping, unique pixel values,
min_coverage/colors keys ⊆ pixel_mapping, fractions in [0, 1], RGB shape).
background is an opt-in name that only changes how the segmentation-head label remap
(soma.dense.reader.build_label_remap()) treats unannotated regions.
Background present — background names the unannotated/ignore label. With a class
count one less than the mapping size, background maps to ignore_index and the other
labels take class index = their order. (If the class count equals the mapping size,
background is instead a real class at index 0.) For example, the BEETLE exact-value
vocabulary {background: 1, tumor: 2} with one task class:
preprocessing:
masks:
pixel_mapping: {background: 1, tumor: 2} # raw value 1 -> ignore, 2 -> class 0
min_coverage: {tumor: 0.5}
Background absent — every named label is a real class (index = order) and the task’s
class count must equal the mapping size. Any raw pixel value not in the mapping collapses
to ignore_index — that is how unannotated regions are expressed without naming them. A
background-free vocabulary like {tumor: 2} is accepted:
preprocessing:
masks:
pixel_mapping: {tumor: 2} # raw value 2 -> class 0; every other value -> ignore
min_coverage: {tumor: 0.5}
The “exactly one named label is the ignore label” mode (class count == mapping size − 1)
still requires a name: background stays the opt-in reserved name for that mode only.
Note
Migration (soma #109): masks: and sampling: were previously
top-level config sections. They now live under preprocessing:. A top-level
masks: / sampling: block is no longer accepted — move them under
preprocessing: as shown above.
Annotation-restricted bags (dataset_type: slide or patient)¶
The same preprocessing.masks block also restricts a whole-slide MIL bag to a
chosen compartment. On a dataset_type: slide dataset, declaring a masks block
produces one merged bag per slide containing only the tiles that meet the per-class
coverage threshold — e.g. a tumor-only bag that excludes the surrounding tissue. The
restricted bag then flows through the ordinary featurizer → aggregator → predictor with
its ordinary slide-level label (the dataset’s label column); nothing in the MIL
path changes, only which tiles enter the bag.
The same block is also accepted on a dataset_type: patient dataset (#111): every
slide is tiled to its annotation-restricted merged bag (tiling and selection identical to
the slide path), and patient-level aggregation then consumes those restricted slide bags
to produce compartment-restricted patient features. A patient pipeline uses a
pretrained patient encoder rather than a trainable aggregator, so the masks block again
changes only which tiles enter each slide’s bag.
data:
dataset_type: slide # whole-slide MIL, not segmentation
preprocessing:
requested_tile_size_px: 224
requested_spacing_um: 0.5
tissue_method: otsu
masks:
pixel_mapping: {background: 0, tumor: 1}
min_coverage: {tumor: 0.5} # keep tiles ≥ 50% tumor; tissue-only tiles are excluded
sampling:
output_mode: merged # one merged bag per slide (required; see below)
strategy: joint # joint across classes; 'independent' tiles each class separately
aggregation:
name: abmil # any existing MIL aggregator
task:
name: binary_classification
How it works:
Each dataset row’s
mask_pathis the multi-class annotation raster;pixel_mappingnames the classes (it must includebackground). Tiles are kept by per-classmin_coverageover the annotation mask — binary tissue filtering is bypassed, so the tissue threshold (preprocessing.min_coverage.tissue) does not gate annotation bags.The full
masksblock —pixel_mapping, per-classmin_coverage,colors, an explicitoutput_mode, andindependent_sampling(derived fromsampling.strategy) — is forwarded into slide2vec’s annotation sampling. The default{background: 0, tissue: 1}vocabulary stays byte-for-byte plain tissue tiling; any customization opts into annotation sampling.A relabeled vocabulary is honored as-is:
pixel_mapping: {background: 1, tumor: 2}routes to annotation sampling with those exact mask values (there is no reservedtissue == 1value under amasksblock —pixel_mappingis the single source of truth).The selection (active
pixel_mappingentries, per-classmin_coverage,strategy,output_mode) folds into the cache key, so a tumor-restricted bag never reuses a full-tissue bag’s cached tiles/features.colorsis cosmetic and is excluded from cache identity.
Note
output_mode must be merged for dataset_type: slide and patient (the
default). output_mode: per_annotation (one bag per (slide, class)) is deferred —
see soma issue #86 — and raises at config load on both. A masks block is rejected on
dataset_type: tile (patch manifests have no annotation-sampling step).
A ready-to-run example lives at examples/slide_tumor_restricted_bag.yaml.
Guidance¶
Use smaller spacing when local morphology matters.
Use larger spacing when the label depends on broader structure.
Prefer a small set of meaningful spacing values instead of arbitrary near-duplicates.
Tissue mask preview¶
Preview rendering is inherited from hs2p:
soma.preprocessing.overlay_mask_on_slide()for tissue-mask overlayssoma.preprocessing.save_overlay_preview()for writing mask preview imagessoma.preprocessing.write_coordinate_preview()for tile-grid previews
Read-size fields such as read_tile_size_px are resolved internally from the
requested tile/region size and spacing, so they are not shown in the
user-facing reference config.