slide2vec 6.3.2__tar.gz → 6.3.4__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-6.3.2 → slide2vec-6.3.4}/PKG-INFO +4 -4
- {slide2vec-6.3.2 → slide2vec-6.3.4}/pyproject.toml +5 -5
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/__init__.py +1 -1
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/api.py +17 -6
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/base.py +49 -9
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/registry.py +40 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/inference.py +5 -2
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_encode.py +1 -21
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_regions.py +11 -4
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/PKG-INFO +4 -4
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/SOURCES.txt +1 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/requires.txt +3 -3
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_encode_kit.py +15 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_image_stage.py +30 -1
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_regions.py +68 -5
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_stage.py +31 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_capabilities.py +34 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_patient_manifest.py +1 -2
- slide2vec-6.3.4/tests/test_patient_tiling_only.py +86 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/LICENSE +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/README.md +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/setup.cfg +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/__main__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/_cufile.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/artifacts.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/cli.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/configs/resources.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/cufile.json +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/progress.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/feature_identity.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/config.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_feature_identity.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_gigapath.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_hs2p5_integration.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_image_shard.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_image_stage.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_isight.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_mascaret.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_mettle.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_phaet.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_pooled_identity_api.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_prism.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_prism2.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_progress.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_regression_core.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_regression_inference.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_regression_models.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_sharding.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_slide_latents.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_tile_store.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/tests/test_titan.py +0 -0
- {slide2vec-6.3.2 → slide2vec-6.3.4}/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.
|
|
3
|
+
Version: 6.3.4
|
|
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
|
|
@@ -27,18 +27,18 @@ Requires-Dist: torchvision
|
|
|
27
27
|
Requires-Dist: transformers
|
|
28
28
|
Requires-Dist: wandb
|
|
29
29
|
Requires-Dist: einops
|
|
30
|
-
Requires-Dist: timm
|
|
30
|
+
Requires-Dist: timm>=1.0.3
|
|
31
31
|
Requires-Dist: huggingface_hub
|
|
32
32
|
Provides-Extra: hoptimus
|
|
33
33
|
Requires-Dist: torch>=2.0; extra == "hoptimus"
|
|
34
34
|
Requires-Dist: torchvision>=0.15.0; extra == "hoptimus"
|
|
35
35
|
Requires-Dist: xformers>=0.0.18; extra == "hoptimus"
|
|
36
36
|
Provides-Extra: virchow
|
|
37
|
-
Requires-Dist: timm>=0.
|
|
37
|
+
Requires-Dist: timm>=1.0.3; extra == "virchow"
|
|
38
38
|
Requires-Dist: torch>=2.0; extra == "virchow"
|
|
39
39
|
Provides-Extra: uni
|
|
40
40
|
Requires-Dist: torch>=2.0; extra == "uni"
|
|
41
|
-
Requires-Dist: timm>=0.
|
|
41
|
+
Requires-Dist: timm>=1.0.3; extra == "uni"
|
|
42
42
|
Requires-Dist: xformers>=0.0.18; extra == "uni"
|
|
43
43
|
Provides-Extra: prism
|
|
44
44
|
Requires-Dist: transformers~=4.53.0; extra == "prism"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "6.3.
|
|
7
|
+
version = "6.3.4"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -33,7 +33,7 @@ dependencies = [
|
|
|
33
33
|
"transformers",
|
|
34
34
|
"wandb",
|
|
35
35
|
"einops",
|
|
36
|
-
"timm",
|
|
36
|
+
"timm>=1.0.3",
|
|
37
37
|
"huggingface_hub",
|
|
38
38
|
]
|
|
39
39
|
|
|
@@ -51,12 +51,12 @@ hoptimus = [
|
|
|
51
51
|
"xformers>=0.0.18",
|
|
52
52
|
]
|
|
53
53
|
virchow = [
|
|
54
|
-
"timm>=0.
|
|
54
|
+
"timm>=1.0.3",
|
|
55
55
|
"torch>=2.0",
|
|
56
56
|
]
|
|
57
57
|
uni = [
|
|
58
58
|
"torch>=2.0",
|
|
59
|
-
"timm>=0.
|
|
59
|
+
"timm>=1.0.3",
|
|
60
60
|
"xformers>=0.0.18",
|
|
61
61
|
]
|
|
62
62
|
prism = [
|
|
@@ -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.
|
|
183
|
+
current_version = "6.3.4"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -26,6 +26,7 @@ from slide2vec.encoders.registry import (
|
|
|
26
26
|
encoder_registry,
|
|
27
27
|
list_encoder_provider_diagnostics,
|
|
28
28
|
resolve_preprocessing_fields,
|
|
29
|
+
validate_feature_kind_capability,
|
|
29
30
|
)
|
|
30
31
|
from slide2vec.encoders.validation import validate_encoder_config
|
|
31
32
|
from slide2vec.runtime.model_settings import (
|
|
@@ -457,8 +458,10 @@ class DenseOptions:
|
|
|
457
458
|
#: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
|
|
458
459
|
#: ``window_size is None``).
|
|
459
460
|
overlap: float = 0.0
|
|
460
|
-
#: ``"patch_features"`` (the patch-token grid
|
|
461
|
-
#:
|
|
461
|
+
#: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
|
|
462
|
+
#: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
|
|
463
|
+
#: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
|
|
464
|
+
#: (CLS/register self-attention grid).
|
|
462
465
|
feature_kind: str = "patch_features"
|
|
463
466
|
#: Transformer blocks whose CLS attention is read (``cls_attention`` only).
|
|
464
467
|
attention_blocks: tuple[int, ...] = (-1,)
|
|
@@ -508,8 +511,10 @@ class DenseImageOptions:
|
|
|
508
511
|
#: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
|
|
509
512
|
#: ``window_size is None``).
|
|
510
513
|
overlap: float = 0.0
|
|
511
|
-
#: ``"patch_features"`` (the patch-token grid
|
|
512
|
-
#:
|
|
514
|
+
#: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
|
|
515
|
+
#: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
|
|
516
|
+
#: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
|
|
517
|
+
#: (CLS/register self-attention grid).
|
|
513
518
|
feature_kind: str = "patch_features"
|
|
514
519
|
#: Transformer blocks whose CLS attention is read (``cls_attention`` only).
|
|
515
520
|
attention_blocks: tuple[int, ...] = (-1,)
|
|
@@ -1091,14 +1096,20 @@ class Model:
|
|
|
1091
1096
|
parent stage and every torchrun rank — declares for itself.
|
|
1092
1097
|
|
|
1093
1098
|
*dense* is a :class:`DenseOptions` (ROIs on a slide) or a :class:`DenseImageOptions`
|
|
1094
|
-
(pre-cropped images); only the supervision geometry
|
|
1095
|
-
|
|
1099
|
+
(pre-cropped images); only the supervision geometry and the requested feature kind
|
|
1100
|
+
are read here, and the two state them the same way.
|
|
1101
|
+
|
|
1102
|
+
The feature kind is checked against the registered encoder class at the same
|
|
1103
|
+
point, so a tap this encoder does not implement (``patch_features_prenorm`` on a
|
|
1104
|
+
non-timm backbone, say) fails before any backend is loaded and before resume
|
|
1105
|
+
inspects or invalidates an existing artifact.
|
|
1096
1106
|
"""
|
|
1097
1107
|
contract = EncoderInputContract.declared_dense(
|
|
1098
1108
|
self.name,
|
|
1099
1109
|
target_size_px=dense.target_size,
|
|
1100
1110
|
window_size=None if dense.window_size is None else int(dense.window_size),
|
|
1101
1111
|
)
|
|
1112
|
+
validate_feature_kind_capability(self.name, feature_kind=str(dense.feature_kind))
|
|
1102
1113
|
self._encoder_input = contract
|
|
1103
1114
|
plan = contract.plan
|
|
1104
1115
|
if emit_run_info and plan.requires_variable_model_input:
|
|
@@ -462,6 +462,21 @@ class TileEncoder(Encoder):
|
|
|
462
462
|
"tokens can be reshaped into a spatial grid."
|
|
463
463
|
)
|
|
464
464
|
|
|
465
|
+
def encode_tiles_dense_prenorm(self, batch: Tensor) -> Tensor:
|
|
466
|
+
"""Dense grid tapped before the backbone's final normalisation. (B, C, H, W) -> (B, d, h, w).
|
|
467
|
+
|
|
468
|
+
Same grid as :meth:`encode_tiles_dense` but from the last block's output,
|
|
469
|
+
*before* the final LayerNorm — the tap timm's ``features_only`` backbones
|
|
470
|
+
expose (``forward_intermediates(norm=False)``) and that kaiko-ai/eva's
|
|
471
|
+
segmentation decoders were trained on. Default: unsupported; the timm ViT
|
|
472
|
+
base overrides it.
|
|
473
|
+
"""
|
|
474
|
+
raise NotImplementedError(
|
|
475
|
+
f"{type(self).__name__} does not support pre-norm dense feature "
|
|
476
|
+
"extraction (feature_kind='patch_features_prenorm'). The tap is "
|
|
477
|
+
"available for timm ViT tile encoders only."
|
|
478
|
+
)
|
|
479
|
+
|
|
465
480
|
def encode_tiles_attention(
|
|
466
481
|
self,
|
|
467
482
|
batch: Tensor,
|
|
@@ -634,15 +649,8 @@ class TimmTileEncoder(TileEncoder):
|
|
|
634
649
|
"""Number of leading non-patch tokens (CLS + register tokens)."""
|
|
635
650
|
return int(self._model.num_prefix_tokens)
|
|
636
651
|
|
|
637
|
-
def
|
|
638
|
-
"""
|
|
639
|
-
|
|
640
|
-
Runs the frozen backbone's ``forward_features`` and folds the patch-token
|
|
641
|
-
sequence back into its spatial grid (CLS/register tokens discarded). The
|
|
642
|
-
backbone must accept ``batch`` at its current spatial size (timm ViTs need
|
|
643
|
-
``dynamic_img_size=True`` for sizes other than their native input), and
|
|
644
|
-
``H, W`` must be divisible by the patch size.
|
|
645
|
-
"""
|
|
652
|
+
def _check_dense_batch(self, batch: Tensor) -> tuple[int, int]:
|
|
653
|
+
"""Validate a dense ``(B, C, H, W)`` batch; return ``(H, W)``."""
|
|
646
654
|
if batch.ndim != 4:
|
|
647
655
|
raise ValueError(
|
|
648
656
|
"encode_tiles_dense expects a (B, C, H, W) batch, got shape "
|
|
@@ -656,6 +664,19 @@ class TimmTileEncoder(TileEncoder):
|
|
|
656
664
|
f"divisible by the patch size: got {height}x{width}, patch "
|
|
657
665
|
f"{patch_h}x{patch_w}. Pad the tile up to a patch multiple first."
|
|
658
666
|
)
|
|
667
|
+
return int(height), int(width)
|
|
668
|
+
|
|
669
|
+
def encode_tiles_dense(self, batch: Tensor) -> Tensor:
|
|
670
|
+
"""Encode tiles into a dense spatial grid. (B, C, H, W) -> (B, d, h, w).
|
|
671
|
+
|
|
672
|
+
Runs the frozen backbone's ``forward_features`` and folds the patch-token
|
|
673
|
+
sequence back into its spatial grid (CLS/register tokens discarded). The
|
|
674
|
+
backbone must accept ``batch`` at its current spatial size (timm ViTs need
|
|
675
|
+
``dynamic_img_size=True`` for sizes other than their native input), and
|
|
676
|
+
``H, W`` must be divisible by the patch size.
|
|
677
|
+
"""
|
|
678
|
+
height, width = self._check_dense_batch(batch)
|
|
679
|
+
patch_h, patch_w = self._dense_patch_size()
|
|
659
680
|
tokens = self._model.forward_features(batch)
|
|
660
681
|
return reshape_tokens_to_grid(
|
|
661
682
|
tokens,
|
|
@@ -665,6 +686,25 @@ class TimmTileEncoder(TileEncoder):
|
|
|
665
686
|
encoder_name=type(self).__name__,
|
|
666
687
|
)
|
|
667
688
|
|
|
689
|
+
def encode_tiles_dense_prenorm(self, batch: Tensor) -> Tensor:
|
|
690
|
+
"""Pre-norm dense grid via timm's ``forward_intermediates(norm=False)``.
|
|
691
|
+
|
|
692
|
+
``indices=1`` selects the last block, ``output_fmt="NCHW"`` folds the patch
|
|
693
|
+
tokens into the grid (prefix tokens dropped), ``norm=False`` skips the final
|
|
694
|
+
LayerNorm — exactly timm's ``features_only=True, out_indices=1`` path.
|
|
695
|
+
"""
|
|
696
|
+
self._check_dense_batch(batch)
|
|
697
|
+
forward_intermediates = getattr(self._model, "forward_intermediates", None)
|
|
698
|
+
if forward_intermediates is None:
|
|
699
|
+
raise NotImplementedError(
|
|
700
|
+
f"{type(self).__name__} backbone has no forward_intermediates; the "
|
|
701
|
+
"pre-norm dense tap needs a timm ViT-family model."
|
|
702
|
+
)
|
|
703
|
+
grids = forward_intermediates(
|
|
704
|
+
batch, indices=1, norm=False, intermediates_only=True, output_fmt="NCHW"
|
|
705
|
+
)
|
|
706
|
+
return grids[-1]
|
|
707
|
+
|
|
668
708
|
def encode_tiles_attention(
|
|
669
709
|
self,
|
|
670
710
|
batch: Tensor,
|
|
@@ -149,6 +149,7 @@ class EncoderCapabilities:
|
|
|
149
149
|
level: str
|
|
150
150
|
pooled: bool
|
|
151
151
|
dense: bool
|
|
152
|
+
dense_prenorm: bool
|
|
152
153
|
attention: bool
|
|
153
154
|
slide: bool
|
|
154
155
|
patient: bool
|
|
@@ -164,6 +165,7 @@ def resolve_encoder_capabilities(encoder_name: str) -> EncoderCapabilities:
|
|
|
164
165
|
_validate_encoder_capability_contract(encoder_name, encoder_cls, metadata)
|
|
165
166
|
level = resolve_encoder_level(encoder_name, metadata)
|
|
166
167
|
dense = all(_dense_class_contract(encoder_cls))
|
|
168
|
+
dense_prenorm = dense and _supports_dense_prenorm(encoder_cls)
|
|
167
169
|
attention = _supports_attention(encoder_cls)
|
|
168
170
|
tile_encoder = None
|
|
169
171
|
tile_encoder_output_variant = None
|
|
@@ -189,6 +191,7 @@ def resolve_encoder_capabilities(encoder_name: str) -> EncoderCapabilities:
|
|
|
189
191
|
level=level,
|
|
190
192
|
pooled=issubclass(encoder_cls, TileEncoder),
|
|
191
193
|
dense=dense,
|
|
194
|
+
dense_prenorm=dense_prenorm,
|
|
192
195
|
attention=attention,
|
|
193
196
|
slide=issubclass(encoder_cls, (SlideEncoder, PatientEncoder)),
|
|
194
197
|
patient=issubclass(encoder_cls, PatientEncoder),
|
|
@@ -239,6 +242,43 @@ def _dense_class_contract(encoder_cls: type) -> tuple[bool, ...]:
|
|
|
239
242
|
)
|
|
240
243
|
|
|
241
244
|
|
|
245
|
+
def _supports_dense_prenorm(encoder_cls: type) -> bool:
|
|
246
|
+
return (
|
|
247
|
+
issubclass(encoder_cls, TileEncoder)
|
|
248
|
+
and getattr(encoder_cls, "encode_tiles_dense_prenorm")
|
|
249
|
+
is not TileEncoder.encode_tiles_dense_prenorm
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
#: Dense feature kind -> the ``TileEncoder`` method that produces it.
|
|
254
|
+
_FEATURE_KIND_METHODS = {
|
|
255
|
+
"patch_features": "encode_tiles_dense",
|
|
256
|
+
"patch_features_prenorm": "encode_tiles_dense_prenorm",
|
|
257
|
+
"cls_attention": "encode_tiles_attention",
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def validate_feature_kind_capability(encoder_name: str, *, feature_kind: str) -> None:
|
|
262
|
+
"""Reject a dense feature kind whose method the encoder class leaves unimplemented.
|
|
263
|
+
|
|
264
|
+
Resolved from the registered class, so it runs before any backend is loaded and before
|
|
265
|
+
any on-disk artifact is inspected or invalidated. Every dense route (persisted ROI,
|
|
266
|
+
persisted image, and live kit) goes through it via the dense contract declaration.
|
|
267
|
+
"""
|
|
268
|
+
encoder_cls = encoder_registry.require(encoder_name)
|
|
269
|
+
method_name = _FEATURE_KIND_METHODS.get(str(feature_kind))
|
|
270
|
+
if method_name is None:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"unsupported feature_kind {feature_kind!r}; expected one of "
|
|
273
|
+
+ ", ".join(repr(kind) for kind in _FEATURE_KIND_METHODS)
|
|
274
|
+
)
|
|
275
|
+
if getattr(encoder_cls, method_name) is getattr(TileEncoder, method_name):
|
|
276
|
+
raise ValueError(
|
|
277
|
+
f"Encoder {encoder_name!r} does not support {feature_kind}; choose a "
|
|
278
|
+
"registered dense feature kind implemented by this encoder."
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
242
282
|
def _supports_attention(encoder_cls: type) -> bool:
|
|
243
283
|
return (
|
|
244
284
|
issubclass(encoder_cls, TileEncoder)
|
|
@@ -761,7 +761,10 @@ def run_pipeline(
|
|
|
761
761
|
tiling_only: bool = False,
|
|
762
762
|
execution: ExecutionOptions,
|
|
763
763
|
) -> RunResult:
|
|
764
|
-
|
|
764
|
+
# Tiling does not use patient ids or the patient pipeline: only a run that
|
|
765
|
+
# embeds patients needs them.
|
|
766
|
+
embeds_patients = model.level == "patient" and not tiling_only
|
|
767
|
+
if embeds_patients:
|
|
765
768
|
patient_id_map = manifest.resolve_patient_id_map(slides=slides, manifest_path=manifest_path)
|
|
766
769
|
else:
|
|
767
770
|
patient_id_map = None
|
|
@@ -781,7 +784,7 @@ def run_pipeline(
|
|
|
781
784
|
distributed_stage.validate_multi_gpu_execution(model, execution)
|
|
782
785
|
|
|
783
786
|
resolved_preprocessing = tiling_pipeline.resolve_model_preprocessing(model, preprocessing)
|
|
784
|
-
if
|
|
787
|
+
if embeds_patients:
|
|
785
788
|
patient_pipeline.reject_per_annotation_sampling(model, resolved_preprocessing)
|
|
786
789
|
output_dir = Path(execution.output_dir)
|
|
787
790
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -7,8 +7,7 @@ from typing import TYPE_CHECKING, Any, Callable, cast
|
|
|
7
7
|
|
|
8
8
|
import torch
|
|
9
9
|
|
|
10
|
-
from slide2vec.encoders.
|
|
11
|
-
from slide2vec.encoders.registry import encoder_registry, resolve_patch_size
|
|
10
|
+
from slide2vec.encoders.registry import resolve_patch_size
|
|
12
11
|
from slide2vec.runtime.dense_regions import (
|
|
13
12
|
DenseGridGeometry,
|
|
14
13
|
DenseGridEncoder,
|
|
@@ -94,10 +93,6 @@ class _DenseEncodePlan:
|
|
|
94
93
|
attention_blocks=dense.attention_blocks,
|
|
95
94
|
attention_include_registers=dense.attention_include_registers,
|
|
96
95
|
)
|
|
97
|
-
_validate_registered_feature_capability(
|
|
98
|
-
contract_plan.tile_encoder_name,
|
|
99
|
-
feature_kind=str(dense.feature_kind),
|
|
100
|
-
)
|
|
101
96
|
if execution.precision is None:
|
|
102
97
|
raise ValueError("Live dense encoder precision must be resolved before loading")
|
|
103
98
|
output_precision = resolve_output_precision(
|
|
@@ -122,21 +117,6 @@ class _DenseEncodePlan:
|
|
|
122
117
|
)
|
|
123
118
|
|
|
124
119
|
|
|
125
|
-
def _validate_registered_feature_capability(
|
|
126
|
-
encoder_name: str, *, feature_kind: str
|
|
127
|
-
) -> None:
|
|
128
|
-
"""Reject a feature method left at ``TileEncoder``'s unsupported default."""
|
|
129
|
-
encoder_cls = encoder_registry.require(encoder_name)
|
|
130
|
-
method_name = {
|
|
131
|
-
"patch_features": "encode_tiles_dense",
|
|
132
|
-
"cls_attention": "encode_tiles_attention",
|
|
133
|
-
}[feature_kind]
|
|
134
|
-
if getattr(encoder_cls, method_name) is getattr(TileEncoder, method_name):
|
|
135
|
-
raise ValueError(
|
|
136
|
-
f"Encoder {encoder_name!r} does not support {feature_kind}; choose a "
|
|
137
|
-
"registered dense feature kind implemented by this encoder."
|
|
138
|
-
)
|
|
139
|
-
|
|
140
120
|
|
|
141
121
|
@dataclass(frozen=True, kw_only=True)
|
|
142
122
|
class _DenseItemPreprocessor:
|
|
@@ -51,6 +51,10 @@ from slide2vec.runtime.slide_encode import slide_encode_autocast_ctx
|
|
|
51
51
|
from slide2vec.runtime.tiling import resolve_slide_backend
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
#: Dense feature kinds a request may ask for, each backed by one ``TileEncoder`` method.
|
|
55
|
+
DENSE_FEATURE_KINDS: tuple[str, ...] = ("patch_features", "patch_features_prenorm", "cls_attention")
|
|
56
|
+
|
|
57
|
+
|
|
54
58
|
def _resolve_output_dtype(output_dtype: "torch.dtype | None", precision: str) -> "torch.dtype":
|
|
55
59
|
"""Resolve the dtype emitted grids are materialized in.
|
|
56
60
|
|
|
@@ -152,10 +156,10 @@ def validate_dense_request_settings(
|
|
|
152
156
|
f"dimension; target_size={geometry.target_size} requires bottom/right "
|
|
153
157
|
f"pad={geometry.pad}. Choose a larger target or another pad_mode."
|
|
154
158
|
)
|
|
155
|
-
if feature_kind not in
|
|
159
|
+
if feature_kind not in DENSE_FEATURE_KINDS:
|
|
156
160
|
raise ValueError(
|
|
157
|
-
f"unsupported feature_kind {feature_kind!r}; "
|
|
158
|
-
"
|
|
161
|
+
f"unsupported feature_kind {feature_kind!r}; expected one of "
|
|
162
|
+
+ ", ".join(repr(kind) for kind in DENSE_FEATURE_KINDS)
|
|
159
163
|
)
|
|
160
164
|
if window_size is not None:
|
|
161
165
|
if int(window_size) <= 0:
|
|
@@ -210,6 +214,8 @@ def _resolve_encode_fn(
|
|
|
210
214
|
) -> Callable[[torch.Tensor], torch.Tensor]:
|
|
211
215
|
if feature_kind == "patch_features":
|
|
212
216
|
return model.encode_tiles_dense
|
|
217
|
+
if feature_kind == "patch_features_prenorm":
|
|
218
|
+
return model.encode_tiles_dense_prenorm
|
|
213
219
|
if feature_kind == "cls_attention":
|
|
214
220
|
blocks = tuple(int(b) for b in attention_blocks)
|
|
215
221
|
include_registers = bool(attention_include_registers)
|
|
@@ -221,7 +227,8 @@ def _resolve_encode_fn(
|
|
|
221
227
|
|
|
222
228
|
return encode_fn
|
|
223
229
|
raise ValueError(
|
|
224
|
-
f"unsupported feature_kind {feature_kind!r}; expected
|
|
230
|
+
f"unsupported feature_kind {feature_kind!r}; expected one of "
|
|
231
|
+
+ ", ".join(repr(kind) for kind in DENSE_FEATURE_KINDS)
|
|
225
232
|
)
|
|
226
233
|
|
|
227
234
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 6.3.
|
|
3
|
+
Version: 6.3.4
|
|
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
|
|
@@ -27,18 +27,18 @@ Requires-Dist: torchvision
|
|
|
27
27
|
Requires-Dist: transformers
|
|
28
28
|
Requires-Dist: wandb
|
|
29
29
|
Requires-Dist: einops
|
|
30
|
-
Requires-Dist: timm
|
|
30
|
+
Requires-Dist: timm>=1.0.3
|
|
31
31
|
Requires-Dist: huggingface_hub
|
|
32
32
|
Provides-Extra: hoptimus
|
|
33
33
|
Requires-Dist: torch>=2.0; extra == "hoptimus"
|
|
34
34
|
Requires-Dist: torchvision>=0.15.0; extra == "hoptimus"
|
|
35
35
|
Requires-Dist: xformers>=0.0.18; extra == "hoptimus"
|
|
36
36
|
Provides-Extra: virchow
|
|
37
|
-
Requires-Dist: timm>=0.
|
|
37
|
+
Requires-Dist: timm>=1.0.3; extra == "virchow"
|
|
38
38
|
Requires-Dist: torch>=2.0; extra == "virchow"
|
|
39
39
|
Provides-Extra: uni
|
|
40
40
|
Requires-Dist: torch>=2.0; extra == "uni"
|
|
41
|
-
Requires-Dist: timm>=0.
|
|
41
|
+
Requires-Dist: timm>=1.0.3; extra == "uni"
|
|
42
42
|
Requires-Dist: xformers>=0.0.18; extra == "uni"
|
|
43
43
|
Provides-Extra: prism
|
|
44
44
|
Requires-Dist: transformers~=4.53.0; extra == "prism"
|
|
@@ -157,6 +157,7 @@ tests/test_output_consistency.py
|
|
|
157
157
|
tests/test_patch_size_metadata.py
|
|
158
158
|
tests/test_patient_annotation_sampling.py
|
|
159
159
|
tests/test_patient_manifest.py
|
|
160
|
+
tests/test_patient_tiling_only.py
|
|
160
161
|
tests/test_phaet.py
|
|
161
162
|
tests/test_pooled_encoder_input.py
|
|
162
163
|
tests/test_pooled_geometry.py
|
|
@@ -10,7 +10,7 @@ torchvision
|
|
|
10
10
|
transformers
|
|
11
11
|
wandb
|
|
12
12
|
einops
|
|
13
|
-
timm
|
|
13
|
+
timm>=1.0.3
|
|
14
14
|
huggingface_hub
|
|
15
15
|
|
|
16
16
|
[docs]
|
|
@@ -96,11 +96,11 @@ transformers==4.46.0
|
|
|
96
96
|
|
|
97
97
|
[uni]
|
|
98
98
|
torch>=2.0
|
|
99
|
-
timm>=0.
|
|
99
|
+
timm>=1.0.3
|
|
100
100
|
xformers>=0.0.18
|
|
101
101
|
|
|
102
102
|
[virchow]
|
|
103
|
-
timm>=0.
|
|
103
|
+
timm>=1.0.3
|
|
104
104
|
torch>=2.0
|
|
105
105
|
|
|
106
106
|
[waiv]
|
|
@@ -247,6 +247,21 @@ def test_prepare_dense_encoder_rejects_unsupported_attention_before_loading(
|
|
|
247
247
|
assert load_attempted is False
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
@pytest.mark.parametrize(("model_name", "target_size"), [("phikon", 224), ("hibou-b", 224)])
|
|
251
|
+
def test_prepare_dense_encoder_rejects_unsupported_prenorm_before_loading(
|
|
252
|
+
model_name, target_size, monkeypatch
|
|
253
|
+
):
|
|
254
|
+
model = Model(name=model_name, device="cpu")
|
|
255
|
+
monkeypatch.setattr(
|
|
256
|
+
model, "_load_backend", lambda: (_ for _ in ()).throw(AssertionError("must not load"))
|
|
257
|
+
)
|
|
258
|
+
with pytest.raises(ValueError, match="does not support patch_features_prenorm"):
|
|
259
|
+
model.prepare_dense_encoder(
|
|
260
|
+
dense=_dense_image(target_size=target_size, feature_kind="patch_features_prenorm"),
|
|
261
|
+
execution=ExecutionOptions(num_gpus=1, precision="fp32"),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
250
265
|
def test_prepare_dense_encoder_rejects_non_tile_models_before_loading(monkeypatch):
|
|
251
266
|
model = Model(name="gigapath-slide", device="cpu")
|
|
252
267
|
load_attempted = False
|
|
@@ -22,7 +22,7 @@ PIL = pytest.importorskip("PIL")
|
|
|
22
22
|
|
|
23
23
|
from PIL import Image # noqa: E402
|
|
24
24
|
|
|
25
|
-
from slide2vec.api import DenseImageOptions, ExecutionOptions, ImageSpec # noqa: E402
|
|
25
|
+
from slide2vec.api import DenseImageOptions, ExecutionOptions, ImageSpec, Model # noqa: E402
|
|
26
26
|
from slide2vec.encoders.base import TimmTileEncoder # noqa: E402
|
|
27
27
|
from slide2vec.runtime import dense_image_stage # noqa: E402
|
|
28
28
|
from slide2vec.runtime.types import LoadedModel # noqa: E402
|
|
@@ -1085,3 +1085,32 @@ def test_worker_encodes_only_its_rank_shard(tmp_path, monkeypatch):
|
|
|
1085
1085
|
# World of 2 ranks: plan_contiguous_shards([a,b,c,d], 2) => rank 1 owns [c, d].
|
|
1086
1086
|
dense_dir = tmp_path / "out" / "dense_image_embeddings"
|
|
1087
1087
|
assert sorted(p.name for p in dense_dir.glob("*.pt")) == ["c.pt", "d.pt"]
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def test_embed_images_dense_rejects_unsupported_prenorm_before_loading_or_resume(
|
|
1091
|
+
tmp_path, monkeypatch
|
|
1092
|
+
):
|
|
1093
|
+
"""An unsupported feature kind fails from registry metadata on the persisted image route.
|
|
1094
|
+
|
|
1095
|
+
Phikon is not a timm ViT, so ``patch_features_prenorm`` is rejected before the backend
|
|
1096
|
+
loads and before resume inspects the output dir: an existing post-norm payload and its
|
|
1097
|
+
sidecar are left untouched (the earlier behaviour deleted the sidecar first).
|
|
1098
|
+
"""
|
|
1099
|
+
model = Model(name="phikon", device="cpu")
|
|
1100
|
+
monkeypatch.setattr(
|
|
1101
|
+
model, "_load_backend", lambda: (_ for _ in ()).throw(AssertionError("must not load"))
|
|
1102
|
+
)
|
|
1103
|
+
specs = _images(tmp_path, ["a"])
|
|
1104
|
+
out_dir = tmp_path / "out"
|
|
1105
|
+
existing = out_dir / "dense_image_embeddings"
|
|
1106
|
+
existing.mkdir(parents=True)
|
|
1107
|
+
(existing / "a.pt").write_bytes(b"payload")
|
|
1108
|
+
(existing / "a.meta.json").write_text("{}")
|
|
1109
|
+
with pytest.raises(ValueError, match="does not support patch_features_prenorm"):
|
|
1110
|
+
model.embed_images_dense(
|
|
1111
|
+
specs,
|
|
1112
|
+
dense=_dense(feature_kind="patch_features_prenorm"),
|
|
1113
|
+
execution=ExecutionOptions(num_gpus=1, precision="fp32", output_dir=out_dir),
|
|
1114
|
+
)
|
|
1115
|
+
assert (existing / "a.pt").read_bytes() == b"payload"
|
|
1116
|
+
assert (existing / "a.meta.json").read_text() == "{}"
|
|
@@ -161,7 +161,7 @@ def _make_tiling_result(
|
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
@pytest.mark.parametrize("feature_kind", ["patch_features", "cls_attention"])
|
|
164
|
+
@pytest.mark.parametrize("feature_kind", ["patch_features", "patch_features_prenorm", "cls_attention"])
|
|
165
165
|
@pytest.mark.parametrize("window_size", [None, 32], ids=["whole", "window32"])
|
|
166
166
|
def test_iter_regions_dense_yields_grid_per_coordinate_in_order(fake_backend, window_size, feature_kind):
|
|
167
167
|
enc = _encoder()
|
|
@@ -235,6 +235,8 @@ def _reference_grid(
|
|
|
235
235
|
batch = padded.unsqueeze(0)
|
|
236
236
|
if feature_kind == "patch_features":
|
|
237
237
|
encode_fn = enc.encode_tiles_dense
|
|
238
|
+
elif feature_kind == "patch_features_prenorm":
|
|
239
|
+
encode_fn = enc.encode_tiles_dense_prenorm
|
|
238
240
|
else:
|
|
239
241
|
encode_fn = enc.encode_tiles_attention
|
|
240
242
|
with torch.inference_mode():
|
|
@@ -248,7 +250,7 @@ def _reference_grid(
|
|
|
248
250
|
return out.detach().float().cpu().numpy()[0]
|
|
249
251
|
|
|
250
252
|
|
|
251
|
-
@pytest.mark.parametrize("feature_kind", ["patch_features", "cls_attention"])
|
|
253
|
+
@pytest.mark.parametrize("feature_kind", ["patch_features", "patch_features_prenorm", "cls_attention"])
|
|
252
254
|
@pytest.mark.parametrize("window_size", [None, 32], ids=["whole", "window32"])
|
|
253
255
|
def test_iter_regions_dense_matches_direct_encode(fake_backend, window_size, feature_kind):
|
|
254
256
|
"""Each yielded grid is byte-identical to a hand-rolled transform+pad+encode.
|
|
@@ -276,7 +278,7 @@ def test_iter_regions_dense_matches_direct_encode(fake_backend, window_size, fea
|
|
|
276
278
|
np.testing.assert_array_equal(grid, ref)
|
|
277
279
|
|
|
278
280
|
|
|
279
|
-
@pytest.mark.parametrize("feature_kind", ["patch_features", "cls_attention"])
|
|
281
|
+
@pytest.mark.parametrize("feature_kind", ["patch_features", "patch_features_prenorm", "cls_attention"])
|
|
280
282
|
def test_iter_regions_dense_area_resizes_when_read_differs_from_requested(fake_backend, feature_kind):
|
|
281
283
|
"""When ``read_tile_size_px != requested_tile_size_px`` the region is area-resized.
|
|
282
284
|
|
|
@@ -333,7 +335,7 @@ def test_iter_regions_dense_empty_coordinates_yields_nothing(fake_backend):
|
|
|
333
335
|
assert fake_backend.open_count == 0 # nothing read -> slide never opened
|
|
334
336
|
|
|
335
337
|
|
|
336
|
-
@pytest.mark.parametrize("feature_kind", ["patch_features", "cls_attention"])
|
|
338
|
+
@pytest.mark.parametrize("feature_kind", ["patch_features", "patch_features_prenorm", "cls_attention"])
|
|
337
339
|
@pytest.mark.parametrize("window_size", [None, 32], ids=["whole", "window32"])
|
|
338
340
|
def test_iter_regions_dense_streams_one_batch_at_a_time(fake_backend, window_size, feature_kind):
|
|
339
341
|
"""Reads advance one batch at a time; first grids land before all coords are read.
|
|
@@ -369,7 +371,7 @@ def test_iter_regions_dense_streams_one_batch_at_a_time(fake_backend, window_siz
|
|
|
369
371
|
assert len(backend.locations_read) == len(coords) # total reads never exceed the coordinate count
|
|
370
372
|
|
|
371
373
|
|
|
372
|
-
@pytest.mark.parametrize("feature_kind", ["patch_features", "cls_attention"])
|
|
374
|
+
@pytest.mark.parametrize("feature_kind", ["patch_features", "patch_features_prenorm", "cls_attention"])
|
|
373
375
|
def test_iter_regions_dense_is_batch_invariant(fake_backend, feature_kind):
|
|
374
376
|
"""Composition is irrelevant: only ``B`` matters, not how coords are grouped (adr/0002).
|
|
375
377
|
|
|
@@ -448,3 +450,64 @@ def test_iter_regions_dense_rejects_bfloat16_output_eagerly(fake_backend):
|
|
|
448
450
|
)
|
|
449
451
|
assert fake_backend.backend.locations_read == []
|
|
450
452
|
assert fake_backend.open_count == 0
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@pytest.mark.parametrize("model_name", ["vit_tiny_patch16_224", "vit_small_patch14_reg4_dinov2"])
|
|
456
|
+
def test_prenorm_dense_grid_is_the_last_block_output_before_the_final_norm(model_name):
|
|
457
|
+
"""``encode_tiles_dense_prenorm`` is ``encode_tiles_dense`` minus the backbone's final norm.
|
|
458
|
+
|
|
459
|
+
Applying the backbone's own ``norm`` to the pre-norm grid reproduces the post-norm grid
|
|
460
|
+
exactly (timm's ``features_only`` tap), for a plain ViT and a register-token ViT.
|
|
461
|
+
"""
|
|
462
|
+
enc = TimmTileEncoder(model_name, pretrained=False, num_classes=0, dynamic_img_size=True)
|
|
463
|
+
patch = enc.patch_size[0]
|
|
464
|
+
batch = torch.randn(2, 3, 4 * patch, 3 * patch, generator=torch.Generator().manual_seed(0))
|
|
465
|
+
with torch.inference_mode():
|
|
466
|
+
post = enc.encode_tiles_dense(batch)
|
|
467
|
+
pre = enc.encode_tiles_dense_prenorm(batch)
|
|
468
|
+
normed = enc._model.norm(pre.permute(0, 2, 3, 1)).permute(0, 3, 1, 2)
|
|
469
|
+
assert tuple(pre.shape) == tuple(post.shape) == (2, enc.encode_dim, 4, 3)
|
|
470
|
+
assert not torch.allclose(pre, post)
|
|
471
|
+
assert torch.allclose(normed, post, atol=1e-5)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_prenorm_dense_grid_has_literal_values_for_a_fixed_backbone():
|
|
475
|
+
"""Literal oracle: with every weight zeroed, each block adds only its MLP output bias.
|
|
476
|
+
|
|
477
|
+
Penultimate block bias 5, last block bias 3, final LayerNorm bias 2: the pre-norm grid
|
|
478
|
+
must be the last block's output ``5 + 3 = 8`` everywhere (not the penultimate block's
|
|
479
|
+
5, and not the normed 2), and the post-norm grid must be the LayerNorm bias 2 (a
|
|
480
|
+
constant input has zero variance, so only the bias survives).
|
|
481
|
+
"""
|
|
482
|
+
enc = _encoder()
|
|
483
|
+
with torch.no_grad():
|
|
484
|
+
for parameter in enc._model.parameters():
|
|
485
|
+
parameter.zero_()
|
|
486
|
+
enc._model.blocks[-2].mlp.fc2.bias.fill_(5.0)
|
|
487
|
+
enc._model.blocks[-1].mlp.fc2.bias.fill_(3.0)
|
|
488
|
+
enc._model.norm.bias.fill_(2.0)
|
|
489
|
+
batch = torch.ones(2, 3, 32, 48)
|
|
490
|
+
with torch.inference_mode():
|
|
491
|
+
pre = enc.encode_tiles_dense_prenorm(batch)
|
|
492
|
+
post = enc.encode_tiles_dense(batch)
|
|
493
|
+
assert tuple(pre.shape) == tuple(post.shape) == (2, enc.encode_dim, 2, 3)
|
|
494
|
+
torch.testing.assert_close(pre, torch.full_like(pre, 8.0))
|
|
495
|
+
torch.testing.assert_close(post, torch.full_like(post, 2.0))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def test_prenorm_dense_rejects_batches_not_divisible_by_the_patch_size():
|
|
499
|
+
enc = _encoder()
|
|
500
|
+
with pytest.raises(ValueError, match="divisible by the patch size"):
|
|
501
|
+
enc.encode_tiles_dense_prenorm(torch.randn(1, 3, 40, 32))
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_validate_dense_request_settings_rejects_unknown_feature_kind():
|
|
505
|
+
from slide2vec.runtime.dense_regions import validate_dense_request_settings
|
|
506
|
+
|
|
507
|
+
geometry = compute_dense_geometry(target_size=32, patch_size=(16, 16))
|
|
508
|
+
with pytest.raises(ValueError, match="unsupported feature_kind 'tokens'"):
|
|
509
|
+
validate_dense_request_settings(
|
|
510
|
+
geometry, pad_mode="reflect", window_size=None,
|
|
511
|
+
overlap=0.0, feature_kind="tokens", attention_blocks=(-1,),
|
|
512
|
+
attention_include_registers=False,
|
|
513
|
+
)
|