slide2vec 4.0.3__tar.gz → 4.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-4.0.3 → slide2vec-4.1.0}/PKG-INFO +1 -1
- {slide2vec-4.0.3 → slide2vec-4.1.0}/pyproject.toml +2 -2
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/__init__.py +1 -1
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/direct_embed_worker.py +3 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/inference.py +19 -8
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_inference.py +50 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/LICENSE +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/README.md +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/setup.cfg +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/__main__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/api.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/artifacts.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/cli.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/base.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/main.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/model_settings.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/progress.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/registry.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/resources.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/runtime_types.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/config.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/SOURCES.txt +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_batch_collator_timing.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_encoder_registry.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_output_consistency.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_packaging_metadata.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_progress.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_core.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_models.py +0 -0
- {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_tile_store.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "4.0
|
|
7
|
+
version = "4.1.0"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -154,7 +154,7 @@ no_implicit_reexport = true
|
|
|
154
154
|
max-line-length = 160
|
|
155
155
|
|
|
156
156
|
[tool.bumpver]
|
|
157
|
-
current_version = "4.0
|
|
157
|
+
current_version = "4.1.0"
|
|
158
158
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
159
159
|
commit = false # We do version bumping in CI, not as a commit
|
|
160
160
|
tag = false # Git tag already exists — we don't auto-tag
|
|
@@ -2,7 +2,7 @@ from slide2vec.api import EmbeddedSlide, ExecutionOptions, Model, Pipeline, Prep
|
|
|
2
2
|
from slide2vec.artifacts import HierarchicalEmbeddingArtifact, SlideEmbeddingArtifact, TileEmbeddingArtifact
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__version__ = "4.0
|
|
5
|
+
__version__ = "4.1.0"
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"Model",
|
|
@@ -25,6 +25,7 @@ def main(argv=None) -> int:
|
|
|
25
25
|
_compute_hierarchical_embedding_shard_for_slide,
|
|
26
26
|
_compute_tile_embeddings_for_slide,
|
|
27
27
|
_is_hierarchical_preprocessing,
|
|
28
|
+
_resolve_hierarchical_geometry,
|
|
28
29
|
deserialize_execution,
|
|
29
30
|
deserialize_preprocessing,
|
|
30
31
|
load_successful_tiled_slides,
|
|
@@ -74,9 +75,11 @@ def main(argv=None) -> int:
|
|
|
74
75
|
slide, tiling_result = paired_by_sample[sample_id]
|
|
75
76
|
loaded = model._load_backend()
|
|
76
77
|
if _is_hierarchical_preprocessing(preprocessing):
|
|
78
|
+
geometry = _resolve_hierarchical_geometry(preprocessing, tiling_result)
|
|
77
79
|
index = _build_hierarchical_index(
|
|
78
80
|
tiling_result,
|
|
79
81
|
region_tile_multiple=int(preprocessing.region_tile_multiple),
|
|
82
|
+
tile_size_lv0=int(geometry["tile_size_lv0"]),
|
|
80
83
|
)
|
|
81
84
|
flat_indices = np.array_split(index.flat_index, world_size)[global_rank]
|
|
82
85
|
shard_indices, tile_embeddings = _compute_hierarchical_embedding_shard_for_slide(
|
|
@@ -111,19 +111,23 @@ def _resolve_hierarchical_geometry(preprocessing: PreprocessingConfig, tiling_re
|
|
|
111
111
|
raise ValueError("Hierarchical preprocessing requires target_region_size_px")
|
|
112
112
|
target_tile_size_px = int(preprocessing.target_tile_size_px)
|
|
113
113
|
target_region_size_px = int(preprocessing.target_region_size_px)
|
|
114
|
-
|
|
115
|
-
tile_size_lv0 = int(getattr(tiling_result, "tile_size_lv0"))
|
|
114
|
+
target_spacing_um = float(preprocessing.target_spacing_um)
|
|
116
115
|
multiple = int(preprocessing.region_tile_multiple)
|
|
117
116
|
if target_region_size_px % multiple != 0:
|
|
118
117
|
raise ValueError("target_region_size_px must be divisible by region_tile_multiple")
|
|
118
|
+
effective_spacing_um = float(getattr(tiling_result, "effective_spacing_um"))
|
|
119
|
+
base_spacing_um = float(getattr(tiling_result, "base_spacing_um"))
|
|
120
|
+
effective_tile_size_px = int(round(target_tile_size_px * target_spacing_um / effective_spacing_um))
|
|
121
|
+
effective_region_size_px = effective_tile_size_px * multiple
|
|
122
|
+
tile_size_lv0 = int(round(target_tile_size_px * target_spacing_um / base_spacing_um))
|
|
119
123
|
return {
|
|
120
124
|
"region_tile_multiple": multiple,
|
|
121
125
|
"tiles_per_region": multiple * multiple,
|
|
122
126
|
"target_tile_size_px": target_tile_size_px,
|
|
123
|
-
"effective_tile_size_px":
|
|
127
|
+
"effective_tile_size_px": effective_tile_size_px,
|
|
124
128
|
"target_region_size_px": target_region_size_px,
|
|
125
129
|
"effective_region_size_px": effective_region_size_px,
|
|
126
|
-
"tile_size_lv0": tile_size_lv0
|
|
130
|
+
"tile_size_lv0": tile_size_lv0,
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
|
|
@@ -131,14 +135,18 @@ def _build_hierarchical_index(
|
|
|
131
135
|
tiling_result,
|
|
132
136
|
*,
|
|
133
137
|
region_tile_multiple: int,
|
|
138
|
+
tile_size_lv0: int | None = None,
|
|
134
139
|
) -> HierarchicalIndex:
|
|
135
140
|
x_values, y_values = coordinate_arrays(tiling_result)
|
|
136
141
|
num_regions = int(len(x_values))
|
|
137
142
|
multiple = int(region_tile_multiple)
|
|
138
143
|
if multiple < 2:
|
|
139
144
|
raise ValueError("region_tile_multiple must be at least 2")
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
subtile_size_lv0 = (
|
|
146
|
+
int(tile_size_lv0)
|
|
147
|
+
if tile_size_lv0 is not None
|
|
148
|
+
else int(getattr(tiling_result, "tile_size_lv0")) // multiple
|
|
149
|
+
)
|
|
142
150
|
tiles_per_region = multiple * multiple
|
|
143
151
|
if num_regions == 0:
|
|
144
152
|
empty = np.empty(0, dtype=np.int64)
|
|
@@ -1275,6 +1283,7 @@ def _compute_hierarchical_embeddings_for_slide(
|
|
|
1275
1283
|
index = _build_hierarchical_index(
|
|
1276
1284
|
tiling_result,
|
|
1277
1285
|
region_tile_multiple=int(geometry["region_tile_multiple"]),
|
|
1286
|
+
tile_size_lv0=int(geometry["tile_size_lv0"]),
|
|
1278
1287
|
)
|
|
1279
1288
|
resolved_indices = index.flat_index
|
|
1280
1289
|
if flat_indices is not None:
|
|
@@ -1370,6 +1379,7 @@ def _compute_hierarchical_embedding_shard_for_slide(
|
|
|
1370
1379
|
index = _build_hierarchical_index(
|
|
1371
1380
|
tiling_result,
|
|
1372
1381
|
region_tile_multiple=int(geometry["region_tile_multiple"]),
|
|
1382
|
+
tile_size_lv0=int(geometry["tile_size_lv0"]),
|
|
1373
1383
|
)
|
|
1374
1384
|
resolved_indices = np.asarray(flat_indices, dtype=np.int64)
|
|
1375
1385
|
collate_fn = OnTheFlyHierarchicalBatchCollator(
|
|
@@ -2261,11 +2271,12 @@ def _should_persist_tile_embeddings(model, execution: ExecutionOptions) -> bool:
|
|
|
2261
2271
|
|
|
2262
2272
|
|
|
2263
2273
|
def _resolved_process_list_output_variant(model) -> str | None:
|
|
2274
|
+
requested_output_variant = getattr(model, "_output_variant", None)
|
|
2264
2275
|
if not hasattr(model, "name") or model.name not in encoder_registry:
|
|
2265
|
-
return
|
|
2276
|
+
return requested_output_variant
|
|
2266
2277
|
resolved = resolve_encoder_output(
|
|
2267
2278
|
model.name,
|
|
2268
|
-
requested_output_variant=
|
|
2279
|
+
requested_output_variant=requested_output_variant,
|
|
2269
2280
|
)
|
|
2270
2281
|
return str(resolved["output_variant"])
|
|
2271
2282
|
|
|
@@ -176,6 +176,8 @@ def test_collect_distributed_pipeline_artifacts_runs_stage_collects_and_updates(
|
|
|
176
176
|
persist_tile_embeddings,
|
|
177
177
|
persist_hierarchical_embeddings,
|
|
178
178
|
include_slide_embeddings,
|
|
179
|
+
encoder_name,
|
|
180
|
+
output_variant,
|
|
179
181
|
tile_artifacts,
|
|
180
182
|
hierarchical_artifacts,
|
|
181
183
|
slide_artifacts,
|
|
@@ -186,6 +188,8 @@ def test_collect_distributed_pipeline_artifacts_runs_stage_collects_and_updates(
|
|
|
186
188
|
"persist_tile_embeddings": persist_tile_embeddings,
|
|
187
189
|
"persist_hierarchical_embeddings": persist_hierarchical_embeddings,
|
|
188
190
|
"include_slide_embeddings": include_slide_embeddings,
|
|
191
|
+
"encoder_name": encoder_name,
|
|
192
|
+
"output_variant": output_variant,
|
|
189
193
|
"tile_artifacts": tile_artifacts,
|
|
190
194
|
"hierarchical_artifacts": hierarchical_artifacts,
|
|
191
195
|
"slide_artifacts": slide_artifacts,
|
|
@@ -220,6 +224,8 @@ def test_collect_distributed_pipeline_artifacts_runs_stage_collects_and_updates(
|
|
|
220
224
|
assert captured["update"]["persist_tile_embeddings"] is True
|
|
221
225
|
assert captured["update"]["persist_hierarchical_embeddings"] is False
|
|
222
226
|
assert captured["update"]["include_slide_embeddings"] is True
|
|
227
|
+
assert captured["update"]["encoder_name"] == "prism"
|
|
228
|
+
assert captured["update"]["output_variant"] == "default"
|
|
223
229
|
assert captured["update"]["tile_artifacts"] == ["tile-artifact"]
|
|
224
230
|
assert captured["update"]["hierarchical_artifacts"] == []
|
|
225
231
|
assert captured["update"]["slide_artifacts"] == ["slide-artifact"]
|
|
@@ -2802,6 +2808,49 @@ def test_build_hierarchical_index_is_region_major_and_row_major_within_region():
|
|
|
2802
2808
|
)
|
|
2803
2809
|
|
|
2804
2810
|
|
|
2811
|
+
def test_resolve_hierarchical_geometry_scales_tile_first_under_spacing_mismatch():
|
|
2812
|
+
import slide2vec.inference as inference
|
|
2813
|
+
|
|
2814
|
+
preprocessing = PreprocessingConfig(
|
|
2815
|
+
target_spacing_um=0.5,
|
|
2816
|
+
target_tile_size_px=224,
|
|
2817
|
+
target_region_size_px=1792,
|
|
2818
|
+
region_tile_multiple=8,
|
|
2819
|
+
)
|
|
2820
|
+
tiling_result = SimpleNamespace(
|
|
2821
|
+
effective_tile_size_px=3319,
|
|
2822
|
+
effective_spacing_um=0.27,
|
|
2823
|
+
tile_size_lv0=3319,
|
|
2824
|
+
base_spacing_um=0.27,
|
|
2825
|
+
)
|
|
2826
|
+
|
|
2827
|
+
geometry = inference._resolve_hierarchical_geometry(preprocessing, tiling_result)
|
|
2828
|
+
|
|
2829
|
+
assert geometry["effective_tile_size_px"] == 415
|
|
2830
|
+
assert geometry["effective_region_size_px"] == 3320
|
|
2831
|
+
assert geometry["tile_size_lv0"] == 415
|
|
2832
|
+
assert geometry["tiles_per_region"] == 64
|
|
2833
|
+
|
|
2834
|
+
|
|
2835
|
+
def test_build_hierarchical_index_uses_tile_first_level0_offsets_under_spacing_mismatch():
|
|
2836
|
+
import slide2vec.inference as inference
|
|
2837
|
+
|
|
2838
|
+
tiling_result = SimpleNamespace(
|
|
2839
|
+
x=np.array([100], dtype=np.int64),
|
|
2840
|
+
y=np.array([200], dtype=np.int64),
|
|
2841
|
+
)
|
|
2842
|
+
|
|
2843
|
+
index = inference._build_hierarchical_index(
|
|
2844
|
+
tiling_result,
|
|
2845
|
+
region_tile_multiple=8,
|
|
2846
|
+
tile_size_lv0=415,
|
|
2847
|
+
)
|
|
2848
|
+
|
|
2849
|
+
assert index.tiles_per_region == 64
|
|
2850
|
+
np.testing.assert_array_equal(index.subtile_x[:8], np.array([100, 515, 930, 1345, 1760, 2175, 2590, 3005], dtype=np.int64))
|
|
2851
|
+
np.testing.assert_array_equal(index.subtile_y[::8], np.array([200, 615, 1030, 1445, 1860, 2275, 2690, 3105], dtype=np.int64))
|
|
2852
|
+
|
|
2853
|
+
|
|
2805
2854
|
def test_merge_hierarchical_embedding_shards_restores_original_region_shape():
|
|
2806
2855
|
import slide2vec.inference as inference
|
|
2807
2856
|
|
|
@@ -2926,6 +2975,7 @@ def test_compute_hierarchical_embeddings_for_slide_encodes_flat_tile_batches_and
|
|
|
2926
2975
|
tile_size_lv0=448,
|
|
2927
2976
|
target_spacing_um=0.5,
|
|
2928
2977
|
effective_spacing_um=0.5,
|
|
2978
|
+
base_spacing_um=0.5,
|
|
2929
2979
|
read_level=0,
|
|
2930
2980
|
)
|
|
2931
2981
|
|
|
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
|