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.
Files changed (158) hide show
  1. {slide2vec-5.6.0 → slide2vec-5.8.0}/PKG-INFO +26 -11
  2. {slide2vec-5.6.0 → slide2vec-5.8.0}/README.md +13 -8
  3. {slide2vec-5.6.0 → slide2vec-5.8.0}/pyproject.toml +16 -4
  4. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/__init__.py +10 -1
  5. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/api.py +106 -34
  6. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/tile_reader.py +42 -1
  7. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/__init__.py +8 -0
  8. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/__init__.py +6 -0
  9. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/conch.py +4 -5
  10. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/isight.py +22 -13
  11. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/phikon.py +17 -11
  12. slide2vec-5.8.0/slide2vec/encoders/models/prism2.py +94 -0
  13. slide2vec-5.8.0/slide2vec/encoders/models/rudolfv2.py +351 -0
  14. slide2vec-5.8.0/slide2vec/encoders/models/waiv.py +167 -0
  15. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/registry.py +370 -26
  16. slide2vec-5.8.0/slide2vec/runtime/dense_encode.py +334 -0
  17. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_encoder_input.py +2 -0
  18. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_reading.py +76 -17
  19. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_recipe.py +5 -1
  20. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_shard.py +2 -2
  21. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_image_stage.py +27 -46
  22. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_regions.py +66 -8
  23. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_shard.py +72 -40
  24. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_stage.py +55 -55
  25. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/hierarchical.py +1 -0
  26. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_specs.py +14 -12
  27. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/registry.py +8 -1
  28. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/PKG-INFO +26 -11
  29. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/SOURCES.txt +13 -0
  30. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/requires.txt +14 -2
  31. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_attention_extraction.py +97 -4
  32. slide2vec-5.8.0/tests/test_dense_encode_kit.py +547 -0
  33. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_encoder_input.py +68 -17
  34. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_extraction.py +5 -5
  35. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_resume.py +10 -0
  36. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_stage.py +130 -59
  37. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_shard.py +106 -5
  38. slide2vec-5.8.0/tests/test_dense_source_spacing.py +194 -0
  39. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_stage.py +134 -6
  40. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_worker.py +18 -1
  41. slide2vec-5.8.0/tests/test_encoder_capabilities.py +481 -0
  42. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_encoder_input_contract.py +3 -3
  43. slide2vec-5.8.0/tests/test_encoder_plugins.py +298 -0
  44. slide2vec-5.8.0/tests/test_encoder_provider_failures.py +391 -0
  45. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_encoder_registry.py +175 -0
  46. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_hs2p_package_cutover.py +3 -3
  47. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_isight.py +52 -1
  48. slide2vec-5.8.0/tests/test_mascaret.py +401 -0
  49. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_patch_size_metadata.py +6 -1
  50. slide2vec-5.8.0/tests/test_phaet.py +352 -0
  51. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_pooled_encoder_input.py +106 -6
  52. slide2vec-5.8.0/tests/test_prism2.py +470 -0
  53. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_core.py +6 -0
  54. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_inference.py +1 -0
  55. slide2vec-5.8.0/tests/test_rudolfv2.py +763 -0
  56. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_soma_migration.py +9 -2
  57. {slide2vec-5.6.0 → slide2vec-5.8.0}/LICENSE +0 -0
  58. {slide2vec-5.6.0 → slide2vec-5.8.0}/setup.cfg +0 -0
  59. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/__main__.py +0 -0
  60. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/artifacts.py +0 -0
  61. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/cli.py +0 -0
  62. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/__init__.py +0 -0
  63. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/default.yaml +0 -0
  64. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/configs/resources.py +0 -0
  65. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/__init__.py +0 -0
  66. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/dataset.py +0 -0
  67. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/data/tile_store.py +0 -0
  68. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/__init__.py +0 -0
  69. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/dense_image_worker.py +0 -0
  70. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/dense_worker.py +0 -0
  71. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
  72. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/image_worker.py +0 -0
  73. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/pipeline_worker.py +0 -0
  74. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/distributed/worker_entry.py +0 -0
  75. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/base.py +0 -0
  76. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/dinov2.py +0 -0
  77. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/genbio.py +0 -0
  78. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/gigapath.py +0 -0
  79. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/gpfm.py +0 -0
  80. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/hibou.py +0 -0
  81. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/hoptimus.py +0 -0
  82. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/lunit.py +0 -0
  83. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/midnight.py +0 -0
  84. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  85. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  86. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/case.py +0 -0
  87. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
  88. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
  89. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/moozy/types.py +0 -0
  90. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/mstar.py +0 -0
  91. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/musk.py +0 -0
  92. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/prism.py +0 -0
  93. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/prost40m.py +0 -0
  94. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/titan.py +0 -0
  95. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/uni.py +0 -0
  96. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/models/virchow.py +0 -0
  97. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/encoders/validation.py +0 -0
  98. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/inference.py +0 -0
  99. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/progress.py +0 -0
  100. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/__init__.py +0 -0
  101. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/artifacts_collect.py +0 -0
  102. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/batching.py +0 -0
  103. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/cpu_budget.py +0 -0
  104. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/dense_sliding.py +0 -0
  105. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/distributed.py +0 -0
  106. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/distributed_stage.py +0 -0
  107. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
  108. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding.py +0 -0
  109. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding_persist.py +0 -0
  110. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
  111. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
  112. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_shard.py +0 -0
  113. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/image_stage.py +0 -0
  114. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/manifest.py +0 -0
  115. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/model_settings.py +0 -0
  116. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/patient_pipeline.py +0 -0
  117. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/persist_callbacks.py +0 -0
  118. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/persistence.py +0 -0
  119. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  120. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/preprocessing.py +0 -0
  121. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/process_list.py +0 -0
  122. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/progress_bridge.py +0 -0
  123. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/serialization.py +0 -0
  124. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/sharding.py +0 -0
  125. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/slide_encode.py +0 -0
  126. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/tiling.py +0 -0
  127. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
  128. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/types.py +0 -0
  129. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/runtime/worker_io.py +0 -0
  130. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/__init__.py +0 -0
  131. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/config.py +0 -0
  132. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/coordinates.py +0 -0
  133. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/log_utils.py +0 -0
  134. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/tiling_io.py +0 -0
  135. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec/utils/utils.py +0 -0
  136. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/dependency_links.txt +0 -0
  137. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/entry_points.txt +0 -0
  138. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/not-zip-safe +0 -0
  139. {slide2vec-5.6.0 → slide2vec-5.8.0}/slide2vec.egg-info/top_level.txt +0 -0
  140. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_architecture_runtime_split.py +0 -0
  141. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_reading.py +0 -0
  142. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_image_shard.py +0 -0
  143. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_regions.py +0 -0
  144. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dense_sliding.py +0 -0
  145. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_dinov2_natimage.py +0 -0
  146. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_gpfm_genbio_heavy.py +0 -0
  147. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_image_shard.py +0 -0
  148. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_image_stage.py +0 -0
  149. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_output_consistency.py +0 -0
  150. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_patient_manifest.py +0 -0
  151. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_pooled_geometry.py +0 -0
  152. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_progress.py +0 -0
  153. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_regression_models.py +0 -0
  154. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_runtime_batching.py +0 -0
  155. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_sharding.py +0 -0
  156. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_slide_coordinate_preparation.py +0 -0
  157. {slide2vec-5.6.0 → slide2vec-5.8.0}/tests/test_tile_store.py +0 -0
  158. {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.6.0
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.0
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.0; extra == "fm"
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 [`docs/python-api.md`](docs/python-api.md) for details.
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 preset configs for 22 tile-level models and 3 slide-level models.
229
- For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
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 [`docs/cli.md`](docs/cli.md) for the config-driven workflow, overrides, and common run patterns.
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/) for the polished docs site
261
- - [`docs/python-api.md`](docs/python-api.md) for the detailed API reference
262
- - [`docs/cli.md`](docs/cli.md) for the config-driven CLI guide
263
- - [`docs/models.md`](docs/models.md) for the full supported-model catalog
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 [`docs/python-api.md`](docs/python-api.md) for details.
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 preset configs for 22 tile-level models and 3 slide-level models.
123
- For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
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 [`docs/cli.md`](docs/cli.md) for the config-driven workflow, overrides, and common run patterns.
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/) for the polished docs site
155
- - [`docs/python-api.md`](docs/python-api.md) for the detailed API reference
156
- - [`docs/cli.md`](docs/cli.md) for the config-driven CLI guide
157
- - [`docs/models.md`](docs/models.md) for the full supported-model catalog
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.6.0"
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.0",
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.0",
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.6.0"
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.6.0"
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
- #: On-disk feature dtype — ``"fp16"``, ``"fp32"``, or ``None`` to follow
314
- #: :attr:`precision` (fp16 → fp16, else fp32). Applies to tile, slide, hierarchical,
315
- #: and patient artifacts; ``"bf16"`` is rejected (numpy has no bfloat16).
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
- One run has one reader regime. ``.png``, ``.jpg``, and ``.jpeg`` inputs
450
- (case-insensitive) use Pillow and treat numeric ``spacing_um`` as an assertion about
451
- unchanged pixels. hs2p-supported WSI formats form the spacing-readable regime: hs2p
452
- resolves source spacing, backend, pyramid level, tolerance, complete-extent read, and
453
- area downsampling at the one requested run-level ``spacing_um``. Omitted spacing resolves
454
- the encoder's single registry default for spacing-readable inputs and requires an
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`` overrides level-0 metadata only for spacing-readable
458
- sources. ``target_size`` is always a strict post-read declaration, never a fit-to-size
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-level spacing in µm/px: asserted for raster pixels and requested
469
- #: for spacing-readable reads. ``None`` is unknown for raster and resolves the encoder's
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``. ``annotation`` namespaces the output under a
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`` represents caller metadata for
521
- spacing-readable image sources. The current raster paths reject a non-null value: a
522
- pre-cropped PNG/JPEG has no slide pyramid or level-0 read plan to override. ``sample_id``
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 override for the source's level-0 spacing. Raster image paths reject
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 already exists are skipped. Returns one
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
- One run uses one reader regime. Raster inputs are exactly ``.png``, ``.jpg``, and
873
- ``.jpeg`` (case-insensitive) and always use Pillow's RGB decoder.
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`` overrides source metadata only in this
881
- spacing-readable regime and is rejected for raster inputs.
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
- if level is None:
1096
- return sorted(encoder_registry.names())
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
- normalized_level = str(level).strip().lower()
1099
- if normalized_level not in {"tile", "slide", "patient"}:
1100
- raise ValueError("list_models(level=...) must be one of: tile, slide, patient")
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 encoder_registry.names()
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
- self._reader = _open_wsi_backend(self._image_path, self._backend, self._gpu_decode)
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=False,
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 so the
149
- # dense path can resolve the token grid. open_clip builds the trunk without
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=False,
167
+ supports_variable_input_size=True,
169
168
  patch_size=16,
170
169
  supported_spacing_um=0.5,
171
170
  precision="fp16",