slide2vec 5.6.0__tar.gz → 5.8.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.6.0 → slide2vec-5.8.0}/PKG-INFO +26 -11
- {slide2vec-5.6.0 → slide2vec-5.8.0}/README.md +13 -8
- {slide2vec-5.6.0 → slide2vec-5.8.0}/pyproject.toml +16 -4
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/__init__.py +10 -1
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/api.py +106 -34
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/tile_reader.py +42 -1
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/__init__.py +8 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/__init__.py +6 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/conch.py +4 -5
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/isight.py +22 -13
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/phikon.py +17 -11
- slide2vec-5.8.0/slide2vec/encoders/models/prism2.py +94 -0
- slide2vec-5.8.0/slide2vec/encoders/models/rudolfv2.py +351 -0
- slide2vec-5.8.0/slide2vec/encoders/models/waiv.py +167 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/registry.py +370 -26
- slide2vec-5.8.0/slide2vec/runtime/dense_encode.py +334 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_encoder_input.py +2 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_reading.py +76 -17
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_recipe.py +5 -1
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_shard.py +2 -2
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_stage.py +27 -46
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_regions.py +66 -8
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_shard.py +72 -40
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_stage.py +55 -55
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/hierarchical.py +1 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_specs.py +14 -12
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/registry.py +8 -1
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/PKG-INFO +26 -11
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/SOURCES.txt +13 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/requires.txt +14 -2
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_attention_extraction.py +97 -4
- slide2vec-5.8.0/tests/test_dense_encode_kit.py +547 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_encoder_input.py +68 -17
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_extraction.py +5 -5
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_resume.py +10 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_stage.py +130 -59
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_shard.py +106 -5
- slide2vec-5.8.0/tests/test_dense_source_spacing.py +194 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_stage.py +134 -6
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_worker.py +18 -1
- slide2vec-5.8.0/tests/test_encoder_capabilities.py +481 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_encoder_input_contract.py +3 -3
- slide2vec-5.8.0/tests/test_encoder_plugins.py +298 -0
- slide2vec-5.8.0/tests/test_encoder_provider_failures.py +391 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_encoder_registry.py +175 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_hs2p_package_cutover.py +3 -3
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_isight.py +52 -1
- slide2vec-5.8.0/tests/test_mascaret.py +401 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_patch_size_metadata.py +6 -1
- slide2vec-5.8.0/tests/test_phaet.py +352 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_pooled_encoder_input.py +106 -6
- slide2vec-5.8.0/tests/test_prism2.py +470 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_core.py +6 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_inference.py +1 -0
- slide2vec-5.8.0/tests/test_rudolfv2.py +763 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_soma_migration.py +9 -2
- {slide2vec-5.6.0 → slide2vec-5.8.0}/LICENSE +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/setup.cfg +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/__main__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/cli.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/inference.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/progress.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_image_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_progress.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_models.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_sharding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_tile_store.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.8.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.8.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
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.1
|
|
19
19
|
Requires-Dist: omegaconf
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: numpy<2
|
|
@@ -47,6 +47,12 @@ Requires-Dist: einops==0.8.0; extra == "prism"
|
|
|
47
47
|
Requires-Dist: environs==11.0.0; extra == "prism"
|
|
48
48
|
Requires-Dist: sacremoses==0.1.1; extra == "prism"
|
|
49
49
|
Requires-Dist: xformers==0.0.31; extra == "prism"
|
|
50
|
+
Provides-Extra: prism2
|
|
51
|
+
Requires-Dist: torch>=2.3; extra == "prism2"
|
|
52
|
+
Requires-Dist: transformers==4.51.3; extra == "prism2"
|
|
53
|
+
Requires-Dist: safetensors; extra == "prism2"
|
|
54
|
+
Requires-Dist: einops; extra == "prism2"
|
|
55
|
+
Requires-Dist: flash-attn>=2.6.3; extra == "prism2"
|
|
50
56
|
Provides-Extra: hibou
|
|
51
57
|
Requires-Dist: scipy~=1.8.1; extra == "hibou"
|
|
52
58
|
Requires-Dist: scikit-image~=0.19.3; extra == "hibou"
|
|
@@ -58,6 +64,10 @@ Requires-Dist: timm==1.0.3; extra == "titan"
|
|
|
58
64
|
Requires-Dist: einops==0.6.1; extra == "titan"
|
|
59
65
|
Requires-Dist: einops-exts==0.0.4; extra == "titan"
|
|
60
66
|
Requires-Dist: transformers==4.46.0; extra == "titan"
|
|
67
|
+
Provides-Extra: waiv
|
|
68
|
+
Requires-Dist: transformers<6,>=5.14; extra == "waiv"
|
|
69
|
+
Requires-Dist: safetensors>=0.8; extra == "waiv"
|
|
70
|
+
Requires-Dist: huggingface_hub==1.25.1; extra == "waiv"
|
|
61
71
|
Provides-Extra: fm
|
|
62
72
|
Requires-Dist: omegaconf>=2.3.0; extra == "fm"
|
|
63
73
|
Requires-Dist: matplotlib; extra == "fm"
|
|
@@ -65,7 +75,7 @@ Requires-Dist: numpy<2; extra == "fm"
|
|
|
65
75
|
Requires-Dist: pandas; extra == "fm"
|
|
66
76
|
Requires-Dist: pillow; extra == "fm"
|
|
67
77
|
Requires-Dist: rich; extra == "fm"
|
|
68
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.1; extra == "fm"
|
|
69
79
|
Requires-Dist: wandb; extra == "fm"
|
|
70
80
|
Requires-Dist: torch<2.8,>=2.3; extra == "fm"
|
|
71
81
|
Requires-Dist: torchvision>=0.18.0; extra == "fm"
|
|
@@ -132,6 +142,10 @@ pip install git+https://github.com/prov-gigapath/prov-gigapath.git
|
|
|
132
142
|
|
|
133
143
|
AtlasPatch-backed tissue segmentation is available through hs2p's `sam2` path in the bundled install.
|
|
134
144
|
|
|
145
|
+
Waiv encoders use a separately tested Transformers 5 runtime. Install them in
|
|
146
|
+
their own environment with `pip install "slide2vec[waiv]"`; the `waiv` extra is
|
|
147
|
+
incompatible with the existing `fm`, `prism`, and `titan` dependency pins.
|
|
148
|
+
|
|
135
149
|
## Python API
|
|
136
150
|
|
|
137
151
|
```python
|
|
@@ -193,7 +207,7 @@ embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
|
|
|
193
207
|
|
|
194
208
|
Hierarchical outputs have shape `(num_regions, tiles_per_region, feature_dim)` and are written to `hierarchical_embeddings/` when persisted.
|
|
195
209
|
|
|
196
|
-
See [
|
|
210
|
+
See the [hierarchical features guide](https://clemsgrs.github.io/slide2vec/hierarchical.html) for details.
|
|
197
211
|
|
|
198
212
|
### Input Manifest
|
|
199
213
|
|
|
@@ -225,8 +239,9 @@ The package writes explicit artifact directories:
|
|
|
225
239
|
|
|
226
240
|
### Supported Models
|
|
227
241
|
|
|
228
|
-
`slide2vec` currently ships
|
|
229
|
-
|
|
242
|
+
`slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
|
|
243
|
+
and 1 patient-level model.
|
|
244
|
+
For the full catalog and preset names, see the [model zoo](https://clemsgrs.github.io/slide2vec/models.html).
|
|
230
245
|
|
|
231
246
|
## CLI
|
|
232
247
|
|
|
@@ -239,7 +254,7 @@ slide2vec /path/to/config.yaml
|
|
|
239
254
|
|
|
240
255
|
By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
|
|
241
256
|
|
|
242
|
-
New to the CLI or doing batch runs to disk? Start with [
|
|
257
|
+
New to the CLI or doing batch runs to disk? Start with the [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) for the config-driven workflow and common run patterns.
|
|
243
258
|
|
|
244
259
|
## Docker
|
|
245
260
|
|
|
@@ -257,8 +272,8 @@ docker run --rm -it \
|
|
|
257
272
|
|
|
258
273
|
## Documentation
|
|
259
274
|
|
|
260
|
-
- [Documentation website](https://clemsgrs.github.io/slide2vec/)
|
|
261
|
-
- [
|
|
262
|
-
- [
|
|
263
|
-
- [
|
|
275
|
+
- [Documentation website](https://clemsgrs.github.io/slide2vec/)
|
|
276
|
+
- [API guide](https://clemsgrs.github.io/slide2vec/api.html)
|
|
277
|
+
- [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html)
|
|
278
|
+
- [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
|
|
264
279
|
- [`tutorials/api_walkthrough.ipynb`](tutorials/api_walkthrough.ipynb) for a notebook walkthrough of the API
|
|
@@ -26,6 +26,10 @@ pip install git+https://github.com/prov-gigapath/prov-gigapath.git
|
|
|
26
26
|
|
|
27
27
|
AtlasPatch-backed tissue segmentation is available through hs2p's `sam2` path in the bundled install.
|
|
28
28
|
|
|
29
|
+
Waiv encoders use a separately tested Transformers 5 runtime. Install them in
|
|
30
|
+
their own environment with `pip install "slide2vec[waiv]"`; the `waiv` extra is
|
|
31
|
+
incompatible with the existing `fm`, `prism`, and `titan` dependency pins.
|
|
32
|
+
|
|
29
33
|
## Python API
|
|
30
34
|
|
|
31
35
|
```python
|
|
@@ -87,7 +91,7 @@ embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
|
|
|
87
91
|
|
|
88
92
|
Hierarchical outputs have shape `(num_regions, tiles_per_region, feature_dim)` and are written to `hierarchical_embeddings/` when persisted.
|
|
89
93
|
|
|
90
|
-
See [
|
|
94
|
+
See the [hierarchical features guide](https://clemsgrs.github.io/slide2vec/hierarchical.html) for details.
|
|
91
95
|
|
|
92
96
|
### Input Manifest
|
|
93
97
|
|
|
@@ -119,8 +123,9 @@ The package writes explicit artifact directories:
|
|
|
119
123
|
|
|
120
124
|
### Supported Models
|
|
121
125
|
|
|
122
|
-
`slide2vec` currently ships
|
|
123
|
-
|
|
126
|
+
`slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
|
|
127
|
+
and 1 patient-level model.
|
|
128
|
+
For the full catalog and preset names, see the [model zoo](https://clemsgrs.github.io/slide2vec/models.html).
|
|
124
129
|
|
|
125
130
|
## CLI
|
|
126
131
|
|
|
@@ -133,7 +138,7 @@ slide2vec /path/to/config.yaml
|
|
|
133
138
|
|
|
134
139
|
By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
|
|
135
140
|
|
|
136
|
-
New to the CLI or doing batch runs to disk? Start with [
|
|
141
|
+
New to the CLI or doing batch runs to disk? Start with the [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) for the config-driven workflow and common run patterns.
|
|
137
142
|
|
|
138
143
|
## Docker
|
|
139
144
|
|
|
@@ -151,8 +156,8 @@ docker run --rm -it \
|
|
|
151
156
|
|
|
152
157
|
## Documentation
|
|
153
158
|
|
|
154
|
-
- [Documentation website](https://clemsgrs.github.io/slide2vec/)
|
|
155
|
-
- [
|
|
156
|
-
- [
|
|
157
|
-
- [
|
|
159
|
+
- [Documentation website](https://clemsgrs.github.io/slide2vec/)
|
|
160
|
+
- [API guide](https://clemsgrs.github.io/slide2vec/api.html)
|
|
161
|
+
- [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html)
|
|
162
|
+
- [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
|
|
158
163
|
- [`tutorials/api_walkthrough.ipynb`](tutorials/api_walkthrough.ipynb) for a notebook walkthrough of the API
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.8.0"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
24
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.1",
|
|
25
25
|
"omegaconf",
|
|
26
26
|
"matplotlib",
|
|
27
27
|
"numpy<2",
|
|
@@ -67,6 +67,13 @@ prism = [
|
|
|
67
67
|
"sacremoses==0.1.1",
|
|
68
68
|
"xformers==0.0.31",
|
|
69
69
|
]
|
|
70
|
+
prism2 = [
|
|
71
|
+
"torch>=2.3",
|
|
72
|
+
"transformers==4.51.3",
|
|
73
|
+
"safetensors",
|
|
74
|
+
"einops",
|
|
75
|
+
"flash-attn>=2.6.3",
|
|
76
|
+
]
|
|
70
77
|
hibou = [
|
|
71
78
|
"scipy~=1.8.1",
|
|
72
79
|
"scikit-image~=0.19.3",
|
|
@@ -81,6 +88,11 @@ titan = [
|
|
|
81
88
|
"einops-exts==0.0.4",
|
|
82
89
|
"transformers==4.46.0",
|
|
83
90
|
]
|
|
91
|
+
waiv = [
|
|
92
|
+
"transformers>=5.14,<6",
|
|
93
|
+
"safetensors>=0.8",
|
|
94
|
+
"huggingface_hub==1.25.1",
|
|
95
|
+
]
|
|
84
96
|
fm = [
|
|
85
97
|
"omegaconf>=2.3.0",
|
|
86
98
|
"matplotlib",
|
|
@@ -88,7 +100,7 @@ fm = [
|
|
|
88
100
|
"pandas",
|
|
89
101
|
"pillow",
|
|
90
102
|
"rich",
|
|
91
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
103
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.1",
|
|
92
104
|
"wandb",
|
|
93
105
|
"torch>=2.3,<2.8",
|
|
94
106
|
"torchvision>=0.18.0",
|
|
@@ -168,7 +180,7 @@ no_implicit_reexport = true
|
|
|
168
180
|
max-line-length = 160
|
|
169
181
|
|
|
170
182
|
[tool.bumpver]
|
|
171
|
-
current_version = "5.
|
|
183
|
+
current_version = "5.8.0"
|
|
172
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
173
185
|
commit = false # We do version bumping in CI, not as a commit
|
|
174
186
|
tag = false # Git tag already exists — we don't auto-tag
|
|
@@ -20,17 +20,26 @@ from slide2vec.artifacts import (
|
|
|
20
20
|
SlideEmbeddingArtifact,
|
|
21
21
|
TileEmbeddingArtifact,
|
|
22
22
|
)
|
|
23
|
+
from slide2vec.runtime.dense_encode import DenseEncodeGeometry, DenseEncodeKit
|
|
24
|
+
from slide2vec.encoders import (
|
|
25
|
+
EncoderProviderDiagnostic,
|
|
26
|
+
list_encoder_provider_diagnostics,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
|
|
25
|
-
__version__ = "5.
|
|
30
|
+
__version__ = "5.8.0"
|
|
26
31
|
|
|
27
32
|
__all__ = [
|
|
28
33
|
"Model",
|
|
29
34
|
"list_models",
|
|
35
|
+
"EncoderProviderDiagnostic",
|
|
36
|
+
"list_encoder_provider_diagnostics",
|
|
30
37
|
"Pipeline",
|
|
31
38
|
"PreprocessingConfig",
|
|
32
39
|
"DenseOptions",
|
|
33
40
|
"DenseImageOptions",
|
|
41
|
+
"DenseEncodeGeometry",
|
|
42
|
+
"DenseEncodeKit",
|
|
34
43
|
"SlideRegions",
|
|
35
44
|
"ImageSpec",
|
|
36
45
|
"ExecutionOptions",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import copy
|
|
3
3
|
import logging
|
|
4
|
+
import math
|
|
4
5
|
import os
|
|
6
|
+
import warnings
|
|
5
7
|
from dataclasses import dataclass, field, replace
|
|
6
8
|
from contextlib import contextmanager
|
|
7
9
|
from pathlib import Path
|
|
@@ -22,6 +24,7 @@ from slide2vec.artifacts import (
|
|
|
22
24
|
from slide2vec.configs.resources import load_config
|
|
23
25
|
from slide2vec.encoders.registry import (
|
|
24
26
|
encoder_registry,
|
|
27
|
+
list_encoder_provider_diagnostics,
|
|
25
28
|
resolve_preprocessing_fields,
|
|
26
29
|
)
|
|
27
30
|
from slide2vec.encoders.validation import validate_encoder_config
|
|
@@ -39,6 +42,17 @@ from slide2vec.utils.utils import cpu_worker_limit, slurm_cpu_limit
|
|
|
39
42
|
PathLike = str | Path
|
|
40
43
|
|
|
41
44
|
|
|
45
|
+
def _validated_spacing_at_level_0(value: float | None) -> float | None:
|
|
46
|
+
if value is None:
|
|
47
|
+
return None
|
|
48
|
+
if isinstance(value, bool):
|
|
49
|
+
raise ValueError("spacing_at_level_0 must be a positive, finite value or None")
|
|
50
|
+
spacing = float(value)
|
|
51
|
+
if not math.isfinite(spacing) or spacing <= 0:
|
|
52
|
+
raise ValueError("spacing_at_level_0 must be a positive, finite value or None")
|
|
53
|
+
return spacing
|
|
54
|
+
|
|
55
|
+
|
|
42
56
|
class SlideLike(Protocol):
|
|
43
57
|
sample_id: str
|
|
44
58
|
image_path: PathLike
|
|
@@ -310,9 +324,10 @@ class ExecutionOptions:
|
|
|
310
324
|
#: Forward-pass dtype — ``"fp16"``, ``"bf16"``, ``"fp32"``,
|
|
311
325
|
#: or ``None`` (auto-determined from the model preset).
|
|
312
326
|
precision: str | None = None
|
|
313
|
-
#:
|
|
314
|
-
#: :attr:`precision` (fp16 → fp16, else fp32). Applies to
|
|
315
|
-
#: and patient artifacts; ``"bf16"`` is rejected
|
|
327
|
+
#: Feature output dtype — ``"fp16"``, ``"fp32"``, or ``None`` to follow
|
|
328
|
+
#: :attr:`precision` (fp16 → fp16, else fp32). Applies to live dense grids and to
|
|
329
|
+
#: tile, slide, hierarchical, and patient artifacts; ``"bf16"`` is rejected because
|
|
330
|
+
#: persisted features cross a numpy boundary.
|
|
316
331
|
output_dtype: str | None = None
|
|
317
332
|
#: DataLoader prefetch queue depth per worker (default ``4``).
|
|
318
333
|
prefetch_factor: int = 4
|
|
@@ -446,16 +461,15 @@ class DenseOptions:
|
|
|
446
461
|
class DenseImageOptions:
|
|
447
462
|
"""Dense ``(d, gh, gw)`` extraction over pre-cropped images.
|
|
448
463
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
resolves source spacing, backend, pyramid level, tolerance, complete-extent read,
|
|
453
|
-
area downsampling at the
|
|
454
|
-
the encoder's single registry default
|
|
455
|
-
explicit value when no default is available.
|
|
464
|
+
Every supported source uses hs2p's spacing-aware reader contract. ``.png``, ``.jpg``,
|
|
465
|
+
and ``.jpeg`` inputs use hs2p's one-level PIL reader and therefore require
|
|
466
|
+
``ImageSpec.spacing_at_level_0``; WSI readers may resolve native metadata instead.
|
|
467
|
+
hs2p resolves source spacing, backend, pyramid level, tolerance, complete-extent read,
|
|
468
|
+
and permitted area downsampling at the requested run-level ``spacing_um``. Omitted
|
|
469
|
+
spacing resolves the encoder's single registry default.
|
|
456
470
|
|
|
457
|
-
``ImageSpec.spacing_at_level_0``
|
|
458
|
-
|
|
471
|
+
``ImageSpec.spacing_at_level_0`` is the optional caller declaration used by hs2p when
|
|
472
|
+
resolving level-0 spacing. ``target_size`` is always a strict post-read declaration, never a fit-to-size
|
|
459
473
|
request: each final pixel array must already be exactly this size. Declaring it up front
|
|
460
474
|
lets the effective encoder input be validated (and variable-input constructor settings
|
|
461
475
|
resolved) before model loading or pixel decoding. Differing final geometries therefore
|
|
@@ -465,9 +479,8 @@ class DenseImageOptions:
|
|
|
465
479
|
#: Supervision geometry in pixels the dense grid registers to: a square side length, or
|
|
466
480
|
#: an explicit ``(height, width)`` for non-square images.
|
|
467
481
|
target_size: int | tuple[int, int]
|
|
468
|
-
#: Positive, finite run
|
|
469
|
-
#:
|
|
470
|
-
#: single registry default for spacing-readable inputs.
|
|
482
|
+
#: Positive, finite requested run spacing in µm/px. ``None`` resolves the encoder's
|
|
483
|
+
#: single registry default.
|
|
471
484
|
spacing_um: float | None = None
|
|
472
485
|
#: Relative spacing tolerance used by hs2p for spacing-readable level selection; raster
|
|
473
486
|
#: reads have no tolerance result.
|
|
@@ -502,7 +515,9 @@ class SlideRegions:
|
|
|
502
515
|
The dense input unit soma's slide-manifest path hands to
|
|
503
516
|
:meth:`Model.embed_regions_dense`. ``coordinates`` is an ``(N, 2)`` array of level-0
|
|
504
517
|
top-left ``(x, y)`` pixel coordinates; each ROI is read + encoded into one persisted
|
|
505
|
-
``(d, gh, gw)`` grid named ``<x>_<y>.pt``. ``
|
|
518
|
+
``(d, gh, gw)`` grid named ``<x>_<y>.pt``. ``spacing_at_level_0`` optionally declares
|
|
519
|
+
the source image's level-0 spacing when metadata is missing or must be overridden.
|
|
520
|
+
``annotation`` namespaces the output under a
|
|
506
521
|
per-class subdirectory (reusing the pooled convention); ``None`` is the flat layout.
|
|
507
522
|
"""
|
|
508
523
|
|
|
@@ -510,6 +525,15 @@ class SlideRegions:
|
|
|
510
525
|
image_path: PathLike
|
|
511
526
|
coordinates: Any
|
|
512
527
|
annotation: str | None = None
|
|
528
|
+
#: Optional caller declaration for the source's level-0 spacing in µm/px.
|
|
529
|
+
spacing_at_level_0: float | None = None
|
|
530
|
+
|
|
531
|
+
def __post_init__(self) -> None:
|
|
532
|
+
object.__setattr__(
|
|
533
|
+
self,
|
|
534
|
+
"spacing_at_level_0",
|
|
535
|
+
_validated_spacing_at_level_0(self.spacing_at_level_0),
|
|
536
|
+
)
|
|
513
537
|
|
|
514
538
|
|
|
515
539
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -517,19 +541,25 @@ class ImageSpec:
|
|
|
517
541
|
"""One named image source: ``(sample_id, image_path, spacing_at_level_0)``.
|
|
518
542
|
|
|
519
543
|
The input unit of :meth:`Model.embed_images` — the Given-geometry counterpart of
|
|
520
|
-
:class:`SlideRegions`. ``spacing_at_level_0``
|
|
521
|
-
|
|
522
|
-
|
|
544
|
+
:class:`SlideRegions`. ``spacing_at_level_0`` is the optional finite positive caller
|
|
545
|
+
declaration used by hs2p to resolve source level-0 spacing. Flat PNG/JPEG sources have
|
|
546
|
+
no embedded spacing and therefore require it for dense extraction. ``sample_id``
|
|
523
547
|
is the artifact's whole identity, so it must be unique within a run and a valid filename
|
|
524
548
|
component.
|
|
525
549
|
"""
|
|
526
550
|
|
|
527
551
|
sample_id: str
|
|
528
552
|
image_path: PathLike
|
|
529
|
-
#: Optional caller
|
|
530
|
-
#: non-null overrides because they have no slide pyramid or level-0 read plan.
|
|
553
|
+
#: Optional caller declaration for the source's level-0 spacing in µm/px.
|
|
531
554
|
spacing_at_level_0: float | None = None
|
|
532
555
|
|
|
556
|
+
def __post_init__(self) -> None:
|
|
557
|
+
object.__setattr__(
|
|
558
|
+
self,
|
|
559
|
+
"spacing_at_level_0",
|
|
560
|
+
_validated_spacing_at_level_0(self.spacing_at_level_0),
|
|
561
|
+
)
|
|
562
|
+
|
|
533
563
|
|
|
534
564
|
@dataclass(frozen=True, kw_only=True)
|
|
535
565
|
class RunResult:
|
|
@@ -646,6 +676,33 @@ class Model:
|
|
|
646
676
|
# Construction fact, not an encode: see _load_backend_without_transform.
|
|
647
677
|
return int(self._load_backend_without_transform().feature_dim)
|
|
648
678
|
|
|
679
|
+
def prepare_dense_encoder(
|
|
680
|
+
self,
|
|
681
|
+
*,
|
|
682
|
+
dense: "DenseImageOptions | DenseOptions",
|
|
683
|
+
execution: ExecutionOptions | None = None,
|
|
684
|
+
):
|
|
685
|
+
"""Prepare live dense encoding for one augmented RGB tensor at a time.
|
|
686
|
+
|
|
687
|
+
Only the encoding fields shared by :class:`DenseImageOptions` and
|
|
688
|
+
:class:`DenseOptions` apply after the augmented-pixel handoff. Source-reading
|
|
689
|
+
fields (spacing, tolerance, and backend) are intentionally ignored.
|
|
690
|
+
"""
|
|
691
|
+
from slide2vec.runtime.dense_encode import _prepare_dense_encode_kit
|
|
692
|
+
|
|
693
|
+
if not isinstance(dense, (DenseImageOptions, DenseOptions)):
|
|
694
|
+
raise TypeError(
|
|
695
|
+
"Model.prepare_dense_encoder(dense=...) expects DenseImageOptions "
|
|
696
|
+
f"or DenseOptions, got {type(dense).__name__}"
|
|
697
|
+
)
|
|
698
|
+
if self.level != "tile":
|
|
699
|
+
raise ValueError(
|
|
700
|
+
"Model.prepare_dense_encoder(...) requires a tile-level foundation "
|
|
701
|
+
f"encoder; model {self.name!r} is registered at level {self.level!r}."
|
|
702
|
+
)
|
|
703
|
+
resolved = _coerce_execution_options(execution, model=self)
|
|
704
|
+
return _prepare_dense_encode_kit(self, dense=dense, execution=resolved)
|
|
705
|
+
|
|
649
706
|
def embed_tiles(
|
|
650
707
|
self,
|
|
651
708
|
slides: SlideSequence,
|
|
@@ -832,7 +889,8 @@ class Model:
|
|
|
832
889
|
dense transform, and written to ``dense_embeddings/[<class>/]<sample_id>/<x>_<y>.pt``
|
|
833
890
|
plus a geometry sidecar. The run splits its ROIs across all visible GPUs
|
|
834
891
|
(``execution.num_gpus``); ``num_gpus=1`` encodes fully in-process. Resume is
|
|
835
|
-
automatic — ROIs whose sidecar
|
|
892
|
+
automatic — only ROIs whose sidecar has the same source-spacing declaration and
|
|
893
|
+
resolved hs2p read plan are skipped. Returns one
|
|
836
894
|
:class:`~slide2vec.artifacts.DenseRegionArtifact` per input ROI.
|
|
837
895
|
|
|
838
896
|
The effective encoder input — the padded ROI for a whole-tile run, one
|
|
@@ -869,16 +927,14 @@ class Model:
|
|
|
869
927
|
identity and complete extraction recipe. Returns one
|
|
870
928
|
:class:`~slide2vec.artifacts.DenseImageArtifact` per input image, in input order.
|
|
871
929
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
``dense.spacing_um`` asserts the scale those unchanged pixels already have; ``None``
|
|
875
|
-
records unknown spacing. hs2p-supported WSI inputs are spacing-readable:
|
|
930
|
+
Every source is opened through hs2p. PNG/JPEG inputs use hs2p's one-level PIL reader
|
|
931
|
+
and require ``ImageSpec.spacing_at_level_0`` because they have no embedded spacing.
|
|
876
932
|
``dense.spacing_um`` requests one physical read scale, or ``None`` resolves the
|
|
877
933
|
encoder's single registry default. The parent resolves each source's metadata,
|
|
878
934
|
concrete backend, native level, tolerance result, and final geometry before resume;
|
|
879
935
|
hs2p reads that complete level and area-downsamples when required, but never
|
|
880
|
-
upsamples. ``ImageSpec.spacing_at_level_0``
|
|
881
|
-
spacing
|
|
936
|
+
upsamples. ``ImageSpec.spacing_at_level_0`` is preserved separately from the
|
|
937
|
+
resolved source spacing and is passed to every hs2p backend.
|
|
882
938
|
|
|
883
939
|
Everything after reading is shared with the slide path, including the effective encoder input — the padded image
|
|
884
940
|
for a whole-image run, one patch-aligned window for a sliding one — which is declared
|
|
@@ -1092,16 +1148,32 @@ def list_models(level: str | None = None) -> list[str]:
|
|
|
1092
1148
|
level: Optional model level filter. Supported values are ``"tile"``,
|
|
1093
1149
|
``"slide"``, and ``"patient"``.
|
|
1094
1150
|
"""
|
|
1095
|
-
|
|
1096
|
-
|
|
1151
|
+
normalized_level = None
|
|
1152
|
+
if level is not None:
|
|
1153
|
+
normalized_level = str(level).strip().lower()
|
|
1154
|
+
if normalized_level not in {"tile", "slide", "patient"}:
|
|
1155
|
+
raise ValueError(
|
|
1156
|
+
"list_models(level=...) must be one of: tile, slide, patient"
|
|
1157
|
+
)
|
|
1097
1158
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1159
|
+
names = encoder_registry.names()
|
|
1160
|
+
diagnostics = list_encoder_provider_diagnostics()
|
|
1161
|
+
if diagnostics:
|
|
1162
|
+
detail = "; ".join(
|
|
1163
|
+
f"'{item.provider_key}': {item.concise()}" for item in diagnostics
|
|
1164
|
+
)
|
|
1165
|
+
warnings.warn(
|
|
1166
|
+
f"Skipped Encoder provider{'' if len(diagnostics) == 1 else 's'} {detail}",
|
|
1167
|
+
RuntimeWarning,
|
|
1168
|
+
stacklevel=2,
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
if level is None:
|
|
1172
|
+
return sorted(names)
|
|
1101
1173
|
|
|
1102
1174
|
return sorted(
|
|
1103
1175
|
name
|
|
1104
|
-
for name in
|
|
1176
|
+
for name in names
|
|
1105
1177
|
if encoder_registry.info(name)["level"] == normalized_level
|
|
1106
1178
|
)
|
|
1107
1179
|
|
|
@@ -310,6 +310,7 @@ class WSIRegionReader:
|
|
|
310
310
|
gpu_decode: bool = False,
|
|
311
311
|
resize_to_px: int | None = None,
|
|
312
312
|
interpolation: str = "area",
|
|
313
|
+
spacing_at_level_0: float | None = None,
|
|
313
314
|
):
|
|
314
315
|
self._image_path = str(image_path)
|
|
315
316
|
self._backend = backend
|
|
@@ -319,16 +320,56 @@ class WSIRegionReader:
|
|
|
319
320
|
self._region_size_px = int(region_size_px)
|
|
320
321
|
self._resize_to_px = int(resize_to_px) if resize_to_px is not None else None
|
|
321
322
|
self._interpolation = interpolation
|
|
323
|
+
self._spacing_at_level_0 = spacing_at_level_0
|
|
322
324
|
self._out_size_px = (
|
|
323
325
|
self._resize_to_px if self._resize_to_px is not None else self._region_size_px
|
|
324
326
|
)
|
|
325
327
|
self._reader = None
|
|
328
|
+
self._pil_rgb = None
|
|
326
329
|
|
|
327
330
|
def _ensure_open(self) -> None:
|
|
328
331
|
if self._reader is None:
|
|
329
|
-
|
|
332
|
+
if self._spacing_at_level_0 is None:
|
|
333
|
+
self._reader = _open_wsi_backend(
|
|
334
|
+
self._image_path, self._backend, self._gpu_decode
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
from hs2p.wsi.reader import open_slide
|
|
338
|
+
|
|
339
|
+
self._reader = open_slide(
|
|
340
|
+
self._image_path,
|
|
341
|
+
backend=self._backend,
|
|
342
|
+
spacing_override=self._spacing_at_level_0,
|
|
343
|
+
gpu_decode=self._gpu_decode,
|
|
344
|
+
)
|
|
345
|
+
if self._backend == "pil":
|
|
346
|
+
from PIL import Image
|
|
347
|
+
|
|
348
|
+
with Image.open(self._image_path) as image:
|
|
349
|
+
self._pil_rgb = image.convert("RGB")
|
|
330
350
|
|
|
331
351
|
def _read_regions_batch(self, locations: list[tuple[int, int]]) -> list[np.ndarray]:
|
|
352
|
+
if self._backend == "pil":
|
|
353
|
+
if self._pil_rgb is None:
|
|
354
|
+
raise RuntimeError("PIL dense region reader was not initialized")
|
|
355
|
+
size = self._region_size_px
|
|
356
|
+
width, height = self._pil_rgb.size
|
|
357
|
+
regions = []
|
|
358
|
+
for x, y in locations:
|
|
359
|
+
left, top = max(0, x), max(0, y)
|
|
360
|
+
right, bottom = min(width, x + size), min(height, y + size)
|
|
361
|
+
canvas = np.full((size, size, 3), 255, dtype=np.uint8)
|
|
362
|
+
if right > left and bottom > top:
|
|
363
|
+
crop = np.asarray(
|
|
364
|
+
self._pil_rgb.crop((left, top, right, bottom)),
|
|
365
|
+
dtype=np.uint8,
|
|
366
|
+
)
|
|
367
|
+
canvas[
|
|
368
|
+
top - y : bottom - y,
|
|
369
|
+
left - x : right - x,
|
|
370
|
+
] = crop
|
|
371
|
+
regions.append(canvas)
|
|
372
|
+
return regions
|
|
332
373
|
if self._backend == "cucim":
|
|
333
374
|
return list(
|
|
334
375
|
self._reader.read_regions(
|
|
@@ -15,9 +15,13 @@ from slide2vec.encoders.base import (
|
|
|
15
15
|
resolve_requested_output_variant,
|
|
16
16
|
)
|
|
17
17
|
from slide2vec.encoders.registry import (
|
|
18
|
+
EncoderCapabilities,
|
|
19
|
+
EncoderProviderDiagnostic,
|
|
18
20
|
encoder_registry,
|
|
21
|
+
list_encoder_provider_diagnostics,
|
|
19
22
|
normalize_patch_size,
|
|
20
23
|
register_encoder,
|
|
24
|
+
resolve_encoder_capabilities,
|
|
21
25
|
resolve_encoder_output,
|
|
22
26
|
resolve_patch_size,
|
|
23
27
|
resolve_preprocessing_requirements,
|
|
@@ -29,6 +33,8 @@ from slide2vec.encoders import models # noqa: F401
|
|
|
29
33
|
|
|
30
34
|
__all__ = [
|
|
31
35
|
"Encoder",
|
|
36
|
+
"EncoderCapabilities",
|
|
37
|
+
"EncoderProviderDiagnostic",
|
|
32
38
|
"PatientEncoder",
|
|
33
39
|
"TileEncoder",
|
|
34
40
|
"SlideEncoder",
|
|
@@ -37,8 +43,10 @@ __all__ = [
|
|
|
37
43
|
"resolve_recommended_dynamic_img_size",
|
|
38
44
|
"resolve_requested_output_variant",
|
|
39
45
|
"encoder_registry",
|
|
46
|
+
"list_encoder_provider_diagnostics",
|
|
40
47
|
"normalize_patch_size",
|
|
41
48
|
"register_encoder",
|
|
49
|
+
"resolve_encoder_capabilities",
|
|
42
50
|
"resolve_patch_size",
|
|
43
51
|
"resolve_preprocessing_requirements",
|
|
44
52
|
"resolve_encoder_output",
|
|
@@ -20,9 +20,12 @@ from . import (
|
|
|
20
20
|
phikon,
|
|
21
21
|
prost40m,
|
|
22
22
|
prism,
|
|
23
|
+
prism2,
|
|
24
|
+
rudolfv2,
|
|
23
25
|
titan,
|
|
24
26
|
uni,
|
|
25
27
|
virchow,
|
|
28
|
+
waiv,
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
__all__ = [
|
|
@@ -42,7 +45,10 @@ __all__ = [
|
|
|
42
45
|
"phikon",
|
|
43
46
|
"prost40m",
|
|
44
47
|
"prism",
|
|
48
|
+
"prism2",
|
|
49
|
+
"rudolfv2",
|
|
45
50
|
"titan",
|
|
46
51
|
"uni",
|
|
47
52
|
"virchow",
|
|
53
|
+
"waiv",
|
|
48
54
|
]
|
|
@@ -78,7 +78,7 @@ def _encode_trunk_dense(*, trunk, batch: Tensor, encoder_name: str) -> Tensor:
|
|
|
78
78
|
output_variants={"default": {"encode_dim": 512}},
|
|
79
79
|
default_output_variant="default",
|
|
80
80
|
input_size=448,
|
|
81
|
-
supports_variable_input_size=
|
|
81
|
+
supports_variable_input_size=True,
|
|
82
82
|
patch_size=16,
|
|
83
83
|
supported_spacing_um=0.5,
|
|
84
84
|
precision="fp32",
|
|
@@ -145,9 +145,8 @@ class CONCH(TileEncoder):
|
|
|
145
145
|
|
|
146
146
|
@property
|
|
147
147
|
def patch_size(self) -> tuple[int, int]:
|
|
148
|
-
# The CONCH vision trunk is a timm ViT-B/16; expose its patch size
|
|
149
|
-
# dense
|
|
150
|
-
# dynamic_img_size, so dense extraction must use the native 448 window.
|
|
148
|
+
# The CONCH vision trunk is a dynamic timm ViT-B/16; expose its patch size
|
|
149
|
+
# so dense and attention paths can resolve each runtime token grid.
|
|
151
150
|
return _patch_size_from_trunk(self._model.visual.trunk)
|
|
152
151
|
|
|
153
152
|
@property
|
|
@@ -165,7 +164,7 @@ class CONCH(TileEncoder):
|
|
|
165
164
|
output_variants={"default": {"encode_dim": 768}},
|
|
166
165
|
default_output_variant="default",
|
|
167
166
|
input_size=448,
|
|
168
|
-
supports_variable_input_size=
|
|
167
|
+
supports_variable_input_size=True,
|
|
169
168
|
patch_size=16,
|
|
170
169
|
supported_spacing_um=0.5,
|
|
171
170
|
precision="fp16",
|