slide2vec 6.0.0__tar.gz → 6.1.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.0.0 → slide2vec-6.1.0}/PKG-INFO +3 -3
- {slide2vec-6.0.0 → slide2vec-6.1.0}/pyproject.toml +5 -5
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/__init__.py +5 -1
- slide2vec-6.1.0/slide2vec/_cufile.py +21 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/api.py +8 -3
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/configs/default.yaml +1 -1
- slide2vec-6.1.0/slide2vec/cufile.json +5 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/__init__.py +17 -2
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/direct_embed_worker.py +1 -1
- slide2vec-6.1.0/slide2vec/distributed/pin_gpu.py +47 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/pipeline_worker.py +1 -1
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/worker_entry.py +14 -5
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_stage.py +1 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_stage.py +1 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/distributed.py +19 -3
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/distributed_stage.py +2 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/image_stage.py +1 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/SOURCES.txt +7 -1
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/requires.txt +2 -2
- slide2vec-6.1.0/tests/test_cufile_compat_mode.py +50 -0
- slide2vec-6.1.0/tests/test_hs2p5_integration.py +207 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_hs2p_package_cutover.py +3 -3
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_regression_core.py +16 -0
- slide2vec-6.1.0/tests/test_worker_gpu_pinning.py +234 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/LICENSE +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/README.md +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/setup.cfg +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/__main__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/cli.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/inference.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/progress.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_stage.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_image_shard.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_image_stage.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_isight.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_mascaret.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_phaet.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_prism.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_prism2.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_progress.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_regression_inference.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_regression_models.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_sharding.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_tile_store.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.0.0 → slide2vec-6.1.0}/tests/test_titan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.1.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]>=
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=5.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,vips]>=
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=5.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 = "6.
|
|
7
|
+
version = "6.1.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]>=
|
|
24
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=5.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,vips]>=
|
|
103
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=5.0.0",
|
|
104
104
|
"wandb",
|
|
105
105
|
"torch>=2.3,<2.8",
|
|
106
106
|
"torchvision>=0.18.0",
|
|
@@ -149,7 +149,7 @@ zip-safe = false
|
|
|
149
149
|
include = ["slide2vec*"]
|
|
150
150
|
|
|
151
151
|
[tool.setuptools.package-data]
|
|
152
|
-
slide2vec = ["py.typed"]
|
|
152
|
+
slide2vec = ["py.typed", "cufile.json"]
|
|
153
153
|
"slide2vec.configs" = ["*.yaml", "models/*.yaml", "preprocessing/*.yaml"]
|
|
154
154
|
|
|
155
155
|
[tool.pytest.ini_options]
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "6.
|
|
183
|
+
current_version = "6.1.0"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from slide2vec._cufile import disable_cufile_compat_mode
|
|
2
|
+
|
|
3
|
+
disable_cufile_compat_mode() # Before anything can import cucim.
|
|
4
|
+
|
|
1
5
|
from slide2vec.api import (
|
|
2
6
|
DenseImageOptions,
|
|
3
7
|
DenseOptions,
|
|
@@ -27,7 +31,7 @@ from slide2vec.encoders import (
|
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
|
|
30
|
-
__version__ = "6.
|
|
34
|
+
__version__ = "6.1.0"
|
|
31
35
|
|
|
32
36
|
__all__ = [
|
|
33
37
|
"Model",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Keep ``import cucim`` from opening a CUDA context on every visible GPU (issue #326).
|
|
2
|
+
|
|
3
|
+
cuCIM opens the cuFile (GPUDirect Storage) driver when it is imported. Without the
|
|
4
|
+
``nvidia-fs`` kernel module the driver falls back to "compat mode", which still creates a
|
|
5
|
+
~520 MiB CUDA context on each visible device although every read then goes through plain
|
|
6
|
+
POSIX I/O. Pointing cuFile at a config that disallows compat mode makes the driver open fail
|
|
7
|
+
instead: cuCIM reads through POSIX exactly as before, and no context is created. Where
|
|
8
|
+
``nvidia-fs`` is loaded the driver opens as usual and GDS keeps working.
|
|
9
|
+
|
|
10
|
+
cuFile reads ``CUFILE_ENV_PATH_JSON`` once, when cuCIM is imported, so this must run before
|
|
11
|
+
that import: ``slide2vec/__init__.py`` calls it first. A value the user already set wins.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
CUFILE_ENV = "CUFILE_ENV_PATH_JSON"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def disable_cufile_compat_mode(environ=os.environ) -> None:
|
|
21
|
+
environ.setdefault(CUFILE_ENV, str(Path(__file__).with_name("cufile.json")))
|
|
@@ -145,13 +145,18 @@ class PreprocessingConfig:
|
|
|
145
145
|
|
|
146
146
|
#: Slide reading backend. ``"auto"`` tries cucim → vips → openslide → asap.
|
|
147
147
|
#: Explicit choices: ``"cucim"``, ``"openslide"``, ``"vips"``, ``"asap"``.
|
|
148
|
+
#: Slides without spacing metadata require ``spacing_at_level_0``, including
|
|
149
|
+
#: untagged TIFFs read with VIPS.
|
|
148
150
|
backend: str = "auto"
|
|
149
|
-
#: Source-mask reading backend, resolved independently from the *mask* path
|
|
150
|
-
#:
|
|
151
|
+
#: Source-mask reading backend, resolved independently from the *mask* path.
|
|
152
|
+
#: ``"auto"`` probes openability just like :attr:`backend`. Set this
|
|
151
153
|
#: explicitly (e.g. ``"openslide"``) when a precomputed tissue or annotation mask needs
|
|
152
154
|
#: a different decoder than its slide — hs2p no longer silently falls back to another
|
|
153
155
|
#: reader, so a mask the slide backend cannot decode fails unless overridden here.
|
|
154
|
-
#: Accepts the same values as :attr:`backend
|
|
156
|
+
#: Accepts the same values as :attr:`backend` plus ``"pil"`` for PNG/JPEG masks;
|
|
157
|
+
#: ``"auto"`` selects PIL for these masks. Ignored for slides with no source mask.
|
|
158
|
+
#: Masks need no spacing metadata, but must align to the slide's field of view and
|
|
159
|
+
#: contain only declared label IDs. See :doc:`preprocessing` for hs2p 5 validation.
|
|
155
160
|
mask_backend: str = "auto"
|
|
156
161
|
#: Target spacing in µm/px. Resolved from the model preset when ``None``.
|
|
157
162
|
requested_spacing_um: float | None = None
|
|
@@ -36,7 +36,7 @@ tiling:
|
|
|
36
36
|
# where `mask_path` is read as a multi-label raster. The `tissue` min_coverage entry below is
|
|
37
37
|
# the single source of truth for the tissue threshold.
|
|
38
38
|
output_mode: per_annotation # how sampled tiles are grouped into artifacts. per_annotation: one flat artifact set per sampled class, namespaced under a `<class>/` subdir (the `tissue` class collapses to the flat root). merged: a single flat artifact set per slide over the union of tiles passing any active class threshold — it carries no class label, so it lands at the flat output root (no `<class>/` subdir).
|
|
39
|
-
pixel_mapping: # {class_name: integer pixel value in the mask raster}
|
|
39
|
+
pixel_mapping: # {class_name: integer pixel value in the mask raster}; a list (e.g. tumor: [2, 3]) merges several values into one class; every mask value must be declared
|
|
40
40
|
background: 0
|
|
41
41
|
tissue: 1
|
|
42
42
|
colors: # {class_name: [r, g, b] | null} used when rendering previews
|
|
@@ -2,6 +2,8 @@ import os
|
|
|
2
2
|
|
|
3
3
|
import torch
|
|
4
4
|
|
|
5
|
+
from slide2vec.distributed.worker_entry import PINNED_ENV
|
|
6
|
+
|
|
5
7
|
_RANK = -1
|
|
6
8
|
_WORLD_SIZE = -1
|
|
7
9
|
_LOCAL_RANK = -1
|
|
@@ -43,6 +45,19 @@ def get_local_rank() -> int:
|
|
|
43
45
|
return _LOCAL_RANK
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def get_device_ordinal() -> int:
|
|
49
|
+
"""
|
|
50
|
+
Returns:
|
|
51
|
+
The CUDA ordinal of this process's GPU: 0 once :mod:`slide2vec.distributed.pin_gpu`
|
|
52
|
+
has left it the only visible device, else the local rank.
|
|
53
|
+
"""
|
|
54
|
+
return _device_ordinal(get_local_rank())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _device_ordinal(local_rank: int) -> int:
|
|
58
|
+
return 0 if os.environ.get(PINNED_ENV) == "1" else local_rank
|
|
59
|
+
|
|
60
|
+
|
|
46
61
|
def get_local_size() -> int:
|
|
47
62
|
"""
|
|
48
63
|
Returns:
|
|
@@ -181,7 +196,7 @@ def enable(
|
|
|
181
196
|
|
|
182
197
|
Args:
|
|
183
198
|
set_cuda_current_device: If True, call torch.cuda.set_device() to set the
|
|
184
|
-
current PyTorch CUDA device to
|
|
199
|
+
current PyTorch CUDA device to this rank's GPU (see get_device_ordinal()).
|
|
185
200
|
overwrite: If True, overwrites already set variables. Else fails.
|
|
186
201
|
"""
|
|
187
202
|
|
|
@@ -192,7 +207,7 @@ def enable(
|
|
|
192
207
|
torch_env.export(overwrite=overwrite)
|
|
193
208
|
|
|
194
209
|
if set_cuda_current_device:
|
|
195
|
-
torch.cuda.set_device(torch_env.local_rank)
|
|
210
|
+
torch.cuda.set_device(_device_ordinal(torch_env.local_rank))
|
|
196
211
|
|
|
197
212
|
_RANK = torch_env.rank
|
|
198
213
|
_WORLD_SIZE = torch_env.world_size
|
|
@@ -42,7 +42,7 @@ def main(argv=None) -> int:
|
|
|
42
42
|
model_spec = dict(request["model"])
|
|
43
43
|
model = Model.from_preset(
|
|
44
44
|
model_spec["name"],
|
|
45
|
-
device=f"cuda:{
|
|
45
|
+
device=f"cuda:{distributed.get_device_ordinal()}",
|
|
46
46
|
output_variant=model_spec.get("output_variant"),
|
|
47
47
|
allow_non_recommended_settings=bool(model_spec["allow_non_recommended_settings"]),
|
|
48
48
|
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Bootstrap that shows a torchrun worker only its own GPU, then runs the worker (issue #326).
|
|
2
|
+
|
|
3
|
+
``import cucim`` opens a CUDA context on every visible device, so a rank that can see its
|
|
4
|
+
siblings' GPUs holds an idle context on each of them. CUDA reads ``CUDA_VISIBLE_DEVICES``
|
|
5
|
+
once, when it initialises, and ``import slide2vec`` already initialises it (``transformers``
|
|
6
|
+
calls ``torch.cuda.is_available()`` at import). A worker module therefore cannot narrow its
|
|
7
|
+
own visibility: ``python -m slide2vec.distributed.<worker>`` has imported the package before
|
|
8
|
+
the worker's first line runs. torchrun runs this file **by path** instead, so the narrowing
|
|
9
|
+
happens before anything is imported. Keep this file free of slide2vec and torch imports.
|
|
10
|
+
|
|
11
|
+
Only for workers that run no collectives: a pinned rank cannot see its siblings' GPUs.
|
|
12
|
+
|
|
13
|
+
Usage: ``torchrun ... pin_gpu.py <worker module> [worker args...]``
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import runpy
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
PINNED_ENV = "SLIDE2VEC_WORKER_GPU_PINNED" # Mirrored in worker_entry.PINNED_ENV.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pin_to_own_gpu(environ) -> None:
|
|
24
|
+
"""Narrow ``CUDA_VISIBLE_DEVICES`` to the ``LOCAL_RANK``-th GPU this process inherited."""
|
|
25
|
+
local_rank = int(environ["LOCAL_RANK"])
|
|
26
|
+
visible = [gpu.strip() for gpu in environ.get("CUDA_VISIBLE_DEVICES", "").split(",") if gpu.strip()]
|
|
27
|
+
if visible and local_rank >= len(visible):
|
|
28
|
+
raise RuntimeError(
|
|
29
|
+
f"LOCAL_RANK={local_rank} but CUDA_VISIBLE_DEVICES lists only {len(visible)} GPU(s)"
|
|
30
|
+
)
|
|
31
|
+
environ["CUDA_VISIBLE_DEVICES"] = visible[local_rank] if visible else str(local_rank)
|
|
32
|
+
environ[PINNED_ENV] = "1"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> None:
|
|
36
|
+
pin_to_own_gpu(os.environ)
|
|
37
|
+
module, *worker_args = sys.argv[1:]
|
|
38
|
+
# Prefer the package that supplied this bootstrap over a competing checkout in cwd.
|
|
39
|
+
# Keep cwd importable without changing how relative data and output paths resolve.
|
|
40
|
+
package_root = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
41
|
+
sys.path[:1] = [package_root, os.getcwd()]
|
|
42
|
+
sys.argv = [module, *worker_args]
|
|
43
|
+
runpy.run_module(module, run_name="__main__", alter_sys=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -36,7 +36,7 @@ def main(argv=None) -> int:
|
|
|
36
36
|
model_spec = dict(request["model"])
|
|
37
37
|
model = Model.from_preset(
|
|
38
38
|
model_spec["name"],
|
|
39
|
-
device=f"cuda:{
|
|
39
|
+
device=f"cuda:{distributed.get_device_ordinal()}",
|
|
40
40
|
output_variant=model_spec.get("output_variant"),
|
|
41
41
|
allow_non_recommended_settings=bool(model_spec["allow_non_recommended_settings"]),
|
|
42
42
|
)
|
|
@@ -18,6 +18,10 @@ from contextlib import nullcontext
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
#: Set by :mod:`slide2vec.distributed.pin_gpu` once this process sees only its own GPU.
|
|
22
|
+
PINNED_ENV = "SLIDE2VEC_WORKER_GPU_PINNED"
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
@dataclass(frozen=True, kw_only=True)
|
|
22
26
|
class WorkerRank:
|
|
23
27
|
"""This process's place in the torchrun world: which shard, and which GPU."""
|
|
@@ -26,12 +30,14 @@ class WorkerRank:
|
|
|
26
30
|
global_rank: int
|
|
27
31
|
#: Number of shards the work list is cut into.
|
|
28
32
|
world_size: int
|
|
29
|
-
#:
|
|
33
|
+
#: This process's index among the ranks on its node; names it in progress labels.
|
|
30
34
|
local_rank: int
|
|
35
|
+
#: CUDA ordinal of this rank's GPU: 0 once the process is pinned to it, else ``local_rank``.
|
|
36
|
+
device_ordinal: int
|
|
31
37
|
|
|
32
38
|
@property
|
|
33
39
|
def device(self) -> str:
|
|
34
|
-
return f"cuda:{self.
|
|
40
|
+
return f"cuda:{self.device_ordinal}"
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
def worker_args_parser(module: str, *, add_help: bool = True) -> argparse.ArgumentParser:
|
|
@@ -46,12 +52,15 @@ def resolve_worker_rank() -> WorkerRank:
|
|
|
46
52
|
"""Read this process's rank from the env torchrun exports.
|
|
47
53
|
|
|
48
54
|
Defaults to a single in-process rank when the module is launched bare, so a worker can
|
|
49
|
-
be run by hand (or in a test) without a torchrun agent.
|
|
55
|
+
be run by hand (or in a test) without a torchrun agent. A worker launched through
|
|
56
|
+
:mod:`slide2vec.distributed.pin_gpu` sees only its own GPU, which is then ``cuda:0``.
|
|
50
57
|
"""
|
|
58
|
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
|
51
59
|
return WorkerRank(
|
|
52
60
|
global_rank=int(os.environ.get("RANK", "0")),
|
|
53
61
|
world_size=int(os.environ.get("WORLD_SIZE", "1")),
|
|
54
|
-
local_rank=
|
|
62
|
+
local_rank=local_rank,
|
|
63
|
+
device_ordinal=0 if os.environ.get(PINNED_ENV) == "1" else local_rank,
|
|
55
64
|
)
|
|
56
65
|
|
|
57
66
|
|
|
@@ -82,6 +91,6 @@ def worker_progress_context(request: dict, *, rank: WorkerRank):
|
|
|
82
91
|
return nullcontext()
|
|
83
92
|
return activate_progress_reporter(
|
|
84
93
|
JsonlProgressReporter(
|
|
85
|
-
progress_events_path, rank=rank.global_rank, progress_label=rank.
|
|
94
|
+
progress_events_path, rank=rank.global_rank, progress_label=f"cuda:{rank.local_rank}"
|
|
86
95
|
)
|
|
87
96
|
)
|
|
@@ -275,6 +275,7 @@ def _run_dense_images_distributed(
|
|
|
275
275
|
request_path.write_text(json.dumps(request, indent=2, sort_keys=True), encoding="utf-8")
|
|
276
276
|
run_torchrun_worker(
|
|
277
277
|
module="slide2vec.distributed.dense_image_worker",
|
|
278
|
+
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
278
279
|
num_gpus=execution.num_gpus,
|
|
279
280
|
output_dir=out_dir,
|
|
280
281
|
request_path=request_path,
|
|
@@ -297,6 +297,7 @@ def _run_dense_distributed(model, specs, *, dense, execution, out_dir) -> None:
|
|
|
297
297
|
request_path.write_text(json.dumps(request, indent=2, sort_keys=True), encoding="utf-8")
|
|
298
298
|
run_torchrun_worker(
|
|
299
299
|
module="slide2vec.distributed.dense_worker",
|
|
300
|
+
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
300
301
|
num_gpus=execution.num_gpus,
|
|
301
302
|
output_dir=out_dir,
|
|
302
303
|
request_path=request_path,
|
|
@@ -158,18 +158,28 @@ def run_torchrun_worker(
|
|
|
158
158
|
output_dir: Path,
|
|
159
159
|
request_path: Path,
|
|
160
160
|
failure_title: str,
|
|
161
|
+
pin_gpus: bool = False,
|
|
161
162
|
progress_events_path: Path | None = None,
|
|
162
163
|
progress_event_callback: Callable[[Any], None] | None = None,
|
|
163
164
|
popen_factory=subprocess.Popen,
|
|
164
165
|
) -> None:
|
|
166
|
+
"""Run ``module`` once per GPU under torchrun.
|
|
167
|
+
|
|
168
|
+
``pin_gpus`` shows each rank only its own GPU (see :mod:`slide2vec.distributed.pin_gpu`).
|
|
169
|
+
Use it for workers that run no collectives.
|
|
170
|
+
"""
|
|
171
|
+
if pin_gpus:
|
|
172
|
+
# By path, not ``-m``: the bootstrap must run before the slide2vec package is imported.
|
|
173
|
+
worker = [str(Path(__file__).resolve().parents[1] / "distributed" / "pin_gpu.py"), module]
|
|
174
|
+
else:
|
|
175
|
+
worker = ["-m", module]
|
|
165
176
|
command = [
|
|
166
177
|
sys.executable,
|
|
167
178
|
"-m",
|
|
168
179
|
"torch.distributed.run",
|
|
169
180
|
"--standalone",
|
|
170
181
|
f"--nproc_per_node={num_gpus}",
|
|
171
|
-
|
|
172
|
-
module,
|
|
182
|
+
*worker,
|
|
173
183
|
"--output-dir",
|
|
174
184
|
str(output_dir),
|
|
175
185
|
"--request-path",
|
|
@@ -188,9 +198,15 @@ def run_torchrun_worker(
|
|
|
188
198
|
previous_sigterm = signal.signal(signal.SIGTERM, _raise_on_sigterm)
|
|
189
199
|
except (ValueError, OSError):
|
|
190
200
|
previous_sigterm = None
|
|
201
|
+
# Workers keep the parent's working directory, so a relative output_dir (and every path
|
|
202
|
+
# derived from it: the request, the tiling artifacts, the progress log) means the same
|
|
203
|
+
# place in both. PYTHONPATH, not cwd, is what lets an uninstalled checkout be imported.
|
|
204
|
+
package_root = str(Path(__file__).resolve().parents[2])
|
|
205
|
+
env = dict(os.environ)
|
|
206
|
+
env["PYTHONPATH"] = os.pathsep.join(filter(None, [package_root, env.get("PYTHONPATH")]))
|
|
191
207
|
process = popen_factory(
|
|
192
208
|
command,
|
|
193
|
-
|
|
209
|
+
env=env,
|
|
194
210
|
stdout=subprocess.PIPE,
|
|
195
211
|
stderr=subprocess.PIPE,
|
|
196
212
|
text=True,
|
|
@@ -153,6 +153,7 @@ def run_distributed_embedding_stage(
|
|
|
153
153
|
output_dir=output_dir,
|
|
154
154
|
request_path=request_path,
|
|
155
155
|
failure_title="Distributed feature extraction failed",
|
|
156
|
+
pin_gpus=True,
|
|
156
157
|
progress_events_path=progress_events_path,
|
|
157
158
|
progress_event_callback=on_progress_event,
|
|
158
159
|
popen_factory=Popen,
|
|
@@ -190,6 +191,7 @@ def run_distributed_direct_embedding_stage(
|
|
|
190
191
|
output_dir=output_dir,
|
|
191
192
|
request_path=request_path,
|
|
192
193
|
failure_title="Distributed direct embedding failed",
|
|
194
|
+
pin_gpus=True,
|
|
193
195
|
progress_events_path=progress_events_path,
|
|
194
196
|
popen_factory=Popen,
|
|
195
197
|
)
|
|
@@ -147,6 +147,7 @@ def _run_images_distributed(model, specs, *, execution, out_dir) -> None:
|
|
|
147
147
|
request_path.write_text(json.dumps(request, indent=2, sort_keys=True), encoding="utf-8")
|
|
148
148
|
run_torchrun_worker(
|
|
149
149
|
module="slide2vec.distributed.image_worker",
|
|
150
|
+
pin_gpus=True, # No collectives: each rank needs only its own GPU.
|
|
150
151
|
num_gpus=execution.num_gpus,
|
|
151
152
|
output_dir=out_dir,
|
|
152
153
|
request_path=request_path,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.1.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]>=
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=5.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,vips]>=
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=5.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"
|
|
@@ -3,9 +3,11 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
slide2vec/__init__.py
|
|
5
5
|
slide2vec/__main__.py
|
|
6
|
+
slide2vec/_cufile.py
|
|
6
7
|
slide2vec/api.py
|
|
7
8
|
slide2vec/artifacts.py
|
|
8
9
|
slide2vec/cli.py
|
|
10
|
+
slide2vec/cufile.json
|
|
9
11
|
slide2vec/inference.py
|
|
10
12
|
slide2vec/progress.py
|
|
11
13
|
slide2vec.egg-info/PKG-INFO
|
|
@@ -27,6 +29,7 @@ slide2vec/distributed/dense_image_worker.py
|
|
|
27
29
|
slide2vec/distributed/dense_worker.py
|
|
28
30
|
slide2vec/distributed/direct_embed_worker.py
|
|
29
31
|
slide2vec/distributed/image_worker.py
|
|
32
|
+
slide2vec/distributed/pin_gpu.py
|
|
30
33
|
slide2vec/distributed/pipeline_worker.py
|
|
31
34
|
slide2vec/distributed/worker_entry.py
|
|
32
35
|
slide2vec/encoders/__init__.py
|
|
@@ -113,6 +116,7 @@ slide2vec/utils/utils.py
|
|
|
113
116
|
tests/test_architecture_runtime_split.py
|
|
114
117
|
tests/test_attention_extraction.py
|
|
115
118
|
tests/test_benchmark_tooling.py
|
|
119
|
+
tests/test_cufile_compat_mode.py
|
|
116
120
|
tests/test_dense_encode_kit.py
|
|
117
121
|
tests/test_dense_encoder_input.py
|
|
118
122
|
tests/test_dense_extraction.py
|
|
@@ -135,6 +139,7 @@ tests/test_encoder_preprocessing.py
|
|
|
135
139
|
tests/test_encoder_provider_failures.py
|
|
136
140
|
tests/test_encoder_registry.py
|
|
137
141
|
tests/test_gpfm_genbio_heavy.py
|
|
142
|
+
tests/test_hs2p5_integration.py
|
|
138
143
|
tests/test_hs2p_package_cutover.py
|
|
139
144
|
tests/test_image_shard.py
|
|
140
145
|
tests/test_image_stage.py
|
|
@@ -163,4 +168,5 @@ tests/test_slide_coordinate_preparation.py
|
|
|
163
168
|
tests/test_soma_migration.py
|
|
164
169
|
tests/test_tile_store.py
|
|
165
170
|
tests/test_tiling_pipeline.py
|
|
166
|
-
tests/test_titan.py
|
|
171
|
+
tests/test_titan.py
|
|
172
|
+
tests/test_worker_gpu_pinning.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
hs2p[asap,cucim,openslide,sam2,vips]>=
|
|
1
|
+
hs2p[asap,cucim,openslide,sam2,vips]>=5.0.0
|
|
2
2
|
omegaconf
|
|
3
3
|
matplotlib
|
|
4
4
|
numpy<2
|
|
@@ -27,7 +27,7 @@ numpy<2
|
|
|
27
27
|
pandas
|
|
28
28
|
pillow
|
|
29
29
|
rich
|
|
30
|
-
hs2p[asap,cucim,openslide,sam2,vips]>=
|
|
30
|
+
hs2p[asap,cucim,openslide,sam2,vips]>=5.0.0
|
|
31
31
|
wandb
|
|
32
32
|
torch<2.8,>=2.3
|
|
33
33
|
torchvision>=0.18.0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""``import cucim`` must not open a CUDA context on every visible GPU (issue #326).
|
|
2
|
+
|
|
3
|
+
Without ``nvidia-fs``, cuFile's compat mode creates an idle context per device at import.
|
|
4
|
+
slide2vec points cuFile at a config that disallows compat mode, before cucim can load.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import slide2vec
|
|
17
|
+
from slide2vec import _cufile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_packaged_config_disallows_compat_mode():
|
|
21
|
+
config = json.loads(Path(slide2vec.__file__).with_name("cufile.json").read_text())
|
|
22
|
+
assert config == {"properties": {"allow_compat_mode": False}}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_cufile_is_pointed_at_the_packaged_config():
|
|
26
|
+
environ: dict[str, str] = {}
|
|
27
|
+
_cufile.disable_cufile_compat_mode(environ)
|
|
28
|
+
assert Path(environ[_cufile.CUFILE_ENV]) == Path(slide2vec.__file__).with_name("cufile.json")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_a_config_the_user_chose_is_left_alone():
|
|
32
|
+
environ = {_cufile.CUFILE_ENV: "/etc/my-cufile.json"}
|
|
33
|
+
_cufile.disable_cufile_compat_mode(environ)
|
|
34
|
+
assert environ[_cufile.CUFILE_ENV] == "/etc/my-cufile.json"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_package_import_sets_it_before_any_other_import():
|
|
38
|
+
# cuFile reads the variable once, when cucim is imported; nothing may get there first.
|
|
39
|
+
first, second = ast.parse(Path(slide2vec.__file__).read_text()).body[:2]
|
|
40
|
+
assert isinstance(first, ast.ImportFrom) and first.module == "slide2vec._cufile"
|
|
41
|
+
assert isinstance(second, ast.Expr) and second.value.func.id == "disable_cufile_compat_mode"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_importing_slide2vec_exports_the_variable():
|
|
45
|
+
env = {k: v for k, v in os.environ.items() if k != _cufile.CUFILE_ENV}
|
|
46
|
+
out = subprocess.run(
|
|
47
|
+
[sys.executable, "-c", f"import os, slide2vec; print(os.environ['{_cufile.CUFILE_ENV}'])"],
|
|
48
|
+
env=env, capture_output=True, text=True, check=True, cwd=Path(slide2vec.__file__).parents[1],
|
|
49
|
+
).stdout.strip().splitlines()[-1]
|
|
50
|
+
assert Path(out).name == "cufile.json" and Path(out).is_file()
|