API Guide

Use Model for in-memory slide and patient embeddings, image artifacts, or dense grids. Use Pipeline for manifest-driven slide processing. See Getting Started for installation and a first slide.

Input and outcome

Entry point

Slides → in-memory tile or slide embeddings

embed_slide(), embed_slides()

Manifest → saved slide artifacts

run()

A patient’s slides → patient embedding

embed_patient(), embed_patients()

Image files → saved vectors

embed_images()

Slide coordinates or image files → saved dense grids

embed_regions_dense(), embed_images_dense()

Augmented tensors → live dense grids

prepare_dense_encoder()

EmbeddedSlide

embed_slide returns one EmbeddedSlide when the run produces a single annotation bag. A string annotation selects one bag; a list selects several and returns them in the requested order. embed_slides returns {sample_id: {annotation: EmbeddedSlide}}. See Annotation-Aware Sampling for examples.

class slide2vec.EmbeddedSlide(*, sample_id, tile_embeddings, slide_embedding, x, y, tile_size_lv0, image_path, mask_path=None, annotation=None, num_tiles=None, mask_preview_path=None, tiling_preview_path=None, latents=None, encoder_input_size_px=None)

Bases: object

In-memory result of embedding a single slide.

sample_id: str

Unique slide identifier.

tile_embeddings: Any

Tile embeddings — torch.Tensor of shape (N, D).

slide_embedding: Any | None

Slide-level embedding — torch.Tensor of shape (D,) for slide-level encoders; None for tile-only encoders.

x: Any

x coordinate (pixels at level 0) of each tile’s top-left corner — array of shape (N,).

y: Any

y coordinate (pixels at level 0) of each tile’s top-left corner — array of shape (N,).

tile_size_lv0: int

Tile side length in pixels at level 0.

image_path: Path

Path to the source slide file.

mask_path: Path | None = None

Path to the tissue mask used for tiling, if any.

annotation: str | None = None

Annotation class this bag of tiles was sampled for. "tissue" for the default tissue-only path, "merged" for the union output mode, or the class name (e.g. "tumor") when annotation-aware sampling fans a slide out into one bag per class. See the annotation-aware sampling documentation.

num_tiles: int | None = None

Number of tiles extracted from the slide.

mask_preview_path: Path | None = None

Path to the mask preview image, if generated.

tiling_preview_path: Path | None = None

Path to the tiling preview image, if generated.

latents: Any | None = None

Encoder latent representations when available; None otherwise.

encoder_input_size_px: int | None = None

Factual square tensor side length immediately before tile encoding.

PreprocessingConfig

See Preprocessing for the PreprocessingConfig field reference, readers, segmentation, annotation sampling, and previews.

ExecutionOptions

class slide2vec.ExecutionOptions(*, output_dir=None, output_format='pt', batch_size=32, num_workers_per_gpu=None, num_preprocessing_workers=None, num_gpus=None, precision=None, output_dtype=None, prefetch_factor=4, save_tile_embeddings=False, save_slide_embeddings=False, save_latents=False)

Bases: object

Runtime execution and output settings.

output_dir: Path | None = None

Directory where artifacts are written. Required for Pipeline runs.

output_format: str = 'pt'

Tensor serialization format — "pt" (PyTorch, default) or "npz" (NumPy).

batch_size: int = 32

Number of tiles per forward pass.

num_workers_per_gpu: int | None = None

DataLoader worker count per GPU rank. None means auto (capped by CPU / SLURM limit, then split across the resolved GPU count). Image-only routes safely use zero when auto selection happens after model loading.

num_preprocessing_workers: int | None = None

Tiling worker count. None means auto (capped by CPU / SLURM limit).

num_gpus: int | None = None

Number of GPUs to use. None defaults to all available GPUs.

precision: str | None = None

Forward-pass dtype — "fp16", "bf16", "fp32", or None (auto-determined from the model preset).

output_dtype: str | None = None

Feature output dtype — "fp16", "fp32", or None to follow precision (fp16 → fp16, else fp32). Applies to live dense grids and to tile, slide, hierarchical, and patient artifacts; "bf16" is rejected because persisted features cross a numpy boundary.

prefetch_factor: int = 4

DataLoader prefetch queue depth per worker (default 4).

save_tile_embeddings: bool = False

Persist tile embeddings to disk when running a slide-level model.

save_slide_embeddings: bool = False

Persist slide embeddings to disk when running a patient-level model.

save_latents: bool = False

Persist encoder latent representations when available.

Pipeline

Use Pipeline for manifest-driven batch processing and disk outputs:

from slide2vec import ExecutionOptions, Model, Pipeline, PreprocessingConfig

model = Model.from_preset("virchow2")
pipeline = Pipeline(
    model=model,
    preprocessing=PreprocessingConfig(
        requested_spacing_um=0.5,
        requested_tile_size_px=224,
        masks={"min_coverage": {"tissue": 0.1}},
    ),
    execution=ExecutionOptions(output_dir="outputs/demo", num_gpus=2),
)

result = pipeline.run(manifest_path="/path/to/slides.csv")

See Input Manifest for the full manifest schema.

Pipeline.run(...) returns a RunResult:

class slide2vec.RunResult(*, tile_artifacts, hierarchical_artifacts, slide_artifacts, patient_artifacts=<factory>, process_list_path=None)

Bases: object

Return value of Pipeline.run().

tile_artifacts: list[TileEmbeddingArtifact]

Tile embedding artifacts written to disk.

hierarchical_artifacts: list[HierarchicalEmbeddingArtifact]

Hierarchical embedding artifacts; empty when hierarchical mode is disabled.

slide_artifacts: list[SlideEmbeddingArtifact]

Slide embedding artifacts written to disk.

patient_artifacts: list[PatientEmbeddingArtifact]

Patient embedding artifacts; empty when no patient-level model is used.

process_list_path: Path | None = None

Path to process_list.csv, which tracks processing status per sample.

See Output Layout for the full on-disk directory structure and file schemas.

Per-slide completion callback

Pipeline.run_with_coordinates(coordinates_dir, *, slides=None, on_slide_persisted=None) and Model.embed_tiles(slides, tiling_results, *, preprocessing=None, execution=None, on_slide_persisted=None) accept an optional on_slide_persisted callable. slide2vec calls it in the calling process, synchronously, once per persisted (sample_id, annotation) work unit with that unit’s TileEmbeddingArtifact (or HierarchicalEmbeddingArtifact under hierarchical preprocessing), after the artifact file is complete on disk and before the entry point returns. With num_gpus > 1 it fires as each rank reports a finished slide, not after the whole stage returns.

def commit(artifact):
    print(artifact.sample_id, artifact.path)

result = pipeline.run_with_coordinates("outputs/demo", on_slide_persisted=commit)

Zero-tile slides, slides skipped by resume, and slides that fail do not fire the callback. An exception raised inside it propagates out of the entry point. The return value is unchanged and still lists every artifact.

Hierarchical Feature Extraction

Enable hierarchical mode by setting region_tile_multiple in PreprocessingConfig:

from slide2vec import PreprocessingConfig

preprocessing = PreprocessingConfig(
    requested_spacing_um=0.5,
    requested_tile_size_px=224,
    region_tile_multiple=6,   # 6×6 = 36 tiles per region
)

The tile embeddings tensor will have shape (R, T, D) instead of (N, D). See Hierarchical Features for the full explanation.

Patient-level embedding

For patient-level models, use embed_patient() for a single patient or embed_patients() for a batch.

Single patient

from slide2vec import Model

model = Model.from_preset("moozy")
result = model.embed_patient(
    ["/data/slide_1a.svs", "/data/slide_1b.svs"],
    patient_id="patient_1",
)

print(result.patient_id)              # "patient_1"
print(result.patient_embedding.shape) # torch.Size([768])
print(result.slide_embeddings)        # {"slide_1a": tensor, "slide_1b": tensor}

Multiple patients

results = model.embed_patients(
    [
        {"sample_id": "slide_1a", "image_path": "/data/slide_1a.svs", "patient_id": "patient_1"},
        {"sample_id": "slide_1b", "image_path": "/data/slide_1b.svs", "patient_id": "patient_1"},
        {"sample_id": "slide_2a", "image_path": "/data/slide_2a.svs", "patient_id": "patient_2"},
    ]
)

for r in results:
    print(r.patient_id, r.patient_embedding.shape)

embed_patients(...) returns one EmbeddedPatient per unique patient, ordered by first appearance.

class slide2vec.EmbeddedPatient(*, patient_id, patient_embedding, slide_embeddings)

Bases: object

In-memory result of embedding a single patient.

patient_id: str

Unique patient identifier.

patient_embedding: Any

Aggregated patient embedding — torch.Tensor of shape (D,).

slide_embeddings: dict[str, Any]

Slide-level embeddings keyed by sample_id — each a torch.Tensor of shape (D,).

Images to Embeddings

When your images already exist as files — a patch benchmark (BACH, CRC, PCam, …) or an exported ROI set — there is no slide to tile. embed_images() encodes those images directly and writes one embedding artifact per image:

from slide2vec import ExecutionOptions, ImageSpec, Model

model = Model.from_preset("virchow2")
artifacts = model.embed_images(
    [
        ImageSpec(sample_id="bach-001", image_path="/data/bach/001.tif"),
        ImageSpec(sample_id="bach-002", image_path="/data/bach/002.tif"),
    ],
    execution=ExecutionOptions(output_dir="outputs/bach", num_gpus=2),
)

print(artifacts[0].path)         # outputs/bach/image_embeddings/bach-001.pt
print(artifacts[0].feature_dim)  # 2560

The run uses the GPUs selected by ExecutionOptions.num_gpus and resumes automatically when repeated with the same output directory. sample_id is the artifact’s identity and must be unique within a run; slide2vec never derives it from the filename. Mixed-size inputs are supported: each image goes through the encoder’s shipped transform before batching. spacing_at_level_0 is not accepted by this pooled image API; use dense extraction when physical spacing is part of the request.

class slide2vec.ImageSpec(*, sample_id, image_path, spacing_at_level_0=None)

Bases: object

One named image source: (sample_id, image_path, spacing_at_level_0).

The input unit of Model.embed_images() — the Given-geometry counterpart of SlideRegions. spacing_at_level_0 is the optional finite positive caller declaration used by hs2p to resolve source level-0 spacing. Flat PNG/JPEG sources have no embedded spacing and therefore require it for dense extraction. sample_id is the artifact’s whole identity, so it must be unique within a run and a valid filename component.

sample_id: str
image_path: str | Path
spacing_at_level_0: float | None = None

Optional caller declaration for the source’s level-0 spacing in µm/px.

class slide2vec.ImageEmbeddingArtifact(*, sample_id, path, metadata_path, format, feature_dim)

Bases: object

One persisted given-geometry image embedding: the (D,) payload + its sidecar.

The unit of slide2vec.api.Model.embed_images(): a caller holding pre-cropped tile images (a public patch benchmark) gets one artifact per image, named by the sample_id it supplied. Unlike a tile artifact this holds a single vector, not a bag — the image is the sample.

sample_id: str
path: Path
metadata_path: Path
format: str
feature_dim: int
property metadata: dict[str, Any]

Live Dense Encoding after Augmentation

Use prepare_dense_encoder() when your training or inference loop already owns image/mask reading and joint augmentation. You hand slide2vec one CPU RGB uint8 tensor in (3, H, W) layout; slide2vec owns normalization, padding, device transfer, and the frozen no-grad encode.

import torch

from slide2vec import DenseImageOptions, ExecutionOptions, Model

model = Model.from_preset("virchow2", device="cuda")
kit = model.prepare_dense_encoder(
    dense=DenseImageOptions(
        target_size=(1024, 768),
        window_size=224,
        overlap=0.5,
        feature_kind="patch_features",
    ),
    execution=ExecutionOptions(precision="fp16", output_dtype="fp32"),
)

preprocess = kit.preprocessor()  # lightweight and safe to pickle into workers
items = [preprocess(augmented_rgb_uint8_chw) for augmented_rgb_uint8_chw in images]
cpu_batch = torch.stack(items)    # batching starts after item preprocessing
grids = kit.encode(cpu_batch)

print(cpu_batch.shape)            # (B, 3, Henc, Wenc), on CPU
print(grids.shape)                # (B, D, Gh, Gw), on the model device

preprocessor() accepts one unbatched CPU uint8 RGB tensor whose (H, W) equals kit.geometry.target_size — it never resizes or crops. It applies the encoder’s normalization and bottom/right padding and returns a CPU floating-point tensor ready for normal DataLoader collation.

encode(batch) moves the collated batch to the model device and returns an on-device grid with no gradient history, in ExecutionOptions.output_dtype (or the same precision-derived default as persisted dense extraction). D is the patch-feature dimension for feature_kind="patch_features"; for "cls_attention" it is the selected block/head/prefix-query channel count.

The immutable kit.geometry is authoritative:

  • target_size — required augmented input (H, W);

  • patch_size — encoder patch (Ph, Pw);

  • encoded_size — padded encoder input (Henc, Wenc);

  • grid_shape — output (Gh, Gw);

  • pad(bottom, right) padding;

  • crop_box(left, top, right, bottom) box for mapping the padded extent back to the target.

The kit uses only the encoding fields of DenseImageOptions / DenseOptions (target_size, padding, window/overlap, feature kind, attention selection); source-reading fields are ignored, and this path never reads files or writes artifacts. Reuse one prepared kit across loops or folds with the same geometry.

class slide2vec.DenseEncodeKit(loaded, plan)

Bases: object

A shareable live dense encoder for already-augmented RGB tensors.

This is deliberately a plain object rather than torch.nn.Module; the foundation encoder is private and cannot be registered accidentally by assigning the kit to a trainable decoder.

property geometry: DenseEncodeGeometry

The authoritative immutable input, padding, and output-grid geometry.

preprocessor()

Return the serializable itemwise CPU preprocessor.

Return type:

Callable[[Tensor], Tensor]

encode(batch)

Encode a collated CPU batch into a live on-device grid.

Return type:

Tensor

class slide2vec.DenseEncodeGeometry(target_size, patch_size, encoded_size, grid_shape, pad, crop_box)

Bases: object

Immutable geometry of a live dense encoding run.

All sizes are (height, width) except crop_box, which follows the common (left, top, right, bottom) convention. Padding is bottom/right only.

target_size: tuple[int, int]
patch_size: tuple[int, int]
encoded_size: tuple[int, int]
grid_shape: tuple[int, int]
pad: tuple[int, int]
crop_box: tuple[int, int, int, int]

Persisted Region Grids

embed_regions_dense() accepts level-0 point coordinates and the same optional source-spacing declaration as dense images:

from slide2vec import DenseOptions, ExecutionOptions, Model, SlideRegions

model = Model.from_preset("virchow2")
artifacts = model.embed_regions_dense(
    [SlideRegions(
        sample_id="slide-1",
        image_path="/data/slide-1.svs",
        coordinates=[[1024, 2048], [4096, 2048]],
        spacing_at_level_0=0.252,
    )],
    dense=DenseOptions(spacing_um=0.5, target_size=224),
    execution=ExecutionOptions(output_dir="outputs"),
)

Coordinates stay in the source level-0 pixel frame. Encoder inputs that differ from the encoder’s registered size require an encoder that supports variable input; slide2vec derives the necessary model settings from the declared geometry, and fixed-input encoders fail before any region is read.

Dense Grids from Images

When the supervision arrives as image/mask pairs rather than slides — segmentation and detection datasets, exported ROI sets — embed_images_dense() is the image-sourced counterpart of embed_regions_dense: the image is the region, so there is no ROI coordinate plan.

from slide2vec import DenseImageOptions, ExecutionOptions, ImageSpec, Model

model = Model.from_preset("virchow2")
artifacts = model.embed_images_dense(
    [
        ImageSpec(sample_id="ocelot-001", image_path="/data/ocelot/001.jpg",
                  spacing_at_level_0=0.25),
        ImageSpec(sample_id="ocelot-002", image_path="/data/ocelot/002.jpg",
                  spacing_at_level_0=0.25),
    ],
    dense=DenseImageOptions(
        target_size=1024,
        spacing_um=0.5,
        window_size=224,
    ),
    execution=ExecutionOptions(output_dir="outputs/ocelot", num_gpus=2),
)

print(artifacts[0].path)        # outputs/ocelot/dense_image_embeddings/ocelot-001.pt
print(artifacts[0].grid_shape)  # (74, 74) after padding 1024 to 1036 pixels

The image and region APIs share padding, whole-image or sliding-window encoding, and the feature_kind choice. The run uses the GPUs selected by ExecutionOptions.num_gpus and automatically reuses compatible artifacts when repeated with the same output directory.

PNG/JPEG inputs require ImageSpec.spacing_at_level_0, because they carry no embedded physical spacing. target_size is a declaration, not a resize: every image must arrive at exactly target_size after reading, so a dataset whose images differ in size is several runs, one per geometry.

class slide2vec.DenseImageOptions(*, target_size, spacing_um=None, tolerance=0.05, backend='auto', pad_mode='reflect', image_pad_value=None, window_size=None, overlap=0.0, feature_kind='patch_features', attention_blocks=(-1,), attention_include_registers=False)

Bases: object

Dense (d, gh, gw) extraction over pre-cropped images.

Every supported source uses hs2p’s spacing-aware reader contract. .png, .jpg, and .jpeg inputs use hs2p’s one-level PIL reader and therefore require ImageSpec.spacing_at_level_0; WSI readers may resolve native metadata instead. hs2p resolves source spacing, backend, pyramid level, tolerance, complete-extent read, and permitted area downsampling at the requested run-level spacing_um. Omitted spacing resolves the encoder’s single registry default.

ImageSpec.spacing_at_level_0 is the optional caller declaration used by hs2p when resolving level-0 spacing. target_size is always a strict post-read declaration, never a fit-to-size request: each final pixel array must already be exactly this size. Declaring it up front lets the effective encoder input be validated (and variable-input constructor settings resolved) before model loading or pixel decoding. Differing final geometries therefore require separate runs.

target_size: int | tuple[int, int]

a square side length, or an explicit (height, width) for non-square images.

Type:

Supervision geometry in pixels the dense grid registers to

spacing_um: float | None = None

Positive, finite requested run spacing in µm/px. None resolves the encoder’s single registry default.

tolerance: float = 0.05

Relative spacing tolerance used by hs2p for spacing-readable level selection; raster reads have no tolerance result.

backend: str = 'auto'

Requested hs2p backend for spacing-readable inputs. "auto" is resolved in the parent; raster images always resolve to Pillow.

pad_mode: str = 'reflect'

Padding mode used to pad the image up to the encoder’s patch multiple. One of "reflect" / "replicate" / "constant" / "zero".

image_pad_value: float | None = None

Constant fill value for pad_mode in {"constant", "zero"} (ignored otherwise).

window_size: int | None = None

Encoder field-of-view chunk fed through the backbone per forward. None (default) is one whole-image forward; a smaller value slides the encoder and blends token grids.

overlap: float = 0.0

Fractional window overlap in [0, 1) for the sliding path (ignored when window_size is None).

feature_kind: str = 'patch_features'

"patch_features" (the patch-token grid) or "cls_attention" (CLS/register self-attention grid).

attention_blocks: tuple[int, ...] = (-1,)

Transformer blocks whose CLS attention is read (cls_attention only).

attention_include_registers: bool = False

Include register-token query rows as extra attention channels (cls_attention only).

class slide2vec.DenseImageArtifact(*, sample_id, path, metadata_path, feature_dim, grid_shape)

Bases: object

One persisted dense grid over a pre-cropped image: payload + geometry sidecar.

The unit of slide2vec.api.Model.embed_images_dense(). Same payload as a DenseRegionArtifact — a (d, gh, gw) grid plus the geometry that produced it — but named the way a given-geometry input can be named: by the caller’s sample_id alone, since there is no slide, no level-0 coordinate and no sampled class.

sample_id: str
path: Path
metadata_path: Path
feature_dim: int
grid_shape: tuple[int, int]
property metadata: dict[str, Any]

Dense Attention Map Extraction

Most ViT tile encoders can also return their per-head prefix-token self-attention as a dense spatial grid. This is the attention analog of encode_tiles_dense and uses the same get_normalization_transform().

  • encode_tiles_attention(batch, *, blocks=(-1,), include_registers=False) accepts a normalized (B, C, H, W) tensor and returns (B, K, h, w).

  • K = len(blocks) * (1 + M·include_registers) * nh, where nh is the head count and M the model’s register-token count. Heads are never reduced.

  • Channels are stacked in the deterministic order [block][cls, reg…][head]. The CLS channels do not depend on include_registers — registers only append channels.

  • blocks selects transformer blocks (negative indices count from the end); include_registers adds the register-token query rows for models that carry them (e.g. Hibou).

Example:

import torch
from PIL import Image

from slide2vec.encoders import encoder_registry

encoder = encoder_registry.require("lunit")().to("cuda")
transform = encoder.get_normalization_transform()

tile = Image.open("/data/tile.png").convert("RGB")
batch = transform(tile).unsqueeze(0).to(encoder.device)

with torch.no_grad():
    attn = encoder.encode_tiles_attention(batch)  # last block, CLS only

print(attn.shape)  # (1, nh, 28, 28) for a 224 px Lunit tile

Each value is a softmax weight: one query row’s attention over the patch keys, so values are non-negative and a channel’s spatial sum is <= 1 (the prefix-token key columns carry the remaining mass). The input must be divisible by the encoder patch size.

Method and artifact reference

class slide2vec.Model(*, name, device='auto', output_variant=None, allow_non_recommended_settings=False)

Bases: object

classmethod from_preset(name, *, output_variant=None, allow_non_recommended_settings=False, device='auto')
Return type:

Model

property device: Any
property feature_dim: int
prepare_dense_encoder(*, dense, execution=None)

Prepare live dense encoding for one augmented RGB tensor at a time.

Only the encoding fields shared by DenseImageOptions and DenseOptions apply after the augmented-pixel handoff. Source-reading fields (spacing, tolerance, and backend) are intentionally ignored.

embed_tiles(slides, tiling_results, *, preprocessing=None, execution=None, on_slide_persisted=None)
Return type:

list[TileEmbeddingArtifact] | list[HierarchicalEmbeddingArtifact]

aggregate_tiles(tile_artifacts, *, preprocessing=None, execution=None)
Return type:

list[SlideEmbeddingArtifact]

embed_slide(slide, *, annotation=None, preprocessing=None, execution=None, sample_id=None, mask_path=None, spacing_at_level_0=None)
Return type:

EmbeddedSlide | list[EmbeddedSlide]

embed_slides(slides, *, annotations=None, preprocessing=None, execution=None)
Return type:

dict[str, dict[str, EmbeddedSlide]]

embed_patient(slides, patient_id=None, *, preprocessing=None, execution=None)

Embed a single patient’s slides and return one EmbeddedPatient.

Convenience wrapper around embed_patients() for the common case where all slides belong to the same patient.

Parameters:
  • slides (Sequence[str | Path | Mapping[str, object] | SlideLike | SlideSpec]) – All slides for this patient.

  • patient_id (str | None) – Optional patient identifier applied to every slide. When omitted, patient_id is read from slide dict keys or object attributes; slides that carry no patient_id fall back to sample_id.

Return type:

EmbeddedPatient

embed_patients(slides, patient_id_map=None, *, preprocessing=None, execution=None)

Embed slides and aggregate them into patient-level embeddings.

Requires a patient-level model (e.g. moozy). For each patient all contributing slide embeddings are aggregated by the model’s encode_patient method.

Parameters:
  • slides (Sequence[str | Path | Mapping[str, object] | SlideLike | SlideSpec]) – Slides to process. Each entry may be a path, a SlideSpec, or a dict with sample_id / image_path keys. When patient_id_map is None a patient_id key in each dict is used to group slides.

  • patient_id_map (dict | None) – Optional explicit {sample_id: patient_id} mapping. When provided it takes precedence over any patient_id key embedded in the slide dicts. When omitted and the slide dicts carry no patient_id, each slide is treated as its own patient.

Return type:

list[EmbeddedPatient]

embed_regions_dense(regions, *, dense, execution=None)

Extract + persist a dense (d, gh, gw) grid per caller-supplied ROI.

The dense counterpart of the pooled coordinate path: each SlideRegions names a slide + a set of level-0 ROI coordinates, and every ROI is read, encoded through the dense transform, and written to dense_embeddings/[<class>/]<sample_id>/<x>_<y>.pt plus a geometry sidecar. The run splits its ROIs across all visible GPUs (execution.num_gpus); num_gpus=1 encodes fully in-process. Resume is automatic — only ROIs whose sidecar has the same source-spacing declaration and resolved hs2p read plan are skipped. Returns one DenseRegionArtifact per input ROI.

The effective encoder input — the padded ROI for a whole-tile run, one patch-aligned window for a sliding one — is declared before any region is read, so a geometry the encoder cannot accept raises here rather than at the first forward pass. Variable-input capable encoders get their registry-declared constructor settings applied automatically; there is nothing for the caller to pass.

Return type:

list[DenseRegionArtifact]

embed_images_dense(images, *, dense, execution=None)

Extract + persist a dense (d, gh, gw) grid per caller-supplied image.

The image-sourced counterpart of embed_regions_dense(), for consumers whose supervision arrives as image/mask pairs rather than as slides (segmentation, detection): each ImageSpec is decoded, run through the encoder’s normalization-only transform, padded up to the encoder’s patch multiple, encoded — whole-image, or by sliding the encoder’s native field and blending the token grids — and written to dense_image_embeddings/<sample_id>.pt plus a geometry sidecar. The run splits its images across all visible GPUs (execution.num_gpus); num_gpus=1 encodes fully in-process. Resume is automatic: an image is skipped only when its payload exists and its sidecar records the same normalized source identity and complete extraction recipe. Returns one DenseImageArtifact per input image, in input order.

Every source is opened through hs2p. PNG/JPEG inputs use hs2p’s one-level PIL reader and require ImageSpec.spacing_at_level_0 because they have no embedded spacing. dense.spacing_um requests one physical read scale, or None resolves the encoder’s single registry default. The parent resolves each source’s metadata, concrete backend, native level, tolerance result, and final geometry before resume; hs2p reads that complete level and area-downsamples when required, but never upsamples. ImageSpec.spacing_at_level_0 is preserved separately from the resolved source spacing and is passed to every hs2p backend.

Everything after reading is shared with the slide path, including the effective encoder input — the padded image for a whole-image run, one patch-aligned window for a sliding one — which is declared before any image is decoded, so a geometry the encoder cannot accept raises here rather than on a torchrun rank’s first forward pass.

dense.target_size is a strict post-read declaration, not a fit-to-size request: every final image must already be that size (a non-square (h, w) is fine). Spacing-driven area downsampling establishes physical scale; it never repairs a mismatch with the declared geometry.

Return type:

list[DenseImageArtifact]

embed_images(images, *, execution=None)

Embed + persist one embedding per caller-supplied image.

The Given-geometry entry point: the caller already holds pre-cropped images — a public patch benchmark (BACH, CRC, Gleason, BreakHis, MHIST, PCam), an exported ROI set — and slide2vec neither tiles nor reads a slide. Each ImageSpec is decoded, preprocessed with the encoder’s shipped transform, encoded, and written to image_embeddings/<sample_id>.pt plus a provenance sidecar. The run splits its images across all visible GPUs (execution.num_gpus); num_gpus=1 encodes fully in-process. Resume is automatic — images whose sidecar already exists are skipped. Returns one ImageEmbeddingArtifact per input image, in input order.

Unlike the pooled and dense paths there is no geometry to declare: the images are heterogeneously sized (2048x1536 beside 96x96) and were never requested, so the encoder’s shipped transform is the contract and slide2vec records the resulting encoder input size as run provenance rather than validating it. That also means preprocessing runs itemwise before stacking — in-process by default, or in spawned loader workers when num_workers_per_gpu is explicit — because differently sized images cannot be stacked before they are resized. ImageSpec.spacing_at_level_0 is rejected here rather than ignored because this path has no slide level-0 read plan.

Return type:

list[ImageEmbeddingArtifact]

class slide2vec.Pipeline(model, preprocessing, *, execution=None)

Bases: object

run(slides=None, manifest_path=None, *, tiling_only=False)
Return type:

RunResult

run_with_coordinates(coordinates_dir, *, slides=None, on_slide_persisted=None)
Return type:

RunResult

class slide2vec.DenseOptions(*, spacing_um, target_size, tolerance=0.05, backend='auto', pad_mode='reflect', image_pad_value=None, window_size=None, overlap=0.0, feature_kind='patch_features', attention_blocks=(-1,), attention_include_registers=False)

Bases: object

Dense (d, gh, gw) grid extraction settings (issue #217).

The dense counterpart of the pooled PreprocessingConfig: it names the extraction geometry (spacing → level, supervision target_size, padding) and the dense encode knobs (whole-tile vs sliding-window, patch grid vs CLS-attention). Unlike the pooled path there is no tiling — the caller supplies ROI coordinates directly (see SlideRegions) — so a DenseOptions carries only what slide2vec needs to read and encode each ROI. ExecutionOptions is reused unchanged for output/precision/GPUs.

spacing_um: float

Target spacing in µm/px the ROI is read at (resolved to a pyramid level per slide).

target_size: int

Supervision tile side length in pixels at spacing_um (the dense grid registers to it).

tolerance: float = 0.05

Relative spacing tolerance for pyramid level selection.

backend: str = 'auto'

Slide reading backend. "auto" resolves per slide (cucim → vips → openslide → asap).

pad_mode: str = 'reflect'

Padding mode used to pad the tile up to the encoder’s patch multiple. One of "reflect" / "replicate" / "constant" / "zero".

image_pad_value: float | None = None

Constant fill value for pad_mode in {"constant", "zero"} (ignored otherwise).

window_size: int | None = None

Encoder field-of-view chunk fed through the backbone per forward. None (default) is one whole-tile forward; a smaller value slides the encoder and blends token grids. Together with target_size this fixes the effective encoder input — the geometry handed to encode_tiles_dense — from which the encoder’s variable-input constructor settings are derived; hence no dynamic_img_size knob here.

overlap: float = 0.0

Fractional window overlap in [0, 1) for the sliding path (ignored when window_size is None).

feature_kind: str = 'patch_features'

"patch_features" (the patch-token grid) or "cls_attention" (CLS/register self-attention grid).

attention_blocks: tuple[int, ...] = (-1,)

Transformer blocks whose CLS attention is read (cls_attention only).

attention_include_registers: bool = False

Include register-token query rows as extra attention channels (cls_attention only).

class slide2vec.SlideRegions(*, sample_id, image_path, coordinates, annotation=None, spacing_at_level_0=None)

Bases: object

One slide’s ROIs for dense extraction: (sample_id, image_path, coordinates, annotation).

The dense input unit soma’s slide-manifest path hands to Model.embed_regions_dense(). coordinates is an (N, 2) array of level-0 top-left (x, y) pixel coordinates; each ROI is read + encoded into one persisted (d, gh, gw) grid named <x>_<y>.pt. spacing_at_level_0 optionally declares the source image’s level-0 spacing when metadata is missing or must be overridden. annotation namespaces the output under a per-class subdirectory (reusing the pooled convention); None is the flat layout.

sample_id: str
image_path: str | Path
coordinates: Any
annotation: str | None = None
spacing_at_level_0: float | None = None

Optional caller declaration for the source’s level-0 spacing in µm/px.

class slide2vec.TileEmbeddingArtifact(*, sample_id, path, metadata_path, format, feature_dim, num_tiles, annotation=None)

Bases: object

sample_id: str
path: Path
metadata_path: Path
format: str
feature_dim: int
num_tiles: int
annotation: str | None = None
property metadata: dict[str, Any]
class slide2vec.HierarchicalEmbeddingArtifact(*, sample_id, path, metadata_path, format, feature_dim, num_regions, tiles_per_region, annotation=None)

Bases: object

sample_id: str
path: Path
metadata_path: Path
format: str
feature_dim: int
num_regions: int
tiles_per_region: int
annotation: str | None = None
property metadata: dict[str, Any]
class slide2vec.SlideEmbeddingArtifact(*, sample_id, path, metadata_path, format, feature_dim, latent_path=None, annotation=None)

Bases: object

sample_id: str
path: Path
metadata_path: Path
format: str
feature_dim: int
latent_path: Path | None = None
annotation: str | None = None
property metadata: dict[str, Any]
class slide2vec.DenseRegionArtifact(*, sample_id, x, y, path, metadata_path, feature_dim, grid_shape, annotation=None)

Bases: object

One persisted dense ROI grid: the (d, gh, gw) payload + its geometry sidecar.

Dense emits one directory per slide (dense_embeddings/[<class>/]<sample_id>/) and one <x>_<y>.pt / <x>_<y>.meta.json pair per ROI — the counterpart of the pooled one-file-per-slide artifacts. Named from what slide2vec knows (slide + level-0 top-left coordinate); soma maps its ROI sample_id back onto (x, y).

sample_id: str
x: int
y: int
path: Path
metadata_path: Path
feature_dim: int
grid_shape: tuple[int, int]
annotation: str | None = None
property metadata: dict[str, Any]

Encoder provider diagnostics

class slide2vec.EncoderProviderDiagnostic(provider_key, provider, exception_type, message)

Bases: object

Public, immutable description of one skipped installed provider.

provider_key: str
provider: str
exception_type: str
message: str
concise()

Format the diagnostic without traceback details.

Return type:

str

slide2vec.list_encoder_provider_diagnostics()

List deterministic diagnostics for installed providers skipped at discovery.

Return type:

tuple[EncoderProviderDiagnostic, ...]

See Model Zoo for provider packaging and discovery behavior.