slide2vec 7.1.3__tar.gz → 7.1.5__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 (190) hide show
  1. {slide2vec-7.1.3 → slide2vec-7.1.5}/PKG-INFO +1 -1
  2. {slide2vec-7.1.3 → slide2vec-7.1.5}/pyproject.toml +2 -2
  3. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/__init__.py +1 -1
  4. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/api.py +44 -0
  5. slide2vec-7.1.5/slide2vec/encoders/models/mettle_1_5.py +129 -0
  6. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_recipe.py +50 -2
  7. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_stage.py +3 -28
  8. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/feature_identity.py +63 -0
  9. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/PKG-INFO +1 -1
  10. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/SOURCES.txt +2 -0
  11. slide2vec-7.1.5/tests/test_dense_identity_api.py +430 -0
  12. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_plugins.py +1 -1
  13. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_preprocessing.py +31 -7
  14. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_mettle.py +98 -40
  15. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patch_size_metadata.py +1 -0
  16. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_core.py +1 -0
  17. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_models.py +1 -0
  18. {slide2vec-7.1.3 → slide2vec-7.1.5}/LICENSE +0 -0
  19. {slide2vec-7.1.3 → slide2vec-7.1.5}/README.md +0 -0
  20. {slide2vec-7.1.3 → slide2vec-7.1.5}/setup.cfg +0 -0
  21. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/__main__.py +0 -0
  22. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/_cufile.py +0 -0
  23. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/artifacts.py +0 -0
  24. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/cli.py +0 -0
  25. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/__init__.py +0 -0
  26. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/default.yaml +0 -0
  27. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/resources.py +0 -0
  28. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/cufile.json +0 -0
  29. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/__init__.py +0 -0
  30. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/dataset.py +0 -0
  31. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/tile_reader.py +0 -0
  32. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/tile_store.py +0 -0
  33. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/__init__.py +0 -0
  34. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/dense_image_worker.py +0 -0
  35. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/dense_worker.py +0 -0
  36. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/direct_embed_worker.py +0 -0
  37. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/image_worker.py +0 -0
  38. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/pin_gpu.py +0 -0
  39. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/pipeline_worker.py +0 -0
  40. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/worker_entry.py +0 -0
  41. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/__init__.py +0 -0
  42. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/base.py +0 -0
  43. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/code.py +0 -0
  44. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/__init__.py +0 -0
  45. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/conch.py +0 -0
  46. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov2.py +0 -0
  47. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov3.py +0 -0
  48. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/genbio.py +0 -0
  49. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/gigapath.py +0 -0
  50. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/gpfm.py +0 -0
  51. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/hibou.py +0 -0
  52. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/hoptimus.py +0 -0
  53. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/isight.py +0 -0
  54. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/lunit.py +0 -0
  55. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/mettle.py +0 -0
  56. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/midnight.py +0 -0
  57. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  58. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  59. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/case.py +0 -0
  60. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/loading.py +0 -0
  61. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/slide.py +0 -0
  62. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/types.py +0 -0
  63. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/mstar.py +0 -0
  64. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/musk.py +0 -0
  65. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/phikon.py +0 -0
  66. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prism.py +0 -0
  67. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prism2.py +0 -0
  68. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prost40m.py +0 -0
  69. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/rudolfv2.py +0 -0
  70. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/titan.py +0 -0
  71. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/uni.py +0 -0
  72. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/virchow.py +0 -0
  73. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/waiv.py +0 -0
  74. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/registry.py +0 -0
  75. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/validation.py +0 -0
  76. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/inference.py +0 -0
  77. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/progress.py +0 -0
  78. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/__init__.py +0 -0
  79. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/artifacts_collect.py +0 -0
  80. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/batching.py +0 -0
  81. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/cpu_budget.py +0 -0
  82. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_encode.py +0 -0
  83. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_encoder_input.py +0 -0
  84. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_reading.py +0 -0
  85. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_shard.py +0 -0
  86. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_regions.py +0 -0
  87. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_shard.py +0 -0
  88. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_sliding.py +0 -0
  89. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_stage.py +0 -0
  90. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/distributed.py +0 -0
  91. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/distributed_stage.py +0 -0
  92. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/effective_encoder_input.py +0 -0
  93. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding.py +0 -0
  94. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding_persist.py +0 -0
  95. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding_pipeline.py +0 -0
  96. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/encoder_input_contract.py +0 -0
  97. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/hierarchical.py +0 -0
  98. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_shard.py +0 -0
  99. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_specs.py +0 -0
  100. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_stage.py +0 -0
  101. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/manifest.py +0 -0
  102. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/model_settings.py +0 -0
  103. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/patient_pipeline.py +0 -0
  104. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/persist_callbacks.py +0 -0
  105. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/persistence.py +0 -0
  106. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  107. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/preprocessing.py +0 -0
  108. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/process_list.py +0 -0
  109. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/progress_bridge.py +0 -0
  110. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/registry.py +0 -0
  111. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/serialization.py +0 -0
  112. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/sharding.py +0 -0
  113. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/slide_encode.py +0 -0
  114. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/tiling.py +0 -0
  115. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/tiling_pipeline.py +0 -0
  116. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/types.py +0 -0
  117. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/worker_io.py +0 -0
  118. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/__init__.py +0 -0
  119. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/config.py +0 -0
  120. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/coordinates.py +0 -0
  121. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/log_utils.py +0 -0
  122. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/tiling_io.py +0 -0
  123. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/utils.py +0 -0
  124. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/dependency_links.txt +0 -0
  125. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/entry_points.txt +0 -0
  126. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/not-zip-safe +0 -0
  127. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/requires.txt +0 -0
  128. {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/top_level.txt +0 -0
  129. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_attention_extraction.py +0 -0
  130. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_benchmark_tooling.py +0 -0
  131. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_cli_noninteractive.py +0 -0
  132. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_cufile_compat_mode.py +0 -0
  133. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_encode_kit.py +0 -0
  134. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_encoder_input.py +0 -0
  135. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_extraction.py +0 -0
  136. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_reading.py +0 -0
  137. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_resume.py +0 -0
  138. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_shard.py +0 -0
  139. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_stage.py +0 -0
  140. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_regions.py +0 -0
  141. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_shard.py +0 -0
  142. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_sliding.py +0 -0
  143. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_source_spacing.py +0 -0
  144. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_stage.py +0 -0
  145. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_worker.py +0 -0
  146. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dinov2_natimage.py +0 -0
  147. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dinov3_natimage.py +0 -0
  148. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_drop_in_encoders.py +0 -0
  149. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_capabilities.py +0 -0
  150. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_code.py +0 -0
  151. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_input_contract.py +0 -0
  152. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_provider_failures.py +0 -0
  153. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_registry.py +0 -0
  154. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_execution_options.py +0 -0
  155. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_feature_identity.py +0 -0
  156. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_gigapath.py +0 -0
  157. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_gpfm_genbio_heavy.py +0 -0
  158. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hierarchical_encoding.py +0 -0
  159. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hs2p5_integration.py +0 -0
  160. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hs2p_package_cutover.py +0 -0
  161. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_image_shard.py +0 -0
  162. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_image_stage.py +0 -0
  163. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_inference_benchmark.py +0 -0
  164. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_isight.py +0 -0
  165. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_load_model_hf_auth.py +0 -0
  166. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_mascaret.py +0 -0
  167. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_on_slide_persisted.py +0 -0
  168. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_output_consistency.py +0 -0
  169. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_annotation_sampling.py +0 -0
  170. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_manifest.py +0 -0
  171. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_tiling_only.py +0 -0
  172. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_phaet.py +0 -0
  173. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_encoder_input.py +0 -0
  174. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_geometry.py +0 -0
  175. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_identity_api.py +0 -0
  176. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_prism.py +0 -0
  177. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_prism2.py +0 -0
  178. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_process_list_performance.py +0 -0
  179. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_progress.py +0 -0
  180. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_inference.py +0 -0
  181. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_rudolfv2.py +0 -0
  182. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_runtime_batching.py +0 -0
  183. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_sharding.py +0 -0
  184. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_slide_coordinate_preparation.py +0 -0
  185. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_slide_latents.py +0 -0
  186. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_soma_migration.py +0 -0
  187. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_tile_store.py +0 -0
  188. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_tiling_pipeline.py +0 -0
  189. {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_titan.py +0 -0
  190. {slide2vec-7.1.3 → slide2vec-7.1.5}/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.3
3
+ Version: 7.1.5
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "slide2vec"
7
- version = "7.1.3"
7
+ version = "7.1.5"
8
8
  description = "Embedding of whole slide images with Foundation Models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -240,7 +240,7 @@ no_implicit_reexport = true
240
240
  max-line-length = 160
241
241
 
242
242
  [tool.bumpver]
243
- current_version = "7.1.3"
243
+ current_version = "7.1.5"
244
244
  version_pattern = "MAJOR.MINOR.PATCH"
245
245
  commit = false
246
246
  tag = false
@@ -36,7 +36,7 @@ from slide2vec.encoders import (
36
36
  )
37
37
 
38
38
 
39
- __version__ = "7.1.3"
39
+ __version__ = "7.1.5"
40
40
 
41
41
  __all__ = [
42
42
  "Model",
@@ -708,6 +708,45 @@ class Model:
708
708
  encoded_pixels=encoded_pixels,
709
709
  )
710
710
 
711
+ def dense_identity_differences(
712
+ self,
713
+ recorded: Mapping[str, Any],
714
+ *,
715
+ dense: "DenseOptions | DenseImageOptions",
716
+ execution: ExecutionOptions | None = None,
717
+ ) -> dict[str, tuple[Any, Any]]:
718
+ """Compare a dense sidecar's ``compatibility`` block with the current request.
719
+
720
+ The dense counterpart of :meth:`pooled_identity_differences`. Pass the
721
+ ``dense`` options and ``execution`` of :meth:`embed_regions_dense` (a
722
+ :class:`DenseOptions`) or :meth:`embed_images_dense` (a
723
+ :class:`DenseImageOptions`). Returns ``{field: (recorded, current)}`` for every
724
+ request-wide field that the record holds with a different value or does not
725
+ hold. A field the record does not hold is reported as
726
+ :data:`slide2vec.MISSING_FIELD`. Per-sample fields (the sample, its path and its
727
+ read plan) are not compared. An empty result means the record proves the
728
+ current request-wide identity. The caller owns the policy for a difference.
729
+
730
+ An invalid request raises as it does in the embed methods. ``execution``
731
+ defaults to the model's recommended precision and needs no output directory.
732
+ Comparing the ``transform`` loads weights, on an isolated CPU model. This method
733
+ does not change this model's input contract or loaded backend, encode pixels,
734
+ or write files.
735
+ """
736
+ from slide2vec.runtime.feature_identity import dense_identity_differences
737
+
738
+ if not isinstance(dense, (DenseOptions, DenseImageOptions)):
739
+ raise TypeError(
740
+ "Model.dense_identity_differences(dense=...) expects DenseOptions "
741
+ f"or DenseImageOptions, got {type(dense).__name__}"
742
+ )
743
+ return dense_identity_differences(
744
+ self,
745
+ dict(recorded),
746
+ dense=dense,
747
+ execution=_coerce_execution_options(execution, model=self),
748
+ )
749
+
711
750
  def prepare_dense_encoder(
712
751
  self,
713
752
  *,
@@ -1085,6 +1124,11 @@ class Model:
1085
1124
  non-timm backbone, say) fails before any backend is loaded and before resume
1086
1125
  inspects or invalidates an existing artifact.
1087
1126
  """
1127
+ if self.level != "tile":
1128
+ raise ValueError(
1129
+ "Dense extraction requires a tile-level foundation encoder; model "
1130
+ f"{self.name!r} is registered at level {self.level!r}."
1131
+ )
1088
1132
  contract = EncoderInputContract.declared_dense(
1089
1133
  self.name,
1090
1134
  target_size_px=dense.target_size,
@@ -0,0 +1,129 @@
1
+ """Mettle-1.5 tile encoder.
2
+
3
+ Mettle-1.5 (slideflow labs; ``slideflow-labs/mettle-1.5``) is a later scanner and
4
+ stain robustness fine-tune of H-optimus-0. It shares ``mettle``'s backbone,
5
+ ``MettleRefineHead``, H-optimus mean/std and feature views, so this module reuses
6
+ :mod:`.mettle`. Weights are openly downloadable (not gated). The model card states
7
+ no license; slide2vec only wraps user-downloaded weights and does not redistribute
8
+ them.
9
+
10
+ The given recipe resizes directly to 224x224, as upstream's
11
+ ``preprocessor_config.json`` does. ``mettle`` resizes the shortest side and
12
+ center-crops instead. The two agree on square tiles.
13
+
14
+ slide2vec strictly loads ``model.safetensors`` at a pinned revision, as for
15
+ ``mettle``; no remote code runs.
16
+
17
+ Output variants (upstream ``feature_view``):
18
+
19
+ * ``cls`` (1536, default): the headed CLS token. The card and upstream
20
+ ``config.json`` default to it; the card makes no benchmark claims for this
21
+ checkpoint.
22
+ * ``cls_patch_mean`` (3072; upstream ``cls_mean``): the headed CLS token
23
+ concatenated with the *unheaded* mean of the spatial patch tokens (CLS and the
24
+ 4 register tokens excluded).
25
+
26
+ ``mettle`` defaults to ``cls_patch_mean``, so a config moved from ``mettle`` to
27
+ ``mettle-1.5`` changes the feature dimension from 3072 to 1536.
28
+
29
+ Precision, dense extraction and spacing are as for ``mettle``.
30
+ """
31
+
32
+ from typing import Callable
33
+
34
+ import torch
35
+ from huggingface_hub import hf_hub_download
36
+ from safetensors.torch import load_file
37
+ from torchvision.transforms import v2
38
+
39
+ from slide2vec.encoders.base import (
40
+ resolve_recommended_dynamic_img_size,
41
+ resolve_requested_output_variant,
42
+ )
43
+ from slide2vec.encoders.models.hoptimus import _HOPTIMUS_MEAN, _HOPTIMUS_STD
44
+ from slide2vec.encoders.models.mettle import (
45
+ _EMBED_DIM,
46
+ _HEAD_HIDDEN_SIZE,
47
+ _HEAD_NUM_ATOMS,
48
+ _HEAD_RANK,
49
+ _HF_CHECKPOINT,
50
+ _OUTPUT_DIMS,
51
+ Mettle,
52
+ MettleRefineHead,
53
+ _strip_prefix,
54
+ )
55
+ from slide2vec.encoders.registry import register_encoder
56
+
57
+ _HF_REPO_ID = "slideflow-labs/mettle-1.5"
58
+ _HF_REVISION = "0ab55fcbf1b8d4e3c73885fc56f822fb568ff570"
59
+
60
+
61
+ @register_encoder(
62
+ "mettle-1.5",
63
+ output_variants={
64
+ "cls": {"encode_dim": _OUTPUT_DIMS["cls"]},
65
+ "cls_patch_mean": {"encode_dim": _OUTPUT_DIMS["cls_patch_mean"]},
66
+ },
67
+ default_output_variant="cls",
68
+ input_size=224,
69
+ supports_variable_input_size=True,
70
+ variable_input_model_kwargs={"dynamic_img_size": True},
71
+ patch_size=14,
72
+ supported_spacing_um=0.5, # not stated on the card; inherited from H-optimus-0
73
+ precision="fp16",
74
+ source=_HF_REPO_ID,
75
+ )
76
+ class Mettle15(Mettle):
77
+ # Mettle.__init__ hardcodes the Mettle-1 repo, so this one calls
78
+ # TimmTileEncoder.__init__ directly. mettle.py stays unchanged: its file hash is
79
+ # part of the feature identity of every cached mettle feature.
80
+ def __init__(
81
+ self,
82
+ *,
83
+ output_variant: str | None = None,
84
+ dynamic_img_size: bool | None = None,
85
+ allow_non_recommended_settings: bool = False,
86
+ ):
87
+ self._output_variant = resolve_requested_output_variant(
88
+ output_variant,
89
+ default="cls",
90
+ allowed=tuple(_OUTPUT_DIMS),
91
+ )
92
+ super(Mettle, self).__init__(
93
+ "vit_giant_patch14_reg4_dinov2",
94
+ output_variant="default",
95
+ pretrained=False,
96
+ init_values=1e-5,
97
+ img_size=224,
98
+ dynamic_img_size=resolve_recommended_dynamic_img_size(
99
+ requested=dynamic_img_size,
100
+ recommended=False,
101
+ allow_non_recommended=allow_non_recommended_settings,
102
+ encoder_name="mettle-1.5",
103
+ ),
104
+ )
105
+ self._head = MettleRefineHead(
106
+ dim=_EMBED_DIM,
107
+ num_atoms=_HEAD_NUM_ATOMS,
108
+ rank=_HEAD_RANK,
109
+ hidden_size=_HEAD_HIDDEN_SIZE,
110
+ )
111
+ state_dict = load_file(
112
+ hf_hub_download(repo_id=_HF_REPO_ID, filename=_HF_CHECKPOINT, revision=_HF_REVISION)
113
+ )
114
+ unexpected = [key for key in state_dict if not key.startswith(("backbone.", "head."))]
115
+ if unexpected:
116
+ raise ValueError(f"Unexpected keys in Mettle-1.5 checkpoint: {unexpected}")
117
+ self._model.load_state_dict(_strip_prefix(state_dict, "backbone."), strict=True)
118
+ self._head.load_state_dict(_strip_prefix(state_dict, "head."), strict=True)
119
+ self._model.eval()
120
+ self._head.eval()
121
+
122
+ def get_transform(self) -> Callable:
123
+ # Upstream's ViTImageProcessor resizes directly to 224x224, with no crop.
124
+ return v2.Compose([
125
+ v2.ToImage(),
126
+ v2.Resize((224, 224), interpolation=v2.InterpolationMode.BICUBIC, antialias=True),
127
+ v2.ToDtype(torch.float32, scale=True),
128
+ v2.Normalize(mean=_HOPTIMUS_MEAN, std=_HOPTIMUS_STD),
129
+ ])
@@ -2,12 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import dataclass
5
+ import math
6
+ from dataclasses import dataclass, fields
6
7
  from pathlib import Path
7
8
  from typing import Any
8
9
 
9
10
  from slide2vec.encoders.code import encoder_code
10
- from slide2vec.encoders.registry import resolve_encoder_output
11
+ from slide2vec.encoders.registry import resolve_encoder_output, resolve_preprocessing_defaults
12
+ from slide2vec.encoders.validation import validate_encoder_config
11
13
  from slide2vec.runtime.model_settings import output_dtype_name, resolve_output_precision
12
14
 
13
15
 
@@ -134,6 +136,20 @@ class DenseImageRecipe:
134
136
  payload["transform"] = self.transform
135
137
  return payload
136
138
 
139
+ def request_identity(self) -> dict[str, Any]:
140
+ """The request-wide fields of :meth:`for_image`: the recipe without per-image fields.
141
+
142
+ The per-image fields are the read plan's, which :meth:`for_image` replaces, and
143
+ the read sizes.
144
+ """
145
+ from slide2vec.runtime.dense_image_reading import DenseImageReadPlan
146
+
147
+ per_image = {field.name for field in fields(DenseImageReadPlan)} | {
148
+ "read_tile_size_px",
149
+ "requested_tile_size_px",
150
+ }
151
+ return {key: value for key, value in self.to_dict().items() if key not in per_image}
152
+
137
153
  def for_image(self, spec, read_plan=None) -> dict[str, Any]:
138
154
  """Add the normalized source identity to this request-wide recipe."""
139
155
  if read_plan is None:
@@ -225,6 +241,38 @@ class DenseImageRecipe:
225
241
  )
226
242
 
227
243
 
244
+ def resolve_dense_image_spacing(model, dense) -> tuple[float, str]:
245
+ """Resolve and validate the run spacing of *dense*: ``(spacing_um, spacing_source)``.
246
+
247
+ An omitted spacing resolves the encoder's single registry default.
248
+ """
249
+ spacing_source = "explicit"
250
+ if dense.spacing_um is None:
251
+ try:
252
+ spacing_um = float(
253
+ resolve_preprocessing_defaults(model.name)["spacing_um"]
254
+ )
255
+ except (KeyError, ValueError) as exc:
256
+ raise ValueError(
257
+ "DenseImageOptions.spacing_um must be explicit when encoder "
258
+ f"{model.name!r} has no single resolvable model default: {exc}"
259
+ ) from exc
260
+ spacing_source = "model_default"
261
+ else:
262
+ spacing_um = float(dense.spacing_um)
263
+ if not math.isfinite(spacing_um) or spacing_um <= 0:
264
+ raise ValueError(
265
+ "DenseImageOptions.spacing_um must be a positive, finite value or None."
266
+ )
267
+ validate_encoder_config(
268
+ model.name,
269
+ requested_spacing_um=spacing_um,
270
+ allow_non_recommended=bool(model.allow_non_recommended_settings),
271
+ require_known_spacing=True,
272
+ )
273
+ return spacing_um, spacing_source
274
+
275
+
228
276
  def resolve_dense_image_recipe(
229
277
  *,
230
278
  model,
@@ -29,7 +29,6 @@ from __future__ import annotations
29
29
 
30
30
  import json
31
31
  import logging
32
- import math
33
32
  from dataclasses import replace
34
33
  from pathlib import Path
35
34
  from subprocess import Popen
@@ -37,8 +36,6 @@ from typing import Callable, Sequence
37
36
 
38
37
  from slide2vec.api import DenseImageOptions, ImageSpec
39
38
  from slide2vec.artifacts import DenseImageArtifact
40
- from slide2vec.encoders.registry import resolve_preprocessing_defaults
41
- from slide2vec.encoders.validation import validate_encoder_config
42
39
  from slide2vec.progress import emit_progress
43
40
  from slide2vec.runtime.dense_image_shard import (
44
41
  dense_image_artifact_from_disk,
@@ -48,6 +45,7 @@ from slide2vec.runtime.dense_image_shard import (
48
45
  from slide2vec.runtime.dense_image_recipe import (
49
46
  DenseImageRecipe,
50
47
  resolve_dense_image_recipe,
48
+ resolve_dense_image_spacing,
51
49
  )
52
50
  from slide2vec.runtime.dense_image_reading import DenseImageReadPlan, resolve_spacing_read_plan
53
51
  from slide2vec.runtime.dense_stage import resolve_output_torch_dtype
@@ -115,31 +113,8 @@ def embed_images_dense(
115
113
  artifact_location="dense_image_embeddings/<sample_id>",
116
114
  )
117
115
  reader_regime, probed_auto_backends = validate_dense_image_reader_regime(specs)
118
- spacing_source = "explicit"
119
- if dense.spacing_um is None:
120
- try:
121
- spacing_um = float(
122
- resolve_preprocessing_defaults(model.name)["spacing_um"]
123
- )
124
- except (KeyError, ValueError) as exc:
125
- raise ValueError(
126
- "DenseImageOptions.spacing_um must be explicit when encoder "
127
- f"{model.name!r} has no single resolvable model default: {exc}"
128
- ) from exc
129
- spacing_source = "model_default"
130
- dense = replace(dense, spacing_um=spacing_um)
131
- else:
132
- spacing_um = float(dense.spacing_um)
133
- if spacing_um is not None and (not math.isfinite(spacing_um) or spacing_um <= 0):
134
- raise ValueError(
135
- "DenseImageOptions.spacing_um must be a positive, finite value or None."
136
- )
137
- validate_encoder_config(
138
- model.name,
139
- requested_spacing_um=spacing_um,
140
- allow_non_recommended=bool(model.allow_non_recommended_settings),
141
- require_known_spacing=True,
142
- )
116
+ spacing_um, spacing_source = resolve_dense_image_spacing(model, dense)
117
+ dense = replace(dense, spacing_um=spacing_um)
143
118
  # Declare the effective encoder input before anything is decoded, sharded or launched: an
144
119
  # image geometry this encoder cannot accept must fail here, not on the first forward pass
145
120
  # of a torchrun rank. Idempotent, so every rank re-declares for itself (dense_image_worker).
@@ -225,6 +225,69 @@ def pooled_identity_differences(
225
225
  )
226
226
 
227
227
 
228
+ def dense_identity_differences(
229
+ model,
230
+ recorded: dict[str, Any],
231
+ *,
232
+ dense,
233
+ execution,
234
+ ) -> dict[str, tuple[Any, Any]]:
235
+ """Compare a recorded dense identity with the request-wide identity of the current request.
236
+
237
+ A :class:`~slide2vec.api.DenseImageOptions` request is compared with the image recipe,
238
+ any other with the region identity. The request is validated as the embed methods
239
+ validate it, on an isolated CPU copy of *model* that declares the dense encoder-input
240
+ contract, so *model* keeps its contract and backend. The transform is that copy's
241
+ ``get_normalization_transform()``, as the shards record it.
242
+ """
243
+ from slide2vec.api import DenseImageOptions, Model
244
+
245
+ source = Model.from_preset(
246
+ model.name,
247
+ device="cpu",
248
+ output_variant=model._output_variant,
249
+ allow_non_recommended_settings=model.allow_non_recommended_settings,
250
+ )
251
+ if isinstance(dense, DenseImageOptions):
252
+ from slide2vec.runtime.dense_image_recipe import (
253
+ resolve_dense_image_recipe,
254
+ resolve_dense_image_spacing,
255
+ )
256
+
257
+ resolve_dense_image_spacing(source, dense) # validates; the spacing is per-sample
258
+ contract = source._declare_dense_encoder_input(dense, emit_run_info=False)
259
+ requested = resolve_dense_image_recipe(
260
+ model=source, contract=contract, dense=dense, execution=execution
261
+ ).request_identity()
262
+ else:
263
+ from slide2vec.encoders.registry import resolve_patch_size
264
+ from slide2vec.runtime.dense_regions import (
265
+ compute_dense_geometry,
266
+ validate_dense_request_settings,
267
+ )
268
+
269
+ plan = source._declare_dense_encoder_input(dense, emit_run_info=False).plan
270
+ # The region shards validate the same settings when they encode.
271
+ validate_dense_request_settings(
272
+ compute_dense_geometry(
273
+ target_size=plan.target_size_px,
274
+ patch_size=resolve_patch_size(plan.tile_encoder_name),
275
+ ),
276
+ pad_mode=dense.pad_mode,
277
+ window_size=dense.window_size,
278
+ overlap=dense.overlap,
279
+ feature_kind=dense.feature_kind,
280
+ attention_blocks=dense.attention_blocks,
281
+ attention_include_registers=dense.attention_include_registers,
282
+ )
283
+ requested = dense_feature_identity(source, dense=dense, execution=execution)
284
+
285
+ def resolve_transform() -> dict[str, Any]:
286
+ return transform_record(source._load_backend().model.get_normalization_transform())
287
+
288
+ return differing_fields(recorded, requested, resolve_transform=resolve_transform)
289
+
290
+
228
291
  class PooledResumeCheck:
229
292
  """Compare the completed pooled artifacts of one resume with the run's feature identity.
230
293
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 7.1.3
3
+ Version: 7.1.5
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
@@ -49,6 +49,7 @@ slide2vec/encoders/models/hoptimus.py
49
49
  slide2vec/encoders/models/isight.py
50
50
  slide2vec/encoders/models/lunit.py
51
51
  slide2vec/encoders/models/mettle.py
52
+ slide2vec/encoders/models/mettle_1_5.py
52
53
  slide2vec/encoders/models/midnight.py
53
54
  slide2vec/encoders/models/mstar.py
54
55
  slide2vec/encoders/models/musk.py
@@ -123,6 +124,7 @@ tests/test_cufile_compat_mode.py
123
124
  tests/test_dense_encode_kit.py
124
125
  tests/test_dense_encoder_input.py
125
126
  tests/test_dense_extraction.py
127
+ tests/test_dense_identity_api.py
126
128
  tests/test_dense_image_reading.py
127
129
  tests/test_dense_image_resume.py
128
130
  tests/test_dense_image_shard.py