slide2vec 5.8.1__tar.gz → 5.9.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-5.8.1 → slide2vec-5.9.0}/PKG-INFO +3 -3
- {slide2vec-5.8.1 → slide2vec-5.9.0}/pyproject.toml +13 -9
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/__init__.py +1 -1
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/api.py +9 -9
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/data/tile_reader.py +0 -1
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/__init__.py +0 -3
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/gigapath.py +5 -7
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/slide.py +9 -17
- slide2vec-5.9.0/slide2vec/encoders/models/titan.py +180 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/inference.py +19 -53
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/artifacts_collect.py +7 -8
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/batching.py +1 -9
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_sliding.py +2 -7
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/model_settings.py +2 -6
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/persist_callbacks.py +3 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/log_utils.py +4 -9
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/tiling_io.py +0 -2
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/SOURCES.txt +4 -1
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/requires.txt +2 -2
- slide2vec-5.9.0/tests/test_architecture_runtime_split.py +19 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_sliding.py +4 -7
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_hs2p_package_cutover.py +3 -3
- slide2vec-5.9.0/tests/test_load_model_hf_auth.py +113 -0
- slide2vec-5.9.0/tests/test_on_slide_persisted.py +383 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_regression_inference.py +1 -5
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_regression_models.py +1 -1
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_soma_migration.py +0 -40
- slide2vec-5.9.0/tests/test_titan.py +134 -0
- slide2vec-5.8.1/slide2vec/encoders/models/titan.py +0 -58
- slide2vec-5.8.1/tests/test_architecture_runtime_split.py +0 -60
- {slide2vec-5.8.1 → slide2vec-5.9.0}/LICENSE +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/README.md +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/setup.cfg +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/__main__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/cli.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/progress.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_image_shard.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_image_stage.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_isight.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_mascaret.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_phaet.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_prism2.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_progress.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_regression_core.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_sharding.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_tile_store.py +0 -0
- {slide2vec-5.8.1 → slide2vec-5.9.0}/tests/test_tiling_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.9.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,vips]>=4.4.
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.3
|
|
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,vips]>=4.4.
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.3; 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 = "5.
|
|
7
|
+
version = "5.9.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,vips]>=4.4.
|
|
24
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.3",
|
|
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,vips]>=4.4.
|
|
103
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.3",
|
|
104
104
|
"wandb",
|
|
105
105
|
"torch>=2.3,<2.8",
|
|
106
106
|
"torchvision>=0.18.0",
|
|
@@ -158,7 +158,7 @@ testpaths = [
|
|
|
158
158
|
"tests",
|
|
159
159
|
]
|
|
160
160
|
markers = [
|
|
161
|
-
"heavy: real-weight foundation-model inference on CPU; minutes per test.
|
|
161
|
+
"heavy: real-weight foundation-model inference on CPU; minutes per test. Included in the PR suite and available separately through the manual heavy workflow (.github/workflows/nightly-heavy.yaml).",
|
|
162
162
|
"gpu_integration: real one-GPU versus multi-GPU parity; requires at least two visible CUDA devices. Run explicitly with `CUDA_VISIBLE_DEVICES=0,1 python -m pytest -m gpu_integration --no-cov`.",
|
|
163
163
|
]
|
|
164
164
|
|
|
@@ -180,14 +180,15 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "5.
|
|
183
|
+
current_version = "5.9.0"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
|
-
commit = false
|
|
186
|
-
tag = false
|
|
187
|
-
push = false
|
|
185
|
+
commit = false
|
|
186
|
+
tag = false
|
|
187
|
+
push = false
|
|
188
188
|
files = [
|
|
189
189
|
"pyproject.toml",
|
|
190
|
-
"slide2vec/__init__.py"
|
|
190
|
+
"slide2vec/__init__.py",
|
|
191
|
+
"docs/conf.py",
|
|
191
192
|
]
|
|
192
193
|
|
|
193
194
|
[tool.bumpver.file_patterns]
|
|
@@ -198,3 +199,6 @@ files = [
|
|
|
198
199
|
"slide2vec/__init__.py" = [
|
|
199
200
|
'^__version__ = "{version}"$',
|
|
200
201
|
]
|
|
202
|
+
"docs/conf.py" = [
|
|
203
|
+
'^release = "{version}"$',
|
|
204
|
+
]
|
|
@@ -7,7 +7,7 @@ import warnings
|
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Mapping, Protocol, Sequence
|
|
10
|
+
from typing import Any, Callable, Mapping, Protocol, Sequence
|
|
11
11
|
|
|
12
12
|
import torch
|
|
13
13
|
from hs2p import SlideSpec
|
|
@@ -128,15 +128,12 @@ def resolve_masks(masks: Mapping[str, Any] | None) -> dict[str, Any]:
|
|
|
128
128
|
|
|
129
129
|
def _masks_to_plain_dict(node: Any) -> dict[str, Any]:
|
|
130
130
|
"""Normalize a masks config node (OmegaConf, mapping, or namespace) to a plain dict."""
|
|
131
|
+
from omegaconf import OmegaConf
|
|
132
|
+
|
|
131
133
|
if node is None:
|
|
132
134
|
return {}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if OmegaConf.is_config(node):
|
|
137
|
-
return copy.deepcopy(OmegaConf.to_container(node, resolve=True)) # type: ignore[return-value]
|
|
138
|
-
except ImportError:
|
|
139
|
-
pass
|
|
135
|
+
if OmegaConf.is_config(node):
|
|
136
|
+
return copy.deepcopy(OmegaConf.to_container(node, resolve=True)) # type: ignore[return-value]
|
|
140
137
|
if isinstance(node, Mapping):
|
|
141
138
|
return copy.deepcopy(dict(node))
|
|
142
139
|
return copy.deepcopy(dict(vars(node)))
|
|
@@ -243,7 +240,6 @@ class PreprocessingConfig:
|
|
|
243
240
|
"preview",
|
|
244
241
|
_deep_merge_dicts(DEFAULT_PREPROCESSING["preview"], self.preview),
|
|
245
242
|
)
|
|
246
|
-
# Complete a (possibly partial) masks mapping against the shipped default.
|
|
247
243
|
object.__setattr__(self, "masks", resolve_masks(self.masks))
|
|
248
244
|
|
|
249
245
|
@classmethod
|
|
@@ -710,6 +706,7 @@ class Model:
|
|
|
710
706
|
*,
|
|
711
707
|
preprocessing: PreprocessingConfig | None = None,
|
|
712
708
|
execution: ExecutionOptions | None = None,
|
|
709
|
+
on_slide_persisted: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
713
710
|
) -> list[TileEmbeddingArtifact] | list[HierarchicalEmbeddingArtifact]:
|
|
714
711
|
from slide2vec.inference import embed_tiles
|
|
715
712
|
|
|
@@ -724,6 +721,7 @@ class Model:
|
|
|
724
721
|
tiling_results,
|
|
725
722
|
execution=resolved,
|
|
726
723
|
preprocessing=resolved_preprocessing,
|
|
724
|
+
on_slide_persisted=on_slide_persisted,
|
|
727
725
|
)
|
|
728
726
|
|
|
729
727
|
def aggregate_tiles(
|
|
@@ -1217,6 +1215,7 @@ class Pipeline:
|
|
|
1217
1215
|
coordinates_dir: str | Path,
|
|
1218
1216
|
*,
|
|
1219
1217
|
slides: SlideSequence | None = None,
|
|
1218
|
+
on_slide_persisted: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
1220
1219
|
) -> RunResult:
|
|
1221
1220
|
from slide2vec.inference import run_pipeline_with_coordinates
|
|
1222
1221
|
|
|
@@ -1229,6 +1228,7 @@ class Pipeline:
|
|
|
1229
1228
|
slides=slides,
|
|
1230
1229
|
preprocessing=resolved_preprocessing,
|
|
1231
1230
|
execution=self.execution,
|
|
1231
|
+
on_slide_persisted=on_slide_persisted,
|
|
1232
1232
|
)
|
|
1233
1233
|
|
|
1234
1234
|
|
|
@@ -52,7 +52,6 @@ def _open_wsi_backend(image_path: str, backend: str, gpu_decode: bool):
|
|
|
52
52
|
return VIPSReader(image_path)
|
|
53
53
|
elif backend == "asap":
|
|
54
54
|
from hs2p.wsi.backends.asap import ASAPReader
|
|
55
|
-
from slide2vec.utils.log_utils import suppress_c_stderr
|
|
56
55
|
with suppress_c_stderr():
|
|
57
56
|
return ASAPReader(image_path)
|
|
58
57
|
else:
|
|
@@ -133,7 +133,6 @@ class _TorchDistributedEnvironment:
|
|
|
133
133
|
|
|
134
134
|
# Single node job with preset environment (i.e. torchrun)
|
|
135
135
|
def _set_from_preset_env(self):
|
|
136
|
-
# logger.info("Initialization from preset environment")
|
|
137
136
|
self.rank = int(os.environ["RANK"])
|
|
138
137
|
self.world_size = int(os.environ["WORLD_SIZE"])
|
|
139
138
|
assert self.rank < self.world_size
|
|
@@ -143,7 +142,6 @@ class _TorchDistributedEnvironment:
|
|
|
143
142
|
|
|
144
143
|
# Single node and GPU job (i.e. local script run)
|
|
145
144
|
def _set_from_local(self):
|
|
146
|
-
# logger.info("Initialization from local")
|
|
147
145
|
self.rank = 0
|
|
148
146
|
self.world_size = 1
|
|
149
147
|
self.local_rank = 0
|
|
@@ -196,7 +194,6 @@ def enable(
|
|
|
196
194
|
if set_cuda_current_device:
|
|
197
195
|
torch.cuda.set_device(torch_env.local_rank)
|
|
198
196
|
|
|
199
|
-
# Finalize setup
|
|
200
197
|
_RANK = torch_env.rank
|
|
201
198
|
_WORLD_SIZE = torch_env.world_size
|
|
202
199
|
_LOCAL_RANK = torch_env.local_rank
|
|
@@ -48,13 +48,8 @@ class GigaPath(TimmTileEncoder):
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
def get_transform(self) -> Callable:
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# must NOT route through this — it needs the full uncropped tile so the grid
|
|
54
|
-
# covers the whole source tile. The dense path supplies its own no-crop
|
|
55
|
-
# transform (Resize(256), no CenterCrop) → a 16x16 grid over the full tile;
|
|
56
|
-
# encode_tiles_dense itself is transform-agnostic (inherited from
|
|
57
|
-
# TimmTileEncoder) and operates on whatever batch the dense pipeline feeds.
|
|
51
|
+
# Pooled recipe: center 224px at native spacing. Dense extraction uses
|
|
52
|
+
# get_normalization_transform() to preserve the caller's tile geometry.
|
|
58
53
|
return v2.Compose([
|
|
59
54
|
v2.ToImage(),
|
|
60
55
|
v2.Resize(256, interpolation=v2.InterpolationMode.BICUBIC, antialias=True),
|
|
@@ -123,6 +118,9 @@ class GigaPathSlideEncoder(SlideEncoder):
|
|
|
123
118
|
tile_features = tile_features.unsqueeze(0)
|
|
124
119
|
if coordinates.ndim == 2:
|
|
125
120
|
coordinates = coordinates.unsqueeze(0)
|
|
121
|
+
# prov-gigapath's reference usage feeds fp32 tile embeddings under autocast;
|
|
122
|
+
# fp16 input risks dtype mismatches in ops autocast does not cover.
|
|
123
|
+
tile_features = tile_features.float()
|
|
126
124
|
# gigapath_slide_enc12l768d.forward always returns a list of per-layer
|
|
127
125
|
# embeddings (a single element when all_layer_embed is False); the final
|
|
128
126
|
# slide embedding is the last layer, matching prov-gigapath's own usage.
|
|
@@ -50,7 +50,7 @@ class MOOZYSlideEncoder(nn.Module):
|
|
|
50
50
|
dropout=dropout,
|
|
51
51
|
attn_dropout=attn_dropout,
|
|
52
52
|
alibi=self.pos_bias,
|
|
53
|
-
drop_path_rate=dpr[i]
|
|
53
|
+
drop_path_rate=dpr[i],
|
|
54
54
|
qk_norm=qk_norm,
|
|
55
55
|
layerscale_init=layerscale_init,
|
|
56
56
|
)
|
|
@@ -109,26 +109,18 @@ class MOOZYSlideEncoder(nn.Module):
|
|
|
109
109
|
x = torch.cat(tokens, dim=1)
|
|
110
110
|
|
|
111
111
|
coords_xy = coords_xy.to(device=x.device, dtype=torch.float32).reshape(bsz, n_tokens, 2)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
positions = torch.cat([zeros_cls, zeros_reg, coords_xy], dim=1)
|
|
116
|
-
else:
|
|
117
|
-
positions = torch.cat([zeros_cls, coords_xy], dim=1)
|
|
118
|
-
|
|
119
|
-
valid_flat = ~invalid_flat
|
|
120
|
-
reg_valid = (
|
|
121
|
-
torch.ones(bsz, self.num_registers, dtype=torch.bool, device=x.device)
|
|
122
|
-
if self.num_registers > 0
|
|
123
|
-
else torch.zeros(bsz, 0, dtype=torch.bool, device=x.device)
|
|
112
|
+
num_prefix = 1 + self.num_registers
|
|
113
|
+
prefix_positions = torch.zeros(
|
|
114
|
+
bsz, num_prefix, 2, dtype=coords_xy.dtype, device=coords_xy.device
|
|
124
115
|
)
|
|
125
|
-
|
|
116
|
+
positions = torch.cat([prefix_positions, coords_xy], dim=1)
|
|
117
|
+
prefix_valid = torch.ones(bsz, num_prefix, dtype=torch.bool, device=x.device)
|
|
118
|
+
valid_with_cls = torch.cat([prefix_valid, ~invalid_flat], dim=1)
|
|
126
119
|
|
|
127
120
|
if mask is None and bsz == 1:
|
|
128
121
|
keep = valid_with_cls[0]
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
positions = positions[:, keep, :]
|
|
122
|
+
x = x[:, keep, :]
|
|
123
|
+
positions = positions[:, keep, :]
|
|
132
124
|
attn_mask = None
|
|
133
125
|
else:
|
|
134
126
|
pair_valid = valid_with_cls.unsqueeze(2) & valid_with_cls.unsqueeze(1)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""TITAN slide encoder implementation."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
from transformers import AutoModel
|
|
9
|
+
|
|
10
|
+
from slide2vec.encoders.base import SlideEncoder, preferred_default_device, resolve_requested_output_variant
|
|
11
|
+
from slide2vec.encoders.registry import register_encoder
|
|
12
|
+
|
|
13
|
+
# Pinned so the remote-code patches below stay valid (and so runs are reproducible —
|
|
14
|
+
# an unpinned from_pretrained re-downloads whatever is at the repo HEAD).
|
|
15
|
+
_TITAN_REVISION = "dac6773d9961cfc75503440676ff157a2c6e8d2e"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _alibi_slopes(n: int) -> list[float]:
|
|
19
|
+
# verbatim ALiBi slope schedule from TITAN's get_alibi at the pinned revision
|
|
20
|
+
if math.log2(n).is_integer():
|
|
21
|
+
p = 2 ** (-(2 ** -(math.log2(n) - 3)))
|
|
22
|
+
return [p * (p ** i) for i in range(n)]
|
|
23
|
+
nearest = 2 ** math.floor(math.log2(n))
|
|
24
|
+
base = _alibi_slopes(nearest)
|
|
25
|
+
extra = _alibi_slopes(2 * nearest)[0::2][: n - nearest]
|
|
26
|
+
return base + extra
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _lean_alibi_bias(module, w, h, bg_mask, device, dtype):
|
|
30
|
+
"""Bitwise-identical replacement for TITAN's get_alibi + the caller's cast/move.
|
|
31
|
+
|
|
32
|
+
The reference builds the [1, heads, N, N] bias through N x N float64 numpy on
|
|
33
|
+
the CPU (>100 GB of host RAM for slides in the tens of thousands of tiles) and
|
|
34
|
+
hands SDPA an unaligned bias that forces the math kernel, which materializes a
|
|
35
|
+
second N^2 tensor on the GPU. This builds the same values on-device in fp16, in
|
|
36
|
+
row chunks, with rows padded to a multiple of 8 elements so the memory-efficient
|
|
37
|
+
SDPA kernel accepts the bias — peak memory drops from ~4x to ~1x the bias size.
|
|
38
|
+
"""
|
|
39
|
+
ii, jj = torch.meshgrid(
|
|
40
|
+
torch.arange(w, device=device), torch.arange(h, device=device), indexing="ij"
|
|
41
|
+
)
|
|
42
|
+
if bg_mask is not None:
|
|
43
|
+
mask = bg_mask.to(device).squeeze(0)
|
|
44
|
+
ii, jj = ii[mask], jj[mask]
|
|
45
|
+
points = torch.stack([ii.reshape(-1), jj.reshape(-1)], dim=1).float()
|
|
46
|
+
n = points.shape[0]
|
|
47
|
+
length = n + 1 # +1 for the cls token; its bias row/col stays zero
|
|
48
|
+
padded = ((length + 7) // 8) * 8
|
|
49
|
+
slopes = torch.tensor(
|
|
50
|
+
_alibi_slopes(module.num_heads), device=device, dtype=torch.float32
|
|
51
|
+
).view(module.num_heads, 1, 1)
|
|
52
|
+
bias = torch.zeros(1, module.num_heads, length, padded, device=device, dtype=dtype)
|
|
53
|
+
# chunk intermediate is (heads, step, n) fp32 — stays under ~1.5 GB even at 53k tiles
|
|
54
|
+
step = 512
|
|
55
|
+
for start in range(0, n, step):
|
|
56
|
+
diff = points[start : start + step].unsqueeze(1) - points.unsqueeze(0)
|
|
57
|
+
dist = diff.square().sum(-1).sqrt()
|
|
58
|
+
bias[0, :, 1 + start : 1 + start + dist.shape[0], 1:length] = (
|
|
59
|
+
dist.unsqueeze(0) * slopes * -1
|
|
60
|
+
).to(dtype)
|
|
61
|
+
return bias[..., :length]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _patch_titan_remote_code(model) -> bool:
|
|
65
|
+
"""Patch TITAN's remote code for fp16 input and bounded bias memory.
|
|
66
|
+
|
|
67
|
+
Two patches on the dynamically loaded module (it exists only after
|
|
68
|
+
from_pretrained): preprocess_features runs its grid index_add_ in fp32 (the op
|
|
69
|
+
rejects fp16 features) but returns the grid in the features' own dtype — an
|
|
70
|
+
exact roundtrip that keeps the whole forward on the fp16 path — and
|
|
71
|
+
forward_features' single-slide alibi branch swaps in _lean_alibi_bias.
|
|
72
|
+
Returns False without patching if the module does not look like the pinned
|
|
73
|
+
revision; the caller then falls back to fp32 features (correct, but with the
|
|
74
|
+
reference implementation's memory behavior).
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
vision_encoder = model.vision_encoder
|
|
78
|
+
vit = sys.modules[type(vision_encoder).__module__]
|
|
79
|
+
if getattr(vit, "_slide2vec_titan_patched", False):
|
|
80
|
+
return True
|
|
81
|
+
for attr in ("pos_encode_type", "num_heads", "patch_embed", "_pos_embed", "norm_pre", "blocks", "norm"):
|
|
82
|
+
if not hasattr(vision_encoder, attr):
|
|
83
|
+
return False
|
|
84
|
+
if not callable(getattr(vit, "preprocess_features", None)):
|
|
85
|
+
return False
|
|
86
|
+
except Exception:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
orig_preprocess = vit.preprocess_features
|
|
90
|
+
|
|
91
|
+
def preprocess_features(features, coords, patch_size_lv0):
|
|
92
|
+
grid, coords_grid, bg_mask = orig_preprocess(features.float(), coords, patch_size_lv0)
|
|
93
|
+
return grid.to(features.dtype), coords_grid, bg_mask
|
|
94
|
+
|
|
95
|
+
orig_forward_features = type(vision_encoder).forward_features
|
|
96
|
+
|
|
97
|
+
def forward_features(self, x, coords=None, mask=None, bg_mask=None):
|
|
98
|
+
# single-slide alibi path only; anything else falls through to the original
|
|
99
|
+
if self.pos_encode_type != "alibi" or x.shape[0] != 1 or self.masked_im_modeling:
|
|
100
|
+
return orig_forward_features(self, x, coords=coords, mask=mask, bg_mask=bg_mask)
|
|
101
|
+
B, nc, w, h = x.shape
|
|
102
|
+
# bias dtype = input grid dtype, as in the original, which builds the bias
|
|
103
|
+
# before patch_embed; post-norm activations can be fp32 under autocast
|
|
104
|
+
in_dtype = x.dtype
|
|
105
|
+
x = x.flatten(2, 3).transpose(1, 2)
|
|
106
|
+
x = self.patch_embed(x)
|
|
107
|
+
x = self._pos_embed(x, coords, w, h)
|
|
108
|
+
x = self.norm_pre(x)
|
|
109
|
+
if bg_mask is not None:
|
|
110
|
+
keep = torch.cat(
|
|
111
|
+
(torch.ones((1, 1), dtype=torch.bool, device=x.device), bg_mask.view(1, -1)),
|
|
112
|
+
dim=1,
|
|
113
|
+
)
|
|
114
|
+
x = x[keep].unsqueeze(0)
|
|
115
|
+
attn_bias = _lean_alibi_bias(self, w, h, bg_mask, device=x.device, dtype=in_dtype)
|
|
116
|
+
x = self.blocks(x, attn_bias, bg_mask)
|
|
117
|
+
x = self.norm(x)
|
|
118
|
+
return x
|
|
119
|
+
|
|
120
|
+
vit.preprocess_features = preprocess_features
|
|
121
|
+
type(vision_encoder).forward_features = forward_features
|
|
122
|
+
vit._slide2vec_titan_patched = True
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@register_encoder(
|
|
127
|
+
"titan",
|
|
128
|
+
level="slide",
|
|
129
|
+
tile_encoder="conchv15",
|
|
130
|
+
tile_encoder_output_variant="default",
|
|
131
|
+
output_variants={"default": {"encode_dim": 768}},
|
|
132
|
+
default_output_variant="default",
|
|
133
|
+
supported_spacing_um=0.5,
|
|
134
|
+
precision="fp16",
|
|
135
|
+
source="MahmoodLab/TITAN",
|
|
136
|
+
)
|
|
137
|
+
class TitanSlideEncoder(SlideEncoder):
|
|
138
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
139
|
+
self._model = AutoModel.from_pretrained(
|
|
140
|
+
"MahmoodLab/TITAN", revision=_TITAN_REVISION, trust_remote_code=True
|
|
141
|
+
).eval()
|
|
142
|
+
self._remote_code_patched = _patch_titan_remote_code(self._model)
|
|
143
|
+
self._device = preferred_default_device()
|
|
144
|
+
self._output_variant = resolve_requested_output_variant(output_variant)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def encode_dim(self) -> int:
|
|
148
|
+
return 768
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def device(self) -> torch.device:
|
|
152
|
+
return self._device
|
|
153
|
+
|
|
154
|
+
def to(self, device: torch.device | str) -> "TitanSlideEncoder":
|
|
155
|
+
self._device = torch.device(device)
|
|
156
|
+
self._model = self._model.to(self._device)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def encode_slide(
|
|
160
|
+
self,
|
|
161
|
+
tile_features: torch.Tensor,
|
|
162
|
+
coordinates: torch.Tensor | None = None,
|
|
163
|
+
*,
|
|
164
|
+
tile_size_lv0: int | None = None,
|
|
165
|
+
) -> torch.Tensor:
|
|
166
|
+
if coordinates is None or tile_size_lv0 is None:
|
|
167
|
+
raise ValueError("TITAN slide encoding requires coordinates and tile_size_lv0")
|
|
168
|
+
if tile_features.ndim == 2:
|
|
169
|
+
tile_features = tile_features.unsqueeze(0)
|
|
170
|
+
if coordinates.ndim == 2:
|
|
171
|
+
coordinates = coordinates.unsqueeze(0)
|
|
172
|
+
if not self._remote_code_patched:
|
|
173
|
+
# fallback for unrecognized remote code: fp32 features satisfy its fp32
|
|
174
|
+
# grid index_add_, at the cost of the reference memory behavior
|
|
175
|
+
tile_features = tile_features.float()
|
|
176
|
+
return self._model.encode_slide_from_patch_features(
|
|
177
|
+
tile_features,
|
|
178
|
+
coordinates.long(),
|
|
179
|
+
np.int64(tile_size_lv0),
|
|
180
|
+
).squeeze(0)
|
|
@@ -1,26 +1,14 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import importlib
|
|
3
1
|
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
import threading
|
|
6
|
-
import time
|
|
7
|
-
from contextlib import contextmanager, nullcontext
|
|
8
|
-
from dataclasses import replace
|
|
9
2
|
from pathlib import Path
|
|
10
|
-
from types import SimpleNamespace
|
|
11
3
|
from typing import Any, Callable, Sequence
|
|
12
4
|
|
|
13
|
-
import logging
|
|
14
|
-
import pandas as pd
|
|
15
5
|
import torch
|
|
16
|
-
from hs2p import SlideSpec
|
|
17
|
-
from hs2p.utils.stderr import run_with_filtered_stderr
|
|
6
|
+
from hs2p import SlideSpec
|
|
18
7
|
|
|
19
8
|
from slide2vec.runtime import (
|
|
20
9
|
artifacts_collect,
|
|
21
10
|
batching,
|
|
22
11
|
cpu_budget,
|
|
23
|
-
distributed,
|
|
24
12
|
distributed_stage,
|
|
25
13
|
embedding,
|
|
26
14
|
embedding_persist,
|
|
@@ -31,11 +19,9 @@ from slide2vec.runtime import (
|
|
|
31
19
|
persist_callbacks,
|
|
32
20
|
persistence,
|
|
33
21
|
process_list,
|
|
34
|
-
serialization,
|
|
35
22
|
slide_encode,
|
|
36
23
|
tiling,
|
|
37
24
|
tiling_pipeline,
|
|
38
|
-
worker_io,
|
|
39
25
|
)
|
|
40
26
|
from slide2vec.api import (
|
|
41
27
|
EmbeddedPatient,
|
|
@@ -43,43 +29,21 @@ from slide2vec.api import (
|
|
|
43
29
|
ExecutionOptions,
|
|
44
30
|
PreprocessingConfig,
|
|
45
31
|
RunResult,
|
|
46
|
-
_resolve_hierarchical_preprocessing,
|
|
47
32
|
)
|
|
48
33
|
from slide2vec.artifacts import (
|
|
49
34
|
HierarchicalEmbeddingArtifact,
|
|
50
|
-
PatientEmbeddingArtifact,
|
|
51
35
|
SlideEmbeddingArtifact,
|
|
52
36
|
TileEmbeddingArtifact,
|
|
53
|
-
write_hierarchical_embeddings,
|
|
54
37
|
load_array,
|
|
55
|
-
write_patient_embeddings,
|
|
56
|
-
write_tile_embedding_metadata,
|
|
57
38
|
)
|
|
58
39
|
from slide2vec.encoders.registry import (
|
|
59
40
|
encoder_registry,
|
|
60
|
-
resolve_encoder_output,
|
|
61
41
|
resolve_patch_size,
|
|
62
|
-
resolve_preprocessing_defaults,
|
|
63
42
|
)
|
|
64
43
|
from slide2vec.runtime.model_settings import canonicalize_model_name
|
|
65
44
|
from slide2vec.runtime.types import LoadedModel
|
|
66
45
|
from slide2vec.runtime.encoder_input_contract import EncoderInputContract
|
|
67
|
-
from slide2vec.progress import
|
|
68
|
-
emit_progress,
|
|
69
|
-
read_tiling_progress_snapshot,
|
|
70
|
-
)
|
|
71
|
-
from slide2vec.utils.log_utils import suppress_c_stderr
|
|
72
|
-
from slide2vec.data.dataset import BatchTileCollator, TileIndexDataset
|
|
73
|
-
from slide2vec.data.tile_reader import OnTheFlyBatchTileCollator, OnTheFlyHierarchicalBatchCollator
|
|
74
|
-
from slide2vec.utils.tiling_io import (
|
|
75
|
-
load_embedding_process_df,
|
|
76
|
-
load_patient_id_mapping,
|
|
77
|
-
load_slide_manifest,
|
|
78
|
-
load_tiling_process_df,
|
|
79
|
-
load_tiling_result_from_row,
|
|
80
|
-
_optional_float,
|
|
81
|
-
)
|
|
82
|
-
from slide2vec.utils.utils import cpu_worker_limit, slurm_cpu_limit
|
|
46
|
+
from slide2vec.progress import emit_progress
|
|
83
47
|
|
|
84
48
|
from slide2vec.runtime.hierarchical import num_embedding_items
|
|
85
49
|
|
|
@@ -126,13 +90,15 @@ def load_model(
|
|
|
126
90
|
info = encoder_registry.info(name)
|
|
127
91
|
resolved_level = info["level"]
|
|
128
92
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
93
|
+
# Never call ``huggingface_hub.login()`` here. Every encoder resolves its auth
|
|
94
|
+
# through the process-global ``huggingface_hub.get_token()``, which reads
|
|
95
|
+
# ``HF_TOKEN`` from the environment ahead of any stored token file, so an
|
|
96
|
+
# exported ``HF_TOKEN`` is all the hub needs. ``login()`` additionally rewrites a
|
|
97
|
+
# shared ``stored_tokens`` file without a lock; under distributed extraction every
|
|
98
|
+
# rank re-runs load_model per chunk and the concurrent truncate/re-read races,
|
|
99
|
+
# crashing runs with ``ValueError: Token ... not found``.
|
|
132
100
|
if token is not None:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
hf_login(token=token, add_to_git_credential=False)
|
|
101
|
+
os.environ["HF_TOKEN"] = token
|
|
136
102
|
|
|
137
103
|
encoder_cls = encoder_registry.require(name)
|
|
138
104
|
# Pass allow_non_recommended_settings ONLY to encoders whose constructor accepts it
|
|
@@ -625,6 +591,7 @@ def embed_tiles(
|
|
|
625
591
|
*,
|
|
626
592
|
execution: ExecutionOptions,
|
|
627
593
|
preprocessing: PreprocessingConfig | None = None,
|
|
594
|
+
on_slide_persisted: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
628
595
|
) -> list[TileEmbeddingArtifact] | list[HierarchicalEmbeddingArtifact]:
|
|
629
596
|
if execution.output_dir is None:
|
|
630
597
|
raise ValueError("ExecutionOptions.output_dir is required to persist tile embeddings")
|
|
@@ -694,6 +661,8 @@ def embed_tiles(
|
|
|
694
661
|
annotation=embedding.tiling_result_annotation(tiling_result),
|
|
695
662
|
)
|
|
696
663
|
artifacts.append(artifact)
|
|
664
|
+
if on_slide_persisted is not None:
|
|
665
|
+
on_slide_persisted(artifact)
|
|
697
666
|
return artifacts
|
|
698
667
|
|
|
699
668
|
|
|
@@ -714,11 +683,7 @@ def aggregate_tiles(
|
|
|
714
683
|
outputs: list[SlideEmbeddingArtifact] = []
|
|
715
684
|
for artifact in tile_artifacts:
|
|
716
685
|
metadata = artifact.metadata
|
|
717
|
-
if "coordinates_npz_path" not
|
|
718
|
-
raise ValueError(
|
|
719
|
-
f"Tile artifact for {artifact.sample_id} is missing tiling metadata paths required for slide aggregation"
|
|
720
|
-
)
|
|
721
|
-
if not metadata["coordinates_npz_path"] or not metadata["coordinates_meta_path"]:
|
|
686
|
+
if not metadata.get("coordinates_npz_path") or not metadata.get("coordinates_meta_path"):
|
|
722
687
|
raise ValueError(
|
|
723
688
|
f"Tile artifact for {artifact.sample_id} is missing tiling metadata paths required for slide aggregation"
|
|
724
689
|
)
|
|
@@ -735,13 +700,12 @@ def aggregate_tiles(
|
|
|
735
700
|
tiling_result,
|
|
736
701
|
execution=execution,
|
|
737
702
|
)
|
|
738
|
-
latents = None
|
|
739
703
|
slide_artifact = embedding.write_slide_embedding_artifact(
|
|
740
704
|
artifact.sample_id,
|
|
741
705
|
slide_embedding,
|
|
742
706
|
execution=execution,
|
|
743
707
|
metadata=embedding.build_slide_embedding_metadata(model, image_path=metadata["image_path"]),
|
|
744
|
-
latents=
|
|
708
|
+
latents=None,
|
|
745
709
|
)
|
|
746
710
|
outputs.append(slide_artifact)
|
|
747
711
|
return outputs
|
|
@@ -921,9 +885,8 @@ def run_pipeline(
|
|
|
921
885
|
execution=execution,
|
|
922
886
|
process_list_path=process_list_path,
|
|
923
887
|
)
|
|
924
|
-
embedded_slides: list[EmbeddedSlide] = []
|
|
925
888
|
if pending_slides:
|
|
926
|
-
|
|
889
|
+
embedding_pipeline.compute_embedded_slides(
|
|
927
890
|
model,
|
|
928
891
|
pending_slides,
|
|
929
892
|
pending_tiling_results,
|
|
@@ -972,6 +935,7 @@ def run_pipeline_with_coordinates(
|
|
|
972
935
|
slides=None,
|
|
973
936
|
preprocessing: PreprocessingConfig | None = None,
|
|
974
937
|
execution: ExecutionOptions,
|
|
938
|
+
on_slide_persisted: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
975
939
|
) -> RunResult:
|
|
976
940
|
if execution.output_dir is None:
|
|
977
941
|
raise ValueError("ExecutionOptions.output_dir is required for Pipeline.run_with_coordinates(...)")
|
|
@@ -1031,6 +995,7 @@ def run_pipeline_with_coordinates(
|
|
|
1031
995
|
execution=execution,
|
|
1032
996
|
output_dir=output_dir,
|
|
1033
997
|
tiling_input_dir=Path(coordinates_dir),
|
|
998
|
+
on_slide_persisted=on_slide_persisted,
|
|
1034
999
|
)
|
|
1035
1000
|
return RunResult(
|
|
1036
1001
|
tile_artifacts=tile_artifacts,
|
|
@@ -1043,6 +1008,7 @@ def run_pipeline_with_coordinates(
|
|
|
1043
1008
|
preprocessing=resolved_preprocessing,
|
|
1044
1009
|
execution=execution,
|
|
1045
1010
|
process_list_path=process_list_path,
|
|
1011
|
+
on_artifact=on_slide_persisted,
|
|
1046
1012
|
)
|
|
1047
1013
|
embedding_pipeline.compute_embedded_slides(
|
|
1048
1014
|
model,
|