slide2vec 5.8.0__tar.gz → 5.8.2__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.8.0 → slide2vec-5.8.2}/PKG-INFO +3 -3
- {slide2vec-5.8.0 → slide2vec-5.8.2}/pyproject.toml +4 -4
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/__init__.py +1 -1
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/gigapath.py +3 -0
- slide2vec-5.8.2/slide2vec/encoders/models/titan.py +180 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/PKG-INFO +3 -3
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/SOURCES.txt +2 -1
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/requires.txt +2 -2
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_hs2p_package_cutover.py +3 -3
- slide2vec-5.8.2/tests/test_titan.py +134 -0
- slide2vec-5.8.0/slide2vec/encoders/models/titan.py +0 -58
- {slide2vec-5.8.0 → slide2vec-5.8.2}/LICENSE +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/README.md +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/setup.cfg +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/__main__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/api.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/artifacts.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/cli.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/configs/resources.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/data/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/data/dataset.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/base.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/inference.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/progress.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/types.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/config.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec/utils/utils.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_attention_extraction.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_extraction.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_regions.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_sliding.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dense_worker.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_encoder_registry.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_image_shard.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_image_stage.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_isight.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_mascaret.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_output_consistency.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_patient_manifest.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_phaet.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_prism2.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_progress.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_regression_core.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_regression_inference.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_regression_models.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_rudolfv2.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_runtime_batching.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_sharding.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_soma_migration.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_tile_store.py +0 -0
- {slide2vec-5.8.0 → slide2vec-5.8.2}/tests/test_tiling_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 5.8.
|
|
3
|
+
Version: 5.8.2
|
|
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
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2
|
|
19
19
|
Requires-Dist: omegaconf
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: numpy<2
|
|
@@ -75,7 +75,7 @@ Requires-Dist: numpy<2; extra == "fm"
|
|
|
75
75
|
Requires-Dist: pandas; extra == "fm"
|
|
76
76
|
Requires-Dist: pillow; extra == "fm"
|
|
77
77
|
Requires-Dist: rich; extra == "fm"
|
|
78
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2; extra == "fm"
|
|
79
79
|
Requires-Dist: wandb; extra == "fm"
|
|
80
80
|
Requires-Dist: torch<2.8,>=2.3; extra == "fm"
|
|
81
81
|
Requires-Dist: torchvision>=0.18.0; extra == "fm"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "5.8.
|
|
7
|
+
version = "5.8.2"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
24
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2",
|
|
25
25
|
"omegaconf",
|
|
26
26
|
"matplotlib",
|
|
27
27
|
"numpy<2",
|
|
@@ -100,7 +100,7 @@ fm = [
|
|
|
100
100
|
"pandas",
|
|
101
101
|
"pillow",
|
|
102
102
|
"rich",
|
|
103
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
103
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2",
|
|
104
104
|
"wandb",
|
|
105
105
|
"torch>=2.3,<2.8",
|
|
106
106
|
"torchvision>=0.18.0",
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "5.8.
|
|
183
|
+
current_version = "5.8.2"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false # We do version bumping in CI, not as a commit
|
|
186
186
|
tag = false # Git tag already exists — we don't auto-tag
|
|
@@ -123,6 +123,9 @@ class GigaPathSlideEncoder(SlideEncoder):
|
|
|
123
123
|
tile_features = tile_features.unsqueeze(0)
|
|
124
124
|
if coordinates.ndim == 2:
|
|
125
125
|
coordinates = coordinates.unsqueeze(0)
|
|
126
|
+
# prov-gigapath's reference usage feeds fp32 tile embeddings under autocast;
|
|
127
|
+
# fp16 input risks dtype mismatches in ops autocast does not cover.
|
|
128
|
+
tile_features = tile_features.float()
|
|
126
129
|
# gigapath_slide_enc12l768d.forward always returns a list of per-layer
|
|
127
130
|
# embeddings (a single element when all_layer_embed is False); the final
|
|
128
131
|
# slide embedding is the last layer, matching prov-gigapath's own usage.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""TITAN slide encoder implementation."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
from transformers import AutoModel
|
|
9
|
+
|
|
10
|
+
from slide2vec.encoders.base import SlideEncoder, preferred_default_device, resolve_requested_output_variant
|
|
11
|
+
from slide2vec.encoders.registry import register_encoder
|
|
12
|
+
|
|
13
|
+
# Pinned so the remote-code patches below stay valid (and so runs are reproducible —
|
|
14
|
+
# an unpinned from_pretrained re-downloads whatever is at the repo HEAD).
|
|
15
|
+
_TITAN_REVISION = "dac6773d9961cfc75503440676ff157a2c6e8d2e"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _alibi_slopes(n: int) -> list[float]:
|
|
19
|
+
# verbatim ALiBi slope schedule from TITAN's get_alibi at the pinned revision
|
|
20
|
+
if math.log2(n).is_integer():
|
|
21
|
+
p = 2 ** (-(2 ** -(math.log2(n) - 3)))
|
|
22
|
+
return [p * (p ** i) for i in range(n)]
|
|
23
|
+
nearest = 2 ** math.floor(math.log2(n))
|
|
24
|
+
base = _alibi_slopes(nearest)
|
|
25
|
+
extra = _alibi_slopes(2 * nearest)[0::2][: n - nearest]
|
|
26
|
+
return base + extra
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _lean_alibi_bias(module, w, h, bg_mask, device, dtype):
|
|
30
|
+
"""Bitwise-identical replacement for TITAN's get_alibi + the caller's cast/move.
|
|
31
|
+
|
|
32
|
+
The reference builds the [1, heads, N, N] bias through N x N float64 numpy on
|
|
33
|
+
the CPU (>100 GB of host RAM for slides in the tens of thousands of tiles) and
|
|
34
|
+
hands SDPA an unaligned bias that forces the math kernel, which materializes a
|
|
35
|
+
second N^2 tensor on the GPU. This builds the same values on-device in fp16, in
|
|
36
|
+
row chunks, with rows padded to a multiple of 8 elements so the memory-efficient
|
|
37
|
+
SDPA kernel accepts the bias — peak memory drops from ~4x to ~1x the bias size.
|
|
38
|
+
"""
|
|
39
|
+
ii, jj = torch.meshgrid(
|
|
40
|
+
torch.arange(w, device=device), torch.arange(h, device=device), indexing="ij"
|
|
41
|
+
)
|
|
42
|
+
if bg_mask is not None:
|
|
43
|
+
mask = bg_mask.to(device).squeeze(0)
|
|
44
|
+
ii, jj = ii[mask], jj[mask]
|
|
45
|
+
points = torch.stack([ii.reshape(-1), jj.reshape(-1)], dim=1).float()
|
|
46
|
+
n = points.shape[0]
|
|
47
|
+
length = n + 1 # +1 for the cls token; its bias row/col stays zero
|
|
48
|
+
padded = ((length + 7) // 8) * 8
|
|
49
|
+
slopes = torch.tensor(
|
|
50
|
+
_alibi_slopes(module.num_heads), device=device, dtype=torch.float32
|
|
51
|
+
).view(module.num_heads, 1, 1)
|
|
52
|
+
bias = torch.zeros(1, module.num_heads, length, padded, device=device, dtype=dtype)
|
|
53
|
+
# chunk intermediate is (heads, step, n) fp32 — stays under ~1.5 GB even at 53k tiles
|
|
54
|
+
step = 512
|
|
55
|
+
for start in range(0, n, step):
|
|
56
|
+
diff = points[start : start + step].unsqueeze(1) - points.unsqueeze(0)
|
|
57
|
+
dist = diff.square().sum(-1).sqrt()
|
|
58
|
+
bias[0, :, 1 + start : 1 + start + dist.shape[0], 1:length] = (
|
|
59
|
+
dist.unsqueeze(0) * slopes * -1
|
|
60
|
+
).to(dtype)
|
|
61
|
+
return bias[..., :length]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _patch_titan_remote_code(model) -> bool:
|
|
65
|
+
"""Patch TITAN's remote code for fp16 input and bounded bias memory.
|
|
66
|
+
|
|
67
|
+
Two patches on the dynamically loaded module (it exists only after
|
|
68
|
+
from_pretrained): preprocess_features runs its grid index_add_ in fp32 (the op
|
|
69
|
+
rejects fp16 features) but returns the grid in the features' own dtype — an
|
|
70
|
+
exact roundtrip that keeps the whole forward on the fp16 path — and
|
|
71
|
+
forward_features' single-slide alibi branch swaps in _lean_alibi_bias.
|
|
72
|
+
Returns False without patching if the module does not look like the pinned
|
|
73
|
+
revision; the caller then falls back to fp32 features (correct, but with the
|
|
74
|
+
reference implementation's memory behavior).
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
vision_encoder = model.vision_encoder
|
|
78
|
+
vit = sys.modules[type(vision_encoder).__module__]
|
|
79
|
+
if getattr(vit, "_slide2vec_titan_patched", False):
|
|
80
|
+
return True
|
|
81
|
+
for attr in ("pos_encode_type", "num_heads", "patch_embed", "_pos_embed", "norm_pre", "blocks", "norm"):
|
|
82
|
+
if not hasattr(vision_encoder, attr):
|
|
83
|
+
return False
|
|
84
|
+
if not callable(getattr(vit, "preprocess_features", None)):
|
|
85
|
+
return False
|
|
86
|
+
except Exception:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
orig_preprocess = vit.preprocess_features
|
|
90
|
+
|
|
91
|
+
def preprocess_features(features, coords, patch_size_lv0):
|
|
92
|
+
grid, coords_grid, bg_mask = orig_preprocess(features.float(), coords, patch_size_lv0)
|
|
93
|
+
return grid.to(features.dtype), coords_grid, bg_mask
|
|
94
|
+
|
|
95
|
+
orig_forward_features = type(vision_encoder).forward_features
|
|
96
|
+
|
|
97
|
+
def forward_features(self, x, coords=None, mask=None, bg_mask=None):
|
|
98
|
+
# single-slide alibi path only; anything else falls through to the original
|
|
99
|
+
if self.pos_encode_type != "alibi" or x.shape[0] != 1 or self.masked_im_modeling:
|
|
100
|
+
return orig_forward_features(self, x, coords=coords, mask=mask, bg_mask=bg_mask)
|
|
101
|
+
B, nc, w, h = x.shape
|
|
102
|
+
# bias dtype = input grid dtype, as in the original, which builds the bias
|
|
103
|
+
# before patch_embed; post-norm activations can be fp32 under autocast
|
|
104
|
+
in_dtype = x.dtype
|
|
105
|
+
x = x.flatten(2, 3).transpose(1, 2)
|
|
106
|
+
x = self.patch_embed(x)
|
|
107
|
+
x = self._pos_embed(x, coords, w, h)
|
|
108
|
+
x = self.norm_pre(x)
|
|
109
|
+
if bg_mask is not None:
|
|
110
|
+
keep = torch.cat(
|
|
111
|
+
(torch.ones((1, 1), dtype=torch.bool, device=x.device), bg_mask.view(1, -1)),
|
|
112
|
+
dim=1,
|
|
113
|
+
)
|
|
114
|
+
x = x[keep].unsqueeze(0)
|
|
115
|
+
attn_bias = _lean_alibi_bias(self, w, h, bg_mask, device=x.device, dtype=in_dtype)
|
|
116
|
+
x = self.blocks(x, attn_bias, bg_mask)
|
|
117
|
+
x = self.norm(x)
|
|
118
|
+
return x
|
|
119
|
+
|
|
120
|
+
vit.preprocess_features = preprocess_features
|
|
121
|
+
type(vision_encoder).forward_features = forward_features
|
|
122
|
+
vit._slide2vec_titan_patched = True
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@register_encoder(
|
|
127
|
+
"titan",
|
|
128
|
+
level="slide",
|
|
129
|
+
tile_encoder="conchv15",
|
|
130
|
+
tile_encoder_output_variant="default",
|
|
131
|
+
output_variants={"default": {"encode_dim": 768}},
|
|
132
|
+
default_output_variant="default",
|
|
133
|
+
supported_spacing_um=0.5,
|
|
134
|
+
precision="fp16",
|
|
135
|
+
source="MahmoodLab/TITAN",
|
|
136
|
+
)
|
|
137
|
+
class TitanSlideEncoder(SlideEncoder):
|
|
138
|
+
def __init__(self, *, output_variant: str | None = None):
|
|
139
|
+
self._model = AutoModel.from_pretrained(
|
|
140
|
+
"MahmoodLab/TITAN", revision=_TITAN_REVISION, trust_remote_code=True
|
|
141
|
+
).eval()
|
|
142
|
+
self._remote_code_patched = _patch_titan_remote_code(self._model)
|
|
143
|
+
self._device = preferred_default_device()
|
|
144
|
+
self._output_variant = resolve_requested_output_variant(output_variant)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def encode_dim(self) -> int:
|
|
148
|
+
return 768
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def device(self) -> torch.device:
|
|
152
|
+
return self._device
|
|
153
|
+
|
|
154
|
+
def to(self, device: torch.device | str) -> "TitanSlideEncoder":
|
|
155
|
+
self._device = torch.device(device)
|
|
156
|
+
self._model = self._model.to(self._device)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def encode_slide(
|
|
160
|
+
self,
|
|
161
|
+
tile_features: torch.Tensor,
|
|
162
|
+
coordinates: torch.Tensor | None = None,
|
|
163
|
+
*,
|
|
164
|
+
tile_size_lv0: int | None = None,
|
|
165
|
+
) -> torch.Tensor:
|
|
166
|
+
if coordinates is None or tile_size_lv0 is None:
|
|
167
|
+
raise ValueError("TITAN slide encoding requires coordinates and tile_size_lv0")
|
|
168
|
+
if tile_features.ndim == 2:
|
|
169
|
+
tile_features = tile_features.unsqueeze(0)
|
|
170
|
+
if coordinates.ndim == 2:
|
|
171
|
+
coordinates = coordinates.unsqueeze(0)
|
|
172
|
+
if not self._remote_code_patched:
|
|
173
|
+
# fallback for unrecognized remote code: fp32 features satisfy its fp32
|
|
174
|
+
# grid index_add_, at the cost of the reference memory behavior
|
|
175
|
+
tile_features = tile_features.float()
|
|
176
|
+
return self._model.encode_slide_from_patch_features(
|
|
177
|
+
tile_features,
|
|
178
|
+
coordinates.long(),
|
|
179
|
+
np.int64(tile_size_lv0),
|
|
180
|
+
).squeeze(0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slide2vec
|
|
3
|
-
Version: 5.8.
|
|
3
|
+
Version: 5.8.2
|
|
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
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
18
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2
|
|
19
19
|
Requires-Dist: omegaconf
|
|
20
20
|
Requires-Dist: matplotlib
|
|
21
21
|
Requires-Dist: numpy<2
|
|
@@ -75,7 +75,7 @@ Requires-Dist: numpy<2; extra == "fm"
|
|
|
75
75
|
Requires-Dist: pandas; extra == "fm"
|
|
76
76
|
Requires-Dist: pillow; extra == "fm"
|
|
77
77
|
Requires-Dist: rich; extra == "fm"
|
|
78
|
-
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
78
|
+
Requires-Dist: hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2; extra == "fm"
|
|
79
79
|
Requires-Dist: wandb; extra == "fm"
|
|
80
80
|
Requires-Dist: torch<2.8,>=2.3; extra == "fm"
|
|
81
81
|
Requires-Dist: torchvision>=0.18.0; extra == "fm"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
1
|
+
hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2
|
|
2
2
|
omegaconf
|
|
3
3
|
matplotlib
|
|
4
4
|
numpy<2
|
|
@@ -27,7 +27,7 @@ numpy<2
|
|
|
27
27
|
pandas
|
|
28
28
|
pillow
|
|
29
29
|
rich
|
|
30
|
-
hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
30
|
+
hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2
|
|
31
31
|
wandb
|
|
32
32
|
torch<2.8,>=2.3
|
|
33
33
|
torchvision>=0.18.0
|
|
@@ -23,7 +23,7 @@ def test_package_root_exports_api():
|
|
|
23
23
|
assert hasattr(package, "DenseRegionArtifact")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def test_dependency_declarations_require_hs2p_4_4_2_with_consistent_extras():
|
|
27
27
|
project = tomllib.loads(
|
|
28
28
|
(Path(__file__).resolve().parents[1] / "pyproject.toml").read_text(
|
|
29
29
|
encoding="utf-8"
|
|
@@ -39,8 +39,8 @@ def test_dependency_declarations_require_hs2p_4_4_1_with_consistent_extras():
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
assert hs2p_dependencies == [
|
|
42
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
43
|
-
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.
|
|
42
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2",
|
|
43
|
+
"hs2p[asap,cucim,openslide,sam2,vips]>=4.4.2",
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Regression tests for the TITAN slide encoder input contract."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
torch = pytest.importorskip("torch")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _FakeTitan:
|
|
11
|
+
"""Mirrors TITAN's preprocess_features: index_add_ of the input into an fp32
|
|
12
|
+
grid, which raises on fp16 features."""
|
|
13
|
+
|
|
14
|
+
def eval(self):
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def encode_slide_from_patch_features(self, patch_features, patch_coords, patch_size_lv0):
|
|
18
|
+
grid = torch.zeros(4, patch_features.size(-1))
|
|
19
|
+
indices = torch.zeros(patch_features.size(1), dtype=torch.long)
|
|
20
|
+
grid.index_add_(0, indices, patch_features.squeeze(0))
|
|
21
|
+
return torch.zeros(1, 768)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_titan_encode_slide_accepts_fp16_features(monkeypatch):
|
|
25
|
+
# _FakeTitan has no vision_encoder, so the remote-code patch declines and the
|
|
26
|
+
# encoder must fall back to fp32 features to satisfy the fp32-grid index_add_
|
|
27
|
+
import slide2vec.encoders.models.titan as titan_mod
|
|
28
|
+
|
|
29
|
+
monkeypatch.setattr(
|
|
30
|
+
titan_mod,
|
|
31
|
+
"AutoModel",
|
|
32
|
+
SimpleNamespace(from_pretrained=lambda *args, **kwargs: _FakeTitan()),
|
|
33
|
+
)
|
|
34
|
+
encoder = titan_mod.TitanSlideEncoder()
|
|
35
|
+
assert encoder._remote_code_patched is False
|
|
36
|
+
|
|
37
|
+
features = torch.randn(5, 768, dtype=torch.float16)
|
|
38
|
+
coordinates = torch.zeros(5, 2, dtype=torch.int64)
|
|
39
|
+
embedding = encoder.encode_slide(features, coordinates, tile_size_lv0=512)
|
|
40
|
+
|
|
41
|
+
assert embedding.shape[-1] == 768
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _reference_get_alibi(num_heads, w, h, bg_mask=None):
|
|
45
|
+
"""Verbatim port of TITAN's get_alibi (revision dac6773) + the caller's fp16 cast."""
|
|
46
|
+
import math
|
|
47
|
+
|
|
48
|
+
import numpy as np
|
|
49
|
+
|
|
50
|
+
x, y = np.meshgrid(np.arange(w), np.arange(h), indexing="ij")
|
|
51
|
+
if bg_mask is not None:
|
|
52
|
+
x = x[bg_mask.cpu().squeeze(0)]
|
|
53
|
+
y = y[bg_mask.cpu().squeeze(0)]
|
|
54
|
+
points = np.stack([x.ravel(), y.ravel()], axis=1)
|
|
55
|
+
diffs = points[:, None, :] - points[None, :, :]
|
|
56
|
+
dists = np.sqrt(np.sum(diffs**2, axis=-1))
|
|
57
|
+
|
|
58
|
+
def get_slopes(n):
|
|
59
|
+
if math.log2(n).is_integer():
|
|
60
|
+
p = 2 ** (-(2 ** -(math.log2(n) - 3)))
|
|
61
|
+
return [p * (p**i) for i in range(n)]
|
|
62
|
+
nearest = 2 ** math.floor(math.log2(n))
|
|
63
|
+
return get_slopes(nearest) + get_slopes(2 * nearest)[0::2][: n - nearest]
|
|
64
|
+
|
|
65
|
+
slopes = torch.tensor(get_slopes(num_heads), dtype=torch.float32).view(num_heads, 1, 1)
|
|
66
|
+
n_patches = dists.shape[-1]
|
|
67
|
+
dists_tensor = torch.tensor(dists, dtype=torch.float32).view(1, n_patches, n_patches)
|
|
68
|
+
bias_matrix = dists_tensor * slopes * -1
|
|
69
|
+
embed_len = n_patches + 1
|
|
70
|
+
all_bias = torch.zeros(1, num_heads, embed_len, embed_len)
|
|
71
|
+
all_bias[:, :, 1:, 1:] = bias_matrix
|
|
72
|
+
return all_bias.half()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize("w,h,use_mask", [(7, 5, False), (9, 11, True), (40, 45, True)])
|
|
76
|
+
def test_lean_alibi_bias_matches_reference(w, h, use_mask):
|
|
77
|
+
from slide2vec.encoders.models.titan import _lean_alibi_bias
|
|
78
|
+
|
|
79
|
+
torch.manual_seed(0)
|
|
80
|
+
bg_mask = (torch.rand(1, w, h) > 0.3) if use_mask else None
|
|
81
|
+
reference = _reference_get_alibi(12, w, h, bg_mask)
|
|
82
|
+
lean = _lean_alibi_bias(
|
|
83
|
+
SimpleNamespace(num_heads=12), w, h, bg_mask, device="cpu", dtype=torch.float16
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
assert torch.equal(lean, reference)
|
|
87
|
+
# padded row stride is what makes SDPA's memory-efficient kernel accept the bias
|
|
88
|
+
assert lean.stride(-2) % 8 == 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_patch_declines_unrecognized_remote_code():
|
|
92
|
+
from slide2vec.encoders.models.titan import _patch_titan_remote_code
|
|
93
|
+
|
|
94
|
+
assert _patch_titan_remote_code(_FakeTitan()) is False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_patched_preprocess_keeps_features_dtype():
|
|
98
|
+
import sys
|
|
99
|
+
import types
|
|
100
|
+
|
|
101
|
+
from slide2vec.encoders.models.titan import _patch_titan_remote_code
|
|
102
|
+
|
|
103
|
+
module = types.ModuleType("_fake_titan_remote")
|
|
104
|
+
|
|
105
|
+
def preprocess_features(features, coords, patch_size_lv0):
|
|
106
|
+
# fp32 zero-grid + index_add_, as in the real remote code: raises on fp16
|
|
107
|
+
grid = torch.zeros(4, features.size(-1))
|
|
108
|
+
grid.index_add_(0, torch.arange(features.size(0)) % 4, features)
|
|
109
|
+
return grid, torch.zeros(4, 2, dtype=torch.int64), torch.ones(4, dtype=torch.bool)
|
|
110
|
+
|
|
111
|
+
module.preprocess_features = preprocess_features
|
|
112
|
+
|
|
113
|
+
class _FakeViT:
|
|
114
|
+
pos_encode_type = "alibi"
|
|
115
|
+
num_heads = 12
|
|
116
|
+
patch_embed = _pos_embed = norm_pre = blocks = norm = object()
|
|
117
|
+
masked_im_modeling = False
|
|
118
|
+
|
|
119
|
+
def forward_features(self, x, coords=None, mask=None, bg_mask=None):
|
|
120
|
+
return x
|
|
121
|
+
|
|
122
|
+
_FakeViT.__module__ = module.__name__
|
|
123
|
+
sys.modules[module.__name__] = module
|
|
124
|
+
try:
|
|
125
|
+
model = SimpleNamespace(vision_encoder=_FakeViT())
|
|
126
|
+
assert _patch_titan_remote_code(model) is True
|
|
127
|
+
# repeat call is an idempotent no-op, not a double wrap
|
|
128
|
+
assert _patch_titan_remote_code(model) is True
|
|
129
|
+
|
|
130
|
+
fp16_features = torch.randn(6, 32, dtype=torch.float16)
|
|
131
|
+
grid, _, _ = module.preprocess_features(fp16_features, None, 512)
|
|
132
|
+
assert grid.dtype == torch.float16
|
|
133
|
+
finally:
|
|
134
|
+
del sys.modules[module.__name__]
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""TITAN slide encoder implementation."""
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import torch
|
|
5
|
-
from transformers import AutoModel
|
|
6
|
-
|
|
7
|
-
from slide2vec.encoders.base import SlideEncoder, preferred_default_device, resolve_requested_output_variant
|
|
8
|
-
from slide2vec.encoders.registry import register_encoder
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@register_encoder(
|
|
12
|
-
"titan",
|
|
13
|
-
level="slide",
|
|
14
|
-
tile_encoder="conchv15",
|
|
15
|
-
tile_encoder_output_variant="default",
|
|
16
|
-
output_variants={"default": {"encode_dim": 768}},
|
|
17
|
-
default_output_variant="default",
|
|
18
|
-
supported_spacing_um=0.5,
|
|
19
|
-
precision="fp16",
|
|
20
|
-
source="MahmoodLab/TITAN",
|
|
21
|
-
)
|
|
22
|
-
class TitanSlideEncoder(SlideEncoder):
|
|
23
|
-
def __init__(self, *, output_variant: str | None = None):
|
|
24
|
-
self._model = AutoModel.from_pretrained("MahmoodLab/TITAN", trust_remote_code=True).eval()
|
|
25
|
-
self._device = preferred_default_device()
|
|
26
|
-
self._output_variant = resolve_requested_output_variant(output_variant)
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def encode_dim(self) -> int:
|
|
30
|
-
return 768
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def device(self) -> torch.device:
|
|
34
|
-
return self._device
|
|
35
|
-
|
|
36
|
-
def to(self, device: torch.device | str) -> "TitanSlideEncoder":
|
|
37
|
-
self._device = torch.device(device)
|
|
38
|
-
self._model = self._model.to(self._device)
|
|
39
|
-
return self
|
|
40
|
-
|
|
41
|
-
def encode_slide(
|
|
42
|
-
self,
|
|
43
|
-
tile_features: torch.Tensor,
|
|
44
|
-
coordinates: torch.Tensor | None = None,
|
|
45
|
-
*,
|
|
46
|
-
tile_size_lv0: int | None = None,
|
|
47
|
-
) -> torch.Tensor:
|
|
48
|
-
if coordinates is None or tile_size_lv0 is None:
|
|
49
|
-
raise ValueError("TITAN slide encoding requires coordinates and tile_size_lv0")
|
|
50
|
-
if tile_features.ndim == 2:
|
|
51
|
-
tile_features = tile_features.unsqueeze(0)
|
|
52
|
-
if coordinates.ndim == 2:
|
|
53
|
-
coordinates = coordinates.unsqueeze(0)
|
|
54
|
-
return self._model.encode_slide_from_patch_features(
|
|
55
|
-
tile_features,
|
|
56
|
-
coordinates.long(),
|
|
57
|
-
np.int64(tile_size_lv0),
|
|
58
|
-
).squeeze(0)
|
|
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
|
|
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
|