slide2vec 7.1.0__tar.gz → 7.1.2__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 (189) hide show
  1. {slide2vec-7.1.0 → slide2vec-7.1.2}/PKG-INFO +29 -68
  2. {slide2vec-7.1.0 → slide2vec-7.1.2}/README.md +4 -0
  3. slide2vec-7.1.2/pyproject.toml +264 -0
  4. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/__init__.py +1 -1
  5. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/api.py +70 -37
  6. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/dinov3.py +1 -3
  7. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/loading.py +1 -1
  8. slide2vec-7.1.2/slide2vec/encoders/models/prism2.py +172 -0
  9. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/inference.py +14 -14
  10. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/cpu_budget.py +2 -2
  11. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_image_stage.py +4 -4
  12. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_stage.py +4 -4
  13. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/distributed_stage.py +7 -7
  14. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/embedding_pipeline.py +2 -2
  15. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/image_stage.py +4 -4
  16. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/serialization.py +4 -2
  17. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/slide_encode.py +3 -2
  18. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/PKG-INFO +29 -68
  19. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/SOURCES.txt +1 -0
  20. slide2vec-7.1.2/slide2vec.egg-info/requires.txt +66 -0
  21. slide2vec-7.1.2/tests/test_execution_options.py +116 -0
  22. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_isight.py +1 -1
  23. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_mascaret.py +5 -3
  24. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_phaet.py +5 -3
  25. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_prism2.py +95 -0
  26. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_regression_core.py +4 -6
  27. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_regression_inference.py +65 -0
  28. slide2vec-7.1.0/pyproject.toml +0 -204
  29. slide2vec-7.1.0/slide2vec/encoders/models/prism2.py +0 -94
  30. slide2vec-7.1.0/slide2vec.egg-info/requires.txt +0 -109
  31. {slide2vec-7.1.0 → slide2vec-7.1.2}/LICENSE +0 -0
  32. {slide2vec-7.1.0 → slide2vec-7.1.2}/setup.cfg +0 -0
  33. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/__main__.py +0 -0
  34. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/_cufile.py +0 -0
  35. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/artifacts.py +0 -0
  36. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/cli.py +0 -0
  37. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/configs/__init__.py +0 -0
  38. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/configs/default.yaml +0 -0
  39. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/configs/resources.py +0 -0
  40. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/cufile.json +0 -0
  41. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/data/__init__.py +0 -0
  42. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/data/dataset.py +0 -0
  43. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/data/tile_reader.py +0 -0
  44. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/data/tile_store.py +0 -0
  45. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/__init__.py +0 -0
  46. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/dense_image_worker.py +0 -0
  47. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/dense_worker.py +0 -0
  48. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/direct_embed_worker.py +0 -0
  49. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/image_worker.py +0 -0
  50. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/pin_gpu.py +0 -0
  51. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/pipeline_worker.py +0 -0
  52. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/distributed/worker_entry.py +0 -0
  53. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/__init__.py +0 -0
  54. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/base.py +0 -0
  55. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/__init__.py +0 -0
  56. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/conch.py +0 -0
  57. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/dinov2.py +0 -0
  58. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/genbio.py +0 -0
  59. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/gigapath.py +0 -0
  60. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/gpfm.py +0 -0
  61. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/hibou.py +0 -0
  62. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/hoptimus.py +0 -0
  63. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/isight.py +0 -0
  64. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/lunit.py +0 -0
  65. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/mettle.py +0 -0
  66. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/midnight.py +0 -0
  67. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  68. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  69. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/case.py +0 -0
  70. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/slide.py +0 -0
  71. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/moozy/types.py +0 -0
  72. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/mstar.py +0 -0
  73. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/musk.py +0 -0
  74. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/phikon.py +0 -0
  75. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/prism.py +0 -0
  76. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/prost40m.py +0 -0
  77. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/rudolfv2.py +0 -0
  78. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/titan.py +0 -0
  79. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/uni.py +0 -0
  80. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/virchow.py +0 -0
  81. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/models/waiv.py +0 -0
  82. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/registry.py +0 -0
  83. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/encoders/validation.py +0 -0
  84. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/progress.py +0 -0
  85. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/__init__.py +0 -0
  86. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/artifacts_collect.py +0 -0
  87. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/batching.py +0 -0
  88. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_encode.py +0 -0
  89. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_encoder_input.py +0 -0
  90. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_image_reading.py +0 -0
  91. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_image_recipe.py +0 -0
  92. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_image_shard.py +0 -0
  93. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_regions.py +0 -0
  94. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_shard.py +0 -0
  95. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/dense_sliding.py +0 -0
  96. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/distributed.py +0 -0
  97. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/effective_encoder_input.py +0 -0
  98. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/embedding.py +0 -0
  99. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/embedding_persist.py +0 -0
  100. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/encoder_input_contract.py +0 -0
  101. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/feature_identity.py +0 -0
  102. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/hierarchical.py +0 -0
  103. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/image_shard.py +0 -0
  104. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/image_specs.py +0 -0
  105. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/manifest.py +0 -0
  106. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/model_settings.py +0 -0
  107. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/patient_pipeline.py +0 -0
  108. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/persist_callbacks.py +0 -0
  109. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/persistence.py +0 -0
  110. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  111. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/preprocessing.py +0 -0
  112. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/process_list.py +0 -0
  113. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/progress_bridge.py +0 -0
  114. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/registry.py +0 -0
  115. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/sharding.py +0 -0
  116. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/tiling.py +0 -0
  117. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/tiling_pipeline.py +0 -0
  118. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/types.py +0 -0
  119. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/runtime/worker_io.py +0 -0
  120. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/__init__.py +0 -0
  121. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/config.py +0 -0
  122. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/coordinates.py +0 -0
  123. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/log_utils.py +0 -0
  124. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/tiling_io.py +0 -0
  125. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec/utils/utils.py +0 -0
  126. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/dependency_links.txt +0 -0
  127. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/entry_points.txt +0 -0
  128. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/not-zip-safe +0 -0
  129. {slide2vec-7.1.0 → slide2vec-7.1.2}/slide2vec.egg-info/top_level.txt +0 -0
  130. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_attention_extraction.py +0 -0
  131. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_benchmark_tooling.py +0 -0
  132. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_cli_noninteractive.py +0 -0
  133. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_cufile_compat_mode.py +0 -0
  134. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_encode_kit.py +0 -0
  135. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_encoder_input.py +0 -0
  136. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_extraction.py +0 -0
  137. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_image_reading.py +0 -0
  138. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_image_resume.py +0 -0
  139. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_image_shard.py +0 -0
  140. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_image_stage.py +0 -0
  141. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_regions.py +0 -0
  142. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_shard.py +0 -0
  143. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_sliding.py +0 -0
  144. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_source_spacing.py +0 -0
  145. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_stage.py +0 -0
  146. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dense_worker.py +0 -0
  147. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dinov2_natimage.py +0 -0
  148. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_dinov3_natimage.py +0 -0
  149. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_drop_in_encoders.py +0 -0
  150. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_capabilities.py +0 -0
  151. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_input_contract.py +0 -0
  152. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_plugins.py +0 -0
  153. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_preprocessing.py +0 -0
  154. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_provider_failures.py +0 -0
  155. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_encoder_registry.py +0 -0
  156. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_feature_identity.py +0 -0
  157. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_gigapath.py +0 -0
  158. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_gpfm_genbio_heavy.py +0 -0
  159. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_hierarchical_encoding.py +0 -0
  160. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_hs2p5_integration.py +0 -0
  161. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_hs2p_package_cutover.py +0 -0
  162. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_image_shard.py +0 -0
  163. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_image_stage.py +0 -0
  164. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_inference_benchmark.py +0 -0
  165. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_load_model_hf_auth.py +0 -0
  166. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_mettle.py +0 -0
  167. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_on_slide_persisted.py +0 -0
  168. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_output_consistency.py +0 -0
  169. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_patch_size_metadata.py +0 -0
  170. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_patient_annotation_sampling.py +0 -0
  171. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_patient_manifest.py +0 -0
  172. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_patient_tiling_only.py +0 -0
  173. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_pooled_encoder_input.py +0 -0
  174. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_pooled_geometry.py +0 -0
  175. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_pooled_identity_api.py +0 -0
  176. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_prism.py +0 -0
  177. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_process_list_performance.py +0 -0
  178. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_progress.py +0 -0
  179. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_regression_models.py +0 -0
  180. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_rudolfv2.py +0 -0
  181. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_runtime_batching.py +0 -0
  182. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_sharding.py +0 -0
  183. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_slide_coordinate_preparation.py +0 -0
  184. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_slide_latents.py +0 -0
  185. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_soma_migration.py +0 -0
  186. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_tile_store.py +0 -0
  187. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_tiling_pipeline.py +0 -0
  188. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_titan.py +0 -0
  189. {slide2vec-7.1.0 → slide2vec-7.1.2}/tests/test_worker_gpu_pinning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 7.1.0
3
+ Version: 7.1.2
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
@@ -11,94 +11,51 @@ Classifier: Programming Language :: Python :: 3 :: Only
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
14
  Requires-Python: >=3.10
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE
18
17
  Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.1.0
19
18
  Requires-Dist: omegaconf
20
19
  Requires-Dist: matplotlib
21
- Requires-Dist: numpy<2
22
20
  Requires-Dist: pandas
23
21
  Requires-Dist: pillow
24
22
  Requires-Dist: rich
25
23
  Requires-Dist: torch
26
24
  Requires-Dist: torchvision
27
- Requires-Dist: transformers
25
+ Requires-Dist: transformers<5,>=4.47
28
26
  Requires-Dist: wandb
29
27
  Requires-Dist: einops
30
- Requires-Dist: timm>=1.0.3
28
+ Requires-Dist: einops-exts
29
+ Requires-Dist: timm>=1.0.20
31
30
  Requires-Dist: huggingface_hub
32
- Provides-Extra: hoptimus
33
- Requires-Dist: torch>=2.0; extra == "hoptimus"
34
- Requires-Dist: torchvision>=0.15.0; extra == "hoptimus"
35
- Requires-Dist: xformers>=0.0.18; extra == "hoptimus"
36
- Provides-Extra: virchow
37
- Requires-Dist: timm>=1.0.3; extra == "virchow"
38
- Requires-Dist: torch>=2.0; extra == "virchow"
39
- Provides-Extra: uni
40
- Requires-Dist: torch>=2.0; extra == "uni"
41
- Requires-Dist: timm>=1.0.3; extra == "uni"
42
- Requires-Dist: xformers>=0.0.18; extra == "uni"
31
+ Requires-Dist: packaging
43
32
  Provides-Extra: prism
44
- Requires-Dist: transformers~=4.53.0; extra == "prism"
45
- Requires-Dist: torch<2.8,>=2.3; extra == "prism"
46
- Requires-Dist: einops==0.8.0; extra == "prism"
47
- Requires-Dist: environs==11.0.0; extra == "prism"
48
- Requires-Dist: sacremoses==0.1.1; extra == "prism"
49
- Requires-Dist: xformers==0.0.31; extra == "prism"
33
+ Requires-Dist: torch>=2.3; extra == "prism"
34
+ Requires-Dist: environs; extra == "prism"
35
+ Requires-Dist: sacremoses; extra == "prism"
50
36
  Provides-Extra: prism2
51
37
  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"
56
- Provides-Extra: hibou
57
- Requires-Dist: scipy~=1.8.1; extra == "hibou"
58
- Requires-Dist: scikit-image~=0.19.3; extra == "hibou"
59
- Provides-Extra: moozy
60
- Requires-Dist: huggingface_hub<1.0,>=0.30.0; extra == "moozy"
38
+ Requires-Dist: transformers<5,>=4.51; extra == "prism2"
61
39
  Provides-Extra: titan
62
- Requires-Dist: torch==2.0.1; extra == "titan"
63
- Requires-Dist: timm==1.0.3; extra == "titan"
64
- Requires-Dist: einops==0.6.1; extra == "titan"
65
- Requires-Dist: einops-exts==0.0.4; extra == "titan"
66
- Requires-Dist: transformers==4.46.0; extra == "titan"
40
+ Requires-Dist: einops; extra == "titan"
41
+ Requires-Dist: einops-exts; extra == "titan"
42
+ Requires-Dist: timm>=1.0.3; extra == "titan"
43
+ Requires-Dist: transformers<5,>=4.46; extra == "titan"
67
44
  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"
45
+ Requires-Dist: transformers>=4.57; extra == "waiv"
46
+ Provides-Extra: flash-attn
47
+ Requires-Dist: flash-attn>=2.6.3; extra == "flash-attn"
48
+ Provides-Extra: xformers
49
+ Requires-Dist: xformers; extra == "xformers"
71
50
  Provides-Extra: fm
72
- Requires-Dist: omegaconf>=2.3.0; extra == "fm"
73
- Requires-Dist: matplotlib; extra == "fm"
74
- Requires-Dist: numpy<2; extra == "fm"
75
- Requires-Dist: pandas; extra == "fm"
76
- Requires-Dist: pillow; extra == "fm"
77
- Requires-Dist: rich; extra == "fm"
78
- Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.1.0; extra == "fm"
79
- Requires-Dist: wandb; extra == "fm"
80
- Requires-Dist: torch<2.8,>=2.3; extra == "fm"
81
- Requires-Dist: torchvision>=0.18.0; extra == "fm"
82
- Requires-Dist: einops>=0.8.0; extra == "fm"
83
- Requires-Dist: timm>=1.0.20; extra == "fm"
84
- Requires-Dist: huggingface_hub<1.0,>=0.30.0; extra == "fm"
85
- Requires-Dist: environs; extra == "fm"
86
- Requires-Dist: einops-exts>=0.0.4; extra == "fm"
87
- Requires-Dist: transformers>=4.53; extra == "fm"
88
- Requires-Dist: sacremoses; extra == "fm"
89
- Requires-Dist: xformers>=0.0.31; extra == "fm"
90
- Requires-Dist: scipy>=1.8.1; extra == "fm"
91
- Requires-Dist: scikit-image>=0.19.3; extra == "fm"
92
- Requires-Dist: torchmetrics>=0.10.3; extra == "fm"
93
- Requires-Dist: fvcore; extra == "fm"
94
- Requires-Dist: iopath; extra == "fm"
95
- Requires-Dist: webdataset; extra == "fm"
96
- Requires-Dist: scikit-survival; extra == "fm"
97
- Requires-Dist: scikit-learn; extra == "fm"
51
+ Requires-Dist: slide2vec[prism,prism2,titan,waiv]; extra == "fm"
98
52
  Requires-Dist: fairscale; extra == "fm"
99
- Requires-Dist: packaging==23.2; extra == "fm"
100
- Requires-Dist: ninja==1.11.1.1; extra == "fm"
101
- Requires-Dist: psutil<6; extra == "fm"
53
+ Provides-Extra: cpu
54
+ Requires-Dist: torch; extra == "cpu"
55
+ Requires-Dist: torchvision; extra == "cpu"
56
+ Provides-Extra: cu128
57
+ Requires-Dist: torch; extra == "cu128"
58
+ Requires-Dist: torchvision; extra == "cu128"
102
59
  Provides-Extra: docs
103
60
  Requires-Dist: sphinx>=8.1; extra == "docs"
104
61
  Requires-Dist: furo; extra == "docs"
@@ -131,6 +88,10 @@ pip install slide2vec
131
88
 
132
89
  Many models need additional dependencies available through `pip install "slide2vec[fm]"`. See the [model installation guide](https://clemsgrs.github.io/slide2vec/models.html#model-installation) for model-specific extras, separate environments, and upstream packages.
133
90
 
91
+ ### Reproducible model environments
92
+
93
+ The repository ships a `uv.lock` file that pins every package for each extra. From a checkout, `uv sync --extra cu128 --extra fm` installs the exact locked stack (use `--extra cpu` for CPU-only PyTorch). Add `--extra flash-attn` for PRISM2 and `gigapath-slide`. The Docker image is built from the same lock; its `SLIDE2VEC_EXTRAS` build argument selects the extras, for example `--build-arg SLIDE2VEC_EXTRAS="cu128"` for the base install only.
94
+
134
95
  For gated models such as Virchow2, request access on the model's Hugging Face page and authenticate with `hf auth login` or an `HF_TOKEN` environment variable.
135
96
 
136
97
  ## Embed a slide
@@ -15,6 +15,10 @@ pip install slide2vec
15
15
 
16
16
  Many models need additional dependencies available through `pip install "slide2vec[fm]"`. See the [model installation guide](https://clemsgrs.github.io/slide2vec/models.html#model-installation) for model-specific extras, separate environments, and upstream packages.
17
17
 
18
+ ### Reproducible model environments
19
+
20
+ The repository ships a `uv.lock` file that pins every package for each extra. From a checkout, `uv sync --extra cu128 --extra fm` installs the exact locked stack (use `--extra cpu` for CPU-only PyTorch). Add `--extra flash-attn` for PRISM2 and `gigapath-slide`. The Docker image is built from the same lock; its `SLIDE2VEC_EXTRAS` build argument selects the extras, for example `--build-arg SLIDE2VEC_EXTRAS="cu128"` for the base install only.
21
+
18
22
  For gated models such as Virchow2, request access on the model's Hugging Face page and authenticate with `hf auth login` or an `HF_TOKEN` environment variable.
19
23
 
20
24
  ## Embed a slide
@@ -0,0 +1,264 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "slide2vec"
7
+ version = "7.1.2"
8
+ description = "Embedding of whole slide images with Foundation Models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [
13
+ { name = "Clément Grisi", email = "clement.grisi@radboudumc.nl" },
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+ dependencies = [
23
+ "hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.1.0",
24
+ "omegaconf",
25
+ "matplotlib",
26
+ "pandas",
27
+ "pillow",
28
+ "rich",
29
+ "torch",
30
+ "torchvision",
31
+ # <5: Hibou's remote code imports transformers.onnx and
32
+ # pytorch_utils.find_pruneable_heads_and_indices, both removed in v5.
33
+ # >=4.47: PRISM's configuring_prism.py needs sub_configs / base_config_key.
34
+ "transformers>=4.47,<5",
35
+ "wandb",
36
+ "einops",
37
+ # TITAN remote code (used by the built-in conchv15 preset) imports it.
38
+ "einops-exts",
39
+ # first release that registers vit_base_patch16_dinov3
40
+ "timm>=1.0.20",
41
+ "huggingface_hub",
42
+ "packaging",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/clemsgrs/slide2vec"
47
+ "Bug Tracker" = "https://github.com/clemsgrs/slide2vec/issues"
48
+
49
+ [project.scripts]
50
+ slide2vec = "slide2vec.cli:entrypoint"
51
+
52
+ # Rule for extras: lower bounds only. An exact or upper pin is kept only when it
53
+ # is structural (the model's code breaks outside it); the reason sits next to
54
+ # the pin. A model whose stack conflicts with the shared stack gets its own
55
+ # extra and a uv conflict below. numpy<2 comes from hs2p[asap].
56
+ [project.optional-dependencies]
57
+ prism = [
58
+ # perceiver.py calls torch.nn.attention.sdpa_kernel with a list (absent in 2.2)
59
+ "torch>=2.3",
60
+ "environs",
61
+ "sacremoses",
62
+ ]
63
+ prism2 = [
64
+ "torch>=2.3",
65
+ # the text (diagnostic) variant calls a Phi3Model helper whose signature
66
+ # changed in 4.52 and which 4.53 removed; slide2vec.encoders.models.prism2
67
+ # vendors it, so no upper bound. PRISM2 also imports flash_attn: add the
68
+ # flash-attn extra (CUDA only, so not pulled in here)
69
+ "transformers>=4.51,<5",
70
+ ]
71
+ titan = [
72
+ "einops",
73
+ "einops-exts",
74
+ "timm>=1.0.3",
75
+ "transformers>=4.46,<5",
76
+ ]
77
+ waiv = [
78
+ # phaet and mascaret load with zero missing keys at the pinned revisions
79
+ "transformers>=4.57",
80
+ ]
81
+ # gigapath-slide needs flash-attn on GPUs with compute capability above 7
82
+ flash-attn = [
83
+ "flash-attn>=2.6.3",
84
+ ]
85
+ xformers = [
86
+ # optional: PRISM's PERCEIVER_MEM_EFF_ATTN path; gigapath-slide needs it
87
+ # on GPUs with compute capability 7 or below. Unpinned: each xformers
88
+ # build pins its torch.
89
+ "xformers",
90
+ ]
91
+ fm = [
92
+ "slide2vec[prism,prism2,titan,waiv]",
93
+ # hard import in MUSK and gigapath-slide
94
+ "fairscale",
95
+ ]
96
+ # torch wheel flavour for uv; when neither is selected torch comes from PyPI
97
+ cpu = [
98
+ "torch",
99
+ "torchvision",
100
+ ]
101
+ cu128 = [
102
+ "torch",
103
+ "torchvision",
104
+ ]
105
+ docs = [
106
+ "sphinx>=8.1",
107
+ "furo",
108
+ "myst-parser",
109
+ "sphinx-copybutton",
110
+ "sphinx-autodoc-typehints",
111
+ ]
112
+ testing = [
113
+ "pytest>=6.0",
114
+ "pytest-cov>=2.0",
115
+ "mypy>=0.910",
116
+ "flake8>=3.9",
117
+ "flake8-pyproject>=1.2.3",
118
+ "tox>=3.24",
119
+ ]
120
+
121
+ [dependency-groups]
122
+ dev = [
123
+ "pytest>=6.0",
124
+ "pytest-cov>=2.0",
125
+ "mypy>=0.910",
126
+ "flake8>=3.9",
127
+ "flake8-pyproject>=1.2.3",
128
+ "tox>=3.24",
129
+ # tests/test_encoder_plugins.py installs a plugin with `python -m pip`;
130
+ # uv environments ship without pip
131
+ "pip",
132
+ ]
133
+ docs = [
134
+ "sphinx>=8.1",
135
+ "furo",
136
+ "myst-parser",
137
+ "sphinx-copybutton",
138
+ "sphinx-autodoc-typehints",
139
+ ]
140
+ # encoder packages that are not on PyPI (musk, conch, gigapath-slide, sam2)
141
+ models-git = [
142
+ "musk",
143
+ "conch",
144
+ "gigapath",
145
+ "sam-2",
146
+ ]
147
+
148
+ [tool.uv]
149
+ # ASAP and cucim are Linux-only. numpy<2 (from hs2p[asap]) has no wheels for
150
+ # Python 3.13+, so the lock covers Python 3.10 to 3.12.
151
+ environments = ["sys_platform == 'linux' and python_full_version < '3.13'"]
152
+ # Lock-only: the cu128 index stops at torch 2.11. Hold the CPU and PyPI forks at
153
+ # the same release so the CPU CI image tests the torch the CUDA image ships.
154
+ # This is not part of the published metadata.
155
+ constraint-dependencies = ["torch<2.12"]
156
+ conflicts = [
157
+ [
158
+ { extra = "cpu" },
159
+ { extra = "cu128" },
160
+ ],
161
+ ]
162
+
163
+ # flash-attn ships only an sdist whose build imports torch; declare its metadata
164
+ # so uv can lock it without building it
165
+ [[tool.uv.dependency-metadata]]
166
+ name = "flash-attn"
167
+ requires-dist = ["torch", "einops"]
168
+
169
+ # flash-attn's setup.py imports torch, packaging, wheel and psutil. Build it in
170
+ # an isolated environment that holds the same torch as the runtime environment.
171
+ [tool.uv.extra-build-dependencies]
172
+ flash-attn = [
173
+ { requirement = "torch", match-runtime = true },
174
+ "packaging",
175
+ "psutil",
176
+ "wheel",
177
+ "ninja",
178
+ ]
179
+
180
+ [tool.uv.sources]
181
+ torch = [
182
+ { index = "pytorch-cpu", extra = "cpu" },
183
+ { index = "pytorch-cu128", extra = "cu128" },
184
+ ]
185
+ torchvision = [
186
+ { index = "pytorch-cpu", extra = "cpu" },
187
+ { index = "pytorch-cu128", extra = "cu128" },
188
+ ]
189
+ musk = { git = "https://github.com/lilab-stanford/MUSK.git" }
190
+ conch = { git = "https://github.com/Mahmoodlab/CONCH.git" }
191
+ gigapath = { git = "https://github.com/prov-gigapath/prov-gigapath.git" }
192
+ sam-2 = { git = "https://github.com/facebookresearch/sam2.git" }
193
+
194
+ [[tool.uv.index]]
195
+ name = "pytorch-cpu"
196
+ url = "https://download.pytorch.org/whl/cpu"
197
+ explicit = true
198
+
199
+ [[tool.uv.index]]
200
+ name = "pytorch-cu128"
201
+ url = "https://download.pytorch.org/whl/cu128"
202
+ explicit = true
203
+
204
+ [tool.setuptools]
205
+ include-package-data = true
206
+ zip-safe = false
207
+
208
+ [tool.setuptools.packages.find]
209
+ include = ["slide2vec*"]
210
+
211
+ [tool.setuptools.package-data]
212
+ slide2vec = ["py.typed", "cufile.json"]
213
+ "slide2vec.configs" = ["*.yaml"]
214
+
215
+ [tool.pytest.ini_options]
216
+ addopts = "--cov=slide2vec -m 'not gpu_integration'"
217
+ testpaths = [
218
+ "tests",
219
+ ]
220
+ markers = [
221
+ "heavy: real-weight foundation-model inference on CPU; minutes per test. Included in the PR suite and available separately through the manual heavy workflow (.github/workflows/nightly-heavy.yaml).",
222
+ "gpu_integration: real one-GPU versus multi-GPU parity; requires at least two visible CUDA devices. Run explicitly with `CUDA_VISIBLE_DEVICES=0,1 python -m pytest -m gpu_integration --no-cov`.",
223
+ ]
224
+
225
+ [tool.mypy]
226
+ mypy_path = "."
227
+ check_untyped_defs = true
228
+ disallow_any_generics = true
229
+ ignore_missing_imports = true
230
+ no_implicit_optional = true
231
+ show_error_codes = true
232
+ strict_equality = true
233
+ warn_redundant_casts = true
234
+ warn_return_any = true
235
+ warn_unreachable = true
236
+ warn_unused_configs = true
237
+ no_implicit_reexport = true
238
+
239
+ [tool.flake8]
240
+ max-line-length = 160
241
+
242
+ [tool.bumpver]
243
+ current_version = "7.1.2"
244
+ version_pattern = "MAJOR.MINOR.PATCH"
245
+ commit = false
246
+ tag = false
247
+ push = false
248
+ files = [
249
+ "pyproject.toml",
250
+ "slide2vec/__init__.py",
251
+ "docs/conf.py",
252
+ ]
253
+
254
+ [tool.bumpver.file_patterns]
255
+ "pyproject.toml" = [
256
+ '^current_version = "{version}"$',
257
+ '^version = "{version}"$',
258
+ ]
259
+ "slide2vec/__init__.py" = [
260
+ '^__version__ = "{version}"$',
261
+ ]
262
+ "docs/conf.py" = [
263
+ '^release = "{version}"$',
264
+ ]
@@ -36,7 +36,7 @@ from slide2vec.encoders import (
36
36
  )
37
37
 
38
38
 
39
- __version__ = "7.1.0"
39
+ __version__ = "7.1.2"
40
40
 
41
41
  __all__ = [
42
42
  "Model",
@@ -7,7 +7,7 @@ import warnings
7
7
  from dataclasses import dataclass, field, replace
8
8
  from contextlib import contextmanager
9
9
  from pathlib import Path
10
- from typing import Any, Callable, Mapping, Protocol, Sequence
10
+ from typing import Any, Callable, Mapping, NamedTuple, Protocol, Sequence
11
11
 
12
12
  import torch
13
13
  from hs2p import (
@@ -261,9 +261,12 @@ class ExecutionOptions:
261
261
  #: (capped by CPU / SLURM limit, then split across the resolved GPU count).
262
262
  #: Image-only routes safely use zero when auto selection happens after model loading.
263
263
  num_workers_per_gpu: int | None = None
264
- #: Tiling worker count. ``None`` means auto (capped by CPU / SLURM limit).
264
+ #: Tiling worker count. ``None`` means auto. The field keeps the requested value;
265
+ #: :meth:`resolved_num_preprocessing_workers` gives the count a run uses, capped by the
266
+ #: CPU / SLURM limit.
265
267
  num_preprocessing_workers: int | None = None
266
- #: Number of GPUs to use. ``None`` defaults to all available GPUs.
268
+ #: Number of GPUs to use. ``None`` means all available GPUs. The field keeps the
269
+ #: requested value; :meth:`resolved_num_gpus` gives the count a run uses.
267
270
  num_gpus: int | None = None
268
271
  #: Forward-pass dtype — ``"fp16"``, ``"bf16"``, ``"fp32"``,
269
272
  #: or ``None`` (auto-determined from the model preset).
@@ -313,11 +316,9 @@ class ExecutionOptions:
313
316
  )
314
317
 
315
318
  def __post_init__(self) -> None:
316
- resolved_num_gpus = _default_num_gpus() if self.num_gpus is None else self.num_gpus
317
- object.__setattr__(self, "num_gpus", resolved_num_gpus)
318
319
  object.__setattr__(self, "precision", normalize_precision_name(self.precision))
319
320
  object.__setattr__(self, "output_dtype", normalize_output_dtype(self.output_dtype))
320
- if resolved_num_gpus < 1:
321
+ if self.num_gpus is not None and self.num_gpus < 1:
321
322
  raise ValueError("ExecutionOptions.num_gpus must be at least 1")
322
323
  if self.batch_size < 1:
323
324
  raise ValueError("ExecutionOptions.batch_size must be at least 1")
@@ -325,40 +326,62 @@ class ExecutionOptions:
325
326
  raise ValueError("ExecutionOptions.prefetch_factor must be at least 1")
326
327
  if self.num_workers_per_gpu is not None and self.num_workers_per_gpu < 0:
327
328
  raise ValueError("ExecutionOptions.num_workers_per_gpu must be non-negative")
329
+ if self.num_preprocessing_workers is not None and self.num_preprocessing_workers < 0:
330
+ raise ValueError("ExecutionOptions.num_preprocessing_workers must be non-negative")
328
331
  if self.on_image_mismatch not in IMAGE_MISMATCH_POLICIES:
329
332
  raise ValueError(
330
333
  "ExecutionOptions.on_image_mismatch must be one of "
331
334
  f"{', '.join(repr(policy) for policy in IMAGE_MISMATCH_POLICIES)}; "
332
335
  f"got {self.on_image_mismatch!r}"
333
336
  )
337
+
338
+ def resolved_num_gpus(self) -> int:
339
+ """GPU count a run uses: :attr:`num_gpus`, or every visible GPU when it is ``None``."""
340
+ return self._machine_resolution.num_gpus
341
+
342
+ def resolved_num_preprocessing_workers(self) -> int:
343
+ """Tiling worker count a run uses: :attr:`num_preprocessing_workers`, or the CPU /
344
+ SLURM limit when it is ``None``, and never more than that limit."""
345
+ return self._machine_resolution.num_preprocessing_workers
346
+
347
+ def resolved_num_workers_per_gpu(self) -> int:
348
+ if self.num_workers_per_gpu is not None:
349
+ return self.num_workers_per_gpu
350
+ return max(1, cpu_worker_limit() // self.resolved_num_gpus())
351
+
352
+ @functools.cached_property
353
+ def _machine_resolution(self) -> "_MachineResolution":
354
+ # Resolved once per instance, on first use, so every reader in a run agrees and the
355
+ # log line is written once. The cache sits outside the dataclass fields: equality,
356
+ # hashing and repr see only what the caller asked for.
357
+ num_gpus = _default_num_gpus() if self.num_gpus is None else self.num_gpus
334
358
  cap = cpu_worker_limit()
335
- cpu_count = os.cpu_count() or 1
336
- slurm_limit = slurm_cpu_limit()
337
- capped_num_preprocessing_workers = (
359
+ num_preprocessing_workers = (
338
360
  cap if self.num_preprocessing_workers is None else min(self.num_preprocessing_workers, cap)
339
361
  )
340
- object.__setattr__(self, "num_preprocessing_workers", capped_num_preprocessing_workers)
341
- logger = logging.getLogger(__name__)
342
- cap_source = f"slurm_cpu_limit={slurm_limit}" if slurm_limit is not None else f"cpu_count={cpu_count}"
343
- resolved_num_workers = self.resolved_num_workers_per_gpu()
344
- num_workers_per_gpu_label = (
345
- f"{resolved_num_workers} (requested=auto)"
346
- if self.num_workers_per_gpu is None
347
- else str(resolved_num_workers)
362
+ num_workers_per_gpu = (
363
+ max(1, cap // num_gpus) if self.num_workers_per_gpu is None else self.num_workers_per_gpu
364
+ )
365
+ slurm_limit = slurm_cpu_limit()
366
+ cap_source = (
367
+ f"slurm_cpu_limit={slurm_limit}" if slurm_limit is not None else f"cpu_count={os.cpu_count() or 1}"
348
368
  )
349
- logger.info(
350
- "ExecutionOptions: num_workers_per_gpu=%s, num_preprocessing_workers=%d "
369
+ logging.getLogger(__name__).info(
370
+ "ExecutionOptions: num_gpus=%s, num_workers_per_gpu=%s, num_preprocessing_workers=%d "
351
371
  "(preprocessing cap=%d via %s)",
352
- num_workers_per_gpu_label,
353
- capped_num_preprocessing_workers,
372
+ _auto_label(num_gpus, requested=self.num_gpus),
373
+ _auto_label(num_workers_per_gpu, requested=self.num_workers_per_gpu),
374
+ num_preprocessing_workers,
354
375
  cap,
355
376
  cap_source,
356
377
  )
378
+ return _MachineResolution(num_gpus=num_gpus, num_preprocessing_workers=num_preprocessing_workers)
357
379
 
358
- def resolved_num_workers_per_gpu(self) -> int:
359
- if self.num_workers_per_gpu is not None:
360
- return self.num_workers_per_gpu
361
- return max(1, cpu_worker_limit() // self.num_gpus)
380
+ def __getstate__(self) -> dict[str, Any]:
381
+ # A copy or an unpickled instance resolves again on the machine that uses it.
382
+ state = dict(self.__dict__)
383
+ state.pop("_machine_resolution", None)
384
+ return state
362
385
 
363
386
  def resolved_image_num_workers_per_gpu(self) -> int:
364
387
  """Resolve safe post-model-load image-transform workers for this rank."""
@@ -898,8 +921,9 @@ class Model:
898
921
  The dense counterpart of the pooled coordinate path: each ``SlideRegions`` names a
899
922
  slide + a set of level-0 ROI coordinates, and every ROI is read, encoded through the
900
923
  dense transform, and written to ``dense_embeddings/[<class>/]<sample_id>/<x>_<y>.pt``
901
- plus a geometry sidecar. The run splits its ROIs across all visible GPUs
902
- (``execution.num_gpus``); ``num_gpus=1`` encodes fully in-process. Resume is
924
+ plus a geometry sidecar. The run splits its ROIs across
925
+ ``execution.resolved_num_gpus()`` GPUs (all visible GPUs by default);
926
+ ``num_gpus=1`` encodes fully in-process. Resume is
903
927
  automatic — only ROIs whose sidecar has the same source-spacing declaration,
904
928
  resolved hs2p read plan and feature identity are skipped. Returns one
905
929
  :class:`~slide2vec.artifacts.DenseRegionArtifact` per input ROI.
@@ -932,10 +956,10 @@ class Model:
932
956
  **normalization-only** transform, padded up to the encoder's patch multiple, encoded
933
957
  — whole-image, or by sliding the encoder's native field and blending the token grids
934
958
  — and written to ``dense_image_embeddings/<sample_id>.pt`` plus a geometry sidecar.
935
- The run splits its images across all visible GPUs (``execution.num_gpus``);
936
- ``num_gpus=1`` encodes fully in-process. Resume is automatic: an image is skipped
937
- only when its payload exists and its sidecar records the same normalized source
938
- identity and complete extraction recipe. Returns one
959
+ The run splits its images across ``execution.resolved_num_gpus()`` GPUs (all
960
+ visible GPUs by default); ``num_gpus=1`` encodes fully in-process. Resume is
961
+ automatic: an image is skipped only when its payload exists and its sidecar records
962
+ the same normalized source identity and complete extraction recipe. Returns one
939
963
  :class:`~slide2vec.artifacts.DenseImageArtifact` per input image, in input order.
940
964
 
941
965
  Every source is opened through hs2p. PNG/JPEG inputs use hs2p's one-level PIL reader
@@ -977,12 +1001,12 @@ class Model:
977
1001
  set — and slide2vec neither tiles nor reads a slide. Each :class:`ImageSpec` is
978
1002
  decoded, preprocessed with the encoder's **shipped** transform, encoded, and written
979
1003
  to ``image_embeddings/<sample_id>.pt`` plus a provenance sidecar. The run splits its
980
- images across all visible GPUs (``execution.num_gpus``); ``num_gpus=1`` encodes
981
- fully in-process. Resume is automatic: an image is skipped only when its sidecar
982
- records this ``output_format``, the requested ``image_path`` and the full feature
983
- identity. A different recorded feature identity raises; a different recorded
984
- ``image_path`` raises unless ``execution.on_image_mismatch="reencode"``; missing
985
- provenance re-encodes. Returns
1004
+ images across ``execution.resolved_num_gpus()`` GPUs (all visible GPUs by
1005
+ default); ``num_gpus=1`` encodes fully in-process. Resume is automatic: an image is
1006
+ skipped only when its sidecar records this ``output_format``, the requested
1007
+ ``image_path`` and the full feature identity. A different recorded feature identity
1008
+ raises; a different recorded ``image_path`` raises unless
1009
+ ``execution.on_image_mismatch="reencode"``; missing provenance re-encodes. Returns
986
1010
  one :class:`~slide2vec.artifacts.ImageEmbeddingArtifact` per input image, in input
987
1011
  order.
988
1012
 
@@ -1342,6 +1366,15 @@ def _default_num_gpus() -> int:
1342
1366
  return max(1, torch.cuda.device_count()) if torch.cuda.is_available() else 1
1343
1367
 
1344
1368
 
1369
+ class _MachineResolution(NamedTuple):
1370
+ num_gpus: int
1371
+ num_preprocessing_workers: int
1372
+
1373
+
1374
+ def _auto_label(resolved: int, *, requested: int | None) -> str:
1375
+ return f"{resolved} (requested=auto)" if requested is None else str(resolved)
1376
+
1377
+
1345
1378
  def _require_output_dir_for_persistence(execution: ExecutionOptions, *, method_name: str) -> None:
1346
1379
  if execution.output_dir is None:
1347
1380
  raise ValueError(f"ExecutionOptions.output_dir is required for {method_name}")
@@ -36,9 +36,7 @@ def require_timm_for_dinov3() -> None:
36
36
  if Version(timm.__version__) < Version(_MIN_TIMM_VERSION):
37
37
  raise ImportError(
38
38
  f"dinov3-vitb16 requires timm>={_MIN_TIMM_VERSION} (the DINOv3 "
39
- f"architectures were added in that release); found timm=={timm.__version__}. "
40
- "Note that the 'titan' extra pins timm==1.0.3 and cannot be installed "
41
- "alongside dinov3-vitb16."
39
+ f"architectures were added in that release); found timm=={timm.__version__}."
42
40
  )
43
41
 
44
42
 
@@ -79,7 +79,7 @@ def load_moozy_inference_components(device: torch.device) -> MoozyInferenceCompo
79
79
  except Exception as exc:
80
80
  raise RuntimeError(
81
81
  "huggingface_hub is required for bundled MOOZY inference support. "
82
- "Install it with: pip install 'slide2vec[moozy]'"
82
+ "Install it with: pip install huggingface_hub"
83
83
  ) from exc
84
84
 
85
85
  checkpoint_path = hf_hub_download(repo_id=_HF_REPO_ID, filename=_HF_CHECKPOINT)