slide2vec 6.3.3__tar.gz → 7.0.0__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.3 → slide2vec-7.0.0}/PKG-INFO +6 -6
- {slide2vec-6.3.3 → slide2vec-7.0.0}/pyproject.toml +8 -8
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/__init__.py +3 -1
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/api.py +60 -29
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/artifacts.py +38 -18
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/default.yaml +2 -2
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/resources.py +0 -7
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/tile_reader.py +0 -1
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/direct_embed_worker.py +10 -29
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/image_worker.py +11 -1
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/pipeline_worker.py +5 -11
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/base.py +49 -9
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/registry.py +40 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/artifacts_collect.py +10 -30
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/batching.py +1 -14
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_encode.py +1 -21
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_shard.py +5 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_regions.py +11 -4
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_shard.py +12 -12
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding_pipeline.py +54 -86
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/feature_identity.py +70 -31
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/image_shard.py +31 -55
- slide2vec-7.0.0/slide2vec/runtime/image_stage.py +433 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/persist_callbacks.py +20 -33
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/registry.py +0 -11
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/serialization.py +2 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/tiling.py +0 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/worker_io.py +0 -8
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/config.py +7 -3
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/PKG-INFO +6 -6
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/SOURCES.txt +1 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/requires.txt +5 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_benchmark_tooling.py +57 -17
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_encode_kit.py +15 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_resume.py +7 -8
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_shard.py +4 -3
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_stage.py +30 -1
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_regions.py +72 -9
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_shard.py +5 -3
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_stage.py +39 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_capabilities.py +34 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_feature_identity.py +59 -22
- slide2vec-7.0.0/tests/test_hierarchical_encoding.py +281 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_hs2p5_integration.py +41 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_image_shard.py +23 -50
- slide2vec-7.0.0/tests/test_image_stage.py +1557 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_on_slide_persisted.py +74 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_identity_api.py +73 -10
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_core.py +88 -5
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_inference.py +169 -125
- slide2vec-6.3.3/slide2vec/runtime/image_stage.py +0 -197
- slide2vec-6.3.3/tests/test_image_stage.py +0 -427
- {slide2vec-6.3.3 → slide2vec-7.0.0}/LICENSE +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/README.md +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/setup.cfg +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/__main__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/_cufile.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/cli.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/cufile.json +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/inference.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/progress.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_gigapath.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_isight.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_mascaret.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_mettle.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_patient_tiling_only.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_phaet.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_prism.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_prism2.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_progress.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_regression_models.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_sharding.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_slide_latents.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_tile_store.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_titan.py +0 -0
- {slide2vec-6.3.3 → slide2vec-7.0.0}/tests/test_worker_gpu_pinning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0
|
|
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
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0
|
|
19
19
|
Requires-Dist: omegaconf
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: numpy<2
|
|
@@ -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"
|
|
@@ -75,7 +75,7 @@ Requires-Dist: numpy<2; extra == "fm"
|
|
|
75
75
|
Requires-Dist: pandas; extra == "fm"
|
|
76
76
|
Requires-Dist: pillow; extra == "fm"
|
|
77
77
|
Requires-Dist: rich; extra == "fm"
|
|
78
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0; extra == "fm"
|
|
79
79
|
Requires-Dist: wandb; extra == "fm"
|
|
80
80
|
Requires-Dist: torch<2.8,>=2.3; extra == "fm"
|
|
81
81
|
Requires-Dist: torchvision>=0.18.0; extra == "fm"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "
|
|
7
|
+
version = "7.0.0"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=
|
|
24
|
+
"hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0",
|
|
25
25
|
"omegaconf",
|
|
26
26
|
"matplotlib",
|
|
27
27
|
"numpy<2",
|
|
@@ -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 = [
|
|
@@ -100,7 +100,7 @@ fm = [
|
|
|
100
100
|
"pandas",
|
|
101
101
|
"pillow",
|
|
102
102
|
"rich",
|
|
103
|
-
"hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=
|
|
103
|
+
"hs2p[asap,cucim,openslide,sam2,tifffile,vips]>=6.0.0",
|
|
104
104
|
"wandb",
|
|
105
105
|
"torch>=2.3,<2.8",
|
|
106
106
|
"torchvision>=0.18.0",
|
|
@@ -150,7 +150,7 @@ include = ["slide2vec*"]
|
|
|
150
150
|
|
|
151
151
|
[tool.setuptools.package-data]
|
|
152
152
|
slide2vec = ["py.typed", "cufile.json"]
|
|
153
|
-
"slide2vec.configs" = ["*.yaml"
|
|
153
|
+
"slide2vec.configs" = ["*.yaml"]
|
|
154
154
|
|
|
155
155
|
[tool.pytest.ini_options]
|
|
156
156
|
addopts = "--cov=slide2vec -m 'not gpu_integration'"
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "
|
|
183
|
+
current_version = "7.0.0"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -25,13 +25,14 @@ from slide2vec.artifacts import (
|
|
|
25
25
|
TileEmbeddingArtifact,
|
|
26
26
|
)
|
|
27
27
|
from slide2vec.runtime.dense_encode import DenseEncodeGeometry, DenseEncodeKit
|
|
28
|
+
from slide2vec.runtime.feature_identity import MISSING_FIELD
|
|
28
29
|
from slide2vec.encoders import (
|
|
29
30
|
EncoderProviderDiagnostic,
|
|
30
31
|
list_encoder_provider_diagnostics,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
__version__ = "
|
|
35
|
+
__version__ = "7.0.0"
|
|
35
36
|
|
|
36
37
|
__all__ = [
|
|
37
38
|
"Model",
|
|
@@ -56,5 +57,6 @@ __all__ = [
|
|
|
56
57
|
"DenseRegionArtifact",
|
|
57
58
|
"DenseImageArtifact",
|
|
58
59
|
"ImageEmbeddingArtifact",
|
|
60
|
+
"MISSING_FIELD",
|
|
59
61
|
"__version__",
|
|
60
62
|
]
|
|
@@ -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 (
|
|
@@ -65,19 +66,6 @@ SlideSequence = Sequence[SlideInput]
|
|
|
65
66
|
TilingResultsInput = Sequence[Any] | Mapping[str, Any]
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
#: Default annotation-mask vocabulary — plain binary tissue tiling. Mirrors hs2p's
|
|
69
|
-
#: shipped default ``{background: 0, tissue: 1}``; leaving it untouched keeps a run
|
|
70
|
-
#: behaving exactly as a tissue-only run. ``min_coverage.tissue`` is the single source
|
|
71
|
-
#: of truth for the tissue threshold (the standalone ``tissue_threshold`` knob is gone).
|
|
72
|
-
#: A :class:`PreprocessingConfig` ``masks`` value is deep-merged over this default, so
|
|
73
|
-
#: callers only state what they override (e.g. ``{"min_coverage": {"tissue": 0.1}}``).
|
|
74
|
-
DEFAULT_MASKS: dict[str, Any] = {
|
|
75
|
-
"output_mode": "per_annotation",
|
|
76
|
-
"pixel_mapping": {"background": 0, "tissue": 1},
|
|
77
|
-
"colors": {"background": None, "tissue": [157, 219, 129]},
|
|
78
|
-
"min_coverage": {"background": None, "tissue": 0.01},
|
|
79
|
-
}
|
|
80
|
-
|
|
81
69
|
_REQUESTED_TILE_SIZE_INTERPOLATION = "${tiling.params.requested_tile_size_px}"
|
|
82
70
|
|
|
83
71
|
|
|
@@ -91,6 +79,7 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
|
|
|
91
79
|
("segmentation", "seg_params"),
|
|
92
80
|
("filtering", "filter_params"),
|
|
93
81
|
("preview", "preview"),
|
|
82
|
+
("masks", "masks"),
|
|
94
83
|
):
|
|
95
84
|
section = OmegaConf.to_container(getattr(tiling, config_name), resolve=False)
|
|
96
85
|
if not isinstance(section, dict):
|
|
@@ -106,6 +95,13 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
|
|
|
106
95
|
#: ``configs/default.yaml`` so Python and YAML entry points share one source.
|
|
107
96
|
DEFAULT_PREPROCESSING = _load_default_preprocessing()
|
|
108
97
|
|
|
98
|
+
#: Default annotation-mask vocabulary, loaded from ``configs/default.yaml``: plain
|
|
99
|
+
#: binary tissue tiling (``{background: 0, tissue: 1}``) with
|
|
100
|
+
#: ``min_coverage.tissue = 0.1``. ``min_coverage.tissue`` is the single source of truth
|
|
101
|
+
#: for the tissue threshold. A :class:`PreprocessingConfig` ``masks`` value is
|
|
102
|
+
#: deep-merged over this default, so callers only state what they override.
|
|
103
|
+
DEFAULT_MASKS: dict[str, Any] = DEFAULT_PREPROCESSING["masks"]
|
|
104
|
+
|
|
109
105
|
|
|
110
106
|
def _deep_merge_dicts(base: Mapping[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
|
|
111
107
|
"""Deep-merge *override* onto a copy of *base* (nested dicts merge key-by-key)."""
|
|
@@ -212,7 +208,7 @@ class PreprocessingConfig:
|
|
|
212
208
|
#: Annotation-mask vocabulary forwarded to hs2p's sampling resolver. Keys:
|
|
213
209
|
#: ``output_mode``, ``pixel_mapping``, ``colors``, ``min_coverage``. A partial
|
|
214
210
|
#: mapping is deep-merged over :data:`DEFAULT_MASKS`, so callers only state what
|
|
215
|
-
#: they override (e.g. ``{"min_coverage": {"tissue": 0.
|
|
211
|
+
#: they override (e.g. ``{"min_coverage": {"tissue": 0.5}}``). The default
|
|
216
212
|
#: ``{background, tissue}`` block is plain tissue tiling; ``min_coverage.tissue``
|
|
217
213
|
#: is the single source of truth for the tissue threshold.
|
|
218
214
|
masks: dict[str, Any] = field(default_factory=dict)
|
|
@@ -310,6 +306,9 @@ class PreprocessingConfig:
|
|
|
310
306
|
return replace(self, mask_backend=mask_backend)
|
|
311
307
|
|
|
312
308
|
|
|
309
|
+
#: Accepted values of :attr:`ExecutionOptions.on_image_mismatch`.
|
|
310
|
+
IMAGE_MISMATCH_POLICIES = ("raise", "reencode")
|
|
311
|
+
|
|
313
312
|
|
|
314
313
|
@dataclass(frozen=True, kw_only=True)
|
|
315
314
|
class ExecutionOptions:
|
|
@@ -346,6 +345,10 @@ class ExecutionOptions:
|
|
|
346
345
|
#: Return and persist the slide encoder's latent representations (PRISM). Encoders
|
|
347
346
|
#: without latents ignore it.
|
|
348
347
|
save_latents: bool = False
|
|
348
|
+
#: :meth:`Model.embed_images` only: what to do when an existing image embedding
|
|
349
|
+
#: records a different source ``image_path`` for its ``sample_id``. ``"raise"``
|
|
350
|
+
#: (default) refuses to resume; ``"reencode"`` replaces that image's artifacts.
|
|
351
|
+
on_image_mismatch: str = "raise"
|
|
349
352
|
|
|
350
353
|
@classmethod
|
|
351
354
|
def from_config(cls, cfg: Any, *, run_on_cpu: bool = False) -> "ExecutionOptions":
|
|
@@ -379,8 +382,18 @@ class ExecutionOptions:
|
|
|
379
382
|
object.__setattr__(self, "output_dtype", normalize_output_dtype(self.output_dtype))
|
|
380
383
|
if resolved_num_gpus < 1:
|
|
381
384
|
raise ValueError("ExecutionOptions.num_gpus must be at least 1")
|
|
385
|
+
if self.batch_size < 1:
|
|
386
|
+
raise ValueError("ExecutionOptions.batch_size must be at least 1")
|
|
382
387
|
if self.prefetch_factor < 1:
|
|
383
388
|
raise ValueError("ExecutionOptions.prefetch_factor must be at least 1")
|
|
389
|
+
if self.num_workers_per_gpu is not None and self.num_workers_per_gpu < 0:
|
|
390
|
+
raise ValueError("ExecutionOptions.num_workers_per_gpu must be non-negative")
|
|
391
|
+
if self.on_image_mismatch not in IMAGE_MISMATCH_POLICIES:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
"ExecutionOptions.on_image_mismatch must be one of "
|
|
394
|
+
f"{', '.join(repr(policy) for policy in IMAGE_MISMATCH_POLICIES)}; "
|
|
395
|
+
f"got {self.on_image_mismatch!r}"
|
|
396
|
+
)
|
|
384
397
|
cap = cpu_worker_limit()
|
|
385
398
|
cpu_count = os.cpu_count() or 1
|
|
386
399
|
slurm_limit = slurm_cpu_limit()
|
|
@@ -457,8 +470,10 @@ class DenseOptions:
|
|
|
457
470
|
#: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
|
|
458
471
|
#: ``window_size is None``).
|
|
459
472
|
overlap: float = 0.0
|
|
460
|
-
#: ``"patch_features"`` (the patch-token grid
|
|
461
|
-
#:
|
|
473
|
+
#: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
|
|
474
|
+
#: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
|
|
475
|
+
#: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
|
|
476
|
+
#: (CLS/register self-attention grid).
|
|
462
477
|
feature_kind: str = "patch_features"
|
|
463
478
|
#: Transformer blocks whose CLS attention is read (``cls_attention`` only).
|
|
464
479
|
attention_blocks: tuple[int, ...] = (-1,)
|
|
@@ -508,8 +523,10 @@ class DenseImageOptions:
|
|
|
508
523
|
#: Fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
|
|
509
524
|
#: ``window_size is None``).
|
|
510
525
|
overlap: float = 0.0
|
|
511
|
-
#: ``"patch_features"`` (the patch-token grid
|
|
512
|
-
#:
|
|
526
|
+
#: ``"patch_features"`` (the patch-token grid after the backbone's final norm),
|
|
527
|
+
#: ``"patch_features_prenorm"`` (the same grid tapped *before* the final norm, timm's
|
|
528
|
+
#: ``features_only`` tap; timm ViT encoders only) or ``"cls_attention"``
|
|
529
|
+
#: (CLS/register self-attention grid).
|
|
513
530
|
feature_kind: str = "patch_features"
|
|
514
531
|
#: Transformer blocks whose CLS attention is read (``cls_attention`` only).
|
|
515
532
|
attention_blocks: tuple[int, ...] = (-1,)
|
|
@@ -694,22 +711,26 @@ class Model:
|
|
|
694
711
|
*,
|
|
695
712
|
preprocessing: PreprocessingConfig | None,
|
|
696
713
|
execution: ExecutionOptions | None = None,
|
|
714
|
+
encoded_pixels: bool = True,
|
|
697
715
|
) -> dict[str, tuple[Any, Any]]:
|
|
698
716
|
"""Compare a pooled sidecar's ``compatibility`` block with the current recipe.
|
|
699
717
|
|
|
700
|
-
Returns ``{field: (recorded, current)}`` for
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
718
|
+
Returns ``{field: (recorded, current)}`` for every required field that the
|
|
719
|
+
record holds with a different value or does not hold. A field the record does
|
|
720
|
+
not hold is reported as :data:`slide2vec.MISSING_FIELD`, which is distinct
|
|
721
|
+
from a recorded ``None``. An empty result means the record proves the current
|
|
722
|
+
identity. The caller owns the policy for a difference.
|
|
704
723
|
|
|
705
724
|
Explicitly pass ``preprocessing=None`` for pre-cropped images, whose shipped
|
|
706
725
|
transform determines geometry. Pass a :class:`PreprocessingConfig` for
|
|
707
726
|
tiles read from slides; its unset fields resolve as in pooled extraction.
|
|
708
727
|
``execution`` defaults to the model's recommended precision and needs no
|
|
709
|
-
output directory.
|
|
728
|
+
output directory. The ``transform`` is required unless ``encoded_pixels`` is
|
|
729
|
+
false: pass ``encoded_pixels=False`` for a zero-tile slide, which encodes no
|
|
730
|
+
pixels and records no transform.
|
|
710
731
|
|
|
711
|
-
|
|
712
|
-
|
|
732
|
+
Comparing the ``transform`` loads weights, on an isolated CPU model. Slide
|
|
733
|
+
and patient models load their registered tile encoder and tile output
|
|
713
734
|
variant, without loading aggregation weights. This method does not change
|
|
714
735
|
this model's input contract or loaded backend, encode pixels, or write files.
|
|
715
736
|
"""
|
|
@@ -724,6 +745,7 @@ class Model:
|
|
|
724
745
|
dict(recorded),
|
|
725
746
|
execution=resolved_execution,
|
|
726
747
|
preprocessing=resolved_preprocessing,
|
|
748
|
+
encoded_pixels=encoded_pixels,
|
|
727
749
|
)
|
|
728
750
|
|
|
729
751
|
def prepare_dense_encoder(
|
|
@@ -1019,8 +1041,11 @@ class Model:
|
|
|
1019
1041
|
decoded, preprocessed with the encoder's **shipped** transform, encoded, and written
|
|
1020
1042
|
to ``image_embeddings/<sample_id>.pt`` plus a provenance sidecar. The run splits its
|
|
1021
1043
|
images across all visible GPUs (``execution.num_gpus``); ``num_gpus=1`` encodes
|
|
1022
|
-
fully in-process. Resume is automatic
|
|
1023
|
-
|
|
1044
|
+
fully in-process. Resume is automatic: an image is skipped only when its sidecar
|
|
1045
|
+
records this ``output_format``, the requested ``image_path`` and the full feature
|
|
1046
|
+
identity. A different recorded feature identity raises; a different recorded
|
|
1047
|
+
``image_path`` raises unless ``execution.on_image_mismatch="reencode"``; missing
|
|
1048
|
+
provenance re-encodes. Returns
|
|
1024
1049
|
one :class:`~slide2vec.artifacts.ImageEmbeddingArtifact` per input image, in input
|
|
1025
1050
|
order.
|
|
1026
1051
|
|
|
@@ -1091,14 +1116,20 @@ class Model:
|
|
|
1091
1116
|
parent stage and every torchrun rank — declares for itself.
|
|
1092
1117
|
|
|
1093
1118
|
*dense* is a :class:`DenseOptions` (ROIs on a slide) or a :class:`DenseImageOptions`
|
|
1094
|
-
(pre-cropped images); only the supervision geometry
|
|
1095
|
-
|
|
1119
|
+
(pre-cropped images); only the supervision geometry and the requested feature kind
|
|
1120
|
+
are read here, and the two state them the same way.
|
|
1121
|
+
|
|
1122
|
+
The feature kind is checked against the registered encoder class at the same
|
|
1123
|
+
point, so a tap this encoder does not implement (``patch_features_prenorm`` on a
|
|
1124
|
+
non-timm backbone, say) fails before any backend is loaded and before resume
|
|
1125
|
+
inspects or invalidates an existing artifact.
|
|
1096
1126
|
"""
|
|
1097
1127
|
contract = EncoderInputContract.declared_dense(
|
|
1098
1128
|
self.name,
|
|
1099
1129
|
target_size_px=dense.target_size,
|
|
1100
1130
|
window_size=None if dense.window_size is None else int(dense.window_size),
|
|
1101
1131
|
)
|
|
1132
|
+
validate_feature_kind_capability(self.name, feature_kind=str(dense.feature_kind))
|
|
1102
1133
|
self._encoder_input = contract
|
|
1103
1134
|
plan = contract.plan
|
|
1104
1135
|
if emit_run_info and plan.requires_variable_model_input:
|
|
@@ -137,7 +137,8 @@ class ImageEmbeddingArtifact:
|
|
|
137
137
|
return load_metadata(self.metadata_path)
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
def
|
|
140
|
+
def normalize_output_format(output_format: str) -> str:
|
|
141
|
+
"""The canonical (lowercase) spelling of a supported ``output_format``."""
|
|
141
142
|
normalized = output_format.lower()
|
|
142
143
|
if normalized not in {"pt", "npz"}:
|
|
143
144
|
raise ValueError(f"Unsupported output format: {output_format}")
|
|
@@ -403,27 +404,46 @@ def write_dense_image(
|
|
|
403
404
|
)
|
|
404
405
|
|
|
405
406
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
) -> tuple[Path, Path]:
|
|
409
|
-
"""``(payload_path, sidecar_path)`` for one given-geometry image.
|
|
407
|
+
#: Payload formats an image embedding can be written in; they share one sidecar.
|
|
408
|
+
IMAGE_EMBEDDING_FORMATS = ("pt", "npz")
|
|
410
409
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
410
|
+
|
|
411
|
+
def image_embeddings_dir(output_dir: str | Path) -> Path:
|
|
412
|
+
"""The resolved ``image_embeddings/`` directory, refusing one that escapes ``output_dir``.
|
|
413
|
+
|
|
414
|
+
A run resolves it once and names each image with :func:`image_embedding_names`.
|
|
415
415
|
"""
|
|
416
416
|
output_root = Path(output_dir).expanduser().resolve()
|
|
417
|
-
sample_component = _validate_path_component(sample_id, field="sample_id")
|
|
418
417
|
embeddings_dir = (output_root / "image_embeddings").resolve()
|
|
419
418
|
if not embeddings_dir.is_relative_to(output_root):
|
|
420
419
|
raise ValueError("Image artifact path must stay within output_dir")
|
|
420
|
+
return embeddings_dir
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def image_embedding_names(sample_id: str, *, output_format: str) -> tuple[str, str]:
|
|
424
|
+
"""``(payload_name, sidecar_name)`` of one image inside :func:`image_embeddings_dir`."""
|
|
425
|
+
sample_component = _validate_path_component(sample_id, field="sample_id")
|
|
421
426
|
return (
|
|
422
|
-
|
|
423
|
-
|
|
427
|
+
f"{sample_component}.{normalize_output_format(output_format)}",
|
|
428
|
+
f"{sample_component}.meta.json",
|
|
424
429
|
)
|
|
425
430
|
|
|
426
431
|
|
|
432
|
+
def image_embedding_paths(
|
|
433
|
+
output_dir: str | Path, *, sample_id: str, output_format: str
|
|
434
|
+
) -> tuple[Path, Path]:
|
|
435
|
+
"""``(payload_path, sidecar_path)`` for one given-geometry image.
|
|
436
|
+
|
|
437
|
+
``image_embeddings/<sample_id>.{pt,npz}`` plus ``image_embeddings/<sample_id>.meta.json``.
|
|
438
|
+
A given-geometry run has no slide, no coordinate and no annotation class — the caller's
|
|
439
|
+
``sample_id`` is the whole identity — so the layout is flat. Both payload formats share
|
|
440
|
+
the sidecar, which records the ``format`` it certifies.
|
|
441
|
+
"""
|
|
442
|
+
payload_name, sidecar_name = image_embedding_names(sample_id, output_format=output_format)
|
|
443
|
+
embeddings_dir = image_embeddings_dir(output_dir)
|
|
444
|
+
return embeddings_dir / payload_name, embeddings_dir / sidecar_name
|
|
445
|
+
|
|
446
|
+
|
|
427
447
|
def write_image_embedding(
|
|
428
448
|
embedding,
|
|
429
449
|
*,
|
|
@@ -440,7 +460,7 @@ def write_image_embedding(
|
|
|
440
460
|
ROI write follows (:func:`write_dense_region`), and the reason a rank can be killed
|
|
441
461
|
mid-shard without poisoning the output.
|
|
442
462
|
"""
|
|
443
|
-
output_format =
|
|
463
|
+
output_format = normalize_output_format(output_format)
|
|
444
464
|
payload_path, metadata_path = image_embedding_paths(
|
|
445
465
|
output_dir, sample_id=sample_id, output_format=output_format
|
|
446
466
|
)
|
|
@@ -520,7 +540,7 @@ def write_tile_embeddings(
|
|
|
520
540
|
tile_index: Any | None = None,
|
|
521
541
|
annotation: str | None = None,
|
|
522
542
|
) -> TileEmbeddingArtifact:
|
|
523
|
-
output_format =
|
|
543
|
+
output_format = normalize_output_format(output_format)
|
|
524
544
|
artifact_path, metadata_path = _setup_artifact_paths(
|
|
525
545
|
output_dir, tile_embeddings_subdir(annotation), sample_id, output_format
|
|
526
546
|
)
|
|
@@ -563,7 +583,7 @@ def write_tile_embedding_metadata(
|
|
|
563
583
|
metadata: dict[str, Any] | None = None,
|
|
564
584
|
annotation: str | None = None,
|
|
565
585
|
) -> Path:
|
|
566
|
-
output_format =
|
|
586
|
+
output_format = normalize_output_format(output_format)
|
|
567
587
|
_, metadata_path = _setup_artifact_paths(
|
|
568
588
|
output_dir, tile_embeddings_subdir(annotation), sample_id, output_format
|
|
569
589
|
)
|
|
@@ -589,7 +609,7 @@ def write_slide_embeddings(
|
|
|
589
609
|
latents: Any | None = None,
|
|
590
610
|
annotation: str | None = None,
|
|
591
611
|
) -> SlideEmbeddingArtifact:
|
|
592
|
-
output_format =
|
|
612
|
+
output_format = normalize_output_format(output_format)
|
|
593
613
|
annotation = structural_artifact_annotation(annotation)
|
|
594
614
|
artifact_path, metadata_path = _setup_artifact_paths(
|
|
595
615
|
output_dir, slide_embeddings_subdir(annotation), sample_id, output_format
|
|
@@ -638,7 +658,7 @@ def write_patient_embeddings(
|
|
|
638
658
|
metadata: dict[str, Any] | None = None,
|
|
639
659
|
num_slides: int = 0,
|
|
640
660
|
) -> PatientEmbeddingArtifact:
|
|
641
|
-
output_format =
|
|
661
|
+
output_format = normalize_output_format(output_format)
|
|
642
662
|
artifact_path, metadata_path = _setup_artifact_paths(
|
|
643
663
|
output_dir, "patient_embeddings", patient_id, output_format
|
|
644
664
|
)
|
|
@@ -677,7 +697,7 @@ def write_hierarchical_embeddings(
|
|
|
677
697
|
metadata: dict[str, Any] | None = None,
|
|
678
698
|
annotation: str | None = None,
|
|
679
699
|
) -> HierarchicalEmbeddingArtifact:
|
|
680
|
-
output_format =
|
|
700
|
+
output_format = normalize_output_format(output_format)
|
|
681
701
|
annotation = structural_artifact_annotation(annotation)
|
|
682
702
|
artifact_path, metadata_path = _setup_artifact_paths(
|
|
683
703
|
output_dir, hierarchical_embeddings_subdir(annotation), sample_id, output_format
|
|
@@ -9,7 +9,7 @@ save_tiles: false # save extracted tiles as {sample_id}.tiles.tar in addition to
|
|
|
9
9
|
seed: 0 # seed for reproducibility
|
|
10
10
|
|
|
11
11
|
model:
|
|
12
|
-
name: # preset model name; see docs/models.
|
|
12
|
+
name: # preset model name; see docs/models.rst for supported request strings
|
|
13
13
|
output_variant: # requested output variant for presets that expose multiple outputs
|
|
14
14
|
batch_size: 32
|
|
15
15
|
save_tile_embeddings: false # whether to save tile embeddings alongside the pooled slide embedding when level is "slide"
|
|
@@ -18,7 +18,7 @@ model:
|
|
|
18
18
|
allow_non_recommended_settings: false # when true, non-recommended spacing / tile size / precision combinations warn instead of erroring
|
|
19
19
|
|
|
20
20
|
tiling:
|
|
21
|
-
on_the_fly: true # read tiles directly from WSI during embedding
|
|
21
|
+
on_the_fly: true # read tiles directly from the WSI during embedding; when false, embedding reads tiles from {sample_id}.tiles.tar
|
|
22
22
|
gpu_decode: false # GPU-accelerated batch decoding via device="cuda" in cucim read_region; set true to opt in when the runtime is configured for it (experimental, may not be stable across all environments)
|
|
23
23
|
adaptive_batching: false # when true, vary batch size to align with super tile boundaries (avoids redundant reads but batch size fluctuates)
|
|
24
24
|
use_supertiles: true # group tiles into 8x8/4x4/2x2 super tile reads to reduce WSI read calls (on-the-fly path only)
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from contextlib import contextmanager
|
|
2
1
|
from pathlib import Path
|
|
3
|
-
from typing import Iterator
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
def config_resource(*parts: str):
|
|
@@ -16,8 +14,3 @@ def load_config(*parts: str):
|
|
|
16
14
|
resource = config_resource(*parts)
|
|
17
15
|
with resource.open("r", encoding="utf-8") as handle:
|
|
18
16
|
return OmegaConf.load(handle)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@contextmanager
|
|
22
|
-
def config_path(*parts: str) -> Iterator[Path]:
|
|
23
|
-
yield config_resource(*parts)
|
|
@@ -429,7 +429,6 @@ class OnTheFlyHierarchicalBatchCollator:
|
|
|
429
429
|
):
|
|
430
430
|
self._region_index = np.asarray(region_index, dtype=np.int32)
|
|
431
431
|
self._subtile_index_within_region = np.asarray(subtile_index_within_region, dtype=np.int32)
|
|
432
|
-
self._tiles_per_region = int(self._subtile_index_within_region.max()) + 1 if len(self._subtile_index_within_region) else 0
|
|
433
432
|
self._read_tile_size_px = int(read_tile_size_px)
|
|
434
433
|
self._requested_tile_size_px = int(requested_tile_size_px)
|
|
435
434
|
self._reader = WSIRegionReader(
|
|
@@ -17,8 +17,12 @@ def main(argv=None) -> int:
|
|
|
17
17
|
import torch
|
|
18
18
|
|
|
19
19
|
import slide2vec.distributed as distributed
|
|
20
|
-
import slide2vec.inference as inference
|
|
21
20
|
from slide2vec.api import Model
|
|
21
|
+
from slide2vec.runtime.embedding_pipeline import (
|
|
22
|
+
compute_embedded_slides,
|
|
23
|
+
compute_hierarchical_embedding_shard_for_slide,
|
|
24
|
+
compute_tile_embeddings_for_slide,
|
|
25
|
+
)
|
|
22
26
|
from slide2vec.runtime.hierarchical import (
|
|
23
27
|
build_hierarchical_index,
|
|
24
28
|
is_hierarchical_preprocessing,
|
|
@@ -56,11 +60,9 @@ def main(argv=None) -> int:
|
|
|
56
60
|
)
|
|
57
61
|
from slide2vec.runtime.embedding import tiling_result_annotation
|
|
58
62
|
from slide2vec.runtime.feature_identity import transform_record
|
|
63
|
+
from slide2vec.runtime.manifest import load_successful_tiled_slides
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
if not callable(load_successful_tiled_slides_fn):
|
|
62
|
-
from slide2vec.runtime.manifest import load_successful_tiled_slides as load_successful_tiled_slides_fn
|
|
63
|
-
slide_records, tiling_results = load_successful_tiled_slides_fn(output_dir)
|
|
65
|
+
slide_records, tiling_results = load_successful_tiled_slides(output_dir)
|
|
64
66
|
# Key by the composite (sample_id, annotation) work unit so a multi-class slide's sibling
|
|
65
67
|
# classes never overwrite each other; flat units collapse to the bare sample_id key.
|
|
66
68
|
paired_by_unit = {
|
|
@@ -95,16 +97,7 @@ def main(argv=None) -> int:
|
|
|
95
97
|
tile_size_lv0=int(geometry["tile_size_lv0"]),
|
|
96
98
|
)
|
|
97
99
|
flat_indices = np.array_split(index.flat_index, world_size)[global_rank]
|
|
98
|
-
|
|
99
|
-
inference,
|
|
100
|
-
"_compute_hierarchical_embedding_shard_for_slide",
|
|
101
|
-
None,
|
|
102
|
-
)
|
|
103
|
-
if not callable(compute_hierarchical_embedding_shard_for_slide_fn):
|
|
104
|
-
from slide2vec.runtime.embedding_pipeline import (
|
|
105
|
-
compute_hierarchical_embedding_shard_for_slide as compute_hierarchical_embedding_shard_for_slide_fn,
|
|
106
|
-
)
|
|
107
|
-
shard_indices, tile_embeddings = compute_hierarchical_embedding_shard_for_slide_fn(
|
|
100
|
+
shard_indices, tile_embeddings = compute_hierarchical_embedding_shard_for_slide(
|
|
108
101
|
loaded,
|
|
109
102
|
slide,
|
|
110
103
|
tiling_result,
|
|
@@ -122,16 +115,7 @@ def main(argv=None) -> int:
|
|
|
122
115
|
else:
|
|
123
116
|
num_tiles = len(tiling_result.x)
|
|
124
117
|
tile_indices = np.array_split(np.arange(num_tiles, dtype=np.int64), world_size)[global_rank]
|
|
125
|
-
|
|
126
|
-
inference,
|
|
127
|
-
"_compute_tile_embeddings_for_slide",
|
|
128
|
-
None,
|
|
129
|
-
)
|
|
130
|
-
if not callable(compute_tile_embeddings_for_slide_fn):
|
|
131
|
-
from slide2vec.runtime.embedding_pipeline import (
|
|
132
|
-
compute_tile_embeddings_for_slide as compute_tile_embeddings_for_slide_fn,
|
|
133
|
-
)
|
|
134
|
-
tile_embeddings = compute_tile_embeddings_for_slide_fn(
|
|
118
|
+
tile_embeddings = compute_tile_embeddings_for_slide(
|
|
135
119
|
loaded,
|
|
136
120
|
model,
|
|
137
121
|
slide,
|
|
@@ -170,10 +154,7 @@ def main(argv=None) -> int:
|
|
|
170
154
|
)
|
|
171
155
|
torch.save(payload, coordination_dir / f"{stem}.embedded.pt")
|
|
172
156
|
|
|
173
|
-
|
|
174
|
-
if not callable(compute_embedded_slides_fn):
|
|
175
|
-
from slide2vec.runtime.embedding_pipeline import compute_embedded_slides as compute_embedded_slides_fn
|
|
176
|
-
compute_embedded_slides_fn(
|
|
157
|
+
compute_embedded_slides(
|
|
177
158
|
model,
|
|
178
159
|
assigned_slides,
|
|
179
160
|
assigned_tiling_results,
|
|
@@ -55,7 +55,7 @@ def main(argv=None) -> int:
|
|
|
55
55
|
def _on_batch(count: int) -> None:
|
|
56
56
|
emit_progress("images.batch.finished", rank=rank.global_rank, images=int(count))
|
|
57
57
|
|
|
58
|
-
run_image_shard(
|
|
58
|
+
artifacts = run_image_shard(
|
|
59
59
|
shard,
|
|
60
60
|
loaded=loaded,
|
|
61
61
|
out_dir=output_dir,
|
|
@@ -67,7 +67,17 @@ def main(argv=None) -> int:
|
|
|
67
67
|
num_workers=execution.resolved_image_num_workers_per_gpu(),
|
|
68
68
|
prefetch_factor=int(execution.prefetch_factor),
|
|
69
69
|
on_batch=_on_batch,
|
|
70
|
+
# The parent planned what to invalidate; the shard loop deletes each image's
|
|
71
|
+
# files right before writing it, so a failed launch, load or sibling rank costs
|
|
72
|
+
# at most the images in flight.
|
|
73
|
+
stale=request["stale"],
|
|
70
74
|
)
|
|
75
|
+
# The parent collects artifacts without reading sidecars: report the vector width.
|
|
76
|
+
result_path = Path(request["result_dir"]) / f"image_result.rank{rank.global_rank}.json"
|
|
77
|
+
result_path.write_text(
|
|
78
|
+
json.dumps({"feature_dim": int(artifacts[0].feature_dim), "images": len(artifacts)}),
|
|
79
|
+
encoding="utf-8",
|
|
80
|
+
)
|
|
71
81
|
return 0
|
|
72
82
|
|
|
73
83
|
|
|
@@ -16,8 +16,9 @@ def get_args_parser(add_help: bool = True) -> argparse.ArgumentParser:
|
|
|
16
16
|
|
|
17
17
|
def main(argv=None) -> int:
|
|
18
18
|
import slide2vec.distributed as distributed
|
|
19
|
-
import slide2vec.inference as inference
|
|
20
19
|
from slide2vec.api import Model
|
|
20
|
+
from slide2vec.runtime.embedding_pipeline import compute_embedded_slides
|
|
21
|
+
from slide2vec.runtime.manifest import load_successful_tiled_slides
|
|
21
22
|
from slide2vec.runtime.persist_callbacks import build_incremental_persist_callback
|
|
22
23
|
from slide2vec.progress import JsonlProgressReporter, activate_progress_reporter
|
|
23
24
|
from slide2vec.runtime.serialization import deserialize_execution, deserialize_preprocessing
|
|
@@ -44,10 +45,7 @@ def main(argv=None) -> int:
|
|
|
44
45
|
execution = deserialize_execution(request["execution"])
|
|
45
46
|
model._declare_encoder_input(preprocessing, emit_run_info=False)
|
|
46
47
|
tiling_input_dir = Path(request.get("tiling_input_dir", str(output_dir)))
|
|
47
|
-
|
|
48
|
-
if not callable(load_successful_tiled_slides_fn):
|
|
49
|
-
from slide2vec.runtime.manifest import load_successful_tiled_slides as load_successful_tiled_slides_fn
|
|
50
|
-
slide_records, tiling_results = load_successful_tiled_slides_fn(tiling_input_dir)
|
|
48
|
+
slide_records, tiling_results = load_successful_tiled_slides(tiling_input_dir)
|
|
51
49
|
# Each (sample_id, annotation) row is an independent work unit; key by the composite so a
|
|
52
50
|
# multi-class slide's sibling classes never overwrite each other. Flat units (None / tissue /
|
|
53
51
|
# merged) encode to the bare sample_id, byte-identical to pre-#168 single-class runs.
|
|
@@ -83,17 +81,13 @@ def main(argv=None) -> int:
|
|
|
83
81
|
)
|
|
84
82
|
context = activate_progress_reporter(reporter) if reporter is not None else nullcontext()
|
|
85
83
|
with context:
|
|
86
|
-
|
|
87
|
-
persist_callback, _, _ = build_incremental_persist_callback_fn(
|
|
84
|
+
persist_callback, _, _ = build_incremental_persist_callback(
|
|
88
85
|
model=model,
|
|
89
86
|
preprocessing=preprocessing,
|
|
90
87
|
execution=execution,
|
|
91
88
|
process_list_path=None,
|
|
92
89
|
)
|
|
93
|
-
|
|
94
|
-
if not callable(compute_embedded_slides_fn):
|
|
95
|
-
from slide2vec.runtime.embedding_pipeline import compute_embedded_slides as compute_embedded_slides_fn
|
|
96
|
-
compute_embedded_slides_fn(
|
|
90
|
+
compute_embedded_slides(
|
|
97
91
|
model,
|
|
98
92
|
assigned_slides,
|
|
99
93
|
assigned_tiling_results,
|