slide2vec 5.6.0__tar.gz → 5.7.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.7.0}/PKG-INFO +19 -4
- {slide2vec-5.6.0 → slide2vec-5.7.0}/README.md +6 -1
- {slide2vec-5.6.0 → slide2vec-5.7.0}/pyproject.toml +16 -4
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/__init__.py +4 -1
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/api.py +82 -28
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/tile_reader.py +42 -1
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/__init__.py +6 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/conch.py +4 -5
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/isight.py +22 -13
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/phikon.py +17 -11
- slide2vec-5.7.0/slide2vec/encoders/models/prism2.py +94 -0
- slide2vec-5.7.0/slide2vec/encoders/models/rudolfv2.py +351 -0
- slide2vec-5.7.0/slide2vec/encoders/models/waiv.py +167 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/registry.py +32 -11
- slide2vec-5.7.0/slide2vec/runtime/dense_encode.py +334 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_encoder_input.py +2 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_reading.py +76 -17
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_recipe.py +5 -1
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_shard.py +2 -2
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_stage.py +27 -46
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_regions.py +66 -8
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_shard.py +72 -40
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_stage.py +55 -55
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/hierarchical.py +1 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_specs.py +14 -12
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/PKG-INFO +19 -4
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/SOURCES.txt +10 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/requires.txt +14 -2
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_attention_extraction.py +97 -4
- slide2vec-5.7.0/tests/test_dense_encode_kit.py +547 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_encoder_input.py +68 -17
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_extraction.py +5 -5
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_resume.py +10 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_stage.py +112 -56
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_shard.py +106 -5
- slide2vec-5.7.0/tests/test_dense_source_spacing.py +194 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_stage.py +134 -6
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_worker.py +18 -1
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_encoder_input_contract.py +3 -3
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_encoder_registry.py +175 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_hs2p_package_cutover.py +3 -3
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_isight.py +52 -1
- slide2vec-5.7.0/tests/test_mascaret.py +401 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_patch_size_metadata.py +6 -1
- slide2vec-5.7.0/tests/test_phaet.py +352 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_pooled_encoder_input.py +106 -6
- slide2vec-5.7.0/tests/test_prism2.py +470 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_core.py +6 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_inference.py +1 -0
- slide2vec-5.7.0/tests/test_rudolfv2.py +763 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_soma_migration.py +9 -2
- {slide2vec-5.6.0 → slide2vec-5.7.0}/LICENSE +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/setup.cfg +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/__main__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/cli.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/inference.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/progress.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_image_shard.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_image_stage.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_progress.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_models.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_sharding.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_tile_store.py +0 -0
- {slide2vec-5.6.0 → slide2vec-5.7.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.7.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
|
|
@@ -225,7 +239,8 @@ The package writes explicit artifact directories:
|
|
|
225
239
|
|
|
226
240
|
### Supported Models
|
|
227
241
|
|
|
228
|
-
`slide2vec` currently ships
|
|
242
|
+
`slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
|
|
243
|
+
and 1 patient-level model.
|
|
229
244
|
For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
|
|
230
245
|
|
|
231
246
|
## CLI
|
|
@@ -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
|
|
@@ -119,7 +123,8 @@ The package writes explicit artifact directories:
|
|
|
119
123
|
|
|
120
124
|
### Supported Models
|
|
121
125
|
|
|
122
|
-
`slide2vec` currently ships
|
|
126
|
+
`slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
|
|
127
|
+
and 1 patient-level model.
|
|
123
128
|
For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
|
|
124
129
|
|
|
125
130
|
## 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.7.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.7.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,9 +20,10 @@ from slide2vec.artifacts import (
|
|
|
20
20
|
SlideEmbeddingArtifact,
|
|
21
21
|
TileEmbeddingArtifact,
|
|
22
22
|
)
|
|
23
|
+
from slide2vec.runtime.dense_encode import DenseEncodeGeometry, DenseEncodeKit
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
__version__ = "5.
|
|
26
|
+
__version__ = "5.7.0"
|
|
26
27
|
|
|
27
28
|
__all__ = [
|
|
28
29
|
"Model",
|
|
@@ -31,6 +32,8 @@ __all__ = [
|
|
|
31
32
|
"PreprocessingConfig",
|
|
32
33
|
"DenseOptions",
|
|
33
34
|
"DenseImageOptions",
|
|
35
|
+
"DenseEncodeGeometry",
|
|
36
|
+
"DenseEncodeKit",
|
|
34
37
|
"SlideRegions",
|
|
35
38
|
"ImageSpec",
|
|
36
39
|
"ExecutionOptions",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import copy
|
|
3
3
|
import logging
|
|
4
|
+
import math
|
|
4
5
|
import os
|
|
5
6
|
from dataclasses import dataclass, field, replace
|
|
6
7
|
from contextlib import contextmanager
|
|
@@ -39,6 +40,17 @@ from slide2vec.utils.utils import cpu_worker_limit, slurm_cpu_limit
|
|
|
39
40
|
PathLike = str | Path
|
|
40
41
|
|
|
41
42
|
|
|
43
|
+
def _validated_spacing_at_level_0(value: float | None) -> float | None:
|
|
44
|
+
if value is None:
|
|
45
|
+
return None
|
|
46
|
+
if isinstance(value, bool):
|
|
47
|
+
raise ValueError("spacing_at_level_0 must be a positive, finite value or None")
|
|
48
|
+
spacing = float(value)
|
|
49
|
+
if not math.isfinite(spacing) or spacing <= 0:
|
|
50
|
+
raise ValueError("spacing_at_level_0 must be a positive, finite value or None")
|
|
51
|
+
return spacing
|
|
52
|
+
|
|
53
|
+
|
|
42
54
|
class SlideLike(Protocol):
|
|
43
55
|
sample_id: str
|
|
44
56
|
image_path: PathLike
|
|
@@ -310,9 +322,10 @@ class ExecutionOptions:
|
|
|
310
322
|
#: Forward-pass dtype — ``"fp16"``, ``"bf16"``, ``"fp32"``,
|
|
311
323
|
#: or ``None`` (auto-determined from the model preset).
|
|
312
324
|
precision: str | None = None
|
|
313
|
-
#:
|
|
314
|
-
#: :attr:`precision` (fp16 → fp16, else fp32). Applies to
|
|
315
|
-
#: and patient artifacts; ``"bf16"`` is rejected
|
|
325
|
+
#: Feature output dtype — ``"fp16"``, ``"fp32"``, or ``None`` to follow
|
|
326
|
+
#: :attr:`precision` (fp16 → fp16, else fp32). Applies to live dense grids and to
|
|
327
|
+
#: tile, slide, hierarchical, and patient artifacts; ``"bf16"`` is rejected because
|
|
328
|
+
#: persisted features cross a numpy boundary.
|
|
316
329
|
output_dtype: str | None = None
|
|
317
330
|
#: DataLoader prefetch queue depth per worker (default ``4``).
|
|
318
331
|
prefetch_factor: int = 4
|
|
@@ -446,16 +459,15 @@ class DenseOptions:
|
|
|
446
459
|
class DenseImageOptions:
|
|
447
460
|
"""Dense ``(d, gh, gw)`` extraction over pre-cropped images.
|
|
448
461
|
|
|
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.
|
|
462
|
+
Every supported source uses hs2p's spacing-aware reader contract. ``.png``, ``.jpg``,
|
|
463
|
+
and ``.jpeg`` inputs use hs2p's one-level PIL reader and therefore require
|
|
464
|
+
``ImageSpec.spacing_at_level_0``; WSI readers may resolve native metadata instead.
|
|
465
|
+
hs2p resolves source spacing, backend, pyramid level, tolerance, complete-extent read,
|
|
466
|
+
and permitted area downsampling at the requested run-level ``spacing_um``. Omitted
|
|
467
|
+
spacing resolves the encoder's single registry default.
|
|
456
468
|
|
|
457
|
-
``ImageSpec.spacing_at_level_0``
|
|
458
|
-
|
|
469
|
+
``ImageSpec.spacing_at_level_0`` is the optional caller declaration used by hs2p when
|
|
470
|
+
resolving level-0 spacing. ``target_size`` is always a strict post-read declaration, never a fit-to-size
|
|
459
471
|
request: each final pixel array must already be exactly this size. Declaring it up front
|
|
460
472
|
lets the effective encoder input be validated (and variable-input constructor settings
|
|
461
473
|
resolved) before model loading or pixel decoding. Differing final geometries therefore
|
|
@@ -465,9 +477,8 @@ class DenseImageOptions:
|
|
|
465
477
|
#: Supervision geometry in pixels the dense grid registers to: a square side length, or
|
|
466
478
|
#: an explicit ``(height, width)`` for non-square images.
|
|
467
479
|
target_size: int | tuple[int, int]
|
|
468
|
-
#: Positive, finite run
|
|
469
|
-
#:
|
|
470
|
-
#: single registry default for spacing-readable inputs.
|
|
480
|
+
#: Positive, finite requested run spacing in µm/px. ``None`` resolves the encoder's
|
|
481
|
+
#: single registry default.
|
|
471
482
|
spacing_um: float | None = None
|
|
472
483
|
#: Relative spacing tolerance used by hs2p for spacing-readable level selection; raster
|
|
473
484
|
#: reads have no tolerance result.
|
|
@@ -502,7 +513,9 @@ class SlideRegions:
|
|
|
502
513
|
The dense input unit soma's slide-manifest path hands to
|
|
503
514
|
:meth:`Model.embed_regions_dense`. ``coordinates`` is an ``(N, 2)`` array of level-0
|
|
504
515
|
top-left ``(x, y)`` pixel coordinates; each ROI is read + encoded into one persisted
|
|
505
|
-
``(d, gh, gw)`` grid named ``<x>_<y>.pt``. ``
|
|
516
|
+
``(d, gh, gw)`` grid named ``<x>_<y>.pt``. ``spacing_at_level_0`` optionally declares
|
|
517
|
+
the source image's level-0 spacing when metadata is missing or must be overridden.
|
|
518
|
+
``annotation`` namespaces the output under a
|
|
506
519
|
per-class subdirectory (reusing the pooled convention); ``None`` is the flat layout.
|
|
507
520
|
"""
|
|
508
521
|
|
|
@@ -510,6 +523,15 @@ class SlideRegions:
|
|
|
510
523
|
image_path: PathLike
|
|
511
524
|
coordinates: Any
|
|
512
525
|
annotation: str | None = None
|
|
526
|
+
#: Optional caller declaration for the source's level-0 spacing in µm/px.
|
|
527
|
+
spacing_at_level_0: float | None = None
|
|
528
|
+
|
|
529
|
+
def __post_init__(self) -> None:
|
|
530
|
+
object.__setattr__(
|
|
531
|
+
self,
|
|
532
|
+
"spacing_at_level_0",
|
|
533
|
+
_validated_spacing_at_level_0(self.spacing_at_level_0),
|
|
534
|
+
)
|
|
513
535
|
|
|
514
536
|
|
|
515
537
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -517,19 +539,25 @@ class ImageSpec:
|
|
|
517
539
|
"""One named image source: ``(sample_id, image_path, spacing_at_level_0)``.
|
|
518
540
|
|
|
519
541
|
The input unit of :meth:`Model.embed_images` — the Given-geometry counterpart of
|
|
520
|
-
:class:`SlideRegions`. ``spacing_at_level_0``
|
|
521
|
-
|
|
522
|
-
|
|
542
|
+
:class:`SlideRegions`. ``spacing_at_level_0`` is the optional finite positive caller
|
|
543
|
+
declaration used by hs2p to resolve source level-0 spacing. Flat PNG/JPEG sources have
|
|
544
|
+
no embedded spacing and therefore require it for dense extraction. ``sample_id``
|
|
523
545
|
is the artifact's whole identity, so it must be unique within a run and a valid filename
|
|
524
546
|
component.
|
|
525
547
|
"""
|
|
526
548
|
|
|
527
549
|
sample_id: str
|
|
528
550
|
image_path: PathLike
|
|
529
|
-
#: Optional caller
|
|
530
|
-
#: non-null overrides because they have no slide pyramid or level-0 read plan.
|
|
551
|
+
#: Optional caller declaration for the source's level-0 spacing in µm/px.
|
|
531
552
|
spacing_at_level_0: float | None = None
|
|
532
553
|
|
|
554
|
+
def __post_init__(self) -> None:
|
|
555
|
+
object.__setattr__(
|
|
556
|
+
self,
|
|
557
|
+
"spacing_at_level_0",
|
|
558
|
+
_validated_spacing_at_level_0(self.spacing_at_level_0),
|
|
559
|
+
)
|
|
560
|
+
|
|
533
561
|
|
|
534
562
|
@dataclass(frozen=True, kw_only=True)
|
|
535
563
|
class RunResult:
|
|
@@ -646,6 +674,33 @@ class Model:
|
|
|
646
674
|
# Construction fact, not an encode: see _load_backend_without_transform.
|
|
647
675
|
return int(self._load_backend_without_transform().feature_dim)
|
|
648
676
|
|
|
677
|
+
def prepare_dense_encoder(
|
|
678
|
+
self,
|
|
679
|
+
*,
|
|
680
|
+
dense: "DenseImageOptions | DenseOptions",
|
|
681
|
+
execution: ExecutionOptions | None = None,
|
|
682
|
+
):
|
|
683
|
+
"""Prepare live dense encoding for one augmented RGB tensor at a time.
|
|
684
|
+
|
|
685
|
+
Only the encoding fields shared by :class:`DenseImageOptions` and
|
|
686
|
+
:class:`DenseOptions` apply after the augmented-pixel handoff. Source-reading
|
|
687
|
+
fields (spacing, tolerance, and backend) are intentionally ignored.
|
|
688
|
+
"""
|
|
689
|
+
from slide2vec.runtime.dense_encode import _prepare_dense_encode_kit
|
|
690
|
+
|
|
691
|
+
if not isinstance(dense, (DenseImageOptions, DenseOptions)):
|
|
692
|
+
raise TypeError(
|
|
693
|
+
"Model.prepare_dense_encoder(dense=...) expects DenseImageOptions "
|
|
694
|
+
f"or DenseOptions, got {type(dense).__name__}"
|
|
695
|
+
)
|
|
696
|
+
if self.level != "tile":
|
|
697
|
+
raise ValueError(
|
|
698
|
+
"Model.prepare_dense_encoder(...) requires a tile-level foundation "
|
|
699
|
+
f"encoder; model {self.name!r} is registered at level {self.level!r}."
|
|
700
|
+
)
|
|
701
|
+
resolved = _coerce_execution_options(execution, model=self)
|
|
702
|
+
return _prepare_dense_encode_kit(self, dense=dense, execution=resolved)
|
|
703
|
+
|
|
649
704
|
def embed_tiles(
|
|
650
705
|
self,
|
|
651
706
|
slides: SlideSequence,
|
|
@@ -832,7 +887,8 @@ class Model:
|
|
|
832
887
|
dense transform, and written to ``dense_embeddings/[<class>/]<sample_id>/<x>_<y>.pt``
|
|
833
888
|
plus a geometry sidecar. The run splits its ROIs across all visible GPUs
|
|
834
889
|
(``execution.num_gpus``); ``num_gpus=1`` encodes fully in-process. Resume is
|
|
835
|
-
automatic — ROIs whose sidecar
|
|
890
|
+
automatic — only ROIs whose sidecar has the same source-spacing declaration and
|
|
891
|
+
resolved hs2p read plan are skipped. Returns one
|
|
836
892
|
:class:`~slide2vec.artifacts.DenseRegionArtifact` per input ROI.
|
|
837
893
|
|
|
838
894
|
The effective encoder input — the padded ROI for a whole-tile run, one
|
|
@@ -869,16 +925,14 @@ class Model:
|
|
|
869
925
|
identity and complete extraction recipe. Returns one
|
|
870
926
|
:class:`~slide2vec.artifacts.DenseImageArtifact` per input image, in input order.
|
|
871
927
|
|
|
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:
|
|
928
|
+
Every source is opened through hs2p. PNG/JPEG inputs use hs2p's one-level PIL reader
|
|
929
|
+
and require ``ImageSpec.spacing_at_level_0`` because they have no embedded spacing.
|
|
876
930
|
``dense.spacing_um`` requests one physical read scale, or ``None`` resolves the
|
|
877
931
|
encoder's single registry default. The parent resolves each source's metadata,
|
|
878
932
|
concrete backend, native level, tolerance result, and final geometry before resume;
|
|
879
933
|
hs2p reads that complete level and area-downsamples when required, but never
|
|
880
|
-
upsamples. ``ImageSpec.spacing_at_level_0``
|
|
881
|
-
spacing
|
|
934
|
+
upsamples. ``ImageSpec.spacing_at_level_0`` is preserved separately from the
|
|
935
|
+
resolved source spacing and is passed to every hs2p backend.
|
|
882
936
|
|
|
883
937
|
Everything after reading is shared with the slide path, including the effective encoder input — the padded image
|
|
884
938
|
for a whole-image run, one patch-aligned window for a sliding one — which is declared
|
|
@@ -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(
|
|
@@ -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",
|
|
@@ -19,11 +19,11 @@ Every numerically-relevant choice mirrors https://github.com/zhihuanglab/iSight
|
|
|
19
19
|
* Tile features are ``vision_model(..., output_hidden_states=True)
|
|
20
20
|
.hidden_states[-1]`` passed through the learned ``visual_token_projection``
|
|
21
21
|
— ``patch_encoder_with_clam.py:217-219``.
|
|
22
|
-
* Reduction to one vector per tile is the **mean over all 577
|
|
23
|
-
|
|
24
|
-
is immediately overwritten by the token-mean line, and
|
|
25
|
-
``model_version = v3_all_tokens`` in ``config/config.ini:3``.
|
|
26
|
-
branch is still offered as the non-default ``cls`` output variant.
|
|
22
|
+
* Reduction to one vector per tile is the **mean over all tokens** (577 at the
|
|
23
|
+
native 336 x 336 input, CLS included) — ``patch_encoder_with_clam.py:254-255``,
|
|
24
|
+
where the CLS line is immediately overwritten by the token-mean line, and
|
|
25
|
+
corroborated by ``model_version = v3_all_tokens`` in ``config/config.ini:3``.
|
|
26
|
+
The dead CLS branch is still offered as the non-default ``cls`` output variant.
|
|
27
27
|
* Preprocessing is the model's own ``AutoProcessor`` — the reference builds it
|
|
28
28
|
as ``self.patch_processor`` (``patch_encoder_with_clam.py:128``) and applies
|
|
29
29
|
it per tile in ``dataset/hpadataset.py:152``.
|
|
@@ -65,12 +65,13 @@ check on the download.
|
|
|
65
65
|
Slide level is deliberately absent
|
|
66
66
|
----------------------------------
|
|
67
67
|
iSight's gated attention runs at *token* level, not tile level: ``A`` has shape
|
|
68
|
-
``(n_tiles, n_tokens, 1)`` and is softmaxed over tiles, so
|
|
69
|
-
|
|
68
|
+
``(n_tiles, n_tokens, 1)`` and is softmaxed over tiles, so every token position
|
|
69
|
+
gets its own distribution over tiles
|
|
70
70
|
(``patch_encoder_with_clam.py:230-255``). The pooled result is therefore not a
|
|
71
71
|
function of per-tile ``(N, D)`` vectors, which is what
|
|
72
72
|
:meth:`SlideEncoder.encode_slide` receives. Reproducing it would require caching
|
|
73
|
-
``
|
|
73
|
+
``n_tokens x 1024`` per tile (577 x 1024 at the native input). The heads are
|
|
74
|
+
also specific to the five HPA tasks, so
|
|
74
75
|
the pooled vector is not a general slide representation. Downstream MIL should
|
|
75
76
|
do the pooling instead.
|
|
76
77
|
"""
|
|
@@ -125,7 +126,7 @@ def _load_isight_state_dict() -> dict[str, Tensor]:
|
|
|
125
126
|
},
|
|
126
127
|
default_output_variant="token_mean",
|
|
127
128
|
input_size=336,
|
|
128
|
-
supports_variable_input_size=
|
|
129
|
+
supports_variable_input_size=True,
|
|
129
130
|
patch_size=14,
|
|
130
131
|
supported_spacing_um=0.5,
|
|
131
132
|
precision="fp16",
|
|
@@ -208,15 +209,19 @@ class ISight(TileEncoder):
|
|
|
208
209
|
])
|
|
209
210
|
|
|
210
211
|
def _token_features(self, batch: Tensor) -> Tensor:
|
|
211
|
-
"""Projected
|
|
212
|
-
outputs = self._vision(
|
|
212
|
+
"""Projected ``(B, 1 + H/14 * W/14, 1024)`` token sequence."""
|
|
213
|
+
outputs = self._vision(
|
|
214
|
+
batch,
|
|
215
|
+
output_hidden_states=True,
|
|
216
|
+
interpolate_pos_encoding=True,
|
|
217
|
+
)
|
|
213
218
|
return self._projection(outputs.hidden_states[-1])
|
|
214
219
|
|
|
215
220
|
def encode_tiles(self, batch: Tensor) -> Tensor:
|
|
216
221
|
features = self._token_features(batch)
|
|
217
222
|
if self._output_variant == "cls":
|
|
218
223
|
return features[:, 0]
|
|
219
|
-
# Mean over
|
|
224
|
+
# Mean over every runtime token, CLS included — 577 at the native input.
|
|
220
225
|
return features.mean(dim=1)
|
|
221
226
|
|
|
222
227
|
def encode_tiles_dense(self, batch: Tensor) -> Tensor:
|
|
@@ -273,7 +278,11 @@ class ISight(TileEncoder):
|
|
|
273
278
|
)
|
|
274
279
|
# SDPA silently returns attentions=None; flip the PreTrainedModel to eager.
|
|
275
280
|
with hf_eager_attention(self._clip):
|
|
276
|
-
outputs = self._vision(
|
|
281
|
+
outputs = self._vision(
|
|
282
|
+
batch,
|
|
283
|
+
output_attentions=True,
|
|
284
|
+
interpolate_pos_encoding=True,
|
|
285
|
+
)
|
|
277
286
|
return attentions_tuple_to_grids(
|
|
278
287
|
outputs.attentions,
|
|
279
288
|
num_prefix_tokens=1,
|