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.
- {slide2vec-5.0.0 → slide2vec-5.0.1}/PKG-INFO +1 -1
- {slide2vec-5.0.0 → slide2vec-5.0.1}/pyproject.toml +5 -2
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/__init__.py +1 -1
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/hibou.py +9 -2
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/midnight.py +12 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/virchow.py +2 -8
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/SOURCES.txt +0 -1
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_output_consistency.py +1 -0
- slide2vec-5.0.0/tests/test_dense_locality_gated.py +0 -162
- {slide2vec-5.0.0 → slide2vec-5.0.1}/LICENSE +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/README.md +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/setup.cfg +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/__main__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/api.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/cli.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/inference.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/progress.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_encoder_registry.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_progress.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_core.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_inference.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_regression_models.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_tile_store.py +0 -0
- {slide2vec-5.0.0 → slide2vec-5.0.1}/tests/test_tiling_pipeline.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "5.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.
|
|
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
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
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)
|
|
@@ -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
|
|
@@ -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
|
|
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
|