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.
- {slide2vec-7.1.3 → slide2vec-7.1.5}/PKG-INFO +1 -1
- {slide2vec-7.1.3 → slide2vec-7.1.5}/pyproject.toml +2 -2
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/__init__.py +1 -1
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/api.py +44 -0
- slide2vec-7.1.5/slide2vec/encoders/models/mettle_1_5.py +129 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_recipe.py +50 -2
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_stage.py +3 -28
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/feature_identity.py +63 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/SOURCES.txt +2 -0
- slide2vec-7.1.5/tests/test_dense_identity_api.py +430 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_plugins.py +1 -1
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_preprocessing.py +31 -7
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_mettle.py +98 -40
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patch_size_metadata.py +1 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_core.py +1 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_models.py +1 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/LICENSE +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/README.md +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/setup.cfg +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/__main__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/_cufile.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/artifacts.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/cli.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/configs/resources.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/cufile.json +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/dataset.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/base.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/code.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/inference.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/progress.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/types.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/config.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec/utils/utils.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_attention_extraction.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_extraction.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_regions.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_sliding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dense_worker.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_drop_in_encoders.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_code.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_encoder_registry.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_execution_options.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_feature_identity.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_gigapath.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hierarchical_encoding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hs2p5_integration.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_image_shard.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_image_stage.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_isight.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_mascaret.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_output_consistency.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_manifest.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_patient_tiling_only.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_phaet.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_pooled_identity_api.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_prism.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_prism2.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_process_list_performance.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_progress.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_regression_inference.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_rudolfv2.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_runtime_batching.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_sharding.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_slide_latents.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_soma_migration.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_tile_store.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_titan.py +0 -0
- {slide2vec-7.1.3 → slide2vec-7.1.5}/tests/test_worker_gpu_pinning.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "7.1.
|
|
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.
|
|
243
|
+
current_version = "7.1.5"
|
|
244
244
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
245
245
|
commit = false
|
|
246
246
|
tag = false
|
|
@@ -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
|
-
|
|
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 =
|
|
119
|
-
|
|
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
|
|
|
@@ -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
|