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, transform=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

Slide encoder latent representations when ExecutionOptions.save_latents is set and the encoder has them (PRISM: shape (512, 1280)); None otherwise.

encoder_input_size_px: int | None = None

Factual square tensor side length immediately before tile encoding.

transform: dict[str, Any] | None = None

Record of the image transform applied before tile encoding (JSON data).

PreprocessingConfig

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

Encoder authoring

See Bring your own encoder for the two ways to make a new preset importable.

slide2vec.register_encoder(name, *, encode_dim=None, output_variants=None, default_output_variant=None, input_size=None, supports_variable_input_size=None, variable_input_model_kwargs=None, patch_size=None, level='tile', tile_encoder=None, tile_encoder_output_variant=None, aggregation_revision=None, supported_spacing_um, default_spacing_um=None, precision='fp16', source='')

Decorator that registers an encoder class with required metadata.

Parameters:
  • name (str) – Unique encoder name (e.g. “uni2”, “virchow2”).

  • encode_dim (int | None) – Feature dimension of an encoder with a single output. Shorthand for output_variants={"default": {"encode_dim": encode_dim}} with default_output_variant="default"; pass one form or the other.

  • output_variants (dict[str, dict[str, Any]] | None) – Supported named encoder outputs with concrete metadata.

  • default_output_variant (str | None) – Default output variant name.

  • input_size (int | None) – Default final square model input size in pixels. Declared pooled runs read tiles at this size and encode exactly this size.

  • supports_variable_input_size (bool | None) – Explicit end-to-end capability for accepting exact non-preset square inputs in pooled extraction. Required for tile encoders; slide and patient encoders inherit their tile dependency.

  • variable_input_model_kwargs (dict[str, Any] | None) – Constructor settings required to activate that capability. Pooled planning owns their use; callers do not.

  • patch_size (int | tuple[int, int] | None) – Backbone patch size, as int (square) or (patch_h, patch_w). Optional: only dense-capable ViT tile encoders have a meaningful patch grid. Declared statically so the dense token grid / cache key can be resolved via resolve_patch_size() WITHOUT instantiating the (multi-GB) encoder; the model-load path asserts this static value still equals the loaded model’s runtime patch_size.

  • level (str) – Encoder output level (“tile”, “slide”, or “patient”).

  • tile_encoder (str | None) – Registered tile encoder dependency for slide/patient models.

  • tile_encoder_output_variant (str | None) – Fixed tile output variant for slide/patient models.

  • aggregation_revision (int | None) – Revision of a slide/patient encoder’s aggregation. Bump it when a fix changes the embedding produced from the same tile embeddings. Sidecars record it, so resume does not reuse embeddings from another revision.

  • supported_spacing_um (float | list[float] | None) – The spacing(s) in µm/px the model was trained/validated for; validate_encoder_config() rejects requests outside this set unless allow_non_recommended_settings=True. None marks a spacing-agnostic encoder (e.g. a natural-image control): the spacing check is skipped entirely because no spacing is more “correct” than another. Agnostic encoders MUST pair this with an explicit default_spacing_um so name-only selection still resolves a tiling spacing.

  • default_spacing_um (float | None) – The single spacing in µm/px used to tile a slide when the caller selects this encoder by name without passing an explicit requested_spacing_um. Optional: when omitted it is derived from supported_spacing_um if that is a single value. Encoders that support a list of spacings, or are spacing-agnostic (supported_spacing_um=None), have no derivable default and must declare one here to be selectable with zero config (otherwise resolve_preprocessing_defaults() requires an explicit spacing).

  • precision (str) – Recommended inference precision (“fp16” or “fp32”).

  • source (str) – Model source identifier (e.g. HuggingFace hub path).

class slide2vec.TorchTileEncoder(model, *, encode_dim, input_size, mean, std, output_variant=None)

Bases: TileEncoder

Base for a tile encoder built on one torch.nn.Module.

Owns the module, its device and encode_dim, and builds both transforms from the model’s photometrics: get_transform resizes a given image to input_size then normalizes; get_normalization_transform only normalizes. The module must map a (B, 3, H, W) float batch to (B, encode_dim). Load weights in the subclass constructor and hand the module over; it is switched to eval here.

Pooled extraction only. A subclass with a recoverable patch grid overrides encode_tiles_dense and patch_size together (see TimmTileEncoder).

get_transform()

Image transform pipeline (PIL Image or ndarray -> Tensor).

Return type:

Callable

get_normalization_transform()

Geometry-preserving photometric transform. Required for every tile encoder.

Returns a transform that applies ONLY this encoder’s photometrics — dtype conversion, intensity scaling, channel handling and per-channel mean/std normalization — with no Resize and no CenterCrop. Every declared run (pooled slide tiles and dense ROIs) encodes exactly the geometry it requested through this transform; get_transform (the shipped recipe, which may resize-then-center-crop) is applied only to given pre-cropped images. register_encoder rejects a tile encoder that does not override it.

Return type:

Callable

encode_tiles(batch)

Encode a batch of tiles. (B, C, H, W) -> (B, D).

Return type:

Tensor

property encode_dim: int

Dimensionality of the output feature vector.

property device: device

Current device of the encoder.

to(device)

Move encoder to the given device. Returns self.

Return type:

TorchTileEncoder

class slide2vec.TimmTileEncoder(model_name, *, output_variant=None, **timm_kwargs)

Bases: TorchTileEncoder

Convenience base for timm-backed tile encoders.

Creates the timm model and adds the dense grid, pre-norm and attention taps a timm ViT exposes. Both transforms and encode_dim come from the model’s own pretrained_cfg and num_features; get_transform is timm’s eval recipe.

get_transform()

Image transform pipeline (PIL Image or ndarray -> Tensor).

Return type:

Callable

get_normalization_transform()

Geometry-preserving photometric transform. Required for every tile encoder.

Returns a transform that applies ONLY this encoder’s photometrics — dtype conversion, intensity scaling, channel handling and per-channel mean/std normalization — with no Resize and no CenterCrop. Every declared run (pooled slide tiles and dense ROIs) encodes exactly the geometry it requested through this transform; get_transform (the shipped recipe, which may resize-then-center-crop) is applied only to given pre-cropped images. register_encoder rejects a tile encoder that does not override it.

Return type:

Callable

property encode_dim: int

Dimensionality of the output feature vector.

property patch_size: tuple[int, int]

Backbone patch size (patch_h, patch_w) — only for dense encoders.

Encoder-authoritative (a property of the frozen model), used to resolve the dense token grid. Default: unsupported, mirroring encode_tiles_dense.

encode_tiles_dense(batch)

Encode tiles into a dense spatial grid. (B, C, H, W) -> (B, d, h, w).

Runs the frozen backbone’s forward_features and folds the patch-token sequence back into its spatial grid (CLS/register tokens discarded). The backbone must accept batch at its current spatial size (timm ViTs need dynamic_img_size=True for sizes other than their native input), and H, W must be divisible by the patch size.

Return type:

Tensor

encode_tiles_dense_prenorm(batch)

Pre-norm dense grid via timm’s forward_intermediates(norm=False).

indices=1 selects the last block, output_fmt="NCHW" folds the patch tokens into the grid (prefix tokens dropped), norm=False skips the final LayerNorm — exactly timm’s features_only=True, out_indices=1 path.

Return type:

Tensor

encode_tiles_attention(batch, *, blocks=(-1,), include_registers=False)

Encode tiles into per-head prefix-token attention maps (timm ViT family).

Captures each selected block’s attention input via a forward-pre-hook on blocks[i].attn (the fused SDPA kernel never materializes the attention matrix), then recomputes the softmax weights from the module’s own projection (timm_self_attention_weights()) and folds the prefix-token query rows into spatial grids (prefix_attention_to_grid()). Output is (B, K, h, w) in [block][cls, reg…][head] order — see TileEncoder.encode_tiles_attention().

Return type:

Tensor

class slide2vec.TileEncoder

Bases: Encoder

Base class for encoders that operate directly on image tiles.

abstractmethod get_transform()

Image transform pipeline (PIL Image or ndarray -> Tensor).

Return type:

Callable

abstractmethod encode_tiles(batch)

Encode a batch of tiles. (B, C, H, W) -> (B, D).

Return type:

Tensor

encode_tiles_dense(batch)

Encode tiles into a dense spatial feature grid. (B, C, H, W) -> (B, d, h, w).

Default: unsupported. ViT tile encoders with a recoverable patch grid override this; vision-language / slide-native encoders (no usable patch grid) do not. d is the per-token feature dim and h, w the token grid (H / patch, W / patch).

Return type:

Tensor

encode_tiles_dense_prenorm(batch)

Dense grid tapped before the backbone’s final normalisation. (B, C, H, W) -> (B, d, h, w).

Same grid as encode_tiles_dense() but from the last block’s output, before the final LayerNorm — the tap timm’s features_only backbones expose (forward_intermediates(norm=False)) and that kaiko-ai/eva’s segmentation decoders were trained on. Default: unsupported; the timm ViT base overrides it.

Return type:

Tensor

encode_tiles_attention(batch, *, blocks=(-1,), include_registers=False)

Encode tiles into per-head CLS/register self-attention maps.

(B, C, H, W) -> (B, K, h, w) where each channel is one prefix-token query row’s self-attention over the patch grid for one head, stacked in the deterministic order [block][cls, reg…][head]. K = len(blocks) * (1 + M·include_registers) * nh with M the model’s register-token count (0 for models without them) and nh the head count.

The per-head CLS attention of a frozen ViT doubles as a dense per-pixel feature (Ramchandani et al., arXiv:2602.18747); include_registers adds the register-token query rows (Darcet et al.) as extra, optional channels. Default: unsupported — overridden by ViT tile encoders whose attention blocks can be hooked (timm ViTs and HF transformers ViTs).

Return type:

Tensor

property patch_size: tuple[int, int]

Backbone patch size (patch_h, patch_w) — only for dense encoders.

Encoder-authoritative (a property of the frozen model), used to resolve the dense token grid. Default: unsupported, mirroring encode_tiles_dense.

get_normalization_transform()

Geometry-preserving photometric transform. Required for every tile encoder.

Returns a transform that applies ONLY this encoder’s photometrics — dtype conversion, intensity scaling, channel handling and per-channel mean/std normalization — with no Resize and no CenterCrop. Every declared run (pooled slide tiles and dense ROIs) encodes exactly the geometry it requested through this transform; get_transform (the shipped recipe, which may resize-then-center-crop) is applied only to given pre-cropped images. register_encoder rejects a tile encoder that does not override it.

Return type:

Callable

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, on_image_mismatch='raise')

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. The field keeps the requested value; resolved_num_preprocessing_workers() gives the count a run uses, capped by the CPU / SLURM limit.

num_gpus: int | None = None

Number of GPUs to use. None means all available GPUs. The field keeps the requested value; resolved_num_gpus() gives the count a run uses.

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

Return and persist the slide encoder’s latent representations (PRISM). Encoders without latents ignore it.

on_image_mismatch: str = 'raise'

what to do when an existing image embedding records a different source image_path for its sample_id. "raise" (default) refuses to resume; "reencode" replaces that image’s artifacts.

Type:

Model.embed_images() only

resolved_num_gpus()

GPU count a run uses: num_gpus, or every visible GPU when it is None.

Return type:

int

resolved_num_preprocessing_workers()

Tiling worker count a run uses: num_preprocessing_workers, or the CPU / SLURM limit when it is None, and never more than that limit.

Return type:

int

Pipeline

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

from hs2p import TilingConfig
from slide2vec import ExecutionOptions, Model, Pipeline, PreprocessingConfig

model = Model.from_preset("virchow2")
pipeline = Pipeline(
    model=model,
    preprocessing=PreprocessingConfig(
        tiling=TilingConfig(
            requested_spacing_um=0.5,
            requested_tile_size_px=224,
            tolerance=0.05,
            overlap=0.0,
        ),
    ),
    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 hs2p import TilingConfig
from slide2vec import PreprocessingConfig

preprocessing = PreprocessingConfig(
    tiling=TilingConfig(
        requested_spacing_um=0.5,
        requested_tile_size_px=224,
        tolerance=0.05,
        overlap=0.0,
    ),
    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. Resume reuses an image only when its sidecar records the requested format, the requested image_path and the full feature identity:

  • A different recorded feature identity raises, whatever format the sidecar records.

  • A different recorded image_path raises by default. Set ExecutionOptions(on_image_mismatch="reencode") to replace that image’s artifacts instead.

  • Missing provenance (no image_path, format or required identity field) and a different format re-encode the image.

Source identity is the resolved path: new bytes written at the same path are not detected. 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]

Checking pooled feature identity

Use pooled_identity_differences() to compare a saved sidecar’s compatibility object with the current extraction recipe. This lets a downstream cache check features before deciding which samples to send to extraction.

For pre-cropped images, explicitly pass preprocessing=None to compare the encoder’s shipped transform:

import json
from pathlib import Path

from slide2vec import ExecutionOptions, Model, PreprocessingConfig

recorded = json.loads(Path("image_embeddings/sample.meta.json").read_text())["compatibility"]
model = Model.from_preset("virchow2")
execution = ExecutionOptions(precision="fp16", output_dtype="fp32")
differences = model.pooled_identity_differences(
    recorded,
    preprocessing=None,
    execution=execution,
)
for field, (old, current) in differences.items():
    print(field, old, current)

For tile, hierarchical, slide, or patient features extracted from slides, pass the PreprocessingConfig used for extraction instead:

differences = model.pooled_identity_differences(
    recorded,
    preprocessing=PreprocessingConfig(
        requested_tile_size_px=224,
        requested_spacing_um=0.5,
    ),
    execution=execution,
)

Unset preprocessing fields and execution precision use the same defaults as extraction. The result is {field: (recorded, current)}, with the same feature identity comparison as resume. A required field the saved identity does not record is reported with slide2vec.MISSING_FIELD as its recorded value, so an empty result certifies every required field. Pass encoded_pixels=False for a zero-tile slide, which records no transform. The caller decides whether to warn, reject, or extract again.

Comparing metadata requires no encoder weights. Comparing the transform loads one isolated CPU encoder to resolve the current recipe. Slide and patient models use their registered tile encoder and tile output variant without loading their aggregation weights. The method preserves the caller’s model, does not encode pixels or write artifacts, and needs no output directory.

Checking dense feature identity

Use dense_identity_differences() to compare a dense sidecar’s compatibility object with the current dense request. Pass the dense options and execution used for extraction: a DenseOptions for region grids, or a DenseImageOptions for image grids:

import json
from pathlib import Path

from slide2vec import DenseImageOptions, ExecutionOptions, Model

recorded = json.loads(
    Path("dense_image_embeddings/sample.meta.json").read_text()
)["compatibility"]
model = Model.from_preset("virchow2")
differences = model.dense_identity_differences(
    recorded,
    dense=DenseImageOptions(target_size=1024, window_size=224),
    execution=ExecutionOptions(precision="fp16", output_dtype="fp32"),
)

The result has the same form as for pooled identities. Only request-wide fields are compared: encoder_name, encoder_code, output_variant, precision, dtype, transform, the encode options (pad_mode, image_pad_value, window_size, overlap, feature_kind, attention_blocks, attention_include_registers) and, for images, target_size, patch_size, encoded_size, pad and grid_shape. Per-sample fields (sample_id, image_path, the read plan and the read sizes) are never reported, so a different spacing_um, backend or tolerance is not reported. An invalid request raises as in extraction.

Comparing the transform loads one isolated CPU encoder. The method preserves the caller’s model, does not encode pixels or write artifacts, and needs no output directory.

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" and "patch_features_prenorm"; for "cls_attention" it is the selected block/head/prefix-query channel count.

"patch_features" is the patch-token grid after the backbone’s final normalisation (forward_features). "patch_features_prenorm" taps the same grid one step earlier, from the last block’s output before the final LayerNorm — timm’s features_only / forward_intermediates(norm=False) tap, which decoders trained on timm feature backbones (for example kaiko-ai/eva’s segmentation decoders) expect. It is available for timm ViT encoders only (EncoderCapabilities.dense_prenorm; the core dependency floor timm>=1.0.3 is the first release with forward_intermediates) and is recorded in the feature identity, so the two taps never share an artifact. Every dense route rejects an unsupported feature kind from registry metadata, before loading a backend or touching an existing artifact.

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 after the backbone’s final norm), "patch_features_prenorm" (the same grid tapped before the final norm, timm’s features_only tap; timm ViT encoders only) 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
pooled_identity_differences(recorded, *, preprocessing, execution=None, encoded_pixels=True)

Compare a pooled sidecar’s compatibility block with the current recipe.

Returns {field: (recorded, current)} for every required field that the record holds with a different value or does not hold. A field the record does not hold is reported as slide2vec.MISSING_FIELD, which is distinct from a recorded None. An empty result means the record proves the current identity. The caller owns the policy for a difference.

Explicitly pass preprocessing=None for pre-cropped images, whose shipped transform determines geometry. Pass a PreprocessingConfig for tiles read from slides; its unset fields resolve as in pooled extraction. execution defaults to the model’s recommended precision and needs no output directory. The transform is required unless encoded_pixels is false: pass encoded_pixels=False for a zero-tile slide, which encodes no pixels and records no transform.

Comparing the transform loads weights, on an isolated CPU model. Slide and patient models load their registered tile encoder and tile output variant, without loading aggregation weights. This method does not change this model’s input contract or loaded backend, encode pixels, or write files.

Return type:

dict[str, tuple[Any, Any]]

dense_identity_differences(recorded, *, dense, execution=None)

Compare a dense sidecar’s compatibility block with the current request.

The dense counterpart of pooled_identity_differences(). Pass the dense options and execution of embed_regions_dense() (a DenseOptions) or embed_images_dense() (a DenseImageOptions). Returns {field: (recorded, current)} for every request-wide field that the record holds with a different value or does not hold. A field the record does not hold is reported as slide2vec.MISSING_FIELD. Per-sample fields (the sample, its path and its read plan) are not compared. An empty result means the record proves the current request-wide identity. The caller owns the policy for a difference.

An invalid request raises as it does in the embed methods. execution defaults to the model’s recommended precision and needs no output directory. Comparing the transform loads weights, on an isolated CPU model. This method does not change this model’s input contract or loaded backend, encode pixels, or write files.

Return type:

dict[str, tuple[Any, Any]]

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 execution.resolved_num_gpus() GPUs (all visible GPUs by default); num_gpus=1 encodes fully in-process. Resume is automatic — only ROIs whose sidecar has the same source-spacing declaration, resolved hs2p read plan and feature identity 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 execution.resolved_num_gpus() GPUs (all visible GPUs by default); 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 execution.resolved_num_gpus() GPUs (all visible GPUs by default); num_gpus=1 encodes fully in-process. Resume is automatic: an image is skipped only when its sidecar records this output_format, the requested image_path and the full feature identity. A different recorded feature identity raises; a different recorded image_path raises unless execution.on_image_mismatch="reencode"; missing provenance re-encodes. 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 after the backbone’s final norm), "patch_features_prenorm" (the same grid tapped before the final norm, timm’s features_only tap; timm ViT encoders only) 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.