slide2vec 7.1.4__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.4 → slide2vec-7.1.5}/PKG-INFO +1 -1
  2. {slide2vec-7.1.4 → slide2vec-7.1.5}/pyproject.toml +2 -2
  3. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/__init__.py +1 -1
  4. slide2vec-7.1.5/slide2vec/encoders/models/mettle_1_5.py +129 -0
  5. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/PKG-INFO +1 -1
  6. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/SOURCES.txt +1 -0
  7. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_plugins.py +1 -1
  8. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_preprocessing.py +31 -7
  9. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_mettle.py +98 -40
  10. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_patch_size_metadata.py +1 -0
  11. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_regression_core.py +1 -0
  12. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_regression_models.py +1 -0
  13. {slide2vec-7.1.4 → slide2vec-7.1.5}/LICENSE +0 -0
  14. {slide2vec-7.1.4 → slide2vec-7.1.5}/README.md +0 -0
  15. {slide2vec-7.1.4 → slide2vec-7.1.5}/setup.cfg +0 -0
  16. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/__main__.py +0 -0
  17. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/_cufile.py +0 -0
  18. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/api.py +0 -0
  19. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/artifacts.py +0 -0
  20. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/cli.py +0 -0
  21. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/configs/__init__.py +0 -0
  22. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/configs/default.yaml +0 -0
  23. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/configs/resources.py +0 -0
  24. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/cufile.json +0 -0
  25. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/data/__init__.py +0 -0
  26. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/data/dataset.py +0 -0
  27. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/data/tile_reader.py +0 -0
  28. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/data/tile_store.py +0 -0
  29. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/__init__.py +0 -0
  30. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/dense_image_worker.py +0 -0
  31. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/dense_worker.py +0 -0
  32. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/direct_embed_worker.py +0 -0
  33. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/image_worker.py +0 -0
  34. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/pin_gpu.py +0 -0
  35. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/pipeline_worker.py +0 -0
  36. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/distributed/worker_entry.py +0 -0
  37. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/__init__.py +0 -0
  38. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/base.py +0 -0
  39. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/code.py +0 -0
  40. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/__init__.py +0 -0
  41. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/conch.py +0 -0
  42. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov2.py +0 -0
  43. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov3.py +0 -0
  44. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/genbio.py +0 -0
  45. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/gigapath.py +0 -0
  46. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/gpfm.py +0 -0
  47. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/hibou.py +0 -0
  48. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/hoptimus.py +0 -0
  49. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/isight.py +0 -0
  50. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/lunit.py +0 -0
  51. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/mettle.py +0 -0
  52. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/midnight.py +0 -0
  53. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  54. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  55. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/case.py +0 -0
  56. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/loading.py +0 -0
  57. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/slide.py +0 -0
  58. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/types.py +0 -0
  59. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/mstar.py +0 -0
  60. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/musk.py +0 -0
  61. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/phikon.py +0 -0
  62. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/prism.py +0 -0
  63. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/prism2.py +0 -0
  64. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/prost40m.py +0 -0
  65. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/rudolfv2.py +0 -0
  66. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/titan.py +0 -0
  67. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/uni.py +0 -0
  68. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/virchow.py +0 -0
  69. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/models/waiv.py +0 -0
  70. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/registry.py +0 -0
  71. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/encoders/validation.py +0 -0
  72. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/inference.py +0 -0
  73. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/progress.py +0 -0
  74. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/__init__.py +0 -0
  75. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/artifacts_collect.py +0 -0
  76. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/batching.py +0 -0
  77. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/cpu_budget.py +0 -0
  78. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_encode.py +0 -0
  79. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_encoder_input.py +0 -0
  80. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_reading.py +0 -0
  81. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_recipe.py +0 -0
  82. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_shard.py +0 -0
  83. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_stage.py +0 -0
  84. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_regions.py +0 -0
  85. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_shard.py +0 -0
  86. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_sliding.py +0 -0
  87. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/dense_stage.py +0 -0
  88. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/distributed.py +0 -0
  89. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/distributed_stage.py +0 -0
  90. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/effective_encoder_input.py +0 -0
  91. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/embedding.py +0 -0
  92. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/embedding_persist.py +0 -0
  93. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/embedding_pipeline.py +0 -0
  94. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/encoder_input_contract.py +0 -0
  95. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/feature_identity.py +0 -0
  96. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/hierarchical.py +0 -0
  97. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/image_shard.py +0 -0
  98. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/image_specs.py +0 -0
  99. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/image_stage.py +0 -0
  100. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/manifest.py +0 -0
  101. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/model_settings.py +0 -0
  102. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/patient_pipeline.py +0 -0
  103. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/persist_callbacks.py +0 -0
  104. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/persistence.py +0 -0
  105. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  106. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/preprocessing.py +0 -0
  107. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/process_list.py +0 -0
  108. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/progress_bridge.py +0 -0
  109. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/registry.py +0 -0
  110. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/serialization.py +0 -0
  111. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/sharding.py +0 -0
  112. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/slide_encode.py +0 -0
  113. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/tiling.py +0 -0
  114. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/tiling_pipeline.py +0 -0
  115. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/types.py +0 -0
  116. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/runtime/worker_io.py +0 -0
  117. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/__init__.py +0 -0
  118. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/config.py +0 -0
  119. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/coordinates.py +0 -0
  120. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/log_utils.py +0 -0
  121. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/tiling_io.py +0 -0
  122. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec/utils/utils.py +0 -0
  123. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/dependency_links.txt +0 -0
  124. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/entry_points.txt +0 -0
  125. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/not-zip-safe +0 -0
  126. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/requires.txt +0 -0
  127. {slide2vec-7.1.4 → slide2vec-7.1.5}/slide2vec.egg-info/top_level.txt +0 -0
  128. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_attention_extraction.py +0 -0
  129. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_benchmark_tooling.py +0 -0
  130. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_cli_noninteractive.py +0 -0
  131. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_cufile_compat_mode.py +0 -0
  132. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_encode_kit.py +0 -0
  133. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_encoder_input.py +0 -0
  134. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_extraction.py +0 -0
  135. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_identity_api.py +0 -0
  136. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_image_reading.py +0 -0
  137. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_image_resume.py +0 -0
  138. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_image_shard.py +0 -0
  139. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_image_stage.py +0 -0
  140. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_regions.py +0 -0
  141. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_shard.py +0 -0
  142. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_sliding.py +0 -0
  143. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_source_spacing.py +0 -0
  144. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_stage.py +0 -0
  145. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dense_worker.py +0 -0
  146. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dinov2_natimage.py +0 -0
  147. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_dinov3_natimage.py +0 -0
  148. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_drop_in_encoders.py +0 -0
  149. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_capabilities.py +0 -0
  150. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_code.py +0 -0
  151. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_input_contract.py +0 -0
  152. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_provider_failures.py +0 -0
  153. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_encoder_registry.py +0 -0
  154. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_execution_options.py +0 -0
  155. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_feature_identity.py +0 -0
  156. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_gigapath.py +0 -0
  157. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_gpfm_genbio_heavy.py +0 -0
  158. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_hierarchical_encoding.py +0 -0
  159. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_hs2p5_integration.py +0 -0
  160. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_hs2p_package_cutover.py +0 -0
  161. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_image_shard.py +0 -0
  162. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_image_stage.py +0 -0
  163. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_inference_benchmark.py +0 -0
  164. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_isight.py +0 -0
  165. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_load_model_hf_auth.py +0 -0
  166. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_mascaret.py +0 -0
  167. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_on_slide_persisted.py +0 -0
  168. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_output_consistency.py +0 -0
  169. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_patient_annotation_sampling.py +0 -0
  170. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_patient_manifest.py +0 -0
  171. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_patient_tiling_only.py +0 -0
  172. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_phaet.py +0 -0
  173. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_pooled_encoder_input.py +0 -0
  174. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_pooled_geometry.py +0 -0
  175. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_pooled_identity_api.py +0 -0
  176. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_prism.py +0 -0
  177. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_prism2.py +0 -0
  178. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_process_list_performance.py +0 -0
  179. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_progress.py +0 -0
  180. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_regression_inference.py +0 -0
  181. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_rudolfv2.py +0 -0
  182. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_runtime_batching.py +0 -0
  183. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_sharding.py +0 -0
  184. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_slide_coordinate_preparation.py +0 -0
  185. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_slide_latents.py +0 -0
  186. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_soma_migration.py +0 -0
  187. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_tile_store.py +0 -0
  188. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_tiling_pipeline.py +0 -0
  189. {slide2vec-7.1.4 → slide2vec-7.1.5}/tests/test_titan.py +0 -0
  190. {slide2vec-7.1.4 → 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.4
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.4"
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.4"
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.4"
39
+ __version__ = "7.1.5"
40
40
 
41
41
  __all__ = [
42
42
  "Model",
@@ -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
+ ])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 7.1.4
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
@@ -179,7 +179,7 @@ with ThreadPoolExecutor(max_workers=8) as executor:
179
179
  expected_models = listings[0]
180
180
  assert listings == [expected_models] * 8
181
181
  assert expected_models == sorted(expected_models)
182
- assert {"private-alpha", "private-beta", "virchow2", "mettle"} <= set(expected_models)
182
+ assert {"private-alpha", "private-beta", "virchow2", "mettle", "mettle-1.5"} <= set(expected_models)
183
183
  assert discovered.read_text() == "discovered"
184
184
  assert not constructed.exists()
185
185
 
@@ -353,32 +353,36 @@ def test_dinov3_public_pooled_recipe_reaches_encoding(
353
353
  torch.testing.assert_close(observed[1], observed[0]) # batched == itemwise
354
354
 
355
355
 
356
- # bioptimus/H-optimus-0 mean/std, which slideflow-labs/Mettle's preprocessor_config.json repeats.
356
+ # bioptimus/H-optimus-0 mean/std, which the preprocessor_config.json of slideflow-labs/Mettle
357
+ # and slideflow-labs/mettle-1.5 repeats.
357
358
  HOPTIMUS_RED = [1.381786, -2.514934, -3.96366]
358
359
  HOPTIMUS_BLACK = [-3.3378, -2.514934, -3.96366]
360
+ METTLE_PRESETS = ["mettle", "mettle-1.5"]
359
361
 
360
362
 
363
+ @pytest.mark.parametrize("name", METTLE_PRESETS)
361
364
  @pytest.mark.parametrize("regime", ["pooled", "dense"])
362
- def test_mettle_declared_normalization_uses_hoptimus_photometrics_not_imagenet(regime):
365
+ def test_mettle_declared_normalization_uses_hoptimus_photometrics_not_imagenet(regime, name):
363
366
  """Mettle's generic timm arch carries an ImageNet pretrained_cfg; its card uses H-optimus mean/std."""
364
367
  from slide2vec.runtime.encoder_input_contract import EncoderInputContract
365
368
 
366
369
  if regime == "pooled":
367
370
  contract = EncoderInputContract.declared_pooled(
368
- "mettle", requested_tile_size_px=224, allow_non_recommended_settings=False,
371
+ name, requested_tile_size_px=224, allow_non_recommended_settings=False,
369
372
  )
370
373
  else:
371
- contract = EncoderInputContract.declared_dense("mettle", target_size_px=224, window_size=None)
372
- output = contract.get_transform(recipe_encoder("mettle"))(bordered_image(224))
374
+ contract = EncoderInputContract.declared_dense(name, target_size_px=224, window_size=None)
375
+ output = contract.get_transform(recipe_encoder(name))(bordered_image(224))
373
376
 
374
377
  assert_border_intact(output, size=224, red=HOPTIMUS_RED, black=HOPTIMUS_BLACK)
375
378
 
376
379
 
377
- def test_mettle_given_recipe_is_a_bicubic_224_resize_without_enlargement():
380
+ @pytest.mark.parametrize("name", METTLE_PRESETS)
381
+ def test_mettle_given_recipe_is_a_bicubic_224_resize_without_enlargement(name):
378
382
  from torchvision.transforms import v2
379
383
  from slide2vec.runtime.encoder_input_contract import EncoderInputContract
380
384
 
381
- transform = EncoderInputContract.given().get_transform(recipe_encoder("mettle"))
385
+ transform = EncoderInputContract.given().get_transform(recipe_encoder(name))
382
386
 
383
387
  assert_border_intact(transform(bordered_image(224)), size=224, red=HOPTIMUS_RED, black=HOPTIMUS_BLACK)
384
388
  downsized = transform(Image.new("RGB", (448, 448), (255, 0, 0)))
@@ -386,3 +390,23 @@ def test_mettle_given_recipe_is_a_bicubic_224_resize_without_enlargement():
386
390
  torch.testing.assert_close(downsized[:, 112, 112], torch.tensor(HOPTIMUS_RED))
387
391
  resize = next(step for step in transform.transforms if isinstance(step, v2.Resize))
388
392
  assert resize.interpolation == v2.InterpolationMode.BICUBIC
393
+
394
+
395
+ HOPTIMUS_BLUE = [-3.3378, -2.514934, 1.669603]
396
+
397
+
398
+ def test_mettle_1_5_given_recipe_resizes_rectangular_images_directly_without_a_crop():
399
+ """slideflow-labs/mettle-1.5's ViTImageProcessor resizes to 224x224 with do_center_crop false."""
400
+ from slide2vec.runtime.encoder_input_contract import EncoderInputContract
401
+
402
+ transform = EncoderInputContract.given().get_transform(recipe_encoder("mettle-1.5"))
403
+ image = Image.new("RGB", (448, 224), (0, 0, 0))
404
+ image.paste((255, 0, 0), (0, 0, 112, 224))
405
+ image.paste((0, 0, 255), (336, 0, 448, 224))
406
+ output = transform(image)
407
+
408
+ assert tuple(output.shape) == (3, 224, 224)
409
+ # A shortest-side resize plus a center crop would drop both edge quarters.
410
+ torch.testing.assert_close(output[:, 112, 28], torch.tensor(HOPTIMUS_RED))
411
+ torch.testing.assert_close(output[:, 112, 112], torch.tensor(HOPTIMUS_BLACK))
412
+ torch.testing.assert_close(output[:, 112, 196], torch.tensor(HOPTIMUS_BLUE))
@@ -1,10 +1,12 @@
1
- """Tests for the Mettle tile encoder.
1
+ """Tests for the Mettle tile encoders (``mettle`` and ``mettle-1.5``).
2
2
 
3
3
  Mettle (``slideflow-labs/Mettle``) is H-optimus-0's ViT-g/14 reg4 backbone,
4
- fine-tuned, plus a small ``MettleRefineHead`` applied to the CLS token. It ships
5
- as a ``trust_remote_code`` HF repo; slide2vec rebuilds it from timm + a vendored
6
- head, so these tests pin the contracts that break silently (no exception, just
7
- different features):
4
+ fine-tuned, plus a small ``MettleRefineHead`` applied to the CLS token.
5
+ Mettle-1.5 (``slideflow-labs/mettle-1.5``) differs in its weights, its default
6
+ output (``cls``) and its given recipe (a direct 224x224 resize). Both ship as
7
+ ``trust_remote_code`` HF repos; slide2vec rebuilds them from timm + a vendored
8
+ head, so these tests pin, for both presets, the contracts that break silently
9
+ (no exception, just different features):
8
10
 
9
11
  * ``cls`` is the *headed* CLS token; ``cls_patch_mean`` appends the *unheaded*
10
12
  mean of the spatial patch tokens, with CLS and the 4 register tokens excluded;
@@ -25,11 +27,37 @@ import torch
25
27
 
26
28
  _IMAGE = 28 # -> 2x2 patch grid, 1 CLS + 4 registers + 4 patches
27
29
  _DIM = 384
30
+ _PRESETS = ["mettle", "mettle-1.5"]
28
31
 
29
32
 
30
- def _tiny_encoder(*, output_variant: str = "cls_patch_mean"):
31
- """A tiny random reg4 ViT + refine head wired into Mettle (no download)."""
32
- from slide2vec.encoders.models.mettle import Mettle, MettleRefineHead
33
+ @pytest.mark.parametrize("name, source, default_output_variant", [
34
+ ("mettle", "slideflow-labs/Mettle", "cls_patch_mean"),
35
+ ("mettle-1.5", "slideflow-labs/mettle-1.5", "cls"),
36
+ ])
37
+ def test_registry_declares_the_mettle_presets(name, source, default_output_variant):
38
+ from slide2vec.encoders import encoder_registry
39
+
40
+ info = encoder_registry.info(name)
41
+
42
+ assert info["default_output_variant"] == default_output_variant
43
+ assert info["output_variants"] == {
44
+ "cls": {"encode_dim": 1536},
45
+ "cls_patch_mean": {"encode_dim": 3072},
46
+ }
47
+ assert info["source"] == source
48
+ assert info["level"] == "tile"
49
+ assert info["input_size"] == 224
50
+ assert info["patch_size"] == 14
51
+ assert info["supports_variable_input_size"] is True
52
+ assert info["variable_input_model_kwargs"] == {"dynamic_img_size": True}
53
+ assert info["supported_spacing_um"] == 0.5
54
+ assert info["precision"] == "fp16"
55
+
56
+
57
+ def _tiny_encoder(name: str, *, output_variant: str = "cls_patch_mean"):
58
+ """A tiny random reg4 ViT + refine head wired into preset *name* (no download)."""
59
+ from slide2vec.encoders import encoder_registry
60
+ from slide2vec.encoders.models.mettle import MettleRefineHead
33
61
 
34
62
  torch.manual_seed(0)
35
63
  backbone = timm.create_model(
@@ -44,7 +72,8 @@ def _tiny_encoder(*, output_variant: str = "cls_patch_mean"):
44
72
  head.P.normal_()
45
73
  head.Q.normal_()
46
74
 
47
- encoder = Mettle.__new__(Mettle)
75
+ encoder_cls = encoder_registry.require(name)
76
+ encoder = encoder_cls.__new__(encoder_cls)
48
77
  encoder._model = backbone
49
78
  encoder._head = head
50
79
  encoder._device = torch.device("cpu")
@@ -52,8 +81,9 @@ def _tiny_encoder(*, output_variant: str = "cls_patch_mean"):
52
81
  return encoder
53
82
 
54
83
 
55
- def test_cls_patch_mean_is_headed_cls_then_unheaded_spatial_patch_mean():
56
- encoder = _tiny_encoder()
84
+ @pytest.mark.parametrize("name", _PRESETS)
85
+ def test_cls_patch_mean_is_headed_cls_then_unheaded_spatial_patch_mean(name):
86
+ encoder = _tiny_encoder(name)
57
87
  batch = torch.randn(2, 3, _IMAGE, _IMAGE)
58
88
  with torch.inference_mode():
59
89
  tokens = encoder._model.forward_features(batch)
@@ -67,24 +97,26 @@ def test_cls_patch_mean_is_headed_cls_then_unheaded_spatial_patch_mean():
67
97
  assert not torch.allclose(pooled[:, :_DIM], tokens[:, 0], atol=1e-3)
68
98
 
69
99
 
70
- def test_cls_variant_is_the_headed_cls_prefix_of_cls_patch_mean():
100
+ @pytest.mark.parametrize("name", _PRESETS)
101
+ def test_cls_variant_is_the_headed_cls_prefix_of_cls_patch_mean(name):
71
102
  batch = torch.randn(2, 3, _IMAGE, _IMAGE)
72
103
  with torch.inference_mode():
73
- cls = _tiny_encoder(output_variant="cls").encode_tiles(batch)
74
- cls_patch_mean = _tiny_encoder(output_variant="cls_patch_mean").encode_tiles(batch)
104
+ cls = _tiny_encoder(name, output_variant="cls").encode_tiles(batch)
105
+ cls_patch_mean = _tiny_encoder(name, output_variant="cls_patch_mean").encode_tiles(batch)
75
106
 
76
107
  assert cls.shape == (2, _DIM)
77
108
  torch.testing.assert_close(cls, cls_patch_mean[:, :_DIM], rtol=0, atol=0)
78
109
 
79
110
 
111
+ @pytest.mark.parametrize("name", _PRESETS)
80
112
  @pytest.mark.parametrize("output_variant", ["cls", "cls_patch_mean"])
81
- def test_head_and_patch_mean_run_in_fp32_outside_autocast(output_variant):
113
+ def test_head_and_patch_mean_run_in_fp32_outside_autocast(output_variant, name):
82
114
  """Under low-precision autocast only the backbone is cast; features come back fp32.
83
115
 
84
116
  The expected value applies the head in plain fp32 to the autocast backbone's
85
117
  CLS token; a head run under autocast would drift by the bf16 rounding error.
86
118
  """
87
- encoder = _tiny_encoder(output_variant=output_variant)
119
+ encoder = _tiny_encoder(name, output_variant=output_variant)
88
120
  batch = torch.randn(2, 3, _IMAGE, _IMAGE)
89
121
  with torch.inference_mode():
90
122
  with torch.autocast(device_type="cpu", dtype=torch.bfloat16):
@@ -100,47 +132,70 @@ def test_head_and_patch_mean_run_in_fp32_outside_autocast(output_variant):
100
132
 
101
133
  # --- Heavy: real weights -----------------------------------------------------
102
134
 
135
+ # Hub repo and revision per preset (``None``: the repo's default branch).
136
+ _HUB = {
137
+ "mettle": ("slideflow-labs/Mettle", None),
138
+ "mettle-1.5": ("slideflow-labs/mettle-1.5", "0ab55fcbf1b8d4e3c73885fc56f822fb568ff570"),
139
+ }
140
+ _DEFAULT_DIMS = {"mettle": 3072, "mettle-1.5": 1536}
141
+
103
142
 
104
- def _require_hub_files(*filenames: str) -> None:
143
+ def _require_hub_files(repo_id: str, revision: str | None, *filenames: str) -> None:
105
144
  """Skip when the weights / network are unavailable; fetches into the HF cache."""
106
145
  from huggingface_hub import hf_hub_download
107
146
 
108
147
  for filename in filenames:
109
148
  try:
110
- hf_hub_download(repo_id="slideflow-labs/Mettle", filename=filename)
149
+ hf_hub_download(repo_id=repo_id, filename=filename, revision=revision)
111
150
  except Exception as exc: # network / weights unavailable
112
- pytest.skip(f"mettle {filename} unavailable: {type(exc).__name__}: {exc}")
151
+ pytest.skip(f"{repo_id} {filename} unavailable: {type(exc).__name__}: {exc}")
152
+
153
+
154
+ @pytest.fixture(scope="module", params=_PRESETS)
155
+ def preset(request) -> str:
156
+ """The preset under test.
157
+
158
+ The real-weight fixtures below depend on it, so pytest tears down one preset's
159
+ models before it builds the next: two presets' ViT-g weights are never loaded
160
+ at the same time.
161
+ """
162
+ return request.param
113
163
 
114
164
 
115
165
  @pytest.fixture(scope="module")
116
- def mettle_encoders():
166
+ def mettle_encoders(preset):
117
167
  """Load the real encoder once per output variant (on CPU).
118
168
 
119
- Only the download may skip: a strict-load mismatch in the constructor must fail.
169
+ ``load()`` builds the registry default. Only the download may skip: a
170
+ strict-load mismatch in the constructor must fail.
120
171
  """
121
172
  from slide2vec.encoders import encoder_registry
122
173
 
123
- _require_hub_files("model.safetensors")
174
+ _require_hub_files(*_HUB[preset], "model.safetensors")
175
+ default = encoder_registry.info(preset)["default_output_variant"]
124
176
  loaded = {}
125
177
 
126
- def load(output_variant: str = "cls_patch_mean"):
127
- if output_variant not in loaded:
128
- encoder = encoder_registry.require("mettle")(output_variant=output_variant)
129
- loaded[output_variant] = encoder.to("cpu")
130
- return loaded[output_variant]
178
+ def load(output_variant: str | None = None):
179
+ key = output_variant or default
180
+ if key not in loaded:
181
+ encoder = encoder_registry.require(preset)(output_variant=output_variant)
182
+ loaded[key] = encoder.to("cpu")
183
+ return loaded[key]
131
184
 
132
185
  return load
133
186
 
134
187
 
135
188
  @pytest.fixture(scope="module")
136
- def upstream_mettle():
189
+ def upstream_mettle(preset):
137
190
  """Upstream ``AutoModel(trust_remote_code=True)`` reference, fp32 on CPU."""
138
191
  transformers = pytest.importorskip("transformers")
192
+ repo_id, revision = _HUB[preset]
139
193
  _require_hub_files(
194
+ repo_id, revision,
140
195
  "config.json", "configuration_mettle.py", "modeling_mettle.py", "model.safetensors",
141
196
  )
142
197
  model = transformers.AutoModel.from_pretrained(
143
- "slideflow-labs/Mettle", trust_remote_code=True,
198
+ repo_id, revision=revision, trust_remote_code=True,
144
199
  )
145
200
  return model.to("cpu").eval()
146
201
 
@@ -151,19 +206,22 @@ def _tile_batch() -> torch.Tensor:
151
206
 
152
207
 
153
208
  @pytest.mark.heavy
154
- def test_mettle_loads_strict_and_emits_registered_shapes(mettle_encoders):
155
- mettle_encoder = mettle_encoders() # default variant: cls_patch_mean
209
+ def test_mettle_loads_strict_and_emits_registered_shapes(preset, mettle_encoders):
210
+ default_encoder = mettle_encoders()
156
211
  cls_encoder = mettle_encoders("cls")
212
+ cls_patch_mean_encoder = mettle_encoders("cls_patch_mean")
157
213
  batch = _tile_batch()
158
214
  with torch.inference_mode():
159
- cls_patch_mean = mettle_encoder.encode_tiles(batch)
215
+ default = default_encoder.encode_tiles(batch)
160
216
  cls = cls_encoder.encode_tiles(batch)
161
- dense = mettle_encoder.encode_tiles_dense(batch)
162
-
163
- assert mettle_encoder.encode_dim == 3072 and cls_encoder.encode_dim == 1536
164
- assert mettle_encoder.patch_size == (14, 14)
165
- assert cls_patch_mean.shape == (1, 3072)
166
- assert cls.shape == (1, 1536)
217
+ cls_patch_mean = cls_patch_mean_encoder.encode_tiles(batch)
218
+ dense = default_encoder.encode_tiles_dense(batch)
219
+
220
+ assert default_encoder.encode_dim == _DEFAULT_DIMS[preset]
221
+ assert default.shape == (1, _DEFAULT_DIMS[preset])
222
+ assert cls_encoder.encode_dim == 1536 and cls.shape == (1, 1536)
223
+ assert cls_patch_mean_encoder.encode_dim == 3072 and cls_patch_mean.shape == (1, 3072)
224
+ assert default_encoder.patch_size == (14, 14)
167
225
  # patch 14: 224 / 14 = 16 -> a 16x16 grid of unheaded 1536-d patch tokens.
168
226
  assert dense.shape == (1, 1536, 16, 16)
169
227
 
@@ -184,13 +242,13 @@ def test_mettle_fp32_matches_upstream_remote_code(
184
242
 
185
243
 
186
244
  @pytest.mark.heavy
187
- def test_mettle_fp16_default_preserves_fp32_direction_on_cuda(mettle_encoders):
245
+ def test_mettle_fp16_default_preserves_fp32_direction_on_cuda(preset, mettle_encoders):
188
246
  if not torch.cuda.is_available():
189
247
  pytest.skip("CUDA unavailable")
190
248
  from slide2vec.encoders import encoder_registry
191
249
  from slide2vec.runtime.batching import autocast_dtype
192
250
 
193
- precision = encoder_registry.info("mettle")["precision"]
251
+ precision = encoder_registry.info(preset)["precision"]
194
252
  assert precision == "fp16"
195
253
  mettle_encoder = mettle_encoders()
196
254
  batch = _tile_batch()
@@ -36,6 +36,7 @@ DENSE_PATCH_SIZES: dict[str, tuple[int, int]] = {
36
36
  "h0-mini": (14, 14),
37
37
  "lunit": (8, 8),
38
38
  "mettle": (14, 14),
39
+ "mettle-1.5": (14, 14),
39
40
  "prost40m": (14, 14),
40
41
  "conch": (16, 16),
41
42
  "conchv15": (16, 16),
@@ -216,6 +216,7 @@ def test_get_cfg_from_args_fills_null_spacing_from_model_default(tmp_path: Path)
216
216
  ("virchow2", 224, "fp16"),
217
217
  ("midnight", 224, "fp16"),
218
218
  ("mettle", 224, "fp16"),
219
+ ("mettle-1.5", 224, "fp16"),
219
220
  ("musk", 384, "fp16"),
220
221
  ],
221
222
  )
@@ -260,6 +260,7 @@ def test_model_embed_slide_infers_preprocessing_from_single_spacing_model(monkey
260
260
  ("virchow2", 224, "fp16"),
261
261
  ("midnight", 224, "fp16"),
262
262
  ("mettle", 224, "fp16"),
263
+ ("mettle-1.5", 224, "fp16"),
263
264
  ("musk", 384, "fp16"),
264
265
  ],
265
266
  )
File without changes
File without changes
File without changes
File without changes
File without changes