slide2vec 5.1.0__tar.gz → 5.1.1__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 (100) hide show
  1. {slide2vec-5.1.0 → slide2vec-5.1.1}/PKG-INFO +1 -1
  2. {slide2vec-5.1.0 → slide2vec-5.1.1}/pyproject.toml +2 -2
  3. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/__init__.py +1 -1
  4. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/dense_regions.py +35 -6
  5. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/PKG-INFO +1 -1
  6. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_dense_regions.py +43 -0
  7. {slide2vec-5.1.0 → slide2vec-5.1.1}/LICENSE +0 -0
  8. {slide2vec-5.1.0 → slide2vec-5.1.1}/README.md +0 -0
  9. {slide2vec-5.1.0 → slide2vec-5.1.1}/setup.cfg +0 -0
  10. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/__main__.py +0 -0
  11. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/api.py +0 -0
  12. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/artifacts.py +0 -0
  13. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/cli.py +0 -0
  14. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/configs/__init__.py +0 -0
  15. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/configs/default.yaml +0 -0
  16. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/configs/resources.py +0 -0
  17. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/data/__init__.py +0 -0
  18. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/data/dataset.py +0 -0
  19. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/data/tile_reader.py +0 -0
  20. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/data/tile_store.py +0 -0
  21. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/distributed/__init__.py +0 -0
  22. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/distributed/direct_embed_worker.py +0 -0
  23. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/distributed/pipeline_worker.py +0 -0
  24. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/__init__.py +0 -0
  25. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/base.py +0 -0
  26. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/__init__.py +0 -0
  27. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/conch.py +0 -0
  28. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/gigapath.py +0 -0
  29. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/hibou.py +0 -0
  30. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/hoptimus.py +0 -0
  31. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/lunit.py +0 -0
  32. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/midnight.py +0 -0
  33. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  34. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  35. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/case.py +0 -0
  36. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
  37. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
  38. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/moozy/types.py +0 -0
  39. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/musk.py +0 -0
  40. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/phikon.py +0 -0
  41. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/prism.py +0 -0
  42. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/prost40m.py +0 -0
  43. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/titan.py +0 -0
  44. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/uni.py +0 -0
  45. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/models/virchow.py +0 -0
  46. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/registry.py +0 -0
  47. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/encoders/validation.py +0 -0
  48. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/inference.py +0 -0
  49. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/progress.py +0 -0
  50. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/__init__.py +0 -0
  51. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/artifacts_collect.py +0 -0
  52. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/batching.py +0 -0
  53. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/cpu_budget.py +0 -0
  54. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/dense_sliding.py +0 -0
  55. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/distributed.py +0 -0
  56. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/distributed_stage.py +0 -0
  57. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/embedding.py +0 -0
  58. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/embedding_persist.py +0 -0
  59. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
  60. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/hierarchical.py +0 -0
  61. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/manifest.py +0 -0
  62. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/model_settings.py +0 -0
  63. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/patient_pipeline.py +0 -0
  64. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/persist_callbacks.py +0 -0
  65. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/persistence.py +0 -0
  66. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/process_list.py +0 -0
  67. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/progress_bridge.py +0 -0
  68. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/registry.py +0 -0
  69. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/serialization.py +0 -0
  70. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/slide_encode.py +0 -0
  71. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/tiling.py +0 -0
  72. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
  73. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/types.py +0 -0
  74. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/runtime/worker_io.py +0 -0
  75. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/__init__.py +0 -0
  76. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/config.py +0 -0
  77. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/coordinates.py +0 -0
  78. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/log_utils.py +0 -0
  79. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/tiling_io.py +0 -0
  80. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec/utils/utils.py +0 -0
  81. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/SOURCES.txt +0 -0
  82. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/dependency_links.txt +0 -0
  83. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/entry_points.txt +0 -0
  84. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/not-zip-safe +0 -0
  85. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/requires.txt +0 -0
  86. {slide2vec-5.1.0 → slide2vec-5.1.1}/slide2vec.egg-info/top_level.txt +0 -0
  87. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_architecture_runtime_split.py +0 -0
  88. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_attention_extraction.py +0 -0
  89. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_dense_extraction.py +0 -0
  90. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_dense_sliding.py +0 -0
  91. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_encoder_registry.py +0 -0
  92. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_hs2p_package_cutover.py +0 -0
  93. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_output_consistency.py +0 -0
  94. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_progress.py +0 -0
  95. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_regression_core.py +0 -0
  96. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_regression_inference.py +0 -0
  97. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_regression_models.py +0 -0
  98. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_runtime_batching.py +0 -0
  99. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_tile_store.py +0 -0
  100. {slide2vec-5.1.0 → slide2vec-5.1.1}/tests/test_tiling_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 5.1.0
3
+ Version: 5.1.1
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 = "5.1.0"
7
+ version = "5.1.1"
8
8
  description = "Embedding of whole slide images with Foundation Models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -167,7 +167,7 @@ no_implicit_reexport = true
167
167
  max-line-length = 160
168
168
 
169
169
  [tool.bumpver]
170
- current_version = "5.1.0"
170
+ current_version = "5.1.1"
171
171
  version_pattern = "MAJOR.MINOR.PATCH"
172
172
  commit = false # We do version bumping in CI, not as a commit
173
173
  tag = false # Git tag already exists — we don't auto-tag
@@ -11,7 +11,7 @@ from slide2vec.api import (
11
11
  from slide2vec.artifacts import HierarchicalEmbeddingArtifact, SlideEmbeddingArtifact, TileEmbeddingArtifact
12
12
 
13
13
 
14
- __version__ = "5.1.0"
14
+ __version__ = "5.1.1"
15
15
 
16
16
  __all__ = [
17
17
  "Model",
@@ -39,9 +39,31 @@ import torch
39
39
  import torch.nn.functional as F
40
40
  from PIL import Image
41
41
 
42
+ from slide2vec.runtime.batching import autocast_dtype
42
43
  from slide2vec.runtime.dense_sliding import encode_dense_sliding
43
44
  from slide2vec.runtime.slide_encode import slide_encode_autocast_ctx
44
45
 
46
+
47
+ def _resolve_output_dtype(output_dtype: "torch.dtype | None", precision: str) -> "torch.dtype":
48
+ """Resolve the dtype emitted grids are materialized in.
49
+
50
+ Defaults (``output_dtype is None``) to the model's compute precision — fp16 runs
51
+ yield fp16 grids — so the engine no longer force-upcasts everything to float32.
52
+ numpy has no bfloat16, so a bf16 compute precision widens to float32 (its lossless
53
+ container) and an *explicit* ``torch.bfloat16`` request is rejected: the grid is
54
+ materialized via ``.numpy()`` and bfloat16 cannot cross that boundary.
55
+ """
56
+ if output_dtype is None:
57
+ compute = autocast_dtype(torch, precision) # float16 | bfloat16 | None(fp32)
58
+ return torch.float16 if compute == torch.float16 else torch.float32
59
+ if output_dtype == torch.bfloat16:
60
+ raise ValueError(
61
+ "output_dtype=torch.bfloat16 cannot be materialized as a numpy grid; "
62
+ "request torch.float16 or torch.float32"
63
+ )
64
+ return output_dtype
65
+
66
+
45
67
  _PAD_MODES = {"reflect", "constant", "zero", "replicate"}
46
68
 
47
69
 
@@ -161,14 +183,16 @@ def iter_regions_dense(
161
183
  attention_include_registers: bool = False,
162
184
  batch_size: int = 1,
163
185
  precision: str = "fp32",
186
+ output_dtype: "torch.dtype | None" = None,
164
187
  dense_transform: Callable | None = None,
165
188
  ) -> Iterator[np.ndarray]:
166
189
  """Stream slide regions at ``coordinates`` into dense grids, one per coordinate.
167
190
 
168
- Yields one ``(d, grid_h, grid_w)`` ``float32`` grid per coordinate, in coordinate
169
- order. Regions are read and encoded one ``batch_size`` chunk at a time, so resident
170
- host memory is bounded by ``batch_size`` rather than by a slide's ROI count (the loop
171
- holds at most one batch of grids resident — no per-slide accumulation).
191
+ Yields one ``(d, grid_h, grid_w)`` grid per coordinate, in coordinate order, in the
192
+ model's compute ``precision`` by default (fp16 runs yield fp16 grids; see
193
+ ``output_dtype``). Regions are read and encoded one ``batch_size`` chunk at a time, so
194
+ resident host memory is bounded by ``batch_size`` rather than by a slide's ROI count
195
+ (the loop holds at most one batch of grids resident — no per-slide accumulation).
172
196
 
173
197
  Injectable core: takes a constructed dense-capable ``model`` (with
174
198
  ``encode_tiles_dense`` / ``encode_tiles_attention`` / ``patch_size`` /
@@ -194,8 +218,12 @@ def iter_regions_dense(
194
218
  sliding is internal to extraction.
195
219
  overlap: fractional window overlap in ``[0, 1)`` for the sliding path (ignored when
196
220
  ``window_size is None``); the stride is ``window * (1 - overlap)``.
221
+ output_dtype: torch dtype the grids are materialized in. ``None`` (default) follows
222
+ the compute ``precision`` — fp16 → fp16, fp32 → fp32, bf16 → fp32 (numpy has no
223
+ bfloat16). Pass e.g. ``torch.float32`` to force a lossless cache regardless of
224
+ precision; an explicit ``torch.bfloat16`` is rejected (cannot cross ``.numpy()``).
197
225
 
198
- Yields ``float32`` grids in coordinate order; empty ``coordinates`` yields nothing.
226
+ Yields grids in coordinate order in ``output_dtype``; empty ``coordinates`` yields nothing.
199
227
  ``feature_kind`` selects ``encode_tiles_dense`` (patch grid) vs
200
228
  ``encode_tiles_attention`` (CLS-attention grid); both produce a ``(C, gh, gw)`` grid and
201
229
  share this path. Each yielded grid is a standalone contiguous copy, so it does not pin
@@ -203,6 +231,7 @@ def iter_regions_dense(
203
231
  """
204
232
  if pad_mode not in _PAD_MODES:
205
233
  raise ValueError(f"unsupported pad_mode {pad_mode!r}; expected one of {sorted(_PAD_MODES)}")
234
+ resolved_output_dtype = _resolve_output_dtype(output_dtype, precision)
206
235
  geometry = compute_dense_geometry(target_size=target_size, patch_size=model.patch_size)
207
236
  if dense_transform is None:
208
237
  dense_transform = model.get_dense_transform()
@@ -262,7 +291,7 @@ def iter_regions_dense(
262
291
  raise ValueError(
263
292
  f"{feature_kind} encode returned a {out.ndim}-D tensor; expected (B, d, gh, gw)."
264
293
  )
265
- batch_np = out.detach().float().cpu().numpy()
294
+ batch_np = out.detach().to(resolved_output_dtype).cpu().numpy()
266
295
  for i in range(batch_np.shape[0]):
267
296
  # Standalone C-contiguous copy: a per-row view would pin the whole
268
297
  # batch alive (the blended sliding output is contiguous, so a view of
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 5.1.0
3
+ Version: 5.1.1
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
@@ -18,6 +18,7 @@ timm = pytest.importorskip("timm")
18
18
 
19
19
  from slide2vec.encoders.base import TimmTileEncoder # noqa: E402
20
20
  from slide2vec.runtime.dense_regions import ( # noqa: E402
21
+ _resolve_output_dtype,
21
22
  compute_dense_geometry,
22
23
  iter_regions_dense,
23
24
  pad_image_to_encoded,
@@ -219,3 +220,45 @@ def test_iter_regions_dense_validates_eagerly_before_any_read(kwargs):
219
220
  requested_spacing_um=0.5, target_size=target_size, **kwargs,
220
221
  )
221
222
  assert wsi.calls == []
223
+
224
+
225
+ @pytest.mark.parametrize(
226
+ "precision,expected",
227
+ [("fp16", torch.float16), ("fp32", torch.float32), ("bf16", torch.float32)],
228
+ ids=["fp16->fp16", "fp32->fp32", "bf16->fp32(numpy-safe)"],
229
+ )
230
+ def test_resolve_output_dtype_defaults_follow_precision(precision, expected):
231
+ # output_dtype=None tracks the compute precision; bf16 widens to fp32 (numpy has no
232
+ # bfloat16). An explicit dtype overrides; an explicit bfloat16 is rejected.
233
+ assert _resolve_output_dtype(None, precision) is expected
234
+ assert _resolve_output_dtype(torch.float32, precision) is torch.float32
235
+ assert _resolve_output_dtype(torch.float16, precision) is torch.float16
236
+ with pytest.raises(ValueError):
237
+ _resolve_output_dtype(torch.bfloat16, precision)
238
+
239
+
240
+ @pytest.mark.parametrize("dtype,np_dtype", [(torch.float16, np.float16), (torch.float32, np.float32)])
241
+ def test_iter_regions_dense_honours_output_dtype(dtype, np_dtype):
242
+ """An explicit output_dtype materializes the grids in that dtype, deterministically."""
243
+ enc = _encoder()
244
+ target_size = 64
245
+ wsi = _FakeWSI(target_h=target_size, target_w=target_size)
246
+ grids = list(iter_regions_dense(
247
+ model=enc, device="cpu", wsi=wsi, coordinates=[(0, 0)],
248
+ requested_spacing_um=0.5, target_size=target_size, output_dtype=dtype,
249
+ ))
250
+ assert len(grids) == 1
251
+ assert grids[0].dtype == np_dtype
252
+
253
+
254
+ def test_iter_regions_dense_rejects_bfloat16_output_eagerly():
255
+ """output_dtype=bfloat16 (uncrossable by .numpy()) raises at the call site, no read."""
256
+ enc = _encoder()
257
+ target_size = 64
258
+ wsi = _FakeWSI(target_h=target_size, target_w=target_size)
259
+ with pytest.raises(ValueError):
260
+ iter_regions_dense(
261
+ model=enc, device="cpu", wsi=wsi, coordinates=[(0, 0)],
262
+ requested_spacing_um=0.5, target_size=target_size, output_dtype=torch.bfloat16,
263
+ )
264
+ assert wsi.calls == []
File without changes
File without changes
File without changes
File without changes
File without changes