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.
Files changed (155) hide show
  1. {slide2vec-5.6.0 → slide2vec-5.7.0}/PKG-INFO +19 -4
  2. {slide2vec-5.6.0 → slide2vec-5.7.0}/README.md +6 -1
  3. {slide2vec-5.6.0 → slide2vec-5.7.0}/pyproject.toml +16 -4
  4. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/__init__.py +4 -1
  5. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/api.py +82 -28
  6. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/tile_reader.py +42 -1
  7. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/__init__.py +6 -0
  8. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/conch.py +4 -5
  9. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/isight.py +22 -13
  10. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/phikon.py +17 -11
  11. slide2vec-5.7.0/slide2vec/encoders/models/prism2.py +94 -0
  12. slide2vec-5.7.0/slide2vec/encoders/models/rudolfv2.py +351 -0
  13. slide2vec-5.7.0/slide2vec/encoders/models/waiv.py +167 -0
  14. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/registry.py +32 -11
  15. slide2vec-5.7.0/slide2vec/runtime/dense_encode.py +334 -0
  16. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_encoder_input.py +2 -0
  17. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_reading.py +76 -17
  18. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_recipe.py +5 -1
  19. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_shard.py +2 -2
  20. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_image_stage.py +27 -46
  21. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_regions.py +66 -8
  22. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_shard.py +72 -40
  23. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_stage.py +55 -55
  24. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/hierarchical.py +1 -0
  25. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_specs.py +14 -12
  26. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/PKG-INFO +19 -4
  27. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/SOURCES.txt +10 -0
  28. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/requires.txt +14 -2
  29. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_attention_extraction.py +97 -4
  30. slide2vec-5.7.0/tests/test_dense_encode_kit.py +547 -0
  31. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_encoder_input.py +68 -17
  32. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_extraction.py +5 -5
  33. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_resume.py +10 -0
  34. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_stage.py +112 -56
  35. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_shard.py +106 -5
  36. slide2vec-5.7.0/tests/test_dense_source_spacing.py +194 -0
  37. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_stage.py +134 -6
  38. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_worker.py +18 -1
  39. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_encoder_input_contract.py +3 -3
  40. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_encoder_registry.py +175 -0
  41. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_hs2p_package_cutover.py +3 -3
  42. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_isight.py +52 -1
  43. slide2vec-5.7.0/tests/test_mascaret.py +401 -0
  44. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_patch_size_metadata.py +6 -1
  45. slide2vec-5.7.0/tests/test_phaet.py +352 -0
  46. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_pooled_encoder_input.py +106 -6
  47. slide2vec-5.7.0/tests/test_prism2.py +470 -0
  48. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_core.py +6 -0
  49. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_inference.py +1 -0
  50. slide2vec-5.7.0/tests/test_rudolfv2.py +763 -0
  51. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_soma_migration.py +9 -2
  52. {slide2vec-5.6.0 → slide2vec-5.7.0}/LICENSE +0 -0
  53. {slide2vec-5.6.0 → slide2vec-5.7.0}/setup.cfg +0 -0
  54. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/__main__.py +0 -0
  55. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/artifacts.py +0 -0
  56. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/cli.py +0 -0
  57. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/__init__.py +0 -0
  58. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/default.yaml +0 -0
  59. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/configs/resources.py +0 -0
  60. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/__init__.py +0 -0
  61. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/dataset.py +0 -0
  62. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/data/tile_store.py +0 -0
  63. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/__init__.py +0 -0
  64. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/dense_image_worker.py +0 -0
  65. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/dense_worker.py +0 -0
  66. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
  67. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/image_worker.py +0 -0
  68. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/pipeline_worker.py +0 -0
  69. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/distributed/worker_entry.py +0 -0
  70. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/__init__.py +0 -0
  71. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/base.py +0 -0
  72. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/dinov2.py +0 -0
  73. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/genbio.py +0 -0
  74. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/gigapath.py +0 -0
  75. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/gpfm.py +0 -0
  76. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/hibou.py +0 -0
  77. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/hoptimus.py +0 -0
  78. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/lunit.py +0 -0
  79. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/midnight.py +0 -0
  80. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  81. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  82. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/case.py +0 -0
  83. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
  84. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
  85. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/moozy/types.py +0 -0
  86. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/mstar.py +0 -0
  87. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/musk.py +0 -0
  88. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/prism.py +0 -0
  89. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/prost40m.py +0 -0
  90. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/titan.py +0 -0
  91. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/uni.py +0 -0
  92. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/models/virchow.py +0 -0
  93. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/encoders/validation.py +0 -0
  94. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/inference.py +0 -0
  95. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/progress.py +0 -0
  96. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/__init__.py +0 -0
  97. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/artifacts_collect.py +0 -0
  98. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/batching.py +0 -0
  99. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/cpu_budget.py +0 -0
  100. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/dense_sliding.py +0 -0
  101. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/distributed.py +0 -0
  102. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/distributed_stage.py +0 -0
  103. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
  104. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding.py +0 -0
  105. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding_persist.py +0 -0
  106. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
  107. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
  108. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_shard.py +0 -0
  109. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/image_stage.py +0 -0
  110. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/manifest.py +0 -0
  111. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/model_settings.py +0 -0
  112. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/patient_pipeline.py +0 -0
  113. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/persist_callbacks.py +0 -0
  114. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/persistence.py +0 -0
  115. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  116. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/preprocessing.py +0 -0
  117. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/process_list.py +0 -0
  118. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/progress_bridge.py +0 -0
  119. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/registry.py +0 -0
  120. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/serialization.py +0 -0
  121. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/sharding.py +0 -0
  122. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/slide_encode.py +0 -0
  123. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/tiling.py +0 -0
  124. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
  125. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/types.py +0 -0
  126. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/runtime/worker_io.py +0 -0
  127. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/__init__.py +0 -0
  128. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/config.py +0 -0
  129. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/coordinates.py +0 -0
  130. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/log_utils.py +0 -0
  131. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/tiling_io.py +0 -0
  132. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec/utils/utils.py +0 -0
  133. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/dependency_links.txt +0 -0
  134. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/entry_points.txt +0 -0
  135. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/not-zip-safe +0 -0
  136. {slide2vec-5.6.0 → slide2vec-5.7.0}/slide2vec.egg-info/top_level.txt +0 -0
  137. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_architecture_runtime_split.py +0 -0
  138. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_reading.py +0 -0
  139. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_image_shard.py +0 -0
  140. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_regions.py +0 -0
  141. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dense_sliding.py +0 -0
  142. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_dinov2_natimage.py +0 -0
  143. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_gpfm_genbio_heavy.py +0 -0
  144. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_image_shard.py +0 -0
  145. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_image_stage.py +0 -0
  146. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_output_consistency.py +0 -0
  147. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_patient_manifest.py +0 -0
  148. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_pooled_geometry.py +0 -0
  149. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_progress.py +0 -0
  150. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_regression_models.py +0 -0
  151. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_runtime_batching.py +0 -0
  152. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_sharding.py +0 -0
  153. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_slide_coordinate_preparation.py +0 -0
  154. {slide2vec-5.6.0 → slide2vec-5.7.0}/tests/test_tile_store.py +0 -0
  155. {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.6.0
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.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
@@ -225,7 +239,8 @@ 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.
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 preset configs for 22 tile-level models and 3 slide-level models.
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.6.0"
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.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.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.6.0"
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
- #: 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).
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
- 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.
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`` overrides level-0 metadata only for spacing-readable
458
- sources. ``target_size`` is always a strict post-read declaration, never a fit-to-size
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-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.
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``. ``annotation`` namespaces the output under a
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`` 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``
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 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.
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 already exists are skipped. Returns one
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
- 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:
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`` overrides source metadata only in this
881
- spacing-readable regime and is rejected for raster inputs.
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
- 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(
@@ -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",
@@ -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 tokens**
23
- (CLS included) — ``patch_encoder_with_clam.py:254-255``, where the CLS line
24
- is immediately overwritten by the token-mean line, and corroborated by
25
- ``model_version = v3_all_tokens`` in ``config/config.ini:3``. The dead CLS
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 each of the 577 token
69
- positions gets its own distribution over tiles
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
- ``577 x 1024`` per tile. The heads are also specific to the five HPA tasks, so
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=False,
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 token sequence ``(B, 577, 1024)`` — patch_encoder_with_clam.py:217-219."""
212
- outputs = self._vision(batch, output_hidden_states=True)
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 all 577 tokens, CLS included — patch_encoder_with_clam.py:255.
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(batch, output_attentions=True)
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,