slide2vec 6.0.1__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.1 → slide2vec-6.1.0}/PKG-INFO +3 -3
- {slide2vec-6.0.1 → slide2vec-6.1.0}/pyproject.toml +4 -4
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/__init__.py +1 -1
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/api.py +8 -3
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/configs/default.yaml +1 -1
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/SOURCES.txt +1 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/requires.txt +2 -2
- slide2vec-6.1.0/tests/test_hs2p5_integration.py +207 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_hs2p_package_cutover.py +3 -3
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_regression_core.py +16 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/LICENSE +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/README.md +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/setup.cfg +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/__main__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/_cufile.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/cli.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/configs/resources.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/cufile.json +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/inference.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/progress.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_regions.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_image_shard.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_image_stage.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_isight.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_mascaret.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_patient_manifest.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_phaet.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_prism.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_prism2.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_progress.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_regression_inference.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_regression_models.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_sharding.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_tile_store.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_titan.py +0 -0
- {slide2vec-6.0.1 → slide2vec-6.1.0}/tests/test_worker_gpu_pinning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 6.0
|
|
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.0
|
|
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",
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "6.0
|
|
183
|
+
current_version = "6.1.0"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 6.0
|
|
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"
|
|
@@ -139,6 +139,7 @@ tests/test_encoder_preprocessing.py
|
|
|
139
139
|
tests/test_encoder_provider_failures.py
|
|
140
140
|
tests/test_encoder_registry.py
|
|
141
141
|
tests/test_gpfm_genbio_heavy.py
|
|
142
|
+
tests/test_hs2p5_integration.py
|
|
142
143
|
tests/test_hs2p_package_cutover.py
|
|
143
144
|
tests/test_image_shard.py
|
|
144
145
|
tests/test_image_stage.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,207 @@
|
|
|
1
|
+
"""Exercise the slide2vec/hs2p boundary with real readers and persisted artifacts."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import pytest
|
|
8
|
+
from hs2p import BatchPartialFailureWarning, SlideSpec
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
from slide2vec.api import PreprocessingConfig
|
|
12
|
+
from slide2vec.runtime.embedding import tiling_result_annotation
|
|
13
|
+
from slide2vec.runtime.tiling_pipeline import prepare_tiled_slides
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _flat_slide(tmp_path: Path, labels: np.ndarray) -> SlideSpec:
|
|
17
|
+
image_path = tmp_path / "slide.png"
|
|
18
|
+
mask_path = tmp_path / "mask.png"
|
|
19
|
+
Image.new("RGB", (128, 128), (160, 70, 110)).save(image_path)
|
|
20
|
+
Image.fromarray(labels).save(mask_path)
|
|
21
|
+
return SlideSpec(
|
|
22
|
+
sample_id="slide",
|
|
23
|
+
image_path=image_path,
|
|
24
|
+
mask_path=mask_path,
|
|
25
|
+
spacing_at_level_0=0.5,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _preprocessing(**kwargs) -> PreprocessingConfig:
|
|
30
|
+
return PreprocessingConfig(
|
|
31
|
+
backend="auto",
|
|
32
|
+
requested_spacing_um=0.5,
|
|
33
|
+
requested_tile_size_px=32,
|
|
34
|
+
segmentation={"downsample": 1},
|
|
35
|
+
filtering={"a_t": 0, "a_h": 0},
|
|
36
|
+
preview={"save_mask_preview": False, "save_tiling_preview": False},
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_spacingless_tissue_mask_aligns_and_round_trips(tmp_path):
|
|
42
|
+
slide = _flat_slide(tmp_path, np.ones((32, 32), dtype=np.uint8))
|
|
43
|
+
|
|
44
|
+
slides, results, process_list = prepare_tiled_slides(
|
|
45
|
+
[slide], _preprocessing(), output_dir=tmp_path / "out", num_workers=1
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert slides == [slide]
|
|
49
|
+
result, = results
|
|
50
|
+
expected = np.array([(x, y) for x in range(0, 128, 32) for y in range(0, 128, 32)])
|
|
51
|
+
np.testing.assert_array_equal(np.column_stack((result.x, result.y)), expected)
|
|
52
|
+
assert result.spacing_at_level_0 == 0.5
|
|
53
|
+
assert result.mask_spacing_um == 2.0
|
|
54
|
+
assert result.mask_level == 0
|
|
55
|
+
assert result.mask_backend == "pil"
|
|
56
|
+
row = pd.read_csv(process_list).iloc[0]
|
|
57
|
+
assert row["tiling_status"] == "success"
|
|
58
|
+
assert row["requested_mask_backend"] == "auto"
|
|
59
|
+
assert row["mask_backend"] == "pil"
|
|
60
|
+
assert row["num_tiles"] == 16
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize("output_mode", ["per_annotation", "merged"])
|
|
64
|
+
def test_spacingless_annotation_mask_groups_values_and_preserves_bag_identity(tmp_path, output_mode):
|
|
65
|
+
labels = np.full((32, 32), 2, dtype=np.uint8)
|
|
66
|
+
labels[:, 16:] = 3
|
|
67
|
+
slide = _flat_slide(tmp_path, labels)
|
|
68
|
+
preprocessing = _preprocessing(masks={
|
|
69
|
+
"pixel_mapping": {"tumor": [2, 3]},
|
|
70
|
+
"min_coverage": {"tissue": None, "tumor": 0.5},
|
|
71
|
+
"colors": {"tumor": [255, 0, 0]},
|
|
72
|
+
"output_mode": output_mode,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
slides, results, process_list = prepare_tiled_slides(
|
|
76
|
+
[slide], preprocessing, output_dir=tmp_path / "out", num_workers=1
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
assert slides == [slide]
|
|
80
|
+
result, = results
|
|
81
|
+
annotation = "tumor" if output_mode == "per_annotation" else "merged"
|
|
82
|
+
assert tiling_result_annotation(result) == annotation
|
|
83
|
+
assert result.output_mode == output_mode
|
|
84
|
+
assert result.mask_spacing_um == 2.0
|
|
85
|
+
assert len(result.x) == 16
|
|
86
|
+
row = pd.read_csv(process_list).iloc[0]
|
|
87
|
+
assert row["annotation"] == annotation
|
|
88
|
+
assert row["mask_backend"] == "pil"
|
|
89
|
+
assert row["tiling_status"] == "success"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"labels, error",
|
|
94
|
+
[
|
|
95
|
+
(np.ones((32, 16), dtype=np.uint8), "Mask alignment failed"),
|
|
96
|
+
(np.full((32, 32), 255, dtype=np.uint8), "undeclared label IDs"),
|
|
97
|
+
],
|
|
98
|
+
ids=["misaligned-mask", "undeclared-tissue-value"],
|
|
99
|
+
)
|
|
100
|
+
def test_invalid_source_masks_surface_hs2p_errors(tmp_path, labels, error):
|
|
101
|
+
slide = _flat_slide(tmp_path, labels)
|
|
102
|
+
output_dir = tmp_path / "out"
|
|
103
|
+
|
|
104
|
+
with pytest.warns(BatchPartialFailureWarning, match=error):
|
|
105
|
+
with pytest.raises(RuntimeError, match=error):
|
|
106
|
+
prepare_tiled_slides(
|
|
107
|
+
[slide], _preprocessing(), output_dir=output_dir, num_workers=1
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
row = pd.read_csv(output_dir / "process_list.csv").iloc[0]
|
|
111
|
+
assert row["tiling_status"] == "failed"
|
|
112
|
+
assert error in row["error"]
|
|
113
|
+
assert pd.isna(row["coordinates_npz_path"])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_wsi_mask_coordinates_and_previews_remain_compatible(tmp_path):
|
|
117
|
+
pytest.importorskip("openslide")
|
|
118
|
+
from tests.output_consistency_config import (
|
|
119
|
+
TILING_FILTER_PARAMS,
|
|
120
|
+
TILING_MASKS,
|
|
121
|
+
TILING_PARAMS,
|
|
122
|
+
TILING_SEG_PARAMS,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
fixtures = Path(__file__).parent / "fixtures"
|
|
126
|
+
slide = SlideSpec(
|
|
127
|
+
sample_id="test-wsi",
|
|
128
|
+
image_path=fixtures / "input" / "test-wsi.tif",
|
|
129
|
+
mask_path=fixtures / "input" / "test-mask.tif",
|
|
130
|
+
)
|
|
131
|
+
preprocessing = PreprocessingConfig(
|
|
132
|
+
backend="openslide",
|
|
133
|
+
mask_backend="openslide",
|
|
134
|
+
masks=TILING_MASKS,
|
|
135
|
+
segmentation=TILING_SEG_PARAMS,
|
|
136
|
+
filtering=TILING_FILTER_PARAMS,
|
|
137
|
+
**TILING_PARAMS,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
slides, results, process_list = prepare_tiled_slides(
|
|
141
|
+
[slide], preprocessing, output_dir=tmp_path / "out", num_workers=1
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert slides == [slide]
|
|
145
|
+
result, = results
|
|
146
|
+
with np.load(fixtures / "gt" / "test-wsi.coordinates.npz") as expected:
|
|
147
|
+
np.testing.assert_array_equal(result.x, expected["x"])
|
|
148
|
+
np.testing.assert_array_equal(result.y, expected["y"])
|
|
149
|
+
# hs2p 5 records dimension-derived mask spacing, not the TIFF's nominal tag.
|
|
150
|
+
assert result.mask_level == 1
|
|
151
|
+
assert result.mask_spacing_um == pytest.approx(result.base_spacing_um * 32)
|
|
152
|
+
row = pd.read_csv(process_list).iloc[0]
|
|
153
|
+
for field in ("mask_preview_path", "tiling_preview_path"):
|
|
154
|
+
path = getattr(result, field)
|
|
155
|
+
assert path.is_file()
|
|
156
|
+
assert Path(row[field]) == path
|
|
157
|
+
with Image.open(path) as preview:
|
|
158
|
+
preview.verify()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@pytest.mark.parametrize("output_mode", ["per_annotation", "merged"])
|
|
162
|
+
def test_wsi_annotation_previews_accept_spacingless_masks(tmp_path, output_mode):
|
|
163
|
+
pytest.importorskip("openslide")
|
|
164
|
+
labels = np.full((200, 216), 2, dtype=np.uint8)
|
|
165
|
+
labels[:, 108:] = 3
|
|
166
|
+
mask_path = tmp_path / "annotations.png"
|
|
167
|
+
Image.fromarray(labels).save(mask_path)
|
|
168
|
+
slide = SlideSpec(
|
|
169
|
+
sample_id="test-wsi",
|
|
170
|
+
image_path=Path(__file__).parent / "fixtures" / "input" / "test-wsi.tif",
|
|
171
|
+
mask_path=mask_path,
|
|
172
|
+
)
|
|
173
|
+
preprocessing = PreprocessingConfig(
|
|
174
|
+
backend="openslide",
|
|
175
|
+
requested_spacing_um=0.5,
|
|
176
|
+
requested_tile_size_px=512,
|
|
177
|
+
filtering={"a_t": 0, "a_h": 0},
|
|
178
|
+
masks={
|
|
179
|
+
"pixel_mapping": {"tumor": [2, 3]},
|
|
180
|
+
"min_coverage": {"tissue": None, "tumor": 0.5},
|
|
181
|
+
"colors": {"tumor": [255, 0, 0]},
|
|
182
|
+
"output_mode": output_mode,
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
_, results, process_list = prepare_tiled_slides(
|
|
187
|
+
[slide], preprocessing, output_dir=tmp_path / "out", num_workers=1
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
result, = results
|
|
191
|
+
assert len(result.x) > 0
|
|
192
|
+
assert result.backend == "openslide"
|
|
193
|
+
assert result.mask_backend == "pil"
|
|
194
|
+
assert result.mask_spacing_um == pytest.approx(result.base_spacing_um * 64)
|
|
195
|
+
annotation = "tumor" if output_mode == "per_annotation" else "merged"
|
|
196
|
+
assert tiling_result_annotation(result) == annotation
|
|
197
|
+
preview_dir = tmp_path / "out" / "preview" / "tiling"
|
|
198
|
+
if output_mode == "per_annotation":
|
|
199
|
+
preview_dir /= "tumor"
|
|
200
|
+
assert result.tiling_preview_path == preview_dir / "test-wsi.jpg"
|
|
201
|
+
row = pd.read_csv(process_list).iloc[0]
|
|
202
|
+
for field in ("mask_preview_path", "tiling_preview_path"):
|
|
203
|
+
path = getattr(result, field)
|
|
204
|
+
assert path.is_file()
|
|
205
|
+
assert Path(row[field]) == path
|
|
206
|
+
with Image.open(path) as preview:
|
|
207
|
+
preview.verify()
|
|
@@ -23,7 +23,7 @@ def test_package_root_exports_api():
|
|
|
23
23
|
assert hasattr(package, "DenseRegionArtifact")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def test_dependency_declarations_require_hs2p_5_0_0_with_consistent_extras():
|
|
27
27
|
project = tomllib.loads(
|
|
28
28
|
(Path(__file__).resolve().parents[1] / "pyproject.toml").read_text(
|
|
29
29
|
encoding="utf-8"
|
|
@@ -39,8 +39,8 @@ def test_dependency_declarations_require_hs2p_4_4_3_with_consistent_extras():
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
assert hs2p_dependencies == [
|
|
42
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=
|
|
43
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=
|
|
42
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=5.0.0",
|
|
43
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=5.0.0",
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
|
|
@@ -1535,6 +1535,22 @@ def test_masks_boundary_accepts_distinct_integer_value_255():
|
|
|
1535
1535
|
assert sampling.pixel_mapping["tumor"] == 255
|
|
1536
1536
|
|
|
1537
1537
|
|
|
1538
|
+
def test_masks_boundary_forwards_list_valued_pixel_mapping():
|
|
1539
|
+
from slide2vec.runtime.tiling import build_hs2p_configs
|
|
1540
|
+
|
|
1541
|
+
preprocessing = _masks_preprocessing(
|
|
1542
|
+
{
|
|
1543
|
+
"pixel_mapping": {"tumor": [2, 3]},
|
|
1544
|
+
"colors": {"tumor": [255, 0, 0]},
|
|
1545
|
+
"min_coverage": {"tumor": 0.5},
|
|
1546
|
+
}
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
sampling = build_hs2p_configs(preprocessing)[-3]
|
|
1550
|
+
|
|
1551
|
+
assert list(sampling.pixel_mapping["tumor"]) == [2, 3]
|
|
1552
|
+
|
|
1553
|
+
|
|
1538
1554
|
@pytest.mark.parametrize(
|
|
1539
1555
|
("pixel_mapping", "message"),
|
|
1540
1556
|
[
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|