slide2vec 6.3.3__tar.gz → 7.0.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 (186) hide show
  1. {slide2vec-6.3.3 → slide2vec-7.0.0}/PKG-INFO +6 -6
  2. {slide2vec-6.3.3 → slide2vec-7.0.0}/pyproject.toml +8 -8
  3. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/__init__.py +3 -1
  4. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/api.py +60 -29
  5. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/artifacts.py +38 -18
  6. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/default.yaml +2 -2
  7. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/resources.py +0 -7
  8. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/tile_reader.py +0 -1
  9. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/direct_embed_worker.py +10 -29
  10. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/image_worker.py +11 -1
  11. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/pipeline_worker.py +5 -11
  12. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/base.py +49 -9
  13. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/registry.py +40 -0
  14. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/artifacts_collect.py +10 -30
  15. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/batching.py +1 -14
  16. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_encode.py +1 -21
  17. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_shard.py +5 -5
  18. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_regions.py +11 -4
  19. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_shard.py +12 -12
  20. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding_pipeline.py +54 -86
  21. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/feature_identity.py +70 -31
  22. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/image_shard.py +31 -55
  23. slide2vec-7.0.0/slide2vec/runtime/image_stage.py +433 -0
  24. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/persist_callbacks.py +20 -33
  25. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/registry.py +0 -11
  26. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/serialization.py +2 -0
  27. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/tiling.py +0 -5
  28. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/worker_io.py +0 -8
  29. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/config.py +7 -3
  30. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/PKG-INFO +6 -6
  31. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/SOURCES.txt +1 -0
  32. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/requires.txt +5 -5
  33. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_benchmark_tooling.py +57 -17
  34. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_encode_kit.py +15 -0
  35. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_resume.py +7 -8
  36. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_shard.py +4 -3
  37. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_stage.py +30 -1
  38. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_regions.py +72 -9
  39. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_shard.py +5 -3
  40. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_stage.py +39 -5
  41. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_capabilities.py +34 -0
  42. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_feature_identity.py +59 -22
  43. slide2vec-7.0.0/tests/test_hierarchical_encoding.py +281 -0
  44. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_hs2p5_integration.py +41 -0
  45. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_image_shard.py +23 -50
  46. slide2vec-7.0.0/tests/test_image_stage.py +1557 -0
  47. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_on_slide_persisted.py +74 -5
  48. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_identity_api.py +73 -10
  49. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_core.py +88 -5
  50. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_inference.py +169 -125
  51. slide2vec-6.3.3/slide2vec/runtime/image_stage.py +0 -197
  52. slide2vec-6.3.3/tests/test_image_stage.py +0 -427
  53. {slide2vec-6.3.3 → slide2vec-7.0.0}/LICENSE +0 -0
  54. {slide2vec-6.3.3 → slide2vec-7.0.0}/README.md +0 -0
  55. {slide2vec-6.3.3 → slide2vec-7.0.0}/setup.cfg +0 -0
  56. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/__main__.py +0 -0
  57. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/_cufile.py +0 -0
  58. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/cli.py +0 -0
  59. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/__init__.py +0 -0
  60. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/cufile.json +0 -0
  61. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/__init__.py +0 -0
  62. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/dataset.py +0 -0
  63. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/tile_store.py +0 -0
  64. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/__init__.py +0 -0
  65. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/dense_image_worker.py +0 -0
  66. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/dense_worker.py +0 -0
  67. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/pin_gpu.py +0 -0
  68. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/worker_entry.py +0 -0
  69. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/__init__.py +0 -0
  70. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/__init__.py +0 -0
  71. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/conch.py +0 -0
  72. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov2.py +0 -0
  73. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov3.py +0 -0
  74. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/genbio.py +0 -0
  75. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/gigapath.py +0 -0
  76. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/gpfm.py +0 -0
  77. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/hibou.py +0 -0
  78. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/hoptimus.py +0 -0
  79. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/isight.py +0 -0
  80. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/lunit.py +0 -0
  81. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/mettle.py +0 -0
  82. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/midnight.py +0 -0
  83. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  84. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  85. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/case.py +0 -0
  86. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
  87. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
  88. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/types.py +0 -0
  89. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/mstar.py +0 -0
  90. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/musk.py +0 -0
  91. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/phikon.py +0 -0
  92. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prism.py +0 -0
  93. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prism2.py +0 -0
  94. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prost40m.py +0 -0
  95. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
  96. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/titan.py +0 -0
  97. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/uni.py +0 -0
  98. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/virchow.py +0 -0
  99. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/waiv.py +0 -0
  100. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/validation.py +0 -0
  101. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/inference.py +0 -0
  102. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/progress.py +0 -0
  103. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/__init__.py +0 -0
  104. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/cpu_budget.py +0 -0
  105. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
  106. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_reading.py +0 -0
  107. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
  108. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_stage.py +0 -0
  109. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_sliding.py +0 -0
  110. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_stage.py +0 -0
  111. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/distributed.py +0 -0
  112. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/distributed_stage.py +0 -0
  113. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
  114. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding.py +0 -0
  115. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding_persist.py +0 -0
  116. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
  117. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/hierarchical.py +0 -0
  118. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/image_specs.py +0 -0
  119. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/manifest.py +0 -0
  120. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/model_settings.py +0 -0
  121. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/patient_pipeline.py +0 -0
  122. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/persistence.py +0 -0
  123. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  124. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/preprocessing.py +0 -0
  125. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/process_list.py +0 -0
  126. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/progress_bridge.py +0 -0
  127. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/sharding.py +0 -0
  128. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/slide_encode.py +0 -0
  129. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
  130. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/types.py +0 -0
  131. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/__init__.py +0 -0
  132. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/coordinates.py +0 -0
  133. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/log_utils.py +0 -0
  134. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/tiling_io.py +0 -0
  135. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/utils.py +0 -0
  136. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/dependency_links.txt +0 -0
  137. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/entry_points.txt +0 -0
  138. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/not-zip-safe +0 -0
  139. {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/top_level.txt +0 -0
  140. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_attention_extraction.py +0 -0
  141. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_cli_noninteractive.py +0 -0
  142. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_cufile_compat_mode.py +0 -0
  143. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_encoder_input.py +0 -0
  144. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_extraction.py +0 -0
  145. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_reading.py +0 -0
  146. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_sliding.py +0 -0
  147. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_source_spacing.py +0 -0
  148. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_worker.py +0 -0
  149. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dinov2_natimage.py +0 -0
  150. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dinov3_natimage.py +0 -0
  151. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_input_contract.py +0 -0
  152. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_plugins.py +0 -0
  153. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_preprocessing.py +0 -0
  154. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_provider_failures.py +0 -0
  155. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_registry.py +0 -0
  156. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_gigapath.py +0 -0
  157. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_gpfm_genbio_heavy.py +0 -0
  158. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_hs2p_package_cutover.py +0 -0
  159. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_inference_benchmark.py +0 -0
  160. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_isight.py +0 -0
  161. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_load_model_hf_auth.py +0 -0
  162. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_mascaret.py +0 -0
  163. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_mettle.py +0 -0
  164. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_output_consistency.py +0 -0
  165. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patch_size_metadata.py +0 -0
  166. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_annotation_sampling.py +0 -0
  167. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_manifest.py +0 -0
  168. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_tiling_only.py +0 -0
  169. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_phaet.py +0 -0
  170. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_encoder_input.py +0 -0
  171. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_geometry.py +0 -0
  172. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_prism.py +0 -0
  173. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_prism2.py +0 -0
  174. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_process_list_performance.py +0 -0
  175. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_progress.py +0 -0
  176. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_models.py +0 -0
  177. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_rudolfv2.py +0 -0
  178. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_runtime_batching.py +0 -0
  179. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_sharding.py +0 -0
  180. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_slide_coordinate_preparation.py +0 -0
  181. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_slide_latents.py +0 -0
  182. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_soma_migration.py +0 -0
  183. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_tile_store.py +0 -0
  184. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_tiling_pipeline.py +0 -0
  185. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_titan.py +0 -0
  186. {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_worker_gpu_pinning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 6.3.3
3
+ Version: 7.0.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,tifffile,vips]>=5.1.0
18
+ Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0
19
19
  Requires-Dist: omegaconf
20
20
  Requires-Dist: matplotlib
21
21
  Requires-Dist: numpy<2
@@ -27,18 +27,18 @@ Requires-Dist: torchvision
27
27
  Requires-Dist: transformers
28
28
  Requires-Dist: wandb
29
29
  Requires-Dist: einops
30
- Requires-Dist: timm
30
+ Requires-Dist: timm>=1.0.3
31
31
  Requires-Dist: huggingface_hub
32
32
  Provides-Extra: hoptimus
33
33
  Requires-Dist: torch>=2.0; extra == "hoptimus"
34
34
  Requires-Dist: torchvision>=0.15.0; extra == "hoptimus"
35
35
  Requires-Dist: xformers>=0.0.18; extra == "hoptimus"
36
36
  Provides-Extra: virchow
37
- Requires-Dist: timm>=0.9.11; extra == "virchow"
37
+ Requires-Dist: timm>=1.0.3; extra == "virchow"
38
38
  Requires-Dist: torch>=2.0; extra == "virchow"
39
39
  Provides-Extra: uni
40
40
  Requires-Dist: torch>=2.0; extra == "uni"
41
- Requires-Dist: timm>=0.9.8; extra == "uni"
41
+ Requires-Dist: timm>=1.0.3; extra == "uni"
42
42
  Requires-Dist: xformers>=0.0.18; extra == "uni"
43
43
  Provides-Extra: prism
44
44
  Requires-Dist: transformers~=4.53.0; extra == "prism"
@@ -75,7 +75,7 @@ Requires-Dist: numpy<2; extra == "fm"
75
75
  Requires-Dist: pandas; extra == "fm"
76
76
  Requires-Dist: pillow; extra == "fm"
77
77
  Requires-Dist: rich; extra == "fm"
78
- Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=5.1.0; extra == "fm"
78
+ Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0; extra == "fm"
79
79
  Requires-Dist: wandb; extra == "fm"
80
80
  Requires-Dist: torch<2.8,>=2.3; extra == "fm"
81
81
  Requires-Dist: torchvision>=0.18.0; extra == "fm"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "slide2vec"
7
- version = "6.3.3"
7
+ version = "7.0.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,tifffile,vips]>=5.1.0",
24
+ "hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0",
25
25
  "omegaconf",
26
26
  "matplotlib",
27
27
  "numpy<2",
@@ -33,7 +33,7 @@ dependencies = [
33
33
  "transformers",
34
34
  "wandb",
35
35
  "einops",
36
- "timm",
36
+ "timm>=1.0.3",
37
37
  "huggingface_hub",
38
38
  ]
39
39
 
@@ -51,12 +51,12 @@ hoptimus = [
51
51
  "xformers>=0.0.18",
52
52
  ]
53
53
  virchow = [
54
- "timm>=0.9.11",
54
+ "timm>=1.0.3",
55
55
  "torch>=2.0",
56
56
  ]
57
57
  uni = [
58
58
  "torch>=2.0",
59
- "timm>=0.9.8",
59
+ "timm>=1.0.3",
60
60
  "xformers>=0.0.18",
61
61
  ]
62
62
  prism = [
@@ -100,7 +100,7 @@ fm = [
100
100
  "pandas",
101
101
  "pillow",
102
102
  "rich",
103
- "hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=5.1.0",
103
+ "hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0",
104
104
  "wandb",
105
105
  "torch>=2.3,<2.8",
106
106
  "torchvision>=0.18.0",
@@ -150,7 +150,7 @@ include = ["slide2vec*"]
150
150
 
151
151
  [tool.setuptools.package-data]
152
152
  slide2vec = ["py.typed", "cufile.json"]
153
- "slide2vec.configs" = ["*.yaml", "models/*.yaml", "preprocessing/*.yaml"]
153
+ "slide2vec.configs" = ["*.yaml"]
154
154
 
155
155
  [tool.pytest.ini_options]
156
156
  addopts = "--cov=slide2vec -m 'not gpu_integration'"
@@ -180,7 +180,7 @@ no_implicit_reexport = true
180
180
  max-line-length = 160
181
181
 
182
182
  [tool.bumpver]
183
- current_version = "6.3.3"
183
+ current_version = "7.0.0"
184
184
  version_pattern = "MAJOR.MINOR.PATCH"
185
185
  commit = false
186
186
  tag = false
@@ -25,13 +25,14 @@ from slide2vec.artifacts import (
25
25
  TileEmbeddingArtifact,
26
26
  )
27
27
  from slide2vec.runtime.dense_encode import DenseEncodeGeometry, DenseEncodeKit
28
+ from slide2vec.runtime.feature_identity import MISSING_FIELD
28
29
  from slide2vec.encoders import (
29
30
  EncoderProviderDiagnostic,
30
31
  list_encoder_provider_diagnostics,
31
32
  )
32
33
 
33
34
 
34
- __version__ = "6.3.3"
35
+ __version__ = "7.0.0"
35
36
 
36
37
  __all__ = [
37
38
  "Model",
@@ -56,5 +57,6 @@ __all__ = [
56
57
  "DenseRegionArtifact",
57
58
  "DenseImageArtifact",
58
59
  "ImageEmbeddingArtifact",
60
+ "MISSING_FIELD",
59
61
  "__version__",
60
62
  ]
@@ -26,6 +26,7 @@ from slide2vec.encoders.registry import (
26
26
  encoder_registry,
27
27
  list_encoder_provider_diagnostics,
28
28
  resolve_preprocessing_fields,
29
+ validate_feature_kind_capability,
29
30
  )
30
31
  from slide2vec.encoders.validation import validate_encoder_config
31
32
  from slide2vec.runtime.model_settings import (
@@ -65,19 +66,6 @@ SlideSequence = Sequence[SlideInput]
65
66
  TilingResultsInput = Sequence[Any] | Mapping[str, Any]
66
67
 
67
68
 
68
- #: Default annotation-mask vocabulary — plain binary tissue tiling. Mirrors hs2p's
69
- #: shipped default ``{background: 0, tissue: 1}``; leaving it untouched keeps a run
70
- #: behaving exactly as a tissue-only run. ``min_coverage.tissue`` is the single source
71
- #: of truth for the tissue threshold (the standalone ``tissue_threshold`` knob is gone).
72
- #: A :class:`PreprocessingConfig` ``masks`` value is deep-merged over this default, so
73
- #: callers only state what they override (e.g. ``{"min_coverage": {"tissue": 0.1}}``).
74
- DEFAULT_MASKS: dict[str, Any] = {
75
- "output_mode": "per_annotation",
76
- "pixel_mapping": {"background": 0, "tissue": 1},
77
- "colors": {"background": None, "tissue": [157, 219, 129]},
78
- "min_coverage": {"background": None, "tissue": 0.01},
79
- }
80
-
81
69
  _REQUESTED_TILE_SIZE_INTERPOLATION = "${tiling.params.requested_tile_size_px}"
82
70
 
83
71
 
@@ -91,6 +79,7 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
91
79
  ("segmentation", "seg_params"),
92
80
  ("filtering", "filter_params"),
93
81
  ("preview", "preview"),
82
+ ("masks", "masks"),
94
83
  ):
95
84
  section = OmegaConf.to_container(getattr(tiling, config_name), resolve=False)
96
85
  if not isinstance(section, dict):
@@ -106,6 +95,13 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
106
95
  #: ``configs/default.yaml`` so Python and YAML entry points share one source.
107
96
  DEFAULT_PREPROCESSING = _load_default_preprocessing()
108
97
 
98
+ #: Default annotation-mask vocabulary, loaded from ``configs/default.yaml``: plain
99
+ #: binary tissue tiling (``{background: 0, tissue: 1}``) with
100
+ #: ``min_coverage.tissue = 0.1``. ``min_coverage.tissue`` is the single source of truth
101
+ #: for the tissue threshold. A :class:`PreprocessingConfig` ``masks`` value is
102
+ #: deep-merged over this default, so callers only state what they override.
103
+ DEFAULT_MASKS: dict[str, Any] = DEFAULT_PREPROCESSING["masks"]
104
+
109
105
 
110
106
  def _deep_merge_dicts(base: Mapping[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
111
107
  """Deep-merge *override* onto a copy of *base* (nested dicts merge key-by-key)."""
@@ -212,7 +208,7 @@ class PreprocessingConfig:
212
208
  #: Annotation-mask vocabulary forwarded to hs2p's sampling resolver. Keys:
213
209
  #: ``output_mode``, ``pixel_mapping``, ``colors``, ``min_coverage``. A partial
214
210
  #: mapping is deep-merged over :data:`DEFAULT_MASKS`, so callers only state what
215
- #: they override (e.g. ``{"min_coverage": {"tissue": 0.1}}``). The default
211
+ #: they override (e.g. ``{"min_coverage": {"tissue": 0.5}}``). The default
216
212
  #: ``{background, tissue}`` block is plain tissue tiling; ``min_coverage.tissue``
217
213
  #: is the single source of truth for the tissue threshold.
218
214
  masks: dict[str, Any] = field(default_factory=dict)
@@ -310,6 +306,9 @@ class PreprocessingConfig:
310
306
  return replace(self, mask_backend=mask_backend)
311
307
 
312
308
 
309
+ #: Accepted values of :attr:`ExecutionOptions.on_image_mismatch`.
310
+ IMAGE_MISMATCH_POLICIES = ("raise", "reencode")
311
+
313
312
 
314
313
  @dataclass(frozen=True, kw_only=True)
315
314
  class ExecutionOptions:
@@ -346,6 +345,10 @@ class ExecutionOptions:
346
345
  #: Return and persist the slide encoder's latent representations (PRISM). Encoders
347
346
  #: without latents ignore it.
348
347
  save_latents: bool = False
348
+ #: :meth:`Model.embed_images` only: what to do when an existing image embedding
349
+ #: records a different source ``image_path`` for its ``sample_id``. ``"raise"``
350
+ #: (default) refuses to resume; ``"reencode"`` replaces that image's artifacts.
351
+ on_image_mismatch: str = "raise"
349
352
 
350
353
  @classmethod
351
354
  def from_config(cls, cfg: Any, *, run_on_cpu: bool = False) -> "ExecutionOptions":
@@ -379,8 +382,18 @@ class ExecutionOptions:
379
382
  object.__setattr__(self, "output_dtype", normalize_output_dtype(self.output_dtype))
380
383
  if resolved_num_gpus < 1:
381
384
  raise ValueError("ExecutionOptions.num_gpus must be at least 1")
385
+ if self.batch_size < 1:
386
+ raise ValueError("ExecutionOptions.batch_size must be at least 1")
382
387
  if self.prefetch_factor < 1:
383
388
  raise ValueError("ExecutionOptions.prefetch_factor must be at least 1")
389
+ if self.num_workers_per_gpu is not None and self.num_workers_per_gpu < 0:
390
+ raise ValueError("ExecutionOptions.num_workers_per_gpu must be non-negative")
391
+ if self.on_image_mismatch not in IMAGE_MISMATCH_POLICIES:
392
+ raise ValueError(
393
+ "ExecutionOptions.on_image_mismatch must be one of "
394
+ f"{', '.join(repr(policy) for policy in IMAGE_MISMATCH_POLICIES)}; "
395
+ f"got {self.on_image_mismatch!r}"
396
+ )
384
397
  cap = cpu_worker_limit()
385
398
  cpu_count = os.cpu_count() or 1
386
399
  slurm_limit = slurm_cpu_limit()
@@ -457,8 +470,10 @@ class DenseOptions:
457
470
  #: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
458
471
  #: ``window_size is None``).
459
472
  overlap: float = 0.0
460
- #: ``"patch_features"`` (the patch-token grid) or ``"cls_attention"`` (CLS/register
461
- #: self-attention grid).
473
+ #: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
474
+ #: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
475
+ #: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
476
+ #: (CLS/register self-attention grid).
462
477
  feature_kind: str = "patch_features"
463
478
  #: Transformer blocks whose CLS attention is read (``cls_attention`` only).
464
479
  attention_blocks: tuple[int, ...] = (-1,)
@@ -508,8 +523,10 @@ class DenseImageOptions:
508
523
  #: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
509
524
  #: ``window_size is None``).
510
525
  overlap: float = 0.0
511
- #: ``"patch_features"`` (the patch-token grid) or ``"cls_attention"`` (CLS/register
512
- #: self-attention grid).
526
+ #: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
527
+ #: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
528
+ #: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
529
+ #: (CLS/register self-attention grid).
513
530
  feature_kind: str = "patch_features"
514
531
  #: Transformer blocks whose CLS attention is read (``cls_attention`` only).
515
532
  attention_blocks: tuple[int, ...] = (-1,)
@@ -694,22 +711,26 @@ class Model:
694
711
  *,
695
712
  preprocessing: PreprocessingConfig | None,
696
713
  execution: ExecutionOptions | None = None,
714
+ encoded_pixels: bool = True,
697
715
  ) -> dict[str, tuple[Any, Any]]:
698
716
  """Compare a pooled sidecar's ``compatibility`` block with the current recipe.
699
717
 
700
- Returns ``{field: (recorded, current)}`` for differing fields held by both
701
- identities. Missing fields retain the extraction resume semantics: they
702
- cannot be verified and are accepted. The caller owns the policy for an
703
- unrecorded identity or a difference.
718
+ Returns ``{field: (recorded, current)}`` for every required field that the
719
+ record holds with a different value or does not hold. A field the record does
720
+ not hold is reported as :data:`slide2vec.MISSING_FIELD`, which is distinct
721
+ from a recorded ``None``. An empty result means the record proves the current
722
+ identity. The caller owns the policy for a difference.
704
723
 
705
724
  Explicitly pass ``preprocessing=None`` for pre-cropped images, whose shipped
706
725
  transform determines geometry. Pass a :class:`PreprocessingConfig` for
707
726
  tiles read from slides; its unset fields resolve as in pooled extraction.
708
727
  ``execution`` defaults to the model's recommended precision and needs no
709
- output directory.
728
+ output directory. The ``transform`` is required unless ``encoded_pixels`` is
729
+ false: pass ``encoded_pixels=False`` for a zero-tile slide, which encodes no
730
+ pixels and records no transform.
710
731
 
711
- Only a recorded ``transform`` loads weights, on an isolated CPU model.
712
- Slide and patient models load their registered tile encoder and tile output
732
+ Comparing the ``transform`` loads weights, on an isolated CPU model. Slide
733
+ and patient models load their registered tile encoder and tile output
713
734
  variant, without loading aggregation weights. This method does not change
714
735
  this model's input contract or loaded backend, encode pixels, or write files.
715
736
  """
@@ -724,6 +745,7 @@ class Model:
724
745
  dict(recorded),
725
746
  execution=resolved_execution,
726
747
  preprocessing=resolved_preprocessing,
748
+ encoded_pixels=encoded_pixels,
727
749
  )
728
750
 
729
751
  def prepare_dense_encoder(
@@ -1019,8 +1041,11 @@ class Model:
1019
1041
  decoded, preprocessed with the encoder's **shipped** transform, encoded, and written
1020
1042
  to ``image_embeddings/<sample_id>.pt`` plus a provenance sidecar. The run splits its
1021
1043
  images across all visible GPUs (``execution.num_gpus``); ``num_gpus=1`` encodes
1022
- fully in-process. Resume is automatic — images whose sidecar already exists are
1023
- skipped, and a sidecar that records a different feature identity raises. Returns
1044
+ fully in-process. Resume is automatic: an image is skipped only when its sidecar
1045
+ records this ``output_format``, the requested ``image_path`` and the full feature
1046
+ identity. A different recorded feature identity raises; a different recorded
1047
+ ``image_path`` raises unless ``execution.on_image_mismatch="reencode"``; missing
1048
+ provenance re-encodes. Returns
1024
1049
  one :class:`~slide2vec.artifacts.ImageEmbeddingArtifact` per input image, in input
1025
1050
  order.
1026
1051
 
@@ -1091,14 +1116,20 @@ class Model:
1091
1116
  parent stage and every torchrun rank — declares for itself.
1092
1117
 
1093
1118
  *dense* is a :class:`DenseOptions` (ROIs on a slide) or a :class:`DenseImageOptions`
1094
- (pre-cropped images); only the supervision geometry is read here, and the two state
1095
- it the same way.
1119
+ (pre-cropped images); only the supervision geometry and the requested feature kind
1120
+ are read here, and the two state them the same way.
1121
+
1122
+ The feature kind is checked against the registered encoder class at the same
1123
+ point, so a tap this encoder does not implement (``patch_features_prenorm`` on a
1124
+ non-timm backbone, say) fails before any backend is loaded and before resume
1125
+ inspects or invalidates an existing artifact.
1096
1126
  """
1097
1127
  contract = EncoderInputContract.declared_dense(
1098
1128
  self.name,
1099
1129
  target_size_px=dense.target_size,
1100
1130
  window_size=None if dense.window_size is None else int(dense.window_size),
1101
1131
  )
1132
+ validate_feature_kind_capability(self.name, feature_kind=str(dense.feature_kind))
1102
1133
  self._encoder_input = contract
1103
1134
  plan = contract.plan
1104
1135
  if emit_run_info and plan.requires_variable_model_input:
@@ -137,7 +137,8 @@ class ImageEmbeddingArtifact:
137
137
  return load_metadata(self.metadata_path)
138
138
 
139
139
 
140
- def _validate_output_format(output_format: str) -> str:
140
+ def normalize_output_format(output_format: str) -> str:
141
+ """The canonical (lowercase) spelling of a supported ``output_format``."""
141
142
  normalized = output_format.lower()
142
143
  if normalized not in {"pt", "npz"}:
143
144
  raise ValueError(f"Unsupported output format: {output_format}")
@@ -403,27 +404,46 @@ def write_dense_image(
403
404
  )
404
405
 
405
406
 
406
- def image_embedding_paths(
407
- output_dir: str | Path, *, sample_id: str, output_format: str
408
- ) -> tuple[Path, Path]:
409
- """``(payload_path, sidecar_path)`` for one given-geometry image.
407
+ #: Payload formats an image embedding can be written in; they share one sidecar.
408
+ IMAGE_EMBEDDING_FORMATS = ("pt", "npz")
410
409
 
411
- ``image_embeddings/<sample_id>.{pt,npz}`` plus ``image_embeddings/<sample_id>.meta.json``.
412
- A given-geometry run has no slide, no coordinate and no annotation class — the caller's
413
- ``sample_id`` is the whole identity — so the layout is flat and the resume check needs
414
- nothing but the sample id.
410
+
411
+ def image_embeddings_dir(output_dir: str | Path) -> Path:
412
+ """The resolved ``image_embeddings/`` directory, refusing one that escapes ``output_dir``.
413
+
414
+ A run resolves it once and names each image with :func:`image_embedding_names`.
415
415
  """
416
416
  output_root = Path(output_dir).expanduser().resolve()
417
- sample_component = _validate_path_component(sample_id, field="sample_id")
418
417
  embeddings_dir = (output_root / "image_embeddings").resolve()
419
418
  if not embeddings_dir.is_relative_to(output_root):
420
419
  raise ValueError("Image artifact path must stay within output_dir")
420
+ return embeddings_dir
421
+
422
+
423
+ def image_embedding_names(sample_id: str, *, output_format: str) -> tuple[str, str]:
424
+ """``(payload_name, sidecar_name)`` of one image inside :func:`image_embeddings_dir`."""
425
+ sample_component = _validate_path_component(sample_id, field="sample_id")
421
426
  return (
422
- embeddings_dir / f"{sample_component}.{_validate_output_format(output_format)}",
423
- embeddings_dir / f"{sample_component}.meta.json",
427
+ f"{sample_component}.{normalize_output_format(output_format)}",
428
+ f"{sample_component}.meta.json",
424
429
  )
425
430
 
426
431
 
432
+ def image_embedding_paths(
433
+ output_dir: str | Path, *, sample_id: str, output_format: str
434
+ ) -> tuple[Path, Path]:
435
+ """``(payload_path, sidecar_path)`` for one given-geometry image.
436
+
437
+ ``image_embeddings/<sample_id>.{pt,npz}`` plus ``image_embeddings/<sample_id>.meta.json``.
438
+ A given-geometry run has no slide, no coordinate and no annotation class — the caller's
439
+ ``sample_id`` is the whole identity — so the layout is flat. Both payload formats share
440
+ the sidecar, which records the ``format`` it certifies.
441
+ """
442
+ payload_name, sidecar_name = image_embedding_names(sample_id, output_format=output_format)
443
+ embeddings_dir = image_embeddings_dir(output_dir)
444
+ return embeddings_dir / payload_name, embeddings_dir / sidecar_name
445
+
446
+
427
447
  def write_image_embedding(
428
448
  embedding,
429
449
  *,
@@ -440,7 +460,7 @@ def write_image_embedding(
440
460
  ROI write follows (:func:`write_dense_region`), and the reason a rank can be killed
441
461
  mid-shard without poisoning the output.
442
462
  """
443
- output_format = _validate_output_format(output_format)
463
+ output_format = normalize_output_format(output_format)
444
464
  payload_path, metadata_path = image_embedding_paths(
445
465
  output_dir, sample_id=sample_id, output_format=output_format
446
466
  )
@@ -520,7 +540,7 @@ def write_tile_embeddings(
520
540
  tile_index: Any | None = None,
521
541
  annotation: str | None = None,
522
542
  ) -> TileEmbeddingArtifact:
523
- output_format = _validate_output_format(output_format)
543
+ output_format = normalize_output_format(output_format)
524
544
  artifact_path, metadata_path = _setup_artifact_paths(
525
545
  output_dir, tile_embeddings_subdir(annotation), sample_id, output_format
526
546
  )
@@ -563,7 +583,7 @@ def write_tile_embedding_metadata(
563
583
  metadata: dict[str, Any] | None = None,
564
584
  annotation: str | None = None,
565
585
  ) -> Path:
566
- output_format = _validate_output_format(output_format)
586
+ output_format = normalize_output_format(output_format)
567
587
  _, metadata_path = _setup_artifact_paths(
568
588
  output_dir, tile_embeddings_subdir(annotation), sample_id, output_format
569
589
  )
@@ -589,7 +609,7 @@ def write_slide_embeddings(
589
609
  latents: Any | None = None,
590
610
  annotation: str | None = None,
591
611
  ) -> SlideEmbeddingArtifact:
592
- output_format = _validate_output_format(output_format)
612
+ output_format = normalize_output_format(output_format)
593
613
  annotation = structural_artifact_annotation(annotation)
594
614
  artifact_path, metadata_path = _setup_artifact_paths(
595
615
  output_dir, slide_embeddings_subdir(annotation), sample_id, output_format
@@ -638,7 +658,7 @@ def write_patient_embeddings(
638
658
  metadata: dict[str, Any] | None = None,
639
659
  num_slides: int = 0,
640
660
  ) -> PatientEmbeddingArtifact:
641
- output_format = _validate_output_format(output_format)
661
+ output_format = normalize_output_format(output_format)
642
662
  artifact_path, metadata_path = _setup_artifact_paths(
643
663
  output_dir, "patient_embeddings", patient_id, output_format
644
664
  )
@@ -677,7 +697,7 @@ def write_hierarchical_embeddings(
677
697
  metadata: dict[str, Any] | None = None,
678
698
  annotation: str | None = None,
679
699
  ) -> HierarchicalEmbeddingArtifact:
680
- output_format = _validate_output_format(output_format)
700
+ output_format = normalize_output_format(output_format)
681
701
  annotation = structural_artifact_annotation(annotation)
682
702
  artifact_path, metadata_path = _setup_artifact_paths(
683
703
  output_dir, hierarchical_embeddings_subdir(annotation), sample_id, output_format
@@ -9,7 +9,7 @@ save_tiles: false # save extracted tiles as {sample_id}.tiles.tar in addition to
9
9
  seed: 0 # seed for reproducibility
10
10
 
11
11
  model:
12
- name: # preset model name; see docs/models.md for supported request strings
12
+ name: # preset model name; see docs/models.rst for supported request strings
13
13
  output_variant: # requested output variant for presets that expose multiple outputs
14
14
  batch_size: 32
15
15
  save_tile_embeddings: false # whether to save tile embeddings alongside the pooled slide embedding when level is "slide"
@@ -18,7 +18,7 @@ model:
18
18
  allow_non_recommended_settings: false # when true, non-recommended spacing / tile size / precision combinations warn instead of erroring
19
19
 
20
20
  tiling:
21
- on_the_fly: true # read tiles directly from WSI during embedding (requires cucim backend)
21
+ on_the_fly: true # read tiles directly from the WSI during embedding; when false, embedding reads tiles from {sample_id}.tiles.tar
22
22
  gpu_decode: false # GPU-accelerated batch decoding via device="cuda" in cucim read_region; set true to opt in when the runtime is configured for it (experimental, may not be stable across all environments)
23
23
  adaptive_batching: false # when true, vary batch size to align with super tile boundaries (avoids redundant reads but batch size fluctuates)
24
24
  use_supertiles: true # group tiles into 8x8/4x4/2x2 super tile reads to reduce WSI read calls (on-the-fly path only)
@@ -1,6 +1,4 @@
1
- from contextlib import contextmanager
2
1
  from pathlib import Path
3
- from typing import Iterator
4
2
 
5
3
 
6
4
  def config_resource(*parts: str):
@@ -16,8 +14,3 @@ def load_config(*parts: str):
16
14
  resource = config_resource(*parts)
17
15
  with resource.open("r", encoding="utf-8") as handle:
18
16
  return OmegaConf.load(handle)
19
-
20
-
21
- @contextmanager
22
- def config_path(*parts: str) -> Iterator[Path]:
23
- yield config_resource(*parts)
@@ -429,7 +429,6 @@ class OnTheFlyHierarchicalBatchCollator:
429
429
  ):
430
430
  self._region_index = np.asarray(region_index, dtype=np.int32)
431
431
  self._subtile_index_within_region = np.asarray(subtile_index_within_region, dtype=np.int32)
432
- self._tiles_per_region = int(self._subtile_index_within_region.max()) + 1 if len(self._subtile_index_within_region) else 0
433
432
  self._read_tile_size_px = int(read_tile_size_px)
434
433
  self._requested_tile_size_px = int(requested_tile_size_px)
435
434
  self._reader = WSIRegionReader(
@@ -17,8 +17,12 @@ def main(argv=None) -> int:
17
17
  import torch
18
18
 
19
19
  import slide2vec.distributed as distributed
20
- import slide2vec.inference as inference
21
20
  from slide2vec.api import Model
21
+ from slide2vec.runtime.embedding_pipeline import (
22
+ compute_embedded_slides,
23
+ compute_hierarchical_embedding_shard_for_slide,
24
+ compute_tile_embeddings_for_slide,
25
+ )
22
26
  from slide2vec.runtime.hierarchical import (
23
27
  build_hierarchical_index,
24
28
  is_hierarchical_preprocessing,
@@ -56,11 +60,9 @@ def main(argv=None) -> int:
56
60
  )
57
61
  from slide2vec.runtime.embedding import tiling_result_annotation
58
62
  from slide2vec.runtime.feature_identity import transform_record
63
+ from slide2vec.runtime.manifest import load_successful_tiled_slides
59
64
 
60
- load_successful_tiled_slides_fn = getattr(inference, "load_successful_tiled_slides", None)
61
- if not callable(load_successful_tiled_slides_fn):
62
- from slide2vec.runtime.manifest import load_successful_tiled_slides as load_successful_tiled_slides_fn
63
- slide_records, tiling_results = load_successful_tiled_slides_fn(output_dir)
65
+ slide_records, tiling_results = load_successful_tiled_slides(output_dir)
64
66
  # Key by the composite (sample_id, annotation) work unit so a multi-class slide's sibling
65
67
  # classes never overwrite each other; flat units collapse to the bare sample_id key.
66
68
  paired_by_unit = {
@@ -95,16 +97,7 @@ def main(argv=None) -> int:
95
97
  tile_size_lv0=int(geometry["tile_size_lv0"]),
96
98
  )
97
99
  flat_indices = np.array_split(index.flat_index, world_size)[global_rank]
98
- compute_hierarchical_embedding_shard_for_slide_fn = getattr(
99
- inference,
100
- "_compute_hierarchical_embedding_shard_for_slide",
101
- None,
102
- )
103
- if not callable(compute_hierarchical_embedding_shard_for_slide_fn):
104
- from slide2vec.runtime.embedding_pipeline import (
105
- compute_hierarchical_embedding_shard_for_slide as compute_hierarchical_embedding_shard_for_slide_fn,
106
- )
107
- shard_indices, tile_embeddings = compute_hierarchical_embedding_shard_for_slide_fn(
100
+ shard_indices, tile_embeddings = compute_hierarchical_embedding_shard_for_slide(
108
101
  loaded,
109
102
  slide,
110
103
  tiling_result,
@@ -122,16 +115,7 @@ def main(argv=None) -> int:
122
115
  else:
123
116
  num_tiles = len(tiling_result.x)
124
117
  tile_indices = np.array_split(np.arange(num_tiles, dtype=np.int64), world_size)[global_rank]
125
- compute_tile_embeddings_for_slide_fn = getattr(
126
- inference,
127
- "_compute_tile_embeddings_for_slide",
128
- None,
129
- )
130
- if not callable(compute_tile_embeddings_for_slide_fn):
131
- from slide2vec.runtime.embedding_pipeline import (
132
- compute_tile_embeddings_for_slide as compute_tile_embeddings_for_slide_fn,
133
- )
134
- tile_embeddings = compute_tile_embeddings_for_slide_fn(
118
+ tile_embeddings = compute_tile_embeddings_for_slide(
135
119
  loaded,
136
120
  model,
137
121
  slide,
@@ -170,10 +154,7 @@ def main(argv=None) -> int:
170
154
  )
171
155
  torch.save(payload, coordination_dir / f"{stem}.embedded.pt")
172
156
 
173
- compute_embedded_slides_fn = getattr(inference, "_compute_embedded_slides", None)
174
- if not callable(compute_embedded_slides_fn):
175
- from slide2vec.runtime.embedding_pipeline import compute_embedded_slides as compute_embedded_slides_fn
176
- compute_embedded_slides_fn(
157
+ compute_embedded_slides(
177
158
  model,
178
159
  assigned_slides,
179
160
  assigned_tiling_results,
@@ -55,7 +55,7 @@ def main(argv=None) -> int:
55
55
  def _on_batch(count: int) -> None:
56
56
  emit_progress("images.batch.finished", rank=rank.global_rank, images=int(count))
57
57
 
58
- run_image_shard(
58
+ artifacts = run_image_shard(
59
59
  shard,
60
60
  loaded=loaded,
61
61
  out_dir=output_dir,
@@ -67,7 +67,17 @@ def main(argv=None) -> int:
67
67
  num_workers=execution.resolved_image_num_workers_per_gpu(),
68
68
  prefetch_factor=int(execution.prefetch_factor),
69
69
  on_batch=_on_batch,
70
+ # The parent planned what to invalidate; the shard loop deletes each image's
71
+ # files right before writing it, so a failed launch, load or sibling rank costs
72
+ # at most the images in flight.
73
+ stale=request["stale"],
70
74
  )
75
+ # The parent collects artifacts without reading sidecars: report the vector width.
76
+ result_path = Path(request["result_dir"]) / f"image_result.rank{rank.global_rank}.json"
77
+ result_path.write_text(
78
+ json.dumps({"feature_dim": int(artifacts[0].feature_dim), "images": len(artifacts)}),
79
+ encoding="utf-8",
80
+ )
71
81
  return 0
72
82
 
73
83
 
@@ -16,8 +16,9 @@ def get_args_parser(add_help: bool = True) -> argparse.ArgumentParser:
16
16
 
17
17
  def main(argv=None) -> int:
18
18
  import slide2vec.distributed as distributed
19
- import slide2vec.inference as inference
20
19
  from slide2vec.api import Model
20
+ from slide2vec.runtime.embedding_pipeline import compute_embedded_slides
21
+ from slide2vec.runtime.manifest import load_successful_tiled_slides
21
22
  from slide2vec.runtime.persist_callbacks import build_incremental_persist_callback
22
23
  from slide2vec.progress import JsonlProgressReporter, activate_progress_reporter
23
24
  from slide2vec.runtime.serialization import deserialize_execution, deserialize_preprocessing
@@ -44,10 +45,7 @@ def main(argv=None) -> int:
44
45
  execution = deserialize_execution(request["execution"])
45
46
  model._declare_encoder_input(preprocessing, emit_run_info=False)
46
47
  tiling_input_dir = Path(request.get("tiling_input_dir", str(output_dir)))
47
- load_successful_tiled_slides_fn = getattr(inference, "load_successful_tiled_slides", None)
48
- if not callable(load_successful_tiled_slides_fn):
49
- from slide2vec.runtime.manifest import load_successful_tiled_slides as load_successful_tiled_slides_fn
50
- slide_records, tiling_results = load_successful_tiled_slides_fn(tiling_input_dir)
48
+ slide_records, tiling_results = load_successful_tiled_slides(tiling_input_dir)
51
49
  # Each (sample_id, annotation) row is an independent work unit; key by the composite so a
52
50
  # multi-class slide's sibling classes never overwrite each other. Flat units (None / tissue /
53
51
  # merged) encode to the bare sample_id, byte-identical to pre-#168 single-class runs.
@@ -83,17 +81,13 @@ def main(argv=None) -> int:
83
81
  )
84
82
  context = activate_progress_reporter(reporter) if reporter is not None else nullcontext()
85
83
  with context:
86
- build_incremental_persist_callback_fn = getattr(inference, "_build_incremental_persist_callback", build_incremental_persist_callback)
87
- persist_callback, _, _ = build_incremental_persist_callback_fn(
84
+ persist_callback, _, _ = build_incremental_persist_callback(
88
85
  model=model,
89
86
  preprocessing=preprocessing,
90
87
  execution=execution,
91
88
  process_list_path=None,
92
89
  )
93
- compute_embedded_slides_fn = getattr(inference, "_compute_embedded_slides", None)
94
- if not callable(compute_embedded_slides_fn):
95
- from slide2vec.runtime.embedding_pipeline import compute_embedded_slides as compute_embedded_slides_fn
96
- compute_embedded_slides_fn(
90
+ compute_embedded_slides(
97
91
  model,
98
92
  assigned_slides,
99
93
  assigned_tiling_results,