Preprocessing¶
Use PreprocessingConfig to choose slide readers, tile
geometry, tissue segmentation, annotation sampling, and previews.
Backends¶
The backend field controls which slide-reading library is used:
"auto"— tries cucim → vips → openslide → asap and picks the first backend that can open the path"cucim"— NVIDIA cuCIM for supported slide formats, including SVS and TIFF"openslide"— broad format support, CPU-only"vips"— libvips, good for large TIFF files"asap"— ASAP reader (requires separate installation)
The mask_backend field controls the reader used for source masks —
precomputed tissue masks and annotation masks — and accepts the same values. It
is resolved independently from the mask path, so a mask can use a different
decoder than its slide. hs2p never silently falls back to another reader, so
set mask_backend explicitly (e.g. "openslide") when the slide backend
cannot decode a mask — for example a deflate-compressed label TIFF that cuCIM
can open but not decode. It defaults to "auto" and is ignored for slides
with no source mask.
The auto priority can change when hs2p or the installed backend set
changes. Set backend and mask_backend explicitly when decoder selection
must stay stable across upgrades.
Pooled Tile Geometry¶
Pooled extraction uses three distinct pixel sizes, recorded in tile and hierarchical artifact sidecars:
read_tile_size_px— the raw square read from the selected WSI pyramid level.requested_tile_size_px— the tile after geometry correction. This is also the encoder input: declared runs apply only the encoder’s photometric preprocessing (no resize or center crop).encoder_input_size_px— the tensor side length the encoder received. Equalsrequested_tile_size_pxfor declared runs.
The default requested_tile_size_px is the registry input_size. A
different size requires allow_non_recommended_settings=True, an encoder
that supports variable input, and a size divisible by the model’s patch
geometry. See Model Zoo for the given-input recipes and the sampling
change from earlier releases.
Tissue Segmentation¶
segmentation is forwarded directly to
hs2p‘s segmentation pipeline.
It is a partial override: omitted keys retain the standard configuration
defaults, including method="hsv".
The method key selects the algorithm:
hsv- heuristic based on the HSV colour space. Fast and robust for H&E slides.otsu- thresholds the saturation channel using Otsu’s method.threshold- applies a fixed saturation threshold.sam2- runs the AtlasPatch SAM2 tissue segmentation model on an internal 8.0 µm/px thumbnail. Requires theatlaspatchpackage.sam2_deviceselects"cpu"(the default),"cuda", or a CUDA device such as"cuda:0".
Example:
from slide2vec import Model, PreprocessingConfig
model = Model.from_preset("virchow2")
preprocessing = PreprocessingConfig(
requested_spacing_um=0.5,
segmentation={"method": "sam2", "sam2_device": "cuda"},
)
embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
Or in a YAML config:
tiling:
seg_params:
method: "sam2"
sam2_device: "cuda"
Annotation-Aware Sampling¶
By default slide2vec tiles tissue: the masks vocabulary is the binary
{background: 0, tissue: 1}, and embeddings cover tiles that pass the
sampling filters. To restrict extraction to specific annotated classes
(tumor-only, stroma-only, …), customize the masks block. Any divergence
from the default vocabulary opts the run into annotation-aware sampling; the
plain tissue path is otherwise unchanged.
In annotation mode the per-slide mask_path (argument or manifest column) is
required and is read as a multi-label raster: each class occupies a
distinct integer pixel value. The masks block maps that vocabulary and is
deep-merged over the default, so you only state what you add:
pixel_mapping—{class_name: integer pixel value}. Values must be distinct integers in[0, 255];mergedis a reserved name.min_coverage—{class_name: float | null}; the minimum fraction of a tile covered by that class to keep it.nullmeans don’t sample that class. Thetissueentry is the single source of truth for the tissue threshold.colors—{class_name: [r, g, b] | null}used when rendering previews.output_mode—per_annotation(one artifact set per sampled class) ormerged(one set per slide over the union of tiles passing any class).independent_sampling(a top-level flag onPreprocessingConfig) —Truesamples each class against its own mask;Falsesamples once over the union, then post-filters per class by coverage.
Tumor-only bag features. Add a tumor class, set its min_coverage,
and disable tissue sampling with min_coverage.tissue: null:
from slide2vec import Model, PreprocessingConfig
model = Model.from_preset("virchow2")
preprocessing = PreprocessingConfig(
requested_spacing_um=0.5,
requested_tile_size_px=224,
masks={
"pixel_mapping": {"tumor": 2}, # tumor pixels == 2 in the raster
"min_coverage": {"tissue": None, "tumor": 0.5},
"colors": {"tumor": [255, 0, 0]}, # preview color (optional)
},
)
slide = {
"sample_id": "slide-1",
"image_path": "/path/to/slide.svs",
"mask_path": "/path/to/annotation_mask.tif", # multi-label raster
}
embedded = model.embed_slide(slide, preprocessing=preprocessing)
assert embedded.annotation == "tumor" # every bag is stamped with its label
tumor_bag = embedded.tile_embeddings # shape (N_tumor, D) — tumor tiles only
Because this run produces exactly one bag, bare embed_slide(...) returns it
directly. Requesting a class the run did not produce raises a ValueError
listing the available bags.
Multiple classes per slide. Give each class its own min_coverage and
use a Pipeline so per-class artifacts are persisted under a
<class>/ subdirectory (e.g. tile_embeddings/tumor/<sample_id>.pt):
from slide2vec import ExecutionOptions, Pipeline
preprocessing = PreprocessingConfig(
requested_spacing_um=0.5,
requested_tile_size_px=224,
masks={
"pixel_mapping": {"tumor": 2, "stroma": 3},
"min_coverage": {"tissue": None, "tumor": 0.5, "stroma": 0.5},
},
)
pipeline = Pipeline(
model=model,
preprocessing=preprocessing,
execution=ExecutionOptions(output_dir="outputs/run"),
)
# The manifest's mask_path column points at each slide's multi-label raster.
result = pipeline.run(manifest_path="/path/to/slides.csv")
This fans out per (sample_id, annotation) across tile, slide, and
hierarchical embeddings — and across GPUs — recording each class’s own
feature_path in process_list.csv. See Output Layout for the
namespaced directory structure.
The merged output mode. With masks={"output_mode": "merged"}
the run does not fan out per class: it samples the union of tiles passing any
class and produces one bag per slide, labelled "merged". On disk it lands
at the flat output root (tile_embeddings/<sample_id>.pt) with no
<class>/ subdirectory, exactly like the default tissue case:
from dataclasses import replace
merged_preprocessing = replace(
preprocessing,
masks={**preprocessing.masks, "output_mode": "merged"},
)
results = model.embed_slides([slide], preprocessing=merged_preprocessing)
merged = results["slide-1"]["merged"]
# A single bag is also available directly:
merged = model.embed_slide(slide, preprocessing=merged_preprocessing)
assert merged.annotation == "merged"
Working with several classes in memory. embed_slides returns a nested
mapping {sample_id: {label: EmbeddedSlide}} where the inner key is each
bag’s annotation label (a class name, "tissue", or "merged"; never None). Every
EmbeddedSlide is also stamped with its
annotation:
results = model.embed_slides(
[slide],
preprocessing=preprocessing, # tumor + stroma configuration above
)
tumor_bag = results["slide-1"]["tumor"].tile_embeddings # shape (N_tumor, D)
stroma_bag = results["slide-1"]["stroma"].tile_embeddings # shape (N_stroma, D)
Pass annotations=[...] to restrict the inner keys to the classes you care
about; omit it to receive every bag the run produced:
results = model.embed_slides(
[slide], preprocessing=preprocessing, annotations=["tumor"],
)
results["slide-1"].keys() # dict_keys(['tumor']) — stroma is dropped
Selecting bags with embed_slide. embed_slide returns one bag (or a
list of bags) for a single slide via the annotation selector:
# One class → one EmbeddedSlide.
tumor = model.embed_slide(slide, preprocessing=preprocessing, annotation="tumor")
# A list of classes → a list of EmbeddedSlide in the requested order.
tumor, stroma = model.embed_slide(
slide, preprocessing=preprocessing, annotation=["tumor", "stroma"],
)
Bare embed_slide(slide) returns the single bag when the run produced
exactly one; if the run fanned out into several bags it raises a ValueError
naming the available bags and directing you to embed_slides, so per-class
results are never silently dropped.
Preview Images¶
slide2vec can write a tissue mask preview and a tiling preview for each
slide — useful for quality control. Both are enabled by default. The
preview dict is a partial override, so this disables only the tiling
preview:
preprocessing = PreprocessingConfig(
preview={
"save_tiling_preview": False,
}
)
Preview images are written to <output_dir>/preview/mask/<sample_id>.jpg
and <output_dir>/preview/tiling/<sample_id>.jpg for tissue or merged
output. Per-class tiling previews add a <class>/ subdirectory below
preview/tiling/; the mask preview is shared across classes. Paths are also
recorded in process_list.csv and on the returned
EmbeddedSlide (mask_preview_path,
tiling_preview_path).
When resuming a run, existing preview paths are preserved in
process_list.csv if the preview files still exist on disk.
Field reference¶
- class slide2vec.PreprocessingConfig(*, backend='auto', mask_backend='auto', requested_spacing_um=None, requested_tile_size_px=None, requested_region_size_px=None, region_tile_multiple=None, tolerance=0.05, overlap=0.0, read_coordinates_from=None, read_tiles_from=None, on_the_fly=True, gpu_decode=False, adaptive_batching=False, use_supertiles=True, jpeg_backend='pil', num_cucim_workers=4, resume=False, segmentation=<factory>, filtering=<factory>, preview=<factory>, masks=<factory>, independent_sampling=True)¶
Bases:
objectConfiguration for slide tiling and preprocessing.
- backend: str = 'auto'¶
Slide reading backend.
"auto"tries cucim → vips → openslide → asap. Explicit choices:"cucim","openslide","vips","asap".
- mask_backend: str = 'auto'¶
Source-mask reading backend, resolved independently from the mask path (hs2p ≥ 4.3.0).
"auto"probes openability just likebackend. Set this explicitly (e.g."openslide") when a precomputed tissue or annotation mask needs a different decoder than its slide — hs2p no longer silently falls back to another reader, so a mask the slide backend cannot decode fails unless overridden here. Accepts the same values asbackend; ignored for slides with no source mask.
- requested_spacing_um: float | None = None¶
Target spacing in µm/px. Resolved from the model preset when
None.
- requested_tile_size_px: int | None = None¶
Tile side length in pixels at requested_spacing_um. Resolved from the model preset when
None.
- requested_region_size_px: int | None = None¶
Parent region side length in pixels (hierarchical mode). Auto-derived as
requested_tile_size_px × region_tile_multiplewhenNone.
- region_tile_multiple: int | None = None¶
Region grid width/height in tiles (e.g.
6→ 6×6 = 36 tiles per region). Enables hierarchical extraction when set; must be ≥ 2.
- read_coordinates_from: Path | None = None¶
Directory containing pre-extracted tile coordinates to reuse, skipping tiling.
- read_tiles_from: Path | None = None¶
Directory containing pre-extracted tile images to skip the tiling step entirely.
- jpeg_backend: str = 'pil'¶
JPEG encoder for extracted tile archives — portable
"pil"(default) or explicitly requested"turbojpeg".
- segmentation: dict[str, Any]¶
Partial override forwarded to hs2p segmentation config. Supported keys:
method,downsample,sam2_device. Omitted keys retain the standard configuration defaults. See Preprocessing for details.
- filtering: dict[str, Any]¶
Partial override forwarded to hs2p tile-filtering config. Omitted keys retain the standard configuration defaults.
- preview: dict[str, Any]¶
Partial override controlling whether hs2p writes mask and tiling preview images. Keys:
save_mask_preview,save_tiling_preview,downsample. Omitted keys retain the standard configuration defaults.
- masks: dict[str, Any]¶
Annotation-mask vocabulary forwarded to hs2p’s sampling resolver. Keys:
output_mode,pixel_mapping,colors,min_coverage. A partial mapping is deep-merged overDEFAULT_MASKS, so callers only state what they override (e.g.{"min_coverage": {"tissue": 0.1}}). The default{background, tissue}block is plain tissue tiling;min_coverage.tissueis the single source of truth for the tissue threshold.