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.
Files changed (66) hide show
  1. {slide2vec-4.0.3 → slide2vec-4.1.0}/PKG-INFO +1 -1
  2. {slide2vec-4.0.3 → slide2vec-4.1.0}/pyproject.toml +2 -2
  3. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/__init__.py +1 -1
  4. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/direct_embed_worker.py +3 -0
  5. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/inference.py +19 -8
  6. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/PKG-INFO +1 -1
  7. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_inference.py +50 -0
  8. {slide2vec-4.0.3 → slide2vec-4.1.0}/LICENSE +0 -0
  9. {slide2vec-4.0.3 → slide2vec-4.1.0}/README.md +0 -0
  10. {slide2vec-4.0.3 → slide2vec-4.1.0}/setup.cfg +0 -0
  11. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/__main__.py +0 -0
  12. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/api.py +0 -0
  13. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/artifacts.py +0 -0
  14. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/cli.py +0 -0
  15. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/configs/__init__.py +0 -0
  16. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/configs/default.yaml +0 -0
  17. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/__init__.py +0 -0
  18. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/dataset.py +0 -0
  19. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/tile_reader.py +0 -0
  20. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/data/tile_store.py +0 -0
  21. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/__init__.py +0 -0
  22. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/distributed/pipeline_worker.py +0 -0
  23. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/__init__.py +0 -0
  24. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/base.py +0 -0
  25. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/__init__.py +0 -0
  26. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/conch.py +0 -0
  27. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/gigapath.py +0 -0
  28. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/hibou.py +0 -0
  29. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/hoptimus.py +0 -0
  30. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/midnight.py +0 -0
  31. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/musk.py +0 -0
  32. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/phikon.py +0 -0
  33. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/prism.py +0 -0
  34. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/prost40m.py +0 -0
  35. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/titan.py +0 -0
  36. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/uni.py +0 -0
  37. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/models/virchow.py +0 -0
  38. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/registry.py +0 -0
  39. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/encoders/validation.py +0 -0
  40. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/main.py +0 -0
  41. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/model_settings.py +0 -0
  42. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/progress.py +0 -0
  43. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/registry.py +0 -0
  44. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/resources.py +0 -0
  45. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/runtime_types.py +0 -0
  46. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/__init__.py +0 -0
  47. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/config.py +0 -0
  48. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/coordinates.py +0 -0
  49. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/log_utils.py +0 -0
  50. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/tiling_io.py +0 -0
  51. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec/utils/utils.py +0 -0
  52. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/SOURCES.txt +0 -0
  53. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/dependency_links.txt +0 -0
  54. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/entry_points.txt +0 -0
  55. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/not-zip-safe +0 -0
  56. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/requires.txt +0 -0
  57. {slide2vec-4.0.3 → slide2vec-4.1.0}/slide2vec.egg-info/top_level.txt +0 -0
  58. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_batch_collator_timing.py +0 -0
  59. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_encoder_registry.py +0 -0
  60. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_hs2p_package_cutover.py +0 -0
  61. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_output_consistency.py +0 -0
  62. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_packaging_metadata.py +0 -0
  63. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_progress.py +0 -0
  64. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_core.py +0 -0
  65. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_regression_models.py +0 -0
  66. {slide2vec-4.0.3 → slide2vec-4.1.0}/tests/test_tile_store.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 4.0.3
3
+ Version: 4.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "slide2vec"
7
- version = "4.0.3"
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.3"
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.3"
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
- effective_region_size_px = int(getattr(tiling_result, "effective_tile_size_px"))
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": effective_region_size_px // multiple,
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 // multiple,
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
- tile_size_lv0 = int(getattr(tiling_result, "tile_size_lv0"))
141
- subtile_size_lv0 = tile_size_lv0 // multiple
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 model._output_variant if hasattr(model, "_output_variant") else None
2276
+ return requested_output_variant
2266
2277
  resolved = resolve_encoder_output(
2267
2278
  model.name,
2268
- requested_output_variant=model._output_variant,
2279
+ requested_output_variant=requested_output_variant,
2269
2280
  )
2270
2281
  return str(resolved["output_variant"])
2271
2282
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 4.0.3
3
+ Version: 4.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
@@ -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