slide2vec 5.2.0__tar.gz → 5.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {slide2vec-5.2.0 → slide2vec-5.3.0}/PKG-INFO +2 -2
- {slide2vec-5.2.0 → slide2vec-5.3.0}/README.md +1 -1
- {slide2vec-5.2.0 → slide2vec-5.3.0}/pyproject.toml +2 -2
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/__init__.py +1 -1
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/__init__.py +8 -0
- slide2vec-5.3.0/slide2vec/encoders/models/dinov2.py +56 -0
- slide2vec-5.3.0/slide2vec/encoders/models/genbio.py +155 -0
- slide2vec-5.3.0/slide2vec/encoders/models/gpfm.py +89 -0
- slide2vec-5.3.0/slide2vec/encoders/models/mstar.py +34 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/registry.py +74 -26
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/model_settings.py +4 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/PKG-INFO +2 -2
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/SOURCES.txt +6 -0
- slide2vec-5.3.0/tests/test_dinov2_natimage.py +116 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_encoder_registry.py +51 -0
- slide2vec-5.3.0/tests/test_gpfm_genbio_heavy.py +64 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_patch_size_metadata.py +24 -2
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_regression_core.py +4 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/LICENSE +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/setup.cfg +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/__main__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/api.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/cli.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/inference.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/progress.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_progress.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_regression_inference.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_regression_models.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_tile_store.py +0 -0
- {slide2vec-5.2.0 → slide2vec-5.3.0}/tests/test_tiling_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.3.0
|
|
4
4
|
Summary: Embedding of whole slide images with Foundation Models
|
|
5
5
|
Author-email: Clément Grisi <clement.grisi@radboudumc.nl>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -225,7 +225,7 @@ The package writes explicit artifact directories:
|
|
|
225
225
|
|
|
226
226
|
### Supported Models
|
|
227
227
|
|
|
228
|
-
`slide2vec` currently ships preset configs for
|
|
228
|
+
`slide2vec` currently ships preset configs for 22 tile-level models and 3 slide-level models.
|
|
229
229
|
For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
|
|
230
230
|
|
|
231
231
|
## CLI
|
|
@@ -119,7 +119,7 @@ The package writes explicit artifact directories:
|
|
|
119
119
|
|
|
120
120
|
### Supported Models
|
|
121
121
|
|
|
122
|
-
`slide2vec` currently ships preset configs for
|
|
122
|
+
`slide2vec` currently ships preset configs for 22 tile-level models and 3 slide-level models.
|
|
123
123
|
For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
|
|
124
124
|
|
|
125
125
|
## CLI
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.3.0"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -167,7 +167,7 @@ no_implicit_reexport = true
|
|
|
167
167
|
max-line-length = 160
|
|
168
168
|
|
|
169
169
|
[tool.bumpver]
|
|
170
|
-
current_version = "5.
|
|
170
|
+
current_version = "5.3.0"
|
|
171
171
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
172
172
|
commit = false # We do version bumping in CI, not as a commit
|
|
173
173
|
tag = false # Git tag already exists — we don't auto-tag
|
|
@@ -5,12 +5,16 @@ Importing this package registers all encoders in the encoder_registry.
|
|
|
5
5
|
|
|
6
6
|
from . import (
|
|
7
7
|
conch,
|
|
8
|
+
dinov2,
|
|
9
|
+
genbio,
|
|
8
10
|
gigapath,
|
|
11
|
+
gpfm,
|
|
9
12
|
hibou,
|
|
10
13
|
hoptimus,
|
|
11
14
|
lunit,
|
|
12
15
|
midnight,
|
|
13
16
|
moozy,
|
|
17
|
+
mstar,
|
|
14
18
|
musk,
|
|
15
19
|
phikon,
|
|
16
20
|
prost40m,
|
|
@@ -22,12 +26,16 @@ from . import (
|
|
|
22
26
|
|
|
23
27
|
__all__ = [
|
|
24
28
|
"conch",
|
|
29
|
+
"dinov2",
|
|
30
|
+
"genbio",
|
|
25
31
|
"gigapath",
|
|
32
|
+
"gpfm",
|
|
26
33
|
"hibou",
|
|
27
34
|
"hoptimus",
|
|
28
35
|
"lunit",
|
|
29
36
|
"midnight",
|
|
30
37
|
"moozy",
|
|
38
|
+
"mstar",
|
|
31
39
|
"musk",
|
|
32
40
|
"phikon",
|
|
33
41
|
"prost40m",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Natural-image DINOv2 ViT-B/14 control encoder.
|
|
2
|
+
|
|
3
|
+
``dinov2-vitb14`` is a **non-pathology** ViT: the original DINOv2 ViT-B/14
|
|
4
|
+
(Oquab et al., 2024) self-supervised on LVD-142M *natural* images, shipped by
|
|
5
|
+
``timm`` as ``vit_base_patch14_dinov2.lvd142m`` (weights hosted on Hugging Face
|
|
6
|
+
under ``timm/vit_base_patch14_dinov2.lvd142m`` — public, no gated access).
|
|
7
|
+
|
|
8
|
+
It exists as a **control**: nearly every pathology tile encoder here (UNI,
|
|
9
|
+
Virchow, GigaPath, H-optimus, Midnight, …) is a DINOv2-family ViT, so pairing
|
|
10
|
+
them with a DINOv2 ViT trained on natural images holds the architecture and the
|
|
11
|
+
self-supervised objective fixed and varies only the *pretraining domain*. That
|
|
12
|
+
isolates the question "does pathology-pretraining actually pay off?" for a
|
|
13
|
+
downstream task (e.g. cell detection).
|
|
14
|
+
|
|
15
|
+
Structurally it is a plain :class:`TimmTileEncoder` (mirroring ``lunit`` /
|
|
16
|
+
``prost40m`` / ``uni``): the dense (``encode_tiles_dense``) and attention
|
|
17
|
+
(``encode_tiles_attention``) paths are inherited unchanged from the timm ViT
|
|
18
|
+
base, so the control is dense-extraction- and attention-capable exactly like the
|
|
19
|
+
pathology encoders. ``dynamic_img_size=True`` lets the (natively 518px) backbone
|
|
20
|
+
run at the 224px detection tile geometry via positional-embedding interpolation,
|
|
21
|
+
a no-op at the native size (verified in the shared dense-extraction suite).
|
|
22
|
+
|
|
23
|
+
Spacing note: a natural-image model has **no** intrinsic micron-per-pixel
|
|
24
|
+
spacing, so it declares ``supported_spacing_um=None`` — it is *spacing-agnostic*
|
|
25
|
+
and :func:`validate_encoder_config` never rejects a requested spacing for it
|
|
26
|
+
(unlike the pathology encoders, which are validated at a specific spacing). It
|
|
27
|
+
still needs *a* spacing to tile a slide, so ``default_spacing_um=0.5`` sets the
|
|
28
|
+
tiling default: 0.5 µm/px is the task-spacing the pathology tile encoders
|
|
29
|
+
declare, so selecting this encoder by name lands on identical tile geometry and
|
|
30
|
+
it drops in as a matched control. Because it is agnostic, sweeping other
|
|
31
|
+
task-spacings (e.g. 0.25) needs no ``allow_non_recommended_settings`` escape
|
|
32
|
+
hatch — any requested spacing is accepted as-is.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from slide2vec.encoders.base import TimmTileEncoder
|
|
36
|
+
from slide2vec.encoders.registry import register_encoder
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@register_encoder(
|
|
40
|
+
"dinov2-vitb14",
|
|
41
|
+
output_variants={"default": {"encode_dim": 768}},
|
|
42
|
+
default_output_variant="default",
|
|
43
|
+
input_size=224,
|
|
44
|
+
patch_size=14,
|
|
45
|
+
supported_spacing_um=None, # spacing-agnostic: no intrinsic µm/px, so no validation constraint
|
|
46
|
+
default_spacing_um=0.5, # tiling default: match the pathology encoders' task-spacing
|
|
47
|
+
precision="fp16",
|
|
48
|
+
source="timm/vit_base_patch14_dinov2.lvd142m",
|
|
49
|
+
)
|
|
50
|
+
class DINOv2ViTB14(TimmTileEncoder):
|
|
51
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
52
|
+
super().__init__(
|
|
53
|
+
"vit_base_patch14_dinov2.lvd142m",
|
|
54
|
+
output_variant=output_variant,
|
|
55
|
+
dynamic_img_size=True, # enable dense extraction; no-op at native size
|
|
56
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""GenBio-PathFM tile encoder.
|
|
2
|
+
|
|
3
|
+
GenBio-PathFM (GenBio AI, 2024; ``genbio-ai/genbio-pathfm``) is a 1.1B-param ViT
|
|
4
|
+
histopathology tile encoder (JEDI = JEPA + DINO training on public data). It is
|
|
5
|
+
loaded via HF ``AutoModel(trust_remote_code=True)`` (auto_map ->
|
|
6
|
+
``GenBioPathFMModel``), so it is a custom :class:`TileEncoder`, not a
|
|
7
|
+
``TimmTileEncoder``. Weights are openly downloadable (safetensors); released
|
|
8
|
+
under the custom **GenBio AI Community License** (not OSI-approved — read it for
|
|
9
|
+
acceptable-use / commercial restrictions; slide2vec only wraps user-downloaded
|
|
10
|
+
weights and does not redistribute them).
|
|
11
|
+
|
|
12
|
+
Output dim (4608, verified on real weights): the backbone is a *single-channel*
|
|
13
|
+
ViT (``in_chans=1``, ``embed_dim=1536``). The model's canonical ``forward`` takes
|
|
14
|
+
an RGB ``[B, 3, H, W]`` tensor, treats each colour channel as a separate
|
|
15
|
+
single-channel image, encodes all three, and concatenates the three per-channel
|
|
16
|
+
CLS tokens -> ``[B, embed_dim * 3] = [B, 4608]``. This per-channel-CLS
|
|
17
|
+
concatenation is the model's intrinsic design (matching the HF card's advertised
|
|
18
|
+
feature dimension of 4608), not an ad-hoc CLS+patch pooling; ``encode_tiles``
|
|
19
|
+
therefore returns the model's default ``forward`` output directly. (Confirmed by
|
|
20
|
+
running a ``(1, 3, 224, 224)`` dummy through the real weights -> shape
|
|
21
|
+
``(1, 4608)``; see ``tests/test_gpfm_genbio_heavy.py``.)
|
|
22
|
+
|
|
23
|
+
Normalization is **non-ImageNet** (``config.json`` ``image_mean`` / ``image_std``)
|
|
24
|
+
and must be set explicitly.
|
|
25
|
+
|
|
26
|
+
Dense (spatial-grid) extraction is supported via the model's
|
|
27
|
+
``forward_with_patches`` (a DINOv2-style ``x_norm_patchtokens`` path): it returns
|
|
28
|
+
the fused per-channel patch tokens ``(B, T, 4608)`` — the three single-channel
|
|
29
|
+
patch-token grids concatenated along the feature dim, with the prefix tokens
|
|
30
|
+
(CLS + storage tokens) already stripped — which fold straight into a
|
|
31
|
+
``(B, 4608, h, w)`` grid. The patch size is 16 (a 224 tile -> a 14x14 = 196 grid).
|
|
32
|
+
|
|
33
|
+
Attention-map extraction is **not** supported. The backbone computes attention
|
|
34
|
+
with a fused ``F.scaled_dot_product_attention`` (no materialized weights, no
|
|
35
|
+
``output_attentions``), and — more fundamentally — it encodes the three colour
|
|
36
|
+
channels as three independent single-channel images, so there is no single
|
|
37
|
+
coherent CLS-over-patches attention to extract: any "attention grid" would be
|
|
38
|
+
three separate grayscale-channel attentions. Recovering it would need a bespoke
|
|
39
|
+
per-channel recompute path that diverges from the shared timm/HF attention
|
|
40
|
+
helpers, so GenBio deliberately opts out of ``encode_tiles_attention``.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from typing import Callable
|
|
44
|
+
|
|
45
|
+
import torch
|
|
46
|
+
from torch import Tensor
|
|
47
|
+
from torchvision.transforms import v2
|
|
48
|
+
|
|
49
|
+
from slide2vec.encoders.base import (
|
|
50
|
+
TileEncoder,
|
|
51
|
+
preferred_default_device,
|
|
52
|
+
reshape_tokens_to_grid,
|
|
53
|
+
resolve_requested_output_variant,
|
|
54
|
+
)
|
|
55
|
+
from slide2vec.encoders.registry import register_encoder
|
|
56
|
+
|
|
57
|
+
_HF_REPO_ID = "genbio-ai/genbio-pathfm"
|
|
58
|
+
# Non-ImageNet normalization from the model's config.json (image_mean / image_std).
|
|
59
|
+
_GENBIO_MEAN = (0.697, 0.575, 0.728)
|
|
60
|
+
_GENBIO_STD = (0.188, 0.240, 0.187)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@register_encoder(
|
|
64
|
+
"genbio-pathfm",
|
|
65
|
+
# encode_dim 4608 = embed_dim (1536) x 3 colour channels; patch_size 16 (a 224
|
|
66
|
+
# tile -> a 14x14 = 196 patch-token grid) — see module docstring.
|
|
67
|
+
output_variants={"default": {"encode_dim": 4608}},
|
|
68
|
+
default_output_variant="default",
|
|
69
|
+
input_size=224,
|
|
70
|
+
patch_size=16,
|
|
71
|
+
supported_spacing_um=0.5, # 20x; card states no magnification, house default
|
|
72
|
+
precision="fp32", # upstream runs plain fp32, no autocast
|
|
73
|
+
source="genbio-ai/genbio-pathfm",
|
|
74
|
+
)
|
|
75
|
+
class GenBioPathFM(TileEncoder):
|
|
76
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
77
|
+
from transformers import AutoModel
|
|
78
|
+
|
|
79
|
+
self._model = AutoModel.from_pretrained(_HF_REPO_ID, trust_remote_code=True).eval()
|
|
80
|
+
self._device = preferred_default_device()
|
|
81
|
+
self._output_variant = resolve_requested_output_variant(output_variant)
|
|
82
|
+
|
|
83
|
+
def get_transform(self) -> Callable:
|
|
84
|
+
# Mirrors the model card: Resize to an exact 224x224 (tuple form, as the
|
|
85
|
+
# card's ``Resize((224, 224))``) + custom Normalize. No CenterCrop: the card
|
|
86
|
+
# has none, and after a square resize it would be a no-op anyway.
|
|
87
|
+
return v2.Compose([
|
|
88
|
+
v2.ToImage(),
|
|
89
|
+
v2.Resize((224, 224), interpolation=v2.InterpolationMode.BICUBIC, antialias=True),
|
|
90
|
+
v2.ToDtype(torch.float32, scale=True),
|
|
91
|
+
v2.Normalize(mean=_GENBIO_MEAN, std=_GENBIO_STD),
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
def get_dense_transform(self) -> Callable:
|
|
95
|
+
# Normalization only — no Resize/CenterCrop (see TileEncoder.get_dense_transform),
|
|
96
|
+
# so the dense grid stays registered to the full source tile.
|
|
97
|
+
return v2.Compose([
|
|
98
|
+
v2.ToImage(),
|
|
99
|
+
v2.ToDtype(torch.float32, scale=True),
|
|
100
|
+
v2.Normalize(mean=_GENBIO_MEAN, std=_GENBIO_STD),
|
|
101
|
+
])
|
|
102
|
+
|
|
103
|
+
def encode_tiles(self, batch: Tensor) -> Tensor:
|
|
104
|
+
# Canonical forward: per-channel CLS tokens concatenated -> (B, 4608).
|
|
105
|
+
return self._model(batch)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def patch_size(self) -> tuple[int, int]:
|
|
109
|
+
return (16, 16)
|
|
110
|
+
|
|
111
|
+
def encode_tiles_dense(self, batch: Tensor) -> Tensor:
|
|
112
|
+
"""Encode tiles into a dense spatial grid. (B, C, H, W) -> (B, d, h, w).
|
|
113
|
+
|
|
114
|
+
Uses the model's ``forward_with_patches`` (DINOv2-style), which returns the
|
|
115
|
+
fused per-channel patch tokens ``(B, T, 4608)`` — the three single-channel
|
|
116
|
+
patch-token grids concatenated along the feature dim, with the prefix
|
|
117
|
+
tokens (CLS + storage tokens) already stripped — then folds that token
|
|
118
|
+
sequence back into its spatial grid (``num_prefix_tokens=0``). ``H, W`` must
|
|
119
|
+
be divisible by the patch size; non-224 inputs rely on the backbone's
|
|
120
|
+
DINOv2 positional-embedding interpolation.
|
|
121
|
+
"""
|
|
122
|
+
if batch.ndim != 4:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
"encode_tiles_dense expects a (B, C, H, W) batch, got shape "
|
|
125
|
+
f"{tuple(batch.shape)}."
|
|
126
|
+
)
|
|
127
|
+
_, _, height, width = batch.shape
|
|
128
|
+
patch_h, patch_w = self.patch_size
|
|
129
|
+
if height % patch_h != 0 or width % patch_w != 0:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"Dense extraction for '{type(self).__name__}' requires input "
|
|
132
|
+
f"divisible by the patch size: got {height}x{width}, patch "
|
|
133
|
+
f"{patch_h}x{patch_w}. Pad the tile up to a patch multiple first."
|
|
134
|
+
)
|
|
135
|
+
_, patch_tokens = self._model.forward_with_patches(batch)
|
|
136
|
+
return reshape_tokens_to_grid(
|
|
137
|
+
patch_tokens,
|
|
138
|
+
grid_h=height // patch_h,
|
|
139
|
+
grid_w=width // patch_w,
|
|
140
|
+
num_prefix_tokens=0,
|
|
141
|
+
encoder_name=type(self).__name__,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def encode_dim(self) -> int:
|
|
146
|
+
return 4608
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def device(self) -> torch.device:
|
|
150
|
+
return self._device
|
|
151
|
+
|
|
152
|
+
def to(self, device: torch.device | str) -> "GenBioPathFM":
|
|
153
|
+
self._device = torch.device(device)
|
|
154
|
+
self._model = self._model.to(self._device)
|
|
155
|
+
return self
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""GPFM tile encoder (Generalizable Pathology Foundation Model).
|
|
2
|
+
|
|
3
|
+
GPFM (Ma et al., 2024; ``birkhoffkiki/GPFM``) is a ``ViT-L/14`` DINOv2 tile
|
|
4
|
+
encoder (embedding dim 1024, patch_size 14). Weights are openly available under
|
|
5
|
+
the MIT license at ``majiabo/GPFM`` but ship as a standalone ``GPFM.pth``
|
|
6
|
+
checkpoint, so — unlike ``hf-hub:`` timm presets — we build the timm arch
|
|
7
|
+
unpretrained and ``load_state_dict`` the downloaded checkpoint (the MOOZY
|
|
8
|
+
loading pattern).
|
|
9
|
+
|
|
10
|
+
The published ``GPFM.pth`` is a bare DINOv2 ``state_dict`` (``cls_token``,
|
|
11
|
+
``pos_embed``, ``blocks.*``, …) that loads into
|
|
12
|
+
``vit_large_patch14_dinov2.lvd142m`` with ``strict=True`` and zero missing /
|
|
13
|
+
unexpected keys (verified on the real weights; see
|
|
14
|
+
``tests/test_gpfm_genbio_heavy.py``). The loader below still defensively unwraps
|
|
15
|
+
common checkpoint wrappers (``{"model": ...}`` / ``{"teacher": ...}`` /
|
|
16
|
+
``{"student": ...}`` / ``{"state_dict": ...}``) and strips ``module.`` /
|
|
17
|
+
``backbone.`` prefixes so a re-exported checkpoint keeps loading strictly.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Mapping
|
|
21
|
+
|
|
22
|
+
import torch
|
|
23
|
+
from huggingface_hub import hf_hub_download
|
|
24
|
+
|
|
25
|
+
from slide2vec.encoders.base import TimmTileEncoder
|
|
26
|
+
from slide2vec.encoders.registry import register_encoder
|
|
27
|
+
|
|
28
|
+
_HF_REPO_ID = "majiabo/GPFM"
|
|
29
|
+
_HF_CHECKPOINT = "GPFM.pth"
|
|
30
|
+
_CHECKPOINT_WRAPPER_KEYS = ("model", "teacher", "student", "state_dict", "teacher_backbone")
|
|
31
|
+
_STATE_DICT_PREFIXES = ("module.", "backbone.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _unwrap_gpfm_state_dict(payload: Mapping[str, object]) -> dict[str, torch.Tensor]:
|
|
35
|
+
"""Reduce a GPFM checkpoint payload to a bare ``state_dict``.
|
|
36
|
+
|
|
37
|
+
Unwraps a single common wrapper key if present, then strips ``module.`` /
|
|
38
|
+
``backbone.`` prefixes off every key. A no-op on the published bare
|
|
39
|
+
``GPFM.pth`` (no wrapper, no prefixes), so it preserves strict loading there
|
|
40
|
+
while tolerating a re-wrapped/prefixed re-export.
|
|
41
|
+
"""
|
|
42
|
+
state: object = payload
|
|
43
|
+
for wrapper in _CHECKPOINT_WRAPPER_KEYS:
|
|
44
|
+
if isinstance(state, Mapping) and wrapper in state and isinstance(state[wrapper], Mapping):
|
|
45
|
+
state = state[wrapper]
|
|
46
|
+
break
|
|
47
|
+
if not isinstance(state, Mapping):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Unexpected GPFM checkpoint payload: expected a state_dict mapping, got {type(state)}"
|
|
50
|
+
)
|
|
51
|
+
cleaned: dict[str, torch.Tensor] = {}
|
|
52
|
+
for key, value in state.items():
|
|
53
|
+
name = key
|
|
54
|
+
for prefix in _STATE_DICT_PREFIXES:
|
|
55
|
+
if name.startswith(prefix):
|
|
56
|
+
name = name[len(prefix):]
|
|
57
|
+
cleaned[name] = value
|
|
58
|
+
return cleaned
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@register_encoder(
|
|
62
|
+
"gpfm",
|
|
63
|
+
output_variants={"default": {"encode_dim": 1024}},
|
|
64
|
+
default_output_variant="default",
|
|
65
|
+
input_size=224,
|
|
66
|
+
patch_size=14,
|
|
67
|
+
supported_spacing_um=0.5, # 512px@0.25um (128um FOV) resized to 224 => ~0.5um/px effective (20x), as for UNI
|
|
68
|
+
precision="fp32", # upstream runs plain fp32, no autocast
|
|
69
|
+
source="majiabo/GPFM",
|
|
70
|
+
)
|
|
71
|
+
class GPFM(TimmTileEncoder):
|
|
72
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
73
|
+
super().__init__(
|
|
74
|
+
"vit_large_patch14_dinov2.lvd142m",
|
|
75
|
+
output_variant=output_variant,
|
|
76
|
+
pretrained=False,
|
|
77
|
+
img_size=224,
|
|
78
|
+
init_values=1e-5,
|
|
79
|
+
dynamic_img_size=True,
|
|
80
|
+
)
|
|
81
|
+
checkpoint_path = hf_hub_download(repo_id=_HF_REPO_ID, filename=_HF_CHECKPOINT)
|
|
82
|
+
payload = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
|
|
83
|
+
if not isinstance(payload, Mapping):
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Invalid GPFM checkpoint payload: expected a dict, got {type(payload)}"
|
|
86
|
+
)
|
|
87
|
+
state_dict = _unwrap_gpfm_state_dict(payload)
|
|
88
|
+
self._model.load_state_dict(state_dict, strict=True)
|
|
89
|
+
self._model.eval()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""mSTAR tile encoder implementation.
|
|
2
|
+
|
|
3
|
+
mSTAR (Wang et al., 2024; ``Innse/mSTAR``) is released as a ``ViT-L/16`` patch
|
|
4
|
+
encoder, not a slide aggregator: the published checkpoint is a per-tile feature
|
|
5
|
+
extractor and slide2vec handles WSI -> coordinates -> per-tile features itself.
|
|
6
|
+
We therefore register it as a **tile** encoder.
|
|
7
|
+
|
|
8
|
+
The weights live in the **gated** Hugging Face repo `Wangyh/mSTAR`
|
|
9
|
+
(``hf-hub:Wangyh/mSTAR``). Loading them requires access approval on Hugging Face
|
|
10
|
+
and an ``HF_TOKEN`` in the environment.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from slide2vec.encoders.base import TimmTileEncoder
|
|
14
|
+
from slide2vec.encoders.registry import register_encoder
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register_encoder(
|
|
18
|
+
"mstar",
|
|
19
|
+
output_variants={"default": {"encode_dim": 1024}},
|
|
20
|
+
default_output_variant="default",
|
|
21
|
+
input_size=224,
|
|
22
|
+
patch_size=16,
|
|
23
|
+
supported_spacing_um=0.5, # 256px @ 20x, resized to 224 (per paper)
|
|
24
|
+
precision="fp32", # upstream runs plain fp32, no autocast
|
|
25
|
+
source="Wangyh/mSTAR",
|
|
26
|
+
)
|
|
27
|
+
class mSTAR(TimmTileEncoder):
|
|
28
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
29
|
+
super().__init__(
|
|
30
|
+
"hf-hub:Wangyh/mSTAR",
|
|
31
|
+
output_variant=output_variant,
|
|
32
|
+
init_values=1e-5,
|
|
33
|
+
dynamic_img_size=True,
|
|
34
|
+
)
|
|
@@ -41,7 +41,8 @@ def register_encoder(
|
|
|
41
41
|
level: str = "tile",
|
|
42
42
|
tile_encoder: str | None = None,
|
|
43
43
|
tile_encoder_output_variant: str | None = None,
|
|
44
|
-
supported_spacing_um: float | list[float],
|
|
44
|
+
supported_spacing_um: float | list[float] | None,
|
|
45
|
+
default_spacing_um: float | None = None,
|
|
45
46
|
precision: str = "fp16",
|
|
46
47
|
source: str = "",
|
|
47
48
|
):
|
|
@@ -61,7 +62,22 @@ def register_encoder(
|
|
|
61
62
|
level: Encoder output level ("tile" or "slide").
|
|
62
63
|
tile_encoder: Registered tile encoder dependency for slide-level models.
|
|
63
64
|
tile_encoder_output_variant: Fixed tile-encoder output variant for slide models.
|
|
64
|
-
supported_spacing_um:
|
|
65
|
+
supported_spacing_um: The spacing(s) in µm/px the model was trained/validated
|
|
66
|
+
for; :func:`validate_encoder_config` rejects requests outside this set
|
|
67
|
+
unless ``allow_non_recommended_settings=True``. ``None`` marks a
|
|
68
|
+
*spacing-agnostic* encoder (e.g. a natural-image control): the spacing
|
|
69
|
+
check is skipped entirely because no spacing is more "correct" than
|
|
70
|
+
another. Agnostic encoders MUST pair this with an explicit
|
|
71
|
+
``default_spacing_um`` so name-only selection still resolves a tiling
|
|
72
|
+
spacing.
|
|
73
|
+
default_spacing_um: The single spacing in µm/px used to tile a slide when the
|
|
74
|
+
caller selects this encoder by name without passing an explicit
|
|
75
|
+
``requested_spacing_um``. Optional: when omitted it is derived from
|
|
76
|
+
``supported_spacing_um`` if that is a single value. Encoders that
|
|
77
|
+
support a *list* of spacings, or are spacing-agnostic
|
|
78
|
+
(``supported_spacing_um=None``), have no derivable default and must
|
|
79
|
+
declare one here to be selectable with zero config (otherwise
|
|
80
|
+
:func:`resolve_preprocessing_defaults` requires an explicit spacing).
|
|
65
81
|
precision: Recommended inference precision ("fp16" or "fp32").
|
|
66
82
|
source: Model source identifier (e.g. HuggingFace hub path).
|
|
67
83
|
"""
|
|
@@ -78,6 +94,7 @@ def register_encoder(
|
|
|
78
94
|
"tile_encoder": tile_encoder,
|
|
79
95
|
"tile_encoder_output_variant": tile_encoder_output_variant,
|
|
80
96
|
"supported_spacing_um": supported_spacing_um,
|
|
97
|
+
"default_spacing_um": default_spacing_um,
|
|
81
98
|
"precision": precision,
|
|
82
99
|
"source": source,
|
|
83
100
|
}
|
|
@@ -133,14 +150,13 @@ def resolve_preprocessing_requirements(
|
|
|
133
150
|
|
|
134
151
|
if level == "tile":
|
|
135
152
|
input_size = require_encoder_metadata_field(encoder_name, info, "input_size")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
153
|
+
# supported_spacing_um is the *validated* constraint set and may be None
|
|
154
|
+
# (spacing-agnostic). Kept lazy: do NOT resolve a default here — callers
|
|
155
|
+
# that only need the constraint (e.g. tile-size validation) must not trip
|
|
156
|
+
# over a list/agnostic encoder having no single default.
|
|
141
157
|
return {
|
|
142
158
|
"tile_size_px": input_size,
|
|
143
|
-
"spacing_um":
|
|
159
|
+
"spacing_um": info.get("supported_spacing_um"),
|
|
144
160
|
"source_encoder": encoder_name,
|
|
145
161
|
}
|
|
146
162
|
|
|
@@ -153,35 +169,67 @@ def resolve_preprocessing_requirements(
|
|
|
153
169
|
raise AssertionError("unreachable")
|
|
154
170
|
|
|
155
171
|
|
|
172
|
+
def _resolve_default_spacing(encoder_name: str, info: dict[str, Any]) -> float:
|
|
173
|
+
"""Resolve the single spacing (µm/px) an encoder is tiled at by default.
|
|
174
|
+
|
|
175
|
+
Prefers an explicit ``default_spacing_um``. Otherwise derives it from
|
|
176
|
+
``supported_spacing_um`` when that is a single value. Encoders that support a
|
|
177
|
+
*list* of spacings, or are spacing-agnostic (``supported_spacing_um=None``),
|
|
178
|
+
with no explicit default have no unambiguous tiling spacing and raise — the
|
|
179
|
+
caller must pass ``preprocessing.requested_spacing_um`` (or the encoder must
|
|
180
|
+
declare ``default_spacing_um``).
|
|
181
|
+
"""
|
|
182
|
+
explicit = info.get("default_spacing_um")
|
|
183
|
+
if explicit is not None:
|
|
184
|
+
return float(explicit)
|
|
185
|
+
|
|
186
|
+
supported = info.get("supported_spacing_um")
|
|
187
|
+
if isinstance(supported, list):
|
|
188
|
+
unique_spacings: list[float] = []
|
|
189
|
+
for spacing in supported:
|
|
190
|
+
spacing_value = float(spacing)
|
|
191
|
+
if not any(abs(spacing_value - existing) <= 1e-8 for existing in unique_spacings):
|
|
192
|
+
unique_spacings.append(spacing_value)
|
|
193
|
+
if len(unique_spacings) == 1:
|
|
194
|
+
return unique_spacings[0]
|
|
195
|
+
supported_text = ", ".join(f"{s:g}" for s in unique_spacings)
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"Encoder '{encoder_name}' supports multiple spacings [{supported_text}]; "
|
|
198
|
+
"cannot infer a default requested_spacing_um. Declare default_spacing_um "
|
|
199
|
+
"in its registration or pass preprocessing.requested_spacing_um explicitly."
|
|
200
|
+
)
|
|
201
|
+
if isinstance(supported, (int, float)) and not isinstance(supported, bool):
|
|
202
|
+
return float(supported)
|
|
203
|
+
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"Encoder '{encoder_name}' is spacing-agnostic (supported_spacing_um=None) but "
|
|
206
|
+
"declares no default_spacing_um; declare default_spacing_um in its registration "
|
|
207
|
+
"or pass preprocessing.requested_spacing_um explicitly."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
156
211
|
def resolve_preprocessing_defaults(
|
|
157
212
|
encoder_name: str,
|
|
158
213
|
metadata: dict[str, Any] | None = None,
|
|
159
214
|
) -> dict[str, Any]:
|
|
160
215
|
"""Resolve a single unambiguous preprocessing default for an encoder.
|
|
161
216
|
|
|
162
|
-
This is stricter than :func:`resolve_preprocessing_requirements`: it
|
|
163
|
-
|
|
217
|
+
This is stricter than :func:`resolve_preprocessing_requirements`: it resolves
|
|
218
|
+
exactly one tiling spacing (see :func:`_resolve_default_spacing`) and raises
|
|
219
|
+
when the encoder advertises several supported spacings without an explicit
|
|
220
|
+
``default_spacing_um``.
|
|
164
221
|
"""
|
|
165
222
|
reqs = resolve_preprocessing_requirements(encoder_name, metadata)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
unique_spacings.append(spacing_value)
|
|
173
|
-
if len(unique_spacings) != 1:
|
|
174
|
-
supported_text = ", ".join(f"{s:g}" for s in unique_spacings)
|
|
175
|
-
raise ValueError(
|
|
176
|
-
f"Encoder '{encoder_name}' supports multiple spacings [{supported_text}]; "
|
|
177
|
-
"cannot infer a default requested_spacing_um. "
|
|
178
|
-
"Pass preprocessing.requested_spacing_um explicitly."
|
|
179
|
-
)
|
|
180
|
-
spacing_um = unique_spacings[0]
|
|
223
|
+
source_encoder = reqs["source_encoder"]
|
|
224
|
+
# Resolve the default off the *tile* encoder's metadata: slide/patient
|
|
225
|
+
# encoders inherit both tile size and spacing from their tile encoder, so
|
|
226
|
+
# source_encoder already points at the model that carries the spacing fields.
|
|
227
|
+
source_info = encoder_registry.info(source_encoder)
|
|
228
|
+
spacing_um = _resolve_default_spacing(source_encoder, source_info)
|
|
181
229
|
return {
|
|
182
230
|
"tile_size_px": int(reqs["tile_size_px"]),
|
|
183
231
|
"spacing_um": float(spacing_um),
|
|
184
|
-
"source_encoder":
|
|
232
|
+
"source_encoder": source_encoder,
|
|
185
233
|
}
|
|
186
234
|
|
|
187
235
|
|
|
@@ -29,6 +29,10 @@ MODEL_NAME_ALIASES = {
|
|
|
29
29
|
"prov-gigapath-tile": "gigapath",
|
|
30
30
|
"prov-gigapath-slide": "gigapath-slide",
|
|
31
31
|
"kaiko-midnight": "midnight",
|
|
32
|
+
"mstar-slide": "mstar",
|
|
33
|
+
"dinov2": "dinov2-vitb14",
|
|
34
|
+
"dinov2-base": "dinov2-vitb14",
|
|
35
|
+
"dinov2-vitb": "dinov2-vitb14",
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.3.0
|
|
4
4
|
Summary: Embedding of whole slide images with Foundation Models
|
|
5
5
|
Author-email: Clément Grisi <clement.grisi@radboudumc.nl>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -225,7 +225,7 @@ The package writes explicit artifact directories:
|
|
|
225
225
|
|
|
226
226
|
### Supported Models
|
|
227
227
|
|
|
228
|
-
`slide2vec` currently ships preset configs for
|
|
228
|
+
`slide2vec` currently ships preset configs for 22 tile-level models and 3 slide-level models.
|
|
229
229
|
For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
|
|
230
230
|
|
|
231
231
|
## CLI
|
|
@@ -31,11 +31,15 @@ slide2vec/encoders/registry.py
|
|
|
31
31
|
slide2vec/encoders/validation.py
|
|
32
32
|
slide2vec/encoders/models/__init__.py
|
|
33
33
|
slide2vec/encoders/models/conch.py
|
|
34
|
+
slide2vec/encoders/models/dinov2.py
|
|
35
|
+
slide2vec/encoders/models/genbio.py
|
|
34
36
|
slide2vec/encoders/models/gigapath.py
|
|
37
|
+
slide2vec/encoders/models/gpfm.py
|
|
35
38
|
slide2vec/encoders/models/hibou.py
|
|
36
39
|
slide2vec/encoders/models/hoptimus.py
|
|
37
40
|
slide2vec/encoders/models/lunit.py
|
|
38
41
|
slide2vec/encoders/models/midnight.py
|
|
42
|
+
slide2vec/encoders/models/mstar.py
|
|
39
43
|
slide2vec/encoders/models/musk.py
|
|
40
44
|
slide2vec/encoders/models/phikon.py
|
|
41
45
|
slide2vec/encoders/models/prism.py
|
|
@@ -86,7 +90,9 @@ tests/test_attention_extraction.py
|
|
|
86
90
|
tests/test_dense_extraction.py
|
|
87
91
|
tests/test_dense_regions.py
|
|
88
92
|
tests/test_dense_sliding.py
|
|
93
|
+
tests/test_dinov2_natimage.py
|
|
89
94
|
tests/test_encoder_registry.py
|
|
95
|
+
tests/test_gpfm_genbio_heavy.py
|
|
90
96
|
tests/test_hs2p_package_cutover.py
|
|
91
97
|
tests/test_output_consistency.py
|
|
92
98
|
tests/test_patch_size_metadata.py
|