slide2vec 6.3.4__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.4 → slide2vec-7.0.0}/PKG-INFO +3 -3
- {slide2vec-6.3.4 → slide2vec-7.0.0}/pyproject.toml +5 -5
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/__init__.py +3 -1
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/api.py +43 -23
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/artifacts.py +38 -18
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/configs/default.yaml +2 -2
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/configs/resources.py +0 -7
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/data/tile_reader.py +0 -1
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/direct_embed_worker.py +10 -29
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/image_worker.py +11 -1
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/pipeline_worker.py +5 -11
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/artifacts_collect.py +10 -30
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/batching.py +1 -14
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_shard.py +5 -5
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_shard.py +12 -12
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/embedding_pipeline.py +54 -86
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/feature_identity.py +70 -31
- {slide2vec-6.3.4 → 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.4 → slide2vec-7.0.0}/slide2vec/runtime/persist_callbacks.py +20 -33
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/registry.py +0 -11
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/serialization.py +2 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/tiling.py +0 -5
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/worker_io.py +0 -8
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/config.py +7 -3
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/SOURCES.txt +1 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/requires.txt +2 -2
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_benchmark_tooling.py +57 -17
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_image_resume.py +7 -8
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_image_shard.py +4 -3
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_regions.py +4 -4
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_shard.py +5 -3
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_stage.py +8 -5
- {slide2vec-6.3.4 → 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.4 → slide2vec-7.0.0}/tests/test_hs2p5_integration.py +41 -0
- {slide2vec-6.3.4 → 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.4 → slide2vec-7.0.0}/tests/test_on_slide_persisted.py +74 -5
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_pooled_identity_api.py +73 -10
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_regression_core.py +88 -5
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_regression_inference.py +169 -125
- slide2vec-6.3.4/slide2vec/runtime/image_stage.py +0 -197
- slide2vec-6.3.4/tests/test_image_stage.py +0 -427
- {slide2vec-6.3.4 → slide2vec-7.0.0}/LICENSE +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/README.md +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/setup.cfg +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/__main__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/_cufile.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/cli.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/cufile.json +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/inference.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/progress.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_gigapath.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_isight.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_mascaret.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_mettle.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_patient_tiling_only.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_phaet.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_prism.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_prism2.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_progress.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_regression_models.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_sharding.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_slide_latents.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_tile_store.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.3.4 → slide2vec-7.0.0}/tests/test_titan.py +0 -0
- {slide2vec-6.3.4 → 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
|
|
@@ -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",
|
|
@@ -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
|
]
|
|
@@ -66,19 +66,6 @@ SlideSequence = Sequence[SlideInput]
|
|
|
66
66
|
TilingResultsInput = Sequence[Any] | Mapping[str, Any]
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
#: Default annotation-mask vocabulary — plain binary tissue tiling. Mirrors hs2p's
|
|
70
|
-
#: shipped default ``{background: 0, tissue: 1}``; leaving it untouched keeps a run
|
|
71
|
-
#: behaving exactly as a tissue-only run. ``min_coverage.tissue`` is the single source
|
|
72
|
-
#: of truth for the tissue threshold (the standalone ``tissue_threshold`` knob is gone).
|
|
73
|
-
#: A :class:`PreprocessingConfig` ``masks`` value is deep-merged over this default, so
|
|
74
|
-
#: callers only state what they override (e.g. ``{"min_coverage": {"tissue": 0.1}}``).
|
|
75
|
-
DEFAULT_MASKS: dict[str, Any] = {
|
|
76
|
-
"output_mode": "per_annotation",
|
|
77
|
-
"pixel_mapping": {"background": 0, "tissue": 1},
|
|
78
|
-
"colors": {"background": None, "tissue": [157, 219, 129]},
|
|
79
|
-
"min_coverage": {"background": None, "tissue": 0.01},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
69
|
_REQUESTED_TILE_SIZE_INTERPOLATION = "${tiling.params.requested_tile_size_px}"
|
|
83
70
|
|
|
84
71
|
|
|
@@ -92,6 +79,7 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
|
|
|
92
79
|
("segmentation", "seg_params"),
|
|
93
80
|
("filtering", "filter_params"),
|
|
94
81
|
("preview", "preview"),
|
|
82
|
+
("masks", "masks"),
|
|
95
83
|
):
|
|
96
84
|
section = OmegaConf.to_container(getattr(tiling, config_name), resolve=False)
|
|
97
85
|
if not isinstance(section, dict):
|
|
@@ -107,6 +95,13 @@ def _load_default_preprocessing() -> dict[str, dict[str, Any]]:
|
|
|
107
95
|
#: ``configs/default.yaml`` so Python and YAML entry points share one source.
|
|
108
96
|
DEFAULT_PREPROCESSING = _load_default_preprocessing()
|
|
109
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
|
+
|
|
110
105
|
|
|
111
106
|
def _deep_merge_dicts(base: Mapping[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
|
|
112
107
|
"""Deep-merge *override* onto a copy of *base* (nested dicts merge key-by-key)."""
|
|
@@ -213,7 +208,7 @@ class PreprocessingConfig:
|
|
|
213
208
|
#: Annotation-mask vocabulary forwarded to hs2p's sampling resolver. Keys:
|
|
214
209
|
#: ``output_mode``, ``pixel_mapping``, ``colors``, ``min_coverage``. A partial
|
|
215
210
|
#: mapping is deep-merged over :data:`DEFAULT_MASKS`, so callers only state what
|
|
216
|
-
#: they override (e.g. ``{"min_coverage": {"tissue": 0.
|
|
211
|
+
#: they override (e.g. ``{"min_coverage": {"tissue": 0.5}}``). The default
|
|
217
212
|
#: ``{background, tissue}`` block is plain tissue tiling; ``min_coverage.tissue``
|
|
218
213
|
#: is the single source of truth for the tissue threshold.
|
|
219
214
|
masks: dict[str, Any] = field(default_factory=dict)
|
|
@@ -311,6 +306,9 @@ class PreprocessingConfig:
|
|
|
311
306
|
return replace(self, mask_backend=mask_backend)
|
|
312
307
|
|
|
313
308
|
|
|
309
|
+
#: Accepted values of :attr:`ExecutionOptions.on_image_mismatch`.
|
|
310
|
+
IMAGE_MISMATCH_POLICIES = ("raise", "reencode")
|
|
311
|
+
|
|
314
312
|
|
|
315
313
|
@dataclass(frozen=True, kw_only=True)
|
|
316
314
|
class ExecutionOptions:
|
|
@@ -347,6 +345,10 @@ class ExecutionOptions:
|
|
|
347
345
|
#: Return and persist the slide encoder's latent representations (PRISM). Encoders
|
|
348
346
|
#: without latents ignore it.
|
|
349
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"
|
|
350
352
|
|
|
351
353
|
@classmethod
|
|
352
354
|
def from_config(cls, cfg: Any, *, run_on_cpu: bool = False) -> "ExecutionOptions":
|
|
@@ -380,8 +382,18 @@ class ExecutionOptions:
|
|
|
380
382
|
object.__setattr__(self, "output_dtype", normalize_output_dtype(self.output_dtype))
|
|
381
383
|
if resolved_num_gpus < 1:
|
|
382
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")
|
|
383
387
|
if self.prefetch_factor < 1:
|
|
384
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
|
+
)
|
|
385
397
|
cap = cpu_worker_limit()
|
|
386
398
|
cpu_count = os.cpu_count() or 1
|
|
387
399
|
slurm_limit = slurm_cpu_limit()
|
|
@@ -699,22 +711,26 @@ class Model:
|
|
|
699
711
|
*,
|
|
700
712
|
preprocessing: PreprocessingConfig | None,
|
|
701
713
|
execution: ExecutionOptions | None = None,
|
|
714
|
+
encoded_pixels: bool = True,
|
|
702
715
|
) -> dict[str, tuple[Any, Any]]:
|
|
703
716
|
"""Compare a pooled sidecar's ``compatibility`` block with the current recipe.
|
|
704
717
|
|
|
705
|
-
Returns ``{field: (recorded, current)}`` for
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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.
|
|
709
723
|
|
|
710
724
|
Explicitly pass ``preprocessing=None`` for pre-cropped images, whose shipped
|
|
711
725
|
transform determines geometry. Pass a :class:`PreprocessingConfig` for
|
|
712
726
|
tiles read from slides; its unset fields resolve as in pooled extraction.
|
|
713
727
|
``execution`` defaults to the model's recommended precision and needs no
|
|
714
|
-
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.
|
|
715
731
|
|
|
716
|
-
|
|
717
|
-
|
|
732
|
+
Comparing the ``transform`` loads weights, on an isolated CPU model. Slide
|
|
733
|
+
and patient models load their registered tile encoder and tile output
|
|
718
734
|
variant, without loading aggregation weights. This method does not change
|
|
719
735
|
this model's input contract or loaded backend, encode pixels, or write files.
|
|
720
736
|
"""
|
|
@@ -729,6 +745,7 @@ class Model:
|
|
|
729
745
|
dict(recorded),
|
|
730
746
|
execution=resolved_execution,
|
|
731
747
|
preprocessing=resolved_preprocessing,
|
|
748
|
+
encoded_pixels=encoded_pixels,
|
|
732
749
|
)
|
|
733
750
|
|
|
734
751
|
def prepare_dense_encoder(
|
|
@@ -1024,8 +1041,11 @@ class Model:
|
|
|
1024
1041
|
decoded, preprocessed with the encoder's **shipped** transform, encoded, and written
|
|
1025
1042
|
to ``image_embeddings/<sample_id>.pt`` plus a provenance sidecar. The run splits its
|
|
1026
1043
|
images across all visible GPUs (``execution.num_gpus``); ``num_gpus=1`` encodes
|
|
1027
|
-
fully in-process. Resume is automatic
|
|
1028
|
-
|
|
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
|
|
1029
1049
|
one :class:`~slide2vec.artifacts.ImageEmbeddingArtifact` per input image, in input
|
|
1030
1050
|
order.
|
|
1031
1051
|
|
|
@@ -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,
|
|
@@ -7,7 +7,7 @@ import pandas as pd
|
|
|
7
7
|
from hs2p import SlideSpec
|
|
8
8
|
from hs2p.fileops import read_csv_keyed_by_sample_id
|
|
9
9
|
|
|
10
|
-
from slide2vec.api import
|
|
10
|
+
from slide2vec.api import ExecutionOptions, PreprocessingConfig
|
|
11
11
|
from slide2vec.artifacts import (
|
|
12
12
|
HierarchicalEmbeddingArtifact,
|
|
13
13
|
normalize_artifact_annotation,
|
|
@@ -15,7 +15,6 @@ from slide2vec.artifacts import (
|
|
|
15
15
|
TileEmbeddingArtifact,
|
|
16
16
|
)
|
|
17
17
|
from slide2vec.runtime.distributed_stage import run_distributed_embedding_stage
|
|
18
|
-
from slide2vec.runtime.embedding_persist import persist_embedded_slide
|
|
19
18
|
from slide2vec.runtime.feature_identity import (
|
|
20
19
|
deferred_transform_record,
|
|
21
20
|
pooled_feature_identity,
|
|
@@ -34,34 +33,6 @@ from slide2vec.runtime.process_list import resolved_process_list_output_variant
|
|
|
34
33
|
from slide2vec.progress import emit_progress
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
def collect_local_pipeline_artifacts(
|
|
38
|
-
*,
|
|
39
|
-
model,
|
|
40
|
-
embedded_slides: Sequence[EmbeddedSlide],
|
|
41
|
-
tiling_results,
|
|
42
|
-
preprocessing: PreprocessingConfig,
|
|
43
|
-
execution: ExecutionOptions,
|
|
44
|
-
) -> tuple[list[TileEmbeddingArtifact], list[HierarchicalEmbeddingArtifact], list[SlideEmbeddingArtifact]]:
|
|
45
|
-
tile_artifacts: list[TileEmbeddingArtifact] = []
|
|
46
|
-
hierarchical_artifacts: list[HierarchicalEmbeddingArtifact] = []
|
|
47
|
-
slide_artifacts: list[SlideEmbeddingArtifact] = []
|
|
48
|
-
for embedded_slide, tiling_result in zip(embedded_slides, tiling_results):
|
|
49
|
-
tile_artifact, slide_artifact = persist_embedded_slide(
|
|
50
|
-
model,
|
|
51
|
-
embedded_slide,
|
|
52
|
-
tiling_result,
|
|
53
|
-
preprocessing=preprocessing,
|
|
54
|
-
execution=execution,
|
|
55
|
-
)
|
|
56
|
-
if isinstance(tile_artifact, HierarchicalEmbeddingArtifact):
|
|
57
|
-
hierarchical_artifacts.append(tile_artifact)
|
|
58
|
-
elif tile_artifact is not None:
|
|
59
|
-
tile_artifacts.append(tile_artifact)
|
|
60
|
-
if slide_artifact is not None:
|
|
61
|
-
slide_artifacts.append(slide_artifact)
|
|
62
|
-
return tile_artifacts, hierarchical_artifacts, slide_artifacts
|
|
63
|
-
|
|
64
|
-
|
|
65
36
|
def _embeddable_annotation_groups(
|
|
66
37
|
process_list_path: Path,
|
|
67
38
|
) -> dict[str, list[str | None]]:
|
|
@@ -216,6 +187,11 @@ def collect_distributed_pipeline_artifacts(
|
|
|
216
187
|
)
|
|
217
188
|
slide_by_sample_id = {slide.sample_id: slide for slide in successful_slides}
|
|
218
189
|
live_updated_sample_ids: set[str] = set()
|
|
190
|
+
# Pending classes not yet reported finished, per sample. Resume can requeue a class whose
|
|
191
|
+
# payload is still on disk (unverifiable provenance), so file existence alone is not completion.
|
|
192
|
+
unfinished_annotations: dict[str, set[str | None]] = {}
|
|
193
|
+
for slide, annotation in zip(pending_slides, pending_annotations):
|
|
194
|
+
unfinished_annotations.setdefault(slide.sample_id, set()).add(annotation)
|
|
219
195
|
|
|
220
196
|
def _update_process_list_for_finished_slide(event) -> None:
|
|
221
197
|
if getattr(event, "kind", None) != "embedding.slide.finished":
|
|
@@ -225,6 +201,10 @@ def collect_distributed_pipeline_artifacts(
|
|
|
225
201
|
slide = slide_by_sample_id.get(sample_id)
|
|
226
202
|
if slide is None or sample_id in live_updated_sample_ids:
|
|
227
203
|
return
|
|
204
|
+
unfinished = unfinished_annotations.get(sample_id, set())
|
|
205
|
+
unfinished.discard(_normalized_row_annotation(payload.get("annotation")))
|
|
206
|
+
if unfinished:
|
|
207
|
+
return
|
|
228
208
|
# A multi-class slide emits one finished event per (sample_id, annotation) item, and the
|
|
229
209
|
# other classes may still be running on another rank. Wait until *every* annotation row for
|
|
230
210
|
# the sample is persisted on disk before the whole-sample rewrite, so we never (a) load a
|