slide2vec 6.3.1__tar.gz → 6.3.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 (182) hide show
  1. {slide2vec-6.3.1 → slide2vec-6.3.2}/PKG-INFO +1 -1
  2. {slide2vec-6.3.1 → slide2vec-6.3.2}/pyproject.toml +2 -2
  3. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/__init__.py +1 -1
  4. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/api.py +38 -0
  5. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/feature_identity.py +36 -0
  6. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/PKG-INFO +1 -1
  7. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/SOURCES.txt +1 -0
  8. slide2vec-6.3.2/tests/test_pooled_identity_api.py +283 -0
  9. {slide2vec-6.3.1 → slide2vec-6.3.2}/LICENSE +0 -0
  10. {slide2vec-6.3.1 → slide2vec-6.3.2}/README.md +0 -0
  11. {slide2vec-6.3.1 → slide2vec-6.3.2}/setup.cfg +0 -0
  12. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/__main__.py +0 -0
  13. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/_cufile.py +0 -0
  14. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/artifacts.py +0 -0
  15. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/cli.py +0 -0
  16. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/configs/__init__.py +0 -0
  17. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/configs/default.yaml +0 -0
  18. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/configs/resources.py +0 -0
  19. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/cufile.json +0 -0
  20. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/data/__init__.py +0 -0
  21. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/data/dataset.py +0 -0
  22. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/data/tile_reader.py +0 -0
  23. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/data/tile_store.py +0 -0
  24. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/__init__.py +0 -0
  25. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/dense_image_worker.py +0 -0
  26. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/dense_worker.py +0 -0
  27. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/direct_embed_worker.py +0 -0
  28. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/image_worker.py +0 -0
  29. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/pin_gpu.py +0 -0
  30. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/pipeline_worker.py +0 -0
  31. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/distributed/worker_entry.py +0 -0
  32. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/__init__.py +0 -0
  33. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/base.py +0 -0
  34. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/__init__.py +0 -0
  35. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/conch.py +0 -0
  36. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/dinov2.py +0 -0
  37. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/dinov3.py +0 -0
  38. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/genbio.py +0 -0
  39. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/gigapath.py +0 -0
  40. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/gpfm.py +0 -0
  41. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/hibou.py +0 -0
  42. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/hoptimus.py +0 -0
  43. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/isight.py +0 -0
  44. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/lunit.py +0 -0
  45. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/mettle.py +0 -0
  46. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/midnight.py +0 -0
  47. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  48. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  49. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/case.py +0 -0
  50. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/loading.py +0 -0
  51. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/slide.py +0 -0
  52. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/types.py +0 -0
  53. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/mstar.py +0 -0
  54. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/musk.py +0 -0
  55. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/phikon.py +0 -0
  56. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/prism.py +0 -0
  57. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/prism2.py +0 -0
  58. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/prost40m.py +0 -0
  59. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/rudolfv2.py +0 -0
  60. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/titan.py +0 -0
  61. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/uni.py +0 -0
  62. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/virchow.py +0 -0
  63. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/models/waiv.py +0 -0
  64. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/registry.py +0 -0
  65. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/encoders/validation.py +0 -0
  66. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/inference.py +0 -0
  67. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/progress.py +0 -0
  68. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/__init__.py +0 -0
  69. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/artifacts_collect.py +0 -0
  70. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/batching.py +0 -0
  71. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/cpu_budget.py +0 -0
  72. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_encode.py +0 -0
  73. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_encoder_input.py +0 -0
  74. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_reading.py +0 -0
  75. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_recipe.py +0 -0
  76. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_shard.py +0 -0
  77. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_stage.py +0 -0
  78. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_regions.py +0 -0
  79. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_shard.py +0 -0
  80. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_sliding.py +0 -0
  81. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/dense_stage.py +0 -0
  82. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/distributed.py +0 -0
  83. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/distributed_stage.py +0 -0
  84. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/effective_encoder_input.py +0 -0
  85. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/embedding.py +0 -0
  86. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/embedding_persist.py +0 -0
  87. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/embedding_pipeline.py +0 -0
  88. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/encoder_input_contract.py +0 -0
  89. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/hierarchical.py +0 -0
  90. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/image_shard.py +0 -0
  91. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/image_specs.py +0 -0
  92. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/image_stage.py +0 -0
  93. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/manifest.py +0 -0
  94. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/model_settings.py +0 -0
  95. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/patient_pipeline.py +0 -0
  96. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/persist_callbacks.py +0 -0
  97. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/persistence.py +0 -0
  98. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  99. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/preprocessing.py +0 -0
  100. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/process_list.py +0 -0
  101. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/progress_bridge.py +0 -0
  102. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/registry.py +0 -0
  103. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/serialization.py +0 -0
  104. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/sharding.py +0 -0
  105. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/slide_encode.py +0 -0
  106. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/tiling.py +0 -0
  107. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/tiling_pipeline.py +0 -0
  108. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/types.py +0 -0
  109. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/runtime/worker_io.py +0 -0
  110. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/__init__.py +0 -0
  111. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/config.py +0 -0
  112. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/coordinates.py +0 -0
  113. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/log_utils.py +0 -0
  114. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/tiling_io.py +0 -0
  115. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec/utils/utils.py +0 -0
  116. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/dependency_links.txt +0 -0
  117. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/entry_points.txt +0 -0
  118. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/not-zip-safe +0 -0
  119. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/requires.txt +0 -0
  120. {slide2vec-6.3.1 → slide2vec-6.3.2}/slide2vec.egg-info/top_level.txt +0 -0
  121. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_attention_extraction.py +0 -0
  122. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_benchmark_tooling.py +0 -0
  123. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_cli_noninteractive.py +0 -0
  124. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_cufile_compat_mode.py +0 -0
  125. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_encode_kit.py +0 -0
  126. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_encoder_input.py +0 -0
  127. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_extraction.py +0 -0
  128. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_image_reading.py +0 -0
  129. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_image_resume.py +0 -0
  130. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_image_shard.py +0 -0
  131. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_image_stage.py +0 -0
  132. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_regions.py +0 -0
  133. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_shard.py +0 -0
  134. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_sliding.py +0 -0
  135. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_source_spacing.py +0 -0
  136. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_stage.py +0 -0
  137. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dense_worker.py +0 -0
  138. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dinov2_natimage.py +0 -0
  139. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_dinov3_natimage.py +0 -0
  140. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_capabilities.py +0 -0
  141. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_input_contract.py +0 -0
  142. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_plugins.py +0 -0
  143. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_preprocessing.py +0 -0
  144. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_provider_failures.py +0 -0
  145. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_encoder_registry.py +0 -0
  146. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_feature_identity.py +0 -0
  147. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_gigapath.py +0 -0
  148. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_gpfm_genbio_heavy.py +0 -0
  149. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_hs2p5_integration.py +0 -0
  150. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_hs2p_package_cutover.py +0 -0
  151. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_image_shard.py +0 -0
  152. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_image_stage.py +0 -0
  153. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_inference_benchmark.py +0 -0
  154. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_isight.py +0 -0
  155. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_load_model_hf_auth.py +0 -0
  156. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_mascaret.py +0 -0
  157. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_mettle.py +0 -0
  158. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_on_slide_persisted.py +0 -0
  159. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_output_consistency.py +0 -0
  160. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_patch_size_metadata.py +0 -0
  161. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_patient_annotation_sampling.py +0 -0
  162. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_patient_manifest.py +0 -0
  163. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_phaet.py +0 -0
  164. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_pooled_encoder_input.py +0 -0
  165. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_pooled_geometry.py +0 -0
  166. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_prism.py +0 -0
  167. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_prism2.py +0 -0
  168. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_process_list_performance.py +0 -0
  169. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_progress.py +0 -0
  170. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_regression_core.py +0 -0
  171. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_regression_inference.py +0 -0
  172. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_regression_models.py +0 -0
  173. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_rudolfv2.py +0 -0
  174. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_runtime_batching.py +0 -0
  175. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_sharding.py +0 -0
  176. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_slide_coordinate_preparation.py +0 -0
  177. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_slide_latents.py +0 -0
  178. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_soma_migration.py +0 -0
  179. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_tile_store.py +0 -0
  180. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_tiling_pipeline.py +0 -0
  181. {slide2vec-6.3.1 → slide2vec-6.3.2}/tests/test_titan.py +0 -0
  182. {slide2vec-6.3.1 → slide2vec-6.3.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: 6.3.1
3
+ Version: 6.3.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "slide2vec"
7
- version = "6.3.1"
7
+ version = "6.3.2"
8
8
  description = "Embedding of whole slide images with Foundation Models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -180,7 +180,7 @@ no_implicit_reexport = true
180
180
  max-line-length = 160
181
181
 
182
182
  [tool.bumpver]
183
- current_version = "6.3.1"
183
+ current_version = "6.3.2"
184
184
  version_pattern = "MAJOR.MINOR.PATCH"
185
185
  commit = false
186
186
  tag = false
@@ -31,7 +31,7 @@ from slide2vec.encoders import (
31
31
  )
32
32
 
33
33
 
34
- __version__ = "6.3.1"
34
+ __version__ = "6.3.2"
35
35
 
36
36
  __all__ = [
37
37
  "Model",
@@ -688,6 +688,44 @@ class Model:
688
688
  # Construction fact, not an encode: see _load_backend_without_transform.
689
689
  return int(self._load_backend_without_transform().feature_dim)
690
690
 
691
+ def pooled_identity_differences(
692
+ self,
693
+ recorded: Mapping[str, Any],
694
+ *,
695
+ preprocessing: PreprocessingConfig | None,
696
+ execution: ExecutionOptions | None = None,
697
+ ) -> dict[str, tuple[Any, Any]]:
698
+ """Compare a pooled sidecar's ``compatibility`` block with the current recipe.
699
+
700
+ Returns ``{field: (recorded, current)}`` for differing fields held by both
701
+ identities. Missing fields retain the extraction resume semantics: they
702
+ cannot be verified and are accepted. The caller owns the policy for an
703
+ unrecorded identity or a difference.
704
+
705
+ Explicitly pass ``preprocessing=None`` for pre-cropped images, whose shipped
706
+ transform determines geometry. Pass a :class:`PreprocessingConfig` for
707
+ tiles read from slides; its unset fields resolve as in pooled extraction.
708
+ ``execution`` defaults to the model's recommended precision and needs no
709
+ output directory.
710
+
711
+ Only a recorded ``transform`` loads weights, on an isolated CPU model.
712
+ Slide and patient models load their registered tile encoder and tile output
713
+ variant, without loading aggregation weights. This method does not change
714
+ this model's input contract or loaded backend, encode pixels, or write files.
715
+ """
716
+ from slide2vec.runtime.feature_identity import pooled_identity_differences
717
+
718
+ resolved_execution = _coerce_execution_options(execution, model=self)
719
+ resolved_preprocessing = (
720
+ None if preprocessing is None else _resolve_direct_api_preprocessing(self, preprocessing)
721
+ )
722
+ return pooled_identity_differences(
723
+ self,
724
+ dict(recorded),
725
+ execution=resolved_execution,
726
+ preprocessing=resolved_preprocessing,
727
+ )
728
+
691
729
  def prepare_dense_encoder(
692
730
  self,
693
731
  *,
@@ -136,6 +136,42 @@ def differing_fields(
136
136
  }
137
137
 
138
138
 
139
+ def pooled_identity_differences(
140
+ model,
141
+ recorded: dict[str, Any],
142
+ *,
143
+ execution,
144
+ preprocessing=None,
145
+ ) -> dict[str, tuple[Any, Any]]:
146
+ """Compare a recorded pooled identity with the resolved current request."""
147
+
148
+ def resolve_transform() -> dict[str, Any]:
149
+ from slide2vec.api import Model
150
+
151
+ encoder_name, output_variant = model.name, model._output_variant
152
+ if model.level != "tile":
153
+ info = encoder_registry.info(model.name)
154
+ encoder_name = info["tile_encoder"]
155
+ output_variant = info["tile_encoder_output_variant"]
156
+ source = Model.from_preset(
157
+ encoder_name,
158
+ device="cpu",
159
+ output_variant=output_variant,
160
+ allow_non_recommended_settings=model.allow_non_recommended_settings,
161
+ )
162
+ if preprocessing is None:
163
+ source._declare_given_encoder_input(emit_run_info=False)
164
+ else:
165
+ source._declare_encoder_input(preprocessing, emit_run_info=False)
166
+ return transform_record(source._load_backend().transforms)
167
+
168
+ return differing_fields(
169
+ recorded,
170
+ pooled_feature_identity(model, execution=execution, preprocessing=preprocessing),
171
+ resolve_transform=resolve_transform,
172
+ )
173
+
174
+
139
175
  class PooledResumeCheck:
140
176
  """Compare the completed pooled artifacts of one resume with the run's feature identity.
141
177
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 6.3.1
3
+ Version: 6.3.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
@@ -160,6 +160,7 @@ tests/test_patient_manifest.py
160
160
  tests/test_phaet.py
161
161
  tests/test_pooled_encoder_input.py
162
162
  tests/test_pooled_geometry.py
163
+ tests/test_pooled_identity_api.py
163
164
  tests/test_prism.py
164
165
  tests/test_prism2.py
165
166
  tests/test_process_list_performance.py
@@ -0,0 +1,283 @@
1
+ """The public comparison of recorded pooled identities with the current extraction recipe.
2
+
3
+ Failure modes: wrong metadata differences, eager or GPU model loading, using the
4
+ wrong input regime or tile dependency, changing the caller's model, and requiring
5
+ artifact writes for a metadata comparison. Expected differences are literal records.
6
+ """
7
+
8
+ import pytest
9
+ import torch
10
+ from PIL import Image
11
+ from torchvision.transforms import v2
12
+
13
+ from slide2vec import ExecutionOptions, ImageSpec, Model, PreprocessingConfig
14
+ from slide2vec.encoders import PatientEncoder, SlideEncoder, TileEncoder, encoder_registry, register_encoder
15
+
16
+ TILE_ENCODER = "identity-api-tile"
17
+ SHIPPED_TRANSFORM = {
18
+ "normalize": {"mean": [0.5, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
19
+ "resize": {"size": [40], "interpolation": "bicubic"},
20
+ "center_crop": {"size": [32, 32]},
21
+ }
22
+ OLD_TRANSFORM = {
23
+ "normalize": {"mean": [0.25, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
24
+ "resize": {"size": [40], "interpolation": "bicubic"},
25
+ "center_crop": {"size": [32, 32]},
26
+ }
27
+ NORMALIZATION_ONLY = {
28
+ "normalize": {"mean": [0.5, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
29
+ "resize": None,
30
+ "center_crop": None,
31
+ }
32
+ ALTERNATE_NORMALIZATION = {
33
+ "normalize": {"mean": [0.25, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
34
+ "resize": None,
35
+ "center_crop": None,
36
+ }
37
+
38
+
39
+ @pytest.fixture
40
+ def tile_encoder(monkeypatch):
41
+ """A weight-free provider with known transforms and observable device selection."""
42
+ monkeypatch.setattr(encoder_registry, "_entries", dict(encoder_registry._entries))
43
+
44
+ @register_encoder(
45
+ TILE_ENCODER,
46
+ output_variants={"default": {"encode_dim": 3}, "alternate": {"encode_dim": 3}},
47
+ default_output_variant="default",
48
+ input_size=32,
49
+ supports_variable_input_size=True,
50
+ supported_spacing_um=[0.5],
51
+ default_spacing_um=0.5,
52
+ precision="fp16",
53
+ )
54
+ class TestTileEncoder(TileEncoder):
55
+ variants = []
56
+ devices = []
57
+ encoded_images = 0
58
+
59
+ def __init__(self, *, output_variant=None, allow_non_recommended_settings=False):
60
+ self._device = torch.device("cpu")
61
+ self.output_variant = output_variant or "default"
62
+ type(self).variants.append(self.output_variant)
63
+
64
+ @property
65
+ def encode_dim(self):
66
+ return 3
67
+
68
+ @property
69
+ def device(self):
70
+ return self._device
71
+
72
+ def to(self, device):
73
+ self._device = torch.device(device)
74
+ type(self).devices.append(str(self._device))
75
+ return self
76
+
77
+ def get_normalization_transform(self):
78
+ mean = [0.25, 0.5, 0.5] if self.output_variant == "alternate" else [0.5, 0.5, 0.5]
79
+ return v2.Compose([
80
+ v2.ToImage(),
81
+ v2.ToDtype(torch.float32, scale=True),
82
+ v2.Normalize(mean=mean, std=[0.5, 0.5, 0.5]),
83
+ ])
84
+
85
+ def get_transform(self):
86
+ return v2.Compose([
87
+ v2.Resize(40, interpolation=v2.InterpolationMode.BICUBIC),
88
+ v2.CenterCrop(32),
89
+ self.get_normalization_transform(),
90
+ ])
91
+
92
+ def encode_tiles(self, batch):
93
+ type(self).encoded_images += int(batch.shape[0])
94
+ return batch.mean(dim=(-1, -2))
95
+
96
+ return TestTileEncoder
97
+
98
+
99
+ @pytest.fixture
100
+ def aggregate_encoders(tile_encoder):
101
+ class AggregationLifecycle:
102
+ def __init__(self, **kwargs):
103
+ raise AssertionError("Identity comparison must not load aggregation weights")
104
+
105
+ @property
106
+ def encode_dim(self):
107
+ return 3
108
+
109
+ @property
110
+ def device(self):
111
+ return torch.device("cpu")
112
+
113
+ def to(self, device):
114
+ return self
115
+
116
+ def encode_slide(self, tile_features, coordinates=None, *, tile_size_lv0=None):
117
+ return tile_features.mean(dim=0)
118
+
119
+ class TestSlideEncoder(AggregationLifecycle, SlideEncoder):
120
+ pass
121
+
122
+ class TestPatientEncoder(AggregationLifecycle, PatientEncoder):
123
+ def encode_patient(self, slide_embeddings):
124
+ return slide_embeddings.mean(dim=0)
125
+
126
+ names = {"slide": "identity-api-slide", "patient": "identity-api-patient"}
127
+ for level, cls in (("slide", TestSlideEncoder), ("patient", TestPatientEncoder)):
128
+ register_encoder(
129
+ names[level],
130
+ level=level,
131
+ tile_encoder=TILE_ENCODER,
132
+ tile_encoder_output_variant="alternate",
133
+ output_variants={"default": {"encode_dim": 3}},
134
+ default_output_variant="default",
135
+ supported_spacing_um=[0.5],
136
+ default_spacing_um=0.5,
137
+ precision="fp32",
138
+ )(cls)
139
+ return names
140
+
141
+
142
+ def test_pooled_identity_comparison_reports_recorded_and_current_metadata():
143
+ model = Model.from_preset("virchow2", device="cuda")
144
+ recorded = {
145
+ "encoder_name": "older-encoder",
146
+ "output_variant": "cls",
147
+ "precision": "bf16",
148
+ "feature_dtype": "fp16",
149
+ "older_field": "accepted",
150
+ }
151
+
152
+ differences = model.pooled_identity_differences(
153
+ recorded,
154
+ preprocessing=None,
155
+ execution=ExecutionOptions(precision="fp32", output_dtype="fp32", num_gpus=1),
156
+ )
157
+
158
+ assert differences == {
159
+ "encoder_name": ("older-encoder", "virchow2"),
160
+ "output_variant": ("cls", "cls_patch_mean"),
161
+ "precision": ("bf16", "fp32"),
162
+ "feature_dtype": ("fp16", "fp32"),
163
+ }
164
+
165
+
166
+ def test_pooled_identity_comparison_reads_the_shipped_transform_on_cpu(tile_encoder):
167
+ model = Model.from_preset(TILE_ENCODER, device="cuda")
168
+
169
+ differences = model.pooled_identity_differences(
170
+ {"transform": OLD_TRANSFORM},
171
+ preprocessing=None,
172
+ execution=ExecutionOptions(precision="fp32", num_gpus=1),
173
+ )
174
+
175
+ assert differences == {"transform": (OLD_TRANSFORM, SHIPPED_TRANSFORM)}
176
+ assert tile_encoder.devices == ["cpu"]
177
+ assert tile_encoder.encoded_images == 0
178
+
179
+
180
+ def test_pooled_identity_comparison_reads_the_declared_transform(tile_encoder):
181
+ differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
182
+ {"transform": SHIPPED_TRANSFORM},
183
+ preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
184
+ execution=ExecutionOptions(precision="fp32", num_gpus=1),
185
+ )
186
+
187
+ assert differences == {"transform": (SHIPPED_TRANSFORM, NORMALIZATION_ONLY)}
188
+
189
+
190
+ @pytest.mark.parametrize("level", ["slide", "patient"])
191
+ def test_pooled_identity_comparison_uses_the_registered_tile_dependency(
192
+ tile_encoder, aggregate_encoders, level
193
+ ):
194
+ model = Model.from_preset(aggregate_encoders[level], device="cuda")
195
+
196
+ differences = model.pooled_identity_differences(
197
+ {"tile_encoder_output_variant": "default", "transform": NORMALIZATION_ONLY},
198
+ preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
199
+ execution=ExecutionOptions(precision="fp32", num_gpus=1),
200
+ )
201
+
202
+ assert differences == {
203
+ "tile_encoder_output_variant": ("default", "alternate"),
204
+ "transform": (NORMALIZATION_ONLY, ALTERNATE_NORMALIZATION),
205
+ }
206
+ assert tile_encoder.variants == ["alternate"]
207
+ assert tile_encoder.devices == ["cpu"]
208
+
209
+
210
+ def test_pooled_identity_comparison_accepts_missing_fields_without_loading_weights(tile_encoder):
211
+ differences = Model.from_preset(TILE_ENCODER, device="cuda").pooled_identity_differences(
212
+ {},
213
+ preprocessing=None,
214
+ execution=ExecutionOptions(precision="fp32", num_gpus=1),
215
+ )
216
+
217
+ assert differences == {}
218
+ assert tile_encoder.variants == []
219
+ assert tile_encoder.devices == []
220
+
221
+
222
+ @pytest.mark.parametrize("execution", [None, ExecutionOptions(num_gpus=1)])
223
+ def test_pooled_identity_comparison_resolves_the_extraction_precision_default(tile_encoder, execution):
224
+ differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
225
+ {"precision": "fp32", "feature_dtype": "fp32"},
226
+ preprocessing=None,
227
+ execution=execution,
228
+ )
229
+
230
+ assert differences == {"precision": ("fp32", "fp16"), "feature_dtype": ("fp32", "fp16")}
231
+ assert tile_encoder.variants == []
232
+
233
+
234
+ def test_pooled_identity_comparison_resolves_hierarchical_geometry(tile_encoder):
235
+ differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
236
+ {
237
+ "requested_tile_size_px": 64,
238
+ "encoder_input_size_px": 64,
239
+ "region_tile_multiple": 3,
240
+ "requested_region_size_px": 96,
241
+ },
242
+ preprocessing=PreprocessingConfig(region_tile_multiple=2),
243
+ execution=ExecutionOptions(precision="fp32", num_gpus=1),
244
+ )
245
+
246
+ assert differences == {
247
+ "requested_tile_size_px": (64, 32),
248
+ "encoder_input_size_px": (64, 32),
249
+ "region_tile_multiple": (3, 2),
250
+ "requested_region_size_px": (96, 64),
251
+ }
252
+
253
+
254
+ def test_pooled_identity_comparison_preserves_a_model_used_for_image_extraction(tmp_path, tile_encoder):
255
+ image_path = tmp_path / "image.png"
256
+ Image.new("RGB", (48, 48), color=(255, 255, 255)).save(image_path)
257
+ model = Model.from_preset(TILE_ENCODER, device="cpu")
258
+ execution = ExecutionOptions(
259
+ output_dir=tmp_path / "artifacts",
260
+ precision="fp32",
261
+ num_gpus=1,
262
+ num_workers_per_gpu=0,
263
+ )
264
+ (artifact,) = model.embed_images([ImageSpec(sample_id="first", image_path=image_path)], execution=execution)
265
+ sidecar_before = artifact.metadata_path.read_bytes()
266
+
267
+ differences = model.pooled_identity_differences(
268
+ artifact.metadata["compatibility"],
269
+ preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
270
+ execution=execution,
271
+ )
272
+
273
+ assert differences == {"transform": (SHIPPED_TRANSFORM, NORMALIZATION_ONLY)}
274
+ assert tile_encoder.encoded_images == 1
275
+ assert artifact.metadata_path.read_bytes() == sidecar_before
276
+
277
+ (next_artifact,) = model.embed_images([ImageSpec(sample_id="second", image_path=image_path)], execution=execution)
278
+
279
+ # One original backend and one comparison copy; extraction reuses the original.
280
+ assert tile_encoder.variants == ["default", "default"]
281
+ assert tile_encoder.encoded_images == 2
282
+ assert next_artifact.metadata["compatibility"]["transform"] == SHIPPED_TRANSFORM
283
+ assert torch.equal(torch.load(next_artifact.path, weights_only=True), torch.tensor([1.0, 1.0, 1.0]))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes