slide2vec 5.0.0__tar.gz → 5.0.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 (99) hide show
  1. {slide2vec-5.0.0 → slide2vec-5.0.1}/PKG-INFO +1 -1
  2. {slide2vec-5.0.0 → slide2vec-5.0.1}/pyproject.toml +5 -2
  3. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/__init__.py +1 -1
  4. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/hibou.py +9 -2
  5. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/midnight.py +12 -0
  6. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/virchow.py +2 -8
  7. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/PKG-INFO +1 -1
  8. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/SOURCES.txt +0 -1
  9. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_output_consistency.py +1 -0
  10. slide2vec-5.0.0/tests/test_dense_locality_gated.py +0 -162
  11. {slide2vec-5.0.0 → slide2vec-5.0.1}/LICENSE +0 -0
  12. {slide2vec-5.0.0 → slide2vec-5.0.1}/README.md +0 -0
  13. {slide2vec-5.0.0 → slide2vec-5.0.1}/setup.cfg +0 -0
  14. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/__main__.py +0 -0
  15. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/api.py +0 -0
  16. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/artifacts.py +0 -0
  17. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/cli.py +0 -0
  18. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/__init__.py +0 -0
  19. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/default.yaml +0 -0
  20. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/resources.py +0 -0
  21. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/__init__.py +0 -0
  22. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/dataset.py +0 -0
  23. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/tile_reader.py +0 -0
  24. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/tile_store.py +0 -0
  25. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/__init__.py +0 -0
  26. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/direct_embed_worker.py +0 -0
  27. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/pipeline_worker.py +0 -0
  28. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/__init__.py +0 -0
  29. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/base.py +0 -0
  30. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/__init__.py +0 -0
  31. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/conch.py +0 -0
  32. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/gigapath.py +0 -0
  33. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/hoptimus.py +0 -0
  34. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/lunit.py +0 -0
  35. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  36. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  37. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/case.py +0 -0
  38. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
  39. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
  40. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/types.py +0 -0
  41. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/musk.py +0 -0
  42. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/phikon.py +0 -0
  43. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/prism.py +0 -0
  44. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/prost40m.py +0 -0
  45. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/titan.py +0 -0
  46. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/uni.py +0 -0
  47. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/registry.py +0 -0
  48. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/validation.py +0 -0
  49. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/inference.py +0 -0
  50. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/progress.py +0 -0
  51. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/__init__.py +0 -0
  52. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/artifacts_collect.py +0 -0
  53. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/batching.py +0 -0
  54. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/cpu_budget.py +0 -0
  55. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/dense_regions.py +0 -0
  56. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/distributed.py +0 -0
  57. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/distributed_stage.py +0 -0
  58. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding.py +0 -0
  59. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding_persist.py +0 -0
  60. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
  61. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/hierarchical.py +0 -0
  62. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/manifest.py +0 -0
  63. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/model_settings.py +0 -0
  64. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/patient_pipeline.py +0 -0
  65. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/persist_callbacks.py +0 -0
  66. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/persistence.py +0 -0
  67. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/process_list.py +0 -0
  68. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/progress_bridge.py +0 -0
  69. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/registry.py +0 -0
  70. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/serialization.py +0 -0
  71. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/slide_encode.py +0 -0
  72. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/tiling.py +0 -0
  73. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
  74. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/types.py +0 -0
  75. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/worker_io.py +0 -0
  76. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/__init__.py +0 -0
  77. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/config.py +0 -0
  78. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/coordinates.py +0 -0
  79. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/log_utils.py +0 -0
  80. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/tiling_io.py +0 -0
  81. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/utils.py +0 -0
  82. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/dependency_links.txt +0 -0
  83. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/entry_points.txt +0 -0
  84. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/not-zip-safe +0 -0
  85. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/requires.txt +0 -0
  86. {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/top_level.txt +0 -0
  87. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_architecture_runtime_split.py +0 -0
  88. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_attention_extraction.py +0 -0
  89. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_dense_extraction.py +0 -0
  90. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_dense_regions.py +0 -0
  91. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_encoder_registry.py +0 -0
  92. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_hs2p_package_cutover.py +0 -0
  93. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_progress.py +0 -0
  94. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_core.py +0 -0
  95. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_inference.py +0 -0
  96. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_models.py +0 -0
  97. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_runtime_batching.py +0 -0
  98. {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_tile_store.py +0 -0
  99. {slide2vec-5.0.0 → slide2vec-5.0.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.0.0
3
+ Version: 5.0.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.0.0"
7
+ version = "5.0.1"
8
8
  description = "Embedding of whole slide images with Foundation Models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -145,6 +145,9 @@ addopts = "--cov=slide2vec"
145
145
  testpaths = [
146
146
  "tests",
147
147
  ]
148
+ markers = [
149
+ "heavy: real-weight foundation-model inference on CPU; minutes per test. Excluded from the PR suite via `-m 'not heavy'`; run on the scheduled/manual heavy workflow (.github/workflows/nightly-heavy.yaml).",
150
+ ]
148
151
 
149
152
  [tool.mypy]
150
153
  mypy_path = "."
@@ -164,7 +167,7 @@ no_implicit_reexport = true
164
167
  max-line-length = 160
165
168
 
166
169
  [tool.bumpver]
167
- current_version = "5.0.0"
170
+ current_version = "5.0.1"
168
171
  version_pattern = "MAJOR.MINOR.PATCH"
169
172
  commit = false # We do version bumping in CI, not as a commit
170
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.0.0"
14
+ __version__ = "5.0.1"
15
15
 
16
16
  __all__ = [
17
17
  "Model",
@@ -54,6 +54,13 @@ class _HibouBase(TileEncoder):
54
54
  v2.Normalize(mean=_HIBOU_MEAN, std=_HIBOU_STD),
55
55
  ])
56
56
 
57
+ @property
58
+ def _num_prefix_tokens(self) -> int:
59
+ # CLS + register tokens. Dinov2-with-registers carries the register tokens
60
+ # between the CLS and patch tokens, so both the dense and attention paths
61
+ # must strip them; deriving the count from config keeps the two in sync.
62
+ return 1 + int(getattr(self._model.config, "num_register_tokens", 0))
63
+
57
64
  def encode_tiles(self, batch: Tensor) -> Tensor:
58
65
  output = self._model(pixel_values=batch)
59
66
  return output.pooler_output
@@ -77,7 +84,7 @@ class _HibouBase(TileEncoder):
77
84
  output.last_hidden_state,
78
85
  grid_h=height // patch,
79
86
  grid_w=width // patch,
80
- num_prefix_tokens=1 + int(getattr(self._model.config, "num_register_tokens", 0)),
87
+ num_prefix_tokens=self._num_prefix_tokens,
81
88
  encoder_name=type(self).__name__,
82
89
  )
83
90
 
@@ -111,7 +118,7 @@ class _HibouBase(TileEncoder):
111
118
  output = self._model(pixel_values=batch, output_attentions=True)
112
119
  return attentions_tuple_to_grids(
113
120
  output.attentions,
114
- num_prefix_tokens=1 + int(getattr(self._model.config, "num_register_tokens", 0)),
121
+ num_prefix_tokens=self._num_prefix_tokens,
115
122
  blocks=blocks,
116
123
  include_registers=include_registers,
117
124
  grid_h=height // patch,
@@ -36,6 +36,18 @@ class Midnight(TileEncoder):
36
36
  self._model = AutoModel.from_pretrained("kaiko-ai/midnight").eval()
37
37
  self._device = preferred_default_device()
38
38
  self._output_variant = resolve_requested_output_variant(output_variant)
39
+ # The pooled, dense, and attention paths all assume a single CLS prefix
40
+ # token (kaiko's reference recipe pools over output[:, 1:]). If a future
41
+ # checkpoint adds register tokens, that assumption silently folds them into
42
+ # the patch mean and mislabels the dense/attention grids — fail loudly here.
43
+ num_register_tokens = int(getattr(self._model.config, "num_register_tokens", 0))
44
+ if num_register_tokens:
45
+ raise ValueError(
46
+ "Midnight encoder assumes a single CLS prefix token, but the loaded "
47
+ f"checkpoint reports num_register_tokens={num_register_tokens}. Update "
48
+ "the pooled/dense/attention paths to strip the register tokens before "
49
+ "using this checkpoint."
50
+ )
39
51
 
40
52
  def get_transform(self) -> Callable:
41
53
  return v2.Compose([
@@ -16,8 +16,6 @@ _VIRCHOW_OUTPUT_DIMS = {
16
16
  class _VirchowBase(TimmTileEncoder):
17
17
  """Base for Virchow models that concat CLS + mean-pooled patch tokens."""
18
18
 
19
- _num_prefix_tokens: int = 1 # Override in subclass if needed
20
-
21
19
  def __init__(self, model_name: str, *, output_variant: str | None = None):
22
20
  self._output_variant = resolve_requested_output_variant(
23
21
  output_variant,
@@ -36,7 +34,7 @@ class _VirchowBase(TimmTileEncoder):
36
34
  cls_token = output[:, 0]
37
35
  if self._output_variant == "cls":
38
36
  return cls_token
39
- patch_tokens = output[:, self._num_prefix_tokens:]
37
+ patch_tokens = output[:, self._model.num_prefix_tokens:]
40
38
  return torch.cat([cls_token, patch_tokens.mean(dim=1)], dim=-1)
41
39
 
42
40
  @property
@@ -57,8 +55,6 @@ class _VirchowBase(TimmTileEncoder):
57
55
  source="paige-ai/Virchow",
58
56
  )
59
57
  class Virchow(_VirchowBase):
60
- _num_prefix_tokens = 1
61
-
62
58
  def __init__(self, *, output_variant: str | None = None):
63
59
  super().__init__("hf-hub:paige-ai/Virchow", output_variant=output_variant)
64
60
 
@@ -71,12 +67,10 @@ class Virchow(_VirchowBase):
71
67
  },
72
68
  default_output_variant="cls_patch_mean",
73
69
  input_size=224,
74
- supported_spacing_um=[0.5, 1.0, 2.0],
70
+ supported_spacing_um=[0.25, 0.5, 1.0, 2.0],
75
71
  precision="fp16",
76
72
  source="paige-ai/Virchow2",
77
73
  )
78
74
  class Virchow2(_VirchowBase):
79
- _num_prefix_tokens = 5 # 1 CLS + 4 register tokens
80
-
81
75
  def __init__(self, *, output_variant: str | None = None):
82
76
  super().__init__("hf-hub:paige-ai/Virchow2", output_variant=output_variant)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 5.0.0
3
+ Version: 5.0.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
@@ -83,7 +83,6 @@ slide2vec/utils/utils.py
83
83
  tests/test_architecture_runtime_split.py
84
84
  tests/test_attention_extraction.py
85
85
  tests/test_dense_extraction.py
86
- tests/test_dense_locality_gated.py
87
86
  tests/test_dense_regions.py
88
87
  tests/test_encoder_registry.py
89
88
  tests/test_hs2p_package_cutover.py
@@ -101,6 +101,7 @@ def mask_path() -> Path:
101
101
  return p
102
102
 
103
103
 
104
+ @pytest.mark.heavy
104
105
  @pytest.mark.skipif(
105
106
  not os.environ.get("HF_TOKEN"),
106
107
  reason="HF_TOKEN required for model weight download",
@@ -1,162 +0,0 @@
1
- """Spatial-registration check for dense extraction at the larger (512px) grid.
2
-
3
- This is the "spatially correct, not just non-crashing" oracle. It places a
4
- high-contrast block at a known, asymmetric grid cell and verifies the *change*
5
- in the dense feature map (vs a background-only encoding) peaks at that cell.
6
-
7
- Why a *difference* probe: register-free ViTs (UNI, Virchow, Lunit, Prost40M)
8
- carry high-norm artifact/outlier tokens (Darcet et al., "Vision Transformers
9
- Need Registers") that dominate any raw distance-from-background score and produce
10
- false misregistration. Encoding background-only and localizing the delta cancels
11
- those artifacts, isolating the stimulus.
12
-
13
- Gated on HF_TOKEN (downloads real encoder weights) and skips per-encoder when a
14
- download is unavailable. Heavy on CPU for ViT-giant encoders — intended to be run
15
- deliberately (ideally on GPU), not in the fast offline suite.
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import os
21
-
22
- import pytest
23
-
24
- torch = pytest.importorskip("torch")
25
- pytest.importorskip("timm")
26
- from timm.data import resolve_data_config # noqa: E402
27
-
28
- pytestmark = pytest.mark.skipif(
29
- not os.environ.get("HF_TOKEN"),
30
- reason="HF_TOKEN required to download encoder weights",
31
- )
32
-
33
-
34
- def _encoder_classes() -> dict:
35
- from slide2vec.encoders.models.hoptimus import H0Mini, HOptimus0, HOptimus1
36
- from slide2vec.encoders.models.lunit import LunitTileEncoder
37
- from slide2vec.encoders.models.prost40m import Prost40M
38
- from slide2vec.encoders.models.uni import UNI, UNI2
39
- from slide2vec.encoders.models.virchow import Virchow, Virchow2
40
-
41
- return {
42
- "uni": UNI,
43
- "uni2": UNI2,
44
- "virchow": Virchow,
45
- "virchow2": Virchow2,
46
- "h0-mini": H0Mini,
47
- "h-optimus-0": HOptimus0,
48
- "h-optimus-1": HOptimus1,
49
- "lunit": LunitTileEncoder,
50
- "prost40m": Prost40M,
51
- }
52
-
53
-
54
- def _registration_failures(enc, *, target_size: int = 512, block_cells: int = 2):
55
- """Run the difference-probe; return (encoded_size, grid, list_of_failures)."""
56
- model = enc._model
57
- cfg = resolve_data_config(getattr(model, "pretrained_cfg", {}) or {}, model=model)
58
- mean = torch.tensor(cfg["mean"]).view(1, 3, 1, 1)
59
- std = torch.tensor(cfg["std"]).view(1, 3, 1, 1)
60
-
61
- patch = enc._dense_patch_size()[0]
62
- encoded = (target_size // patch) * patch # largest patch multiple <= target
63
- grid = encoded // patch
64
-
65
- @torch.no_grad()
66
- def encode(image: torch.Tensor) -> torch.Tensor:
67
- return enc.encode_tiles_dense((image - mean) / std)[0] # (d, G, G)
68
-
69
- background = encode(torch.full((1, 3, encoded, encoded), 0.5))
70
-
71
- def stimulus(cell_r: int, cell_c: int) -> torch.Tensor:
72
- image = torch.full((1, 3, encoded, encoded), 0.5)
73
- r0, c0 = cell_r * patch, cell_c * patch
74
- r1, c1 = r0 + block_cells * patch, c0 + block_cells * patch
75
- image[:, 0, r0:r1, c0:c1] = 1.0 # red block on gray
76
- image[:, 1, r0:r1, c0:c1] = 0.0
77
- image[:, 2, r0:r1, c0:c1] = 0.0
78
- return image
79
-
80
- # Asymmetric targets so a transpose / flip is detectable; one centre sanity check.
81
- targets = [
82
- (int(0.15 * grid), int(0.72 * grid)),
83
- (int(0.07 * grid), int(0.22 * grid)),
84
- (int(0.85 * grid), int(0.10 * grid)),
85
- (grid // 2, grid // 2),
86
- ]
87
- failures = []
88
- for cell_r, cell_c in targets:
89
- delta = (encode(stimulus(cell_r, cell_c)) - background).pow(2).sum(0).sqrt()
90
- peak_r, peak_c = divmod(int(delta.argmax().item()), grid)
91
- # Correct if the peak lands inside the stimulus block (+1-cell halo).
92
- in_block = (
93
- cell_r - 1 <= peak_r <= cell_r + block_cells
94
- and cell_c - 1 <= peak_c <= cell_c + block_cells
95
- )
96
- if not in_block:
97
- failures.append(((cell_r, cell_c), (peak_r, peak_c)))
98
- return encoded, grid, failures
99
-
100
-
101
- # The encoders whose dynamic_img_size we FLIPPED this session: real hf-hub source
102
- # + the extra timm kwargs slide2vec passes. The offline proxy test verifies the
103
- # no-op property architecturally; this verifies it on the ACTUAL merged configs
104
- # (no_embed_class, patch-arg quirks) before relying on "native-size no-op" as the
105
- # safety guarantee for the global flag change. Native size is read from the model's
106
- # own pretrained_cfg (NOT the registry input_size, which is the larger preprocessing
107
- # tile size the encoder transform downsizes to the model native, e.g. GigaPath
108
- # 256->224).
109
- _FLIPPED_CONFIGS = {
110
- "h-optimus-0": ("hf-hub:bioptimus/H-optimus-0", {"init_values": 1e-5}),
111
- "h-optimus-1": ("hf-hub:bioptimus/H-optimus-1", {"init_values": 1e-5}),
112
- "gigapath": ("hf_hub:prov-gigapath/prov-gigapath", {}),
113
- "lunit": ("hf_hub:1aurent/vit_small_patch8_224.lunit_dino", {}),
114
- "prost40m": ("hf-hub:waticlems/Prost40M", {}),
115
- }
116
-
117
-
118
- @pytest.mark.parametrize("name", sorted(_FLIPPED_CONFIGS))
119
- def test_flipped_encoder_native_size_is_noop_on_real_config(name: str):
120
- """Flipping dynamic_img_size on the REAL merged config is bit-identical at
121
- native size — the guarantee that the global flag change does not alter
122
- existing pooled extraction for these encoders."""
123
- import timm
124
-
125
- source, extra = _FLIPPED_CONFIGS[name]
126
- try:
127
- static = timm.create_model(
128
- source, pretrained=True, num_classes=0, dynamic_img_size=False, **extra
129
- ).eval()
130
- dynamic = timm.create_model(
131
- source, pretrained=True, num_classes=0, dynamic_img_size=True, **extra
132
- ).eval()
133
- except Exception as exc: # gated weights / network unavailable
134
- pytest.skip(f"{name} weights unavailable: {type(exc).__name__}: {exc}")
135
- dynamic.load_state_dict(static.state_dict()) # identical weights, only flag differs
136
- native = static.pretrained_cfg["input_size"][-1] # model native size, not tile size
137
- x = torch.randn(2, 3, native, native)
138
- with torch.no_grad():
139
- torch.testing.assert_close(
140
- static.forward_features(x), dynamic.forward_features(x), rtol=0, atol=1e-6
141
- )
142
-
143
-
144
- # H-optimus recommends dynamic_img_size=False; dense extraction opts in explicitly.
145
- _DENSE_CTOR_KWARGS = {
146
- "h-optimus-0": dict(dynamic_img_size=True, allow_non_recommended_settings=True),
147
- "h-optimus-1": dict(dynamic_img_size=True, allow_non_recommended_settings=True),
148
- }
149
-
150
-
151
- @pytest.mark.parametrize("name", sorted(_encoder_classes()))
152
- def test_dense_grid_is_spatially_registered(name: str):
153
- encoder_cls = _encoder_classes()[name]
154
- try:
155
- enc = encoder_cls(**_DENSE_CTOR_KWARGS.get(name, {})).to("cpu")
156
- except Exception as exc: # gated weights / network unavailable
157
- pytest.skip(f"{name} weights unavailable: {type(exc).__name__}: {exc}")
158
- encoded, grid, failures = _registration_failures(enc)
159
- assert not failures, (
160
- f"{name} @ {encoded}px ({grid}x{grid}) dense grid is misregistered: "
161
- f"(target -> peak) {failures}"
162
- )
File without changes
File without changes
File without changes
File without changes
File without changes