slide2vec 5.8.2__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.2 → slide2vec-5.9.0}/PKG-INFO +3 -3
- {slide2vec-5.8.2 → slide2vec-5.9.0}/pyproject.toml +13 -9
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/__init__.py +1 -1
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/api.py +9 -9
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/data/tile_reader.py +0 -1
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/__init__.py +0 -3
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/gigapath.py +2 -7
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/slide.py +9 -17
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/inference.py +19 -53
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/artifacts_collect.py +7 -8
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/batching.py +1 -9
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_sliding.py +2 -7
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/model_settings.py +2 -6
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/persist_callbacks.py +3 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/log_utils.py +4 -9
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/tiling_io.py +0 -2
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/SOURCES.txt +2 -0
- {slide2vec-5.8.2 → 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.2 → slide2vec-5.9.0}/tests/test_dense_sliding.py +4 -7
- {slide2vec-5.8.2 → 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.2 → slide2vec-5.9.0}/tests/test_regression_inference.py +1 -5
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_regression_models.py +1 -1
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_soma_migration.py +0 -40
- slide2vec-5.8.2/tests/test_architecture_runtime_split.py +0 -60
- {slide2vec-5.8.2 → slide2vec-5.9.0}/LICENSE +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/README.md +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/setup.cfg +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/__main__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/cli.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/progress.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_image_shard.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_image_stage.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_isight.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_mascaret.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_phaet.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_prism2.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_progress.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_regression_core.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_sharding.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_tile_store.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-5.8.2 → slide2vec-5.9.0}/tests/test_titan.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),
|
|
@@ -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)
|
|
@@ -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,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Collect pipeline-level tile/slide/hierarchical artifacts (local + distributed paths)."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Sequence
|
|
4
|
+
from typing import Callable, Sequence
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from hs2p import SlideSpec
|
|
@@ -154,6 +154,7 @@ def collect_distributed_pipeline_artifacts(
|
|
|
154
154
|
execution: ExecutionOptions,
|
|
155
155
|
output_dir: Path,
|
|
156
156
|
tiling_input_dir: Path | None = None,
|
|
157
|
+
on_slide_persisted: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
157
158
|
) -> tuple[
|
|
158
159
|
list[TileEmbeddingArtifact],
|
|
159
160
|
list[HierarchicalEmbeddingArtifact],
|
|
@@ -170,12 +171,10 @@ def collect_distributed_pipeline_artifacts(
|
|
|
170
171
|
or include_slide_embeddings
|
|
171
172
|
or persist_hierarchical_embeddings
|
|
172
173
|
)
|
|
173
|
-
annotation_groups = (
|
|
174
|
-
_embeddable_annotation_groups(process_list_path) if annotation_aware else {}
|
|
175
|
-
)
|
|
176
174
|
# The embedding stage must fan out per (sample_id, annotation) for every level so each class's
|
|
177
175
|
# tiles are embedded independently.
|
|
178
176
|
stage_annotation_groups = _embeddable_annotation_groups(process_list_path)
|
|
177
|
+
annotation_groups = stage_annotation_groups if annotation_aware else {}
|
|
179
178
|
# One annotation per ``successful_slides`` entry (the tiling spine fans out per class). Carry each
|
|
180
179
|
# on a lightweight placeholder so the resume gate keys by (sample_id, annotation) and the surviving
|
|
181
180
|
# pending entries hand the stage the right per-class work units.
|
|
@@ -258,6 +257,9 @@ def collect_distributed_pipeline_artifacts(
|
|
|
258
257
|
slide_artifacts=slide_artifacts,
|
|
259
258
|
)
|
|
260
259
|
live_updated_sample_ids.add(sample_id)
|
|
260
|
+
if on_slide_persisted is not None:
|
|
261
|
+
for artifact in [*tile_artifacts, *hierarchical_artifacts]:
|
|
262
|
+
on_slide_persisted(artifact)
|
|
261
263
|
|
|
262
264
|
run_distributed_embedding_stage(
|
|
263
265
|
model=model,
|
|
@@ -269,9 +271,6 @@ def collect_distributed_pipeline_artifacts(
|
|
|
269
271
|
annotations=pending_annotations,
|
|
270
272
|
on_progress_event=_update_process_list_for_finished_slide,
|
|
271
273
|
)
|
|
272
|
-
# ``successful_slides`` is already one entry per (sample_id, annotation) row, so resolve a
|
|
273
|
-
# parallel annotations list (not an expansion) to re-read each entry's namespaced artifact.
|
|
274
|
-
annotations_for_collect = _annotations_parallel_to_slides(successful_slides, annotation_groups)
|
|
275
274
|
tile_artifacts, hierarchical_artifacts, slide_artifacts = collect_pipeline_artifacts(
|
|
276
275
|
successful_slides,
|
|
277
276
|
output_dir=output_dir,
|
|
@@ -279,7 +278,7 @@ def collect_distributed_pipeline_artifacts(
|
|
|
279
278
|
include_tile_embeddings=include_tile_embeddings,
|
|
280
279
|
include_hierarchical_embeddings=persist_hierarchical_embeddings,
|
|
281
280
|
include_slide_embeddings=include_slide_embeddings,
|
|
282
|
-
annotations=
|
|
281
|
+
annotations=stage_annotations if annotation_aware else None,
|
|
283
282
|
)
|
|
284
283
|
update_process_list_after_embedding(
|
|
285
284
|
process_list_path,
|
|
@@ -17,6 +17,7 @@ import torch
|
|
|
17
17
|
|
|
18
18
|
from slide2vec.progress import emit_progress
|
|
19
19
|
from slide2vec.runtime.types import LoadedModel
|
|
20
|
+
from slide2vec.runtime.worker_io import uses_cuda_runtime
|
|
20
21
|
from slide2vec.utils.log_utils import suppress_c_stderr
|
|
21
22
|
|
|
22
23
|
from .preprocessing import (
|
|
@@ -29,17 +30,8 @@ from .preprocessing import (
|
|
|
29
30
|
from .types import PreparedBatch
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def uses_cuda_runtime(device) -> bool:
|
|
33
|
-
return str(device).startswith("cuda") and torch.cuda.is_available()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
33
|
def should_suppress_cucim_dataloader_stderr(dataloader) -> bool:
|
|
38
|
-
if dataloader is None:
|
|
39
|
-
return False
|
|
40
34
|
collate_fn = getattr(dataloader, "collate_fn", None)
|
|
41
|
-
if collate_fn is None:
|
|
42
|
-
return False
|
|
43
35
|
return bool(getattr(collate_fn, "_suppress_cucim_stderr", False))
|
|
44
36
|
|
|
45
37
|
|
|
@@ -73,11 +73,6 @@ def cover_origins(extent: int, size: int, stride: int) -> list[int]:
|
|
|
73
73
|
return starts
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def _window_starts(extent: int, win: int, stride: int) -> list[int]:
|
|
77
|
-
"""Patch-aligned encoder-window starts — the token-space use of :func:`cover_origins`."""
|
|
78
|
-
return cover_origins(extent, win, stride)
|
|
79
|
-
|
|
80
|
-
|
|
81
76
|
def resolve_window_geometry(
|
|
82
77
|
geometry: DenseGridGeometry, *, window_size: int | None, overlap: float
|
|
83
78
|
) -> tuple[tuple[int, int], tuple[int, int], list[int], list[int]]:
|
|
@@ -99,8 +94,8 @@ def resolve_window_geometry(
|
|
|
99
94
|
keep = 1.0 - float(overlap)
|
|
100
95
|
stride_h = min(win_h, _round_to(win_h * keep, ph))
|
|
101
96
|
stride_w = min(win_w, _round_to(win_w * keep, pw))
|
|
102
|
-
starts_h =
|
|
103
|
-
starts_w =
|
|
97
|
+
starts_h = cover_origins(enc_h, win_h, stride_h)
|
|
98
|
+
starts_w = cover_origins(enc_w, win_w, stride_w)
|
|
104
99
|
return (win_h, win_w), (stride_h, stride_w), starts_h, starts_w
|
|
105
100
|
|
|
106
101
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
|
-
logger = logging.getLogger("slide2vec")
|
|
5
|
-
|
|
6
3
|
|
|
7
4
|
PRECISION_ALIASES = {
|
|
8
5
|
"fp32": "fp32",
|
|
@@ -66,8 +63,8 @@ def resolve_output_precision(output_dtype: Any, compute_precision: Any) -> str:
|
|
|
66
63
|
"""Resolve the concrete on-disk feature precision (``"fp16"`` or ``"fp32"``).
|
|
67
64
|
|
|
68
65
|
``output_dtype is None`` follows ``compute_precision``: an fp16 forward keeps fp16
|
|
69
|
-
features, while bf16 / fp32 (and an unset
|
|
70
|
-
|
|
66
|
+
features, while bf16 / fp32 (and an unset compute precision) widen to fp32.
|
|
67
|
+
NumPy has no bfloat16 dtype, so fp32 is its lossless storage container.
|
|
71
68
|
A non-null ``output_dtype`` is honored verbatim (after :func:`normalize_output_dtype`).
|
|
72
69
|
This is the single source of truth shared by the pooled write path and the dense
|
|
73
70
|
``iter_regions_dense`` path.
|
|
@@ -98,4 +95,3 @@ def output_torch_dtype(precision: str):
|
|
|
98
95
|
def canonicalize_model_name(name: str) -> str:
|
|
99
96
|
normalized = name.strip().lower()
|
|
100
97
|
return MODEL_NAME_ALIASES.get(normalized, normalized)
|
|
101
|
-
|
|
@@ -187,6 +187,7 @@ def build_incremental_persist_callback(
|
|
|
187
187
|
preprocessing: PreprocessingConfig,
|
|
188
188
|
execution: ExecutionOptions,
|
|
189
189
|
process_list_path: Path | None = None,
|
|
190
|
+
on_artifact: Callable[[TileEmbeddingArtifact | HierarchicalEmbeddingArtifact], None] | None = None,
|
|
190
191
|
) -> tuple[
|
|
191
192
|
Callable[[SlideSpec, Any, EmbeddedSlide], None] | None,
|
|
192
193
|
list[TileEmbeddingArtifact] | list[HierarchicalEmbeddingArtifact],
|
|
@@ -250,6 +251,8 @@ def build_incremental_persist_callback(
|
|
|
250
251
|
)
|
|
251
252
|
if tile_artifact is not None:
|
|
252
253
|
tile_artifacts.append(tile_artifact)
|
|
254
|
+
if on_artifact is not None:
|
|
255
|
+
on_artifact(tile_artifact)
|
|
253
256
|
if slide_artifact is not None:
|
|
254
257
|
slide_artifacts.append(slide_artifact)
|
|
255
258
|
# Buffer this completion; a slide with no successful artifact is still
|
|
@@ -7,12 +7,6 @@ import sys
|
|
|
7
7
|
from slide2vec.progress import emit_progress_log
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def _distributed_module():
|
|
11
|
-
import slide2vec.distributed as distributed
|
|
12
|
-
|
|
13
|
-
return distributed
|
|
14
|
-
|
|
15
|
-
|
|
16
10
|
@contextlib.contextmanager
|
|
17
11
|
def suppress_c_stderr():
|
|
18
12
|
"""Temporarily redirect C-level stderr to /dev/null.
|
|
@@ -62,16 +56,17 @@ def _configure_logger(
|
|
|
62
56
|
level: The logging level to use.
|
|
63
57
|
output: A file name or a directory to save log. If None, will not save log file.
|
|
64
58
|
If ends with ".txt" or ".log", assumed to be a file name.
|
|
65
|
-
Otherwise, logs will be saved to `output/log.txt`.
|
|
59
|
+
Otherwise, logs will be saved to `output/logs/log.txt`.
|
|
66
60
|
|
|
67
61
|
Returns:
|
|
68
62
|
The configured logger.
|
|
69
63
|
"""
|
|
70
64
|
|
|
65
|
+
import slide2vec.distributed as distributed
|
|
66
|
+
|
|
71
67
|
logger = logging.getLogger(name)
|
|
72
68
|
logger.setLevel(level)
|
|
73
69
|
logger.propagate = False
|
|
74
|
-
distributed = _distributed_module()
|
|
75
70
|
|
|
76
71
|
# Loosely match Google glog format:
|
|
77
72
|
# [IWEF]yyyymmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
|
@@ -127,7 +122,7 @@ def setup_logging(
|
|
|
127
122
|
output: A file name or a directory to save log files. If None, log
|
|
128
123
|
files will not be saved. If output ends with ".txt" or ".log", it
|
|
129
124
|
is assumed to be a file name.
|
|
130
|
-
Otherwise, logs will be saved to `output/log.txt`.
|
|
125
|
+
Otherwise, logs will be saved to `output/logs/log.txt`.
|
|
131
126
|
name: The name of the logger to configure, by default the root logger.
|
|
132
127
|
level: The logging level to use.
|
|
133
128
|
capture_warnings: Whether warnings should be captured as logs.
|
|
@@ -211,8 +211,6 @@ def _load_base_process_df(process_list_path: str | Path) -> pd.DataFrame:
|
|
|
211
211
|
df["requested_mask_backend"] = [None] * len(df)
|
|
212
212
|
if "mask_backend" not in df.columns:
|
|
213
213
|
df["mask_backend"] = [None] * len(df)
|
|
214
|
-
if "annotation" not in df.columns:
|
|
215
|
-
df["annotation"] = ["tissue"] * len(df)
|
|
216
214
|
reconstructed_output_mode = df["annotation"].map(
|
|
217
215
|
lambda annotation: "merged" if str(annotation) == "merged" else None
|
|
218
216
|
)
|