slide2vec 5.7.0__tar.gz → 5.8.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 (158) hide show
  1. {slide2vec-5.7.0 → slide2vec-5.8.1}/PKG-INFO +10 -10
  2. {slide2vec-5.7.0 → slide2vec-5.8.1}/README.md +7 -7
  3. {slide2vec-5.7.0 → slide2vec-5.8.1}/pyproject.toml +4 -4
  4. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/__init__.py +7 -1
  5. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/api.py +24 -6
  6. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/__init__.py +8 -0
  7. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/registry.py +338 -15
  8. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/registry.py +8 -1
  9. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/PKG-INFO +10 -10
  10. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/SOURCES.txt +3 -0
  11. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/requires.txt +2 -2
  12. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_image_stage.py +18 -3
  13. slide2vec-5.8.1/tests/test_encoder_capabilities.py +481 -0
  14. slide2vec-5.8.1/tests/test_encoder_plugins.py +298 -0
  15. slide2vec-5.8.1/tests/test_encoder_provider_failures.py +391 -0
  16. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_hs2p_package_cutover.py +3 -3
  17. {slide2vec-5.7.0 → slide2vec-5.8.1}/LICENSE +0 -0
  18. {slide2vec-5.7.0 → slide2vec-5.8.1}/setup.cfg +0 -0
  19. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/__main__.py +0 -0
  20. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/artifacts.py +0 -0
  21. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/cli.py +0 -0
  22. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/configs/__init__.py +0 -0
  23. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/configs/default.yaml +0 -0
  24. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/configs/resources.py +0 -0
  25. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/data/__init__.py +0 -0
  26. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/data/dataset.py +0 -0
  27. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/data/tile_reader.py +0 -0
  28. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/data/tile_store.py +0 -0
  29. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/__init__.py +0 -0
  30. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/dense_image_worker.py +0 -0
  31. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/dense_worker.py +0 -0
  32. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/direct_embed_worker.py +0 -0
  33. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/image_worker.py +0 -0
  34. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/pipeline_worker.py +0 -0
  35. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/distributed/worker_entry.py +0 -0
  36. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/base.py +0 -0
  37. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/__init__.py +0 -0
  38. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/conch.py +0 -0
  39. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/dinov2.py +0 -0
  40. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/genbio.py +0 -0
  41. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/gigapath.py +0 -0
  42. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/gpfm.py +0 -0
  43. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/hibou.py +0 -0
  44. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/hoptimus.py +0 -0
  45. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/isight.py +0 -0
  46. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/lunit.py +0 -0
  47. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/midnight.py +0 -0
  48. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/__init__.py +0 -0
  49. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/blocks.py +0 -0
  50. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/case.py +0 -0
  51. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/loading.py +0 -0
  52. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/slide.py +0 -0
  53. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/moozy/types.py +0 -0
  54. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/mstar.py +0 -0
  55. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/musk.py +0 -0
  56. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/phikon.py +0 -0
  57. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/prism.py +0 -0
  58. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/prism2.py +0 -0
  59. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/prost40m.py +0 -0
  60. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/rudolfv2.py +0 -0
  61. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/titan.py +0 -0
  62. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/uni.py +0 -0
  63. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/virchow.py +0 -0
  64. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/models/waiv.py +0 -0
  65. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/encoders/validation.py +0 -0
  66. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/inference.py +0 -0
  67. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/progress.py +0 -0
  68. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/__init__.py +0 -0
  69. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/artifacts_collect.py +0 -0
  70. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/batching.py +0 -0
  71. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/cpu_budget.py +0 -0
  72. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_encode.py +0 -0
  73. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_encoder_input.py +0 -0
  74. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_image_reading.py +0 -0
  75. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_image_recipe.py +0 -0
  76. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_image_shard.py +0 -0
  77. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_image_stage.py +0 -0
  78. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_regions.py +0 -0
  79. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_shard.py +0 -0
  80. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_sliding.py +0 -0
  81. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/dense_stage.py +0 -0
  82. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/distributed.py +0 -0
  83. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/distributed_stage.py +0 -0
  84. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/effective_encoder_input.py +0 -0
  85. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/embedding.py +0 -0
  86. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/embedding_persist.py +0 -0
  87. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/embedding_pipeline.py +0 -0
  88. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/encoder_input_contract.py +0 -0
  89. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/hierarchical.py +0 -0
  90. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/image_shard.py +0 -0
  91. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/image_specs.py +0 -0
  92. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/image_stage.py +0 -0
  93. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/manifest.py +0 -0
  94. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/model_settings.py +0 -0
  95. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/patient_pipeline.py +0 -0
  96. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/persist_callbacks.py +0 -0
  97. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/persistence.py +0 -0
  98. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/pooled_encoder_input.py +0 -0
  99. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/preprocessing.py +0 -0
  100. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/process_list.py +0 -0
  101. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/progress_bridge.py +0 -0
  102. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/serialization.py +0 -0
  103. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/sharding.py +0 -0
  104. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/slide_encode.py +0 -0
  105. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/tiling.py +0 -0
  106. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/tiling_pipeline.py +0 -0
  107. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/types.py +0 -0
  108. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/runtime/worker_io.py +0 -0
  109. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/__init__.py +0 -0
  110. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/config.py +0 -0
  111. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/coordinates.py +0 -0
  112. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/log_utils.py +0 -0
  113. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/tiling_io.py +0 -0
  114. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec/utils/utils.py +0 -0
  115. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/dependency_links.txt +0 -0
  116. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/entry_points.txt +0 -0
  117. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/not-zip-safe +0 -0
  118. {slide2vec-5.7.0 → slide2vec-5.8.1}/slide2vec.egg-info/top_level.txt +0 -0
  119. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_architecture_runtime_split.py +0 -0
  120. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_attention_extraction.py +0 -0
  121. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_encode_kit.py +0 -0
  122. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_encoder_input.py +0 -0
  123. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_extraction.py +0 -0
  124. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_image_reading.py +0 -0
  125. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_image_resume.py +0 -0
  126. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_image_shard.py +0 -0
  127. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_regions.py +0 -0
  128. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_shard.py +0 -0
  129. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_sliding.py +0 -0
  130. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_source_spacing.py +0 -0
  131. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_stage.py +0 -0
  132. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dense_worker.py +0 -0
  133. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_dinov2_natimage.py +0 -0
  134. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_encoder_input_contract.py +0 -0
  135. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_encoder_registry.py +0 -0
  136. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_gpfm_genbio_heavy.py +0 -0
  137. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_image_shard.py +0 -0
  138. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_image_stage.py +0 -0
  139. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_isight.py +0 -0
  140. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_mascaret.py +0 -0
  141. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_output_consistency.py +0 -0
  142. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_patch_size_metadata.py +0 -0
  143. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_patient_manifest.py +0 -0
  144. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_phaet.py +0 -0
  145. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_pooled_encoder_input.py +0 -0
  146. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_pooled_geometry.py +0 -0
  147. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_prism2.py +0 -0
  148. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_progress.py +0 -0
  149. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_regression_core.py +0 -0
  150. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_regression_inference.py +0 -0
  151. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_regression_models.py +0 -0
  152. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_rudolfv2.py +0 -0
  153. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_runtime_batching.py +0 -0
  154. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_sharding.py +0 -0
  155. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_slide_coordinate_preparation.py +0 -0
  156. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_soma_migration.py +0 -0
  157. {slide2vec-5.7.0 → slide2vec-5.8.1}/tests/test_tile_store.py +0 -0
  158. {slide2vec-5.7.0 → slide2vec-5.8.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.7.0
3
+ Version: 5.8.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
@@ -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.1
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.1; extra == "fm"
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"
@@ -207,7 +207,7 @@ embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
207
207
 
208
208
  Hierarchical outputs have shape `(num_regions, tiles_per_region, feature_dim)` and are written to `hierarchical_embeddings/` when persisted.
209
209
 
210
- See [`docs/python-api.md`](docs/python-api.md) for details.
210
+ See the [hierarchical features guide](https://clemsgrs.github.io/slide2vec/hierarchical.html) for details.
211
211
 
212
212
  ### Input Manifest
213
213
 
@@ -241,7 +241,7 @@ The package writes explicit artifact directories:
241
241
 
242
242
  `slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
243
243
  and 1 patient-level model.
244
- For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
244
+ For the full catalog and preset names, see the [model zoo](https://clemsgrs.github.io/slide2vec/models.html).
245
245
 
246
246
  ## CLI
247
247
 
@@ -254,7 +254,7 @@ slide2vec /path/to/config.yaml
254
254
 
255
255
  By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
256
256
 
257
- New to the CLI or doing batch runs to disk? Start with [`docs/cli.md`](docs/cli.md) for the config-driven workflow, overrides, and common run patterns.
257
+ New to the CLI or doing batch runs to disk? Start with the [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) for the config-driven workflow and common run patterns.
258
258
 
259
259
  ## Docker
260
260
 
@@ -272,8 +272,8 @@ docker run --rm -it \
272
272
 
273
273
  ## Documentation
274
274
 
275
- - [Documentation website](https://clemsgrs.github.io/slide2vec/) for the polished docs site
276
- - [`docs/python-api.md`](docs/python-api.md) for the detailed API reference
277
- - [`docs/cli.md`](docs/cli.md) for the config-driven CLI guide
278
- - [`docs/models.md`](docs/models.md) for the full supported-model catalog
275
+ - [Documentation website](https://clemsgrs.github.io/slide2vec/)
276
+ - [API guide](https://clemsgrs.github.io/slide2vec/api.html)
277
+ - [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html)
278
+ - [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
279
279
  - [`tutorials/api_walkthrough.ipynb`](tutorials/api_walkthrough.ipynb) for a notebook walkthrough of the API
@@ -91,7 +91,7 @@ embedded = model.embed_slide("/path/to/slide.svs", preprocessing=preprocessing)
91
91
 
92
92
  Hierarchical outputs have shape `(num_regions, tiles_per_region, feature_dim)` and are written to `hierarchical_embeddings/` when persisted.
93
93
 
94
- See [`docs/python-api.md`](docs/python-api.md) for details.
94
+ See the [hierarchical features guide](https://clemsgrs.github.io/slide2vec/hierarchical.html) for details.
95
95
 
96
96
  ### Input Manifest
97
97
 
@@ -125,7 +125,7 @@ The package writes explicit artifact directories:
125
125
 
126
126
  `slide2vec` currently ships presets for 28 tile-level models, 4 slide-level models,
127
127
  and 1 patient-level model.
128
- For the full catalog and preset names, see [`docs/models.md`](docs/models.md).
128
+ For the full catalog and preset names, see the [model zoo](https://clemsgrs.github.io/slide2vec/models.html).
129
129
 
130
130
  ## CLI
131
131
 
@@ -138,7 +138,7 @@ slide2vec /path/to/config.yaml
138
138
 
139
139
  By default, manifest-driven CLI runs use all available GPUs. Set `speed.num_gpus=4` when you want to cap the sharding explicitly.
140
140
 
141
- New to the CLI or doing batch runs to disk? Start with [`docs/cli.md`](docs/cli.md) for the config-driven workflow, overrides, and common run patterns.
141
+ New to the CLI or doing batch runs to disk? Start with the [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html) for the config-driven workflow and common run patterns.
142
142
 
143
143
  ## Docker
144
144
 
@@ -156,8 +156,8 @@ docker run --rm -it \
156
156
 
157
157
  ## Documentation
158
158
 
159
- - [Documentation website](https://clemsgrs.github.io/slide2vec/) for the polished docs site
160
- - [`docs/python-api.md`](docs/python-api.md) for the detailed API reference
161
- - [`docs/cli.md`](docs/cli.md) for the config-driven CLI guide
162
- - [`docs/models.md`](docs/models.md) for the full supported-model catalog
159
+ - [Documentation website](https://clemsgrs.github.io/slide2vec/)
160
+ - [API guide](https://clemsgrs.github.io/slide2vec/api.html)
161
+ - [CLI guide](https://clemsgrs.github.io/slide2vec/cli.html)
162
+ - [Model zoo](https://clemsgrs.github.io/slide2vec/models.html)
163
163
  - [`tutorials/api_walkthrough.ipynb`](tutorials/api_walkthrough.ipynb) for a notebook walkthrough of the API
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "slide2vec"
7
- version = "5.7.0"
7
+ version = "5.8.1"
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.1",
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.1",
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.7.0"
183
+ current_version = "5.8.1"
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
@@ -21,13 +21,19 @@ from slide2vec.artifacts import (
21
21
  TileEmbeddingArtifact,
22
22
  )
23
23
  from slide2vec.runtime.dense_encode import DenseEncodeGeometry, DenseEncodeKit
24
+ from slide2vec.encoders import (
25
+ EncoderProviderDiagnostic,
26
+ list_encoder_provider_diagnostics,
27
+ )
24
28
 
25
29
 
26
- __version__ = "5.7.0"
30
+ __version__ = "5.8.1"
27
31
 
28
32
  __all__ = [
29
33
  "Model",
30
34
  "list_models",
35
+ "EncoderProviderDiagnostic",
36
+ "list_encoder_provider_diagnostics",
31
37
  "Pipeline",
32
38
  "PreprocessingConfig",
33
39
  "DenseOptions",
@@ -3,6 +3,7 @@ import copy
3
3
  import logging
4
4
  import math
5
5
  import os
6
+ import warnings
6
7
  from dataclasses import dataclass, field, replace
7
8
  from contextlib import contextmanager
8
9
  from pathlib import Path
@@ -23,6 +24,7 @@ from slide2vec.artifacts import (
23
24
  from slide2vec.configs.resources import load_config
24
25
  from slide2vec.encoders.registry import (
25
26
  encoder_registry,
27
+ list_encoder_provider_diagnostics,
26
28
  resolve_preprocessing_fields,
27
29
  )
28
30
  from slide2vec.encoders.validation import validate_encoder_config
@@ -1146,16 +1148,32 @@ def list_models(level: str | None = None) -> list[str]:
1146
1148
  level: Optional model level filter. Supported values are ``"tile"``,
1147
1149
  ``"slide"``, and ``"patient"``.
1148
1150
  """
1149
- if level is None:
1150
- return sorted(encoder_registry.names())
1151
+ normalized_level = None
1152
+ if level is not None:
1153
+ normalized_level = str(level).strip().lower()
1154
+ if normalized_level not in {"tile", "slide", "patient"}:
1155
+ raise ValueError(
1156
+ "list_models(level=...) must be one of: tile, slide, patient"
1157
+ )
1151
1158
 
1152
- normalized_level = str(level).strip().lower()
1153
- if normalized_level not in {"tile", "slide", "patient"}:
1154
- raise ValueError("list_models(level=...) must be one of: tile, slide, patient")
1159
+ names = encoder_registry.names()
1160
+ diagnostics = list_encoder_provider_diagnostics()
1161
+ if diagnostics:
1162
+ detail = "; ".join(
1163
+ f"'{item.provider_key}': {item.concise()}" for item in diagnostics
1164
+ )
1165
+ warnings.warn(
1166
+ f"Skipped Encoder provider{'' if len(diagnostics) == 1 else 's'} {detail}",
1167
+ RuntimeWarning,
1168
+ stacklevel=2,
1169
+ )
1170
+
1171
+ if level is None:
1172
+ return sorted(names)
1155
1173
 
1156
1174
  return sorted(
1157
1175
  name
1158
- for name in encoder_registry.names()
1176
+ for name in names
1159
1177
  if encoder_registry.info(name)["level"] == normalized_level
1160
1178
  )
1161
1179
 
@@ -15,9 +15,13 @@ from slide2vec.encoders.base import (
15
15
  resolve_requested_output_variant,
16
16
  )
17
17
  from slide2vec.encoders.registry import (
18
+ EncoderCapabilities,
19
+ EncoderProviderDiagnostic,
18
20
  encoder_registry,
21
+ list_encoder_provider_diagnostics,
19
22
  normalize_patch_size,
20
23
  register_encoder,
24
+ resolve_encoder_capabilities,
21
25
  resolve_encoder_output,
22
26
  resolve_patch_size,
23
27
  resolve_preprocessing_requirements,
@@ -29,6 +33,8 @@ from slide2vec.encoders import models # noqa: F401
29
33
 
30
34
  __all__ = [
31
35
  "Encoder",
36
+ "EncoderCapabilities",
37
+ "EncoderProviderDiagnostic",
32
38
  "PatientEncoder",
33
39
  "TileEncoder",
34
40
  "SlideEncoder",
@@ -37,8 +43,10 @@ __all__ = [
37
43
  "resolve_recommended_dynamic_img_size",
38
44
  "resolve_requested_output_variant",
39
45
  "encoder_registry",
46
+ "list_encoder_provider_diagnostics",
40
47
  "normalize_patch_size",
41
48
  "register_encoder",
49
+ "resolve_encoder_capabilities",
42
50
  "resolve_patch_size",
43
51
  "resolve_preprocessing_requirements",
44
52
  "resolve_encoder_output",
@@ -1,10 +1,201 @@
1
1
  """Encoder registry with enforced metadata schema."""
2
2
 
3
+ from dataclasses import dataclass
4
+ from enum import Enum, auto
5
+ from importlib import metadata as importlib_metadata
6
+ import inspect
7
+ from threading import Condition, RLock, get_ident
3
8
  from typing import Any
4
9
 
10
+ from slide2vec.encoders.base import PatientEncoder, SlideEncoder, TileEncoder
5
11
  from slide2vec.runtime.registry import Registry
6
12
 
7
- encoder_registry = Registry("encoders")
13
+
14
+ _ENCODER_ENTRY_POINT_GROUP = "slide2vec.encoders"
15
+
16
+
17
+ class _DiscoveryState(Enum):
18
+ PENDING = auto()
19
+ RUNNING = auto()
20
+ COMPLETE = auto()
21
+ FAILED = auto()
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class EncoderProviderDiagnostic:
26
+ """Public, immutable description of one skipped installed provider."""
27
+
28
+ provider_key: str
29
+ provider: str
30
+ exception_type: str
31
+ message: str
32
+
33
+ def concise(self) -> str:
34
+ """Format the diagnostic without traceback details."""
35
+ return f"{self.exception_type}: {self.message}"
36
+
37
+
38
+ def _installed_encoder_providers() -> list[importlib_metadata.EntryPoint]:
39
+ entry_points = importlib_metadata.entry_points()
40
+ providers = entry_points.select(group=_ENCODER_ENTRY_POINT_GROUP)
41
+ return sorted(providers, key=lambda provider: (provider.name, provider.value))
42
+
43
+
44
+ class EncoderRegistry(Registry):
45
+ """Encoder registry with one lazy, process-global plugin discovery pass."""
46
+
47
+ def __init__(self) -> None:
48
+ super().__init__("encoders")
49
+ self._discovery_condition = Condition(RLock())
50
+ self._discovery_state = _DiscoveryState.PENDING
51
+ self._discovery_owner: int | None = None
52
+ self._discovery_error: BaseException | None = None
53
+ self._provider_diagnostics: tuple[EncoderProviderDiagnostic, ...] = ()
54
+
55
+ def _ensure_plugins_discovered(self) -> None:
56
+ current_thread = get_ident()
57
+ with self._discovery_condition:
58
+ while self._discovery_state is _DiscoveryState.RUNNING:
59
+ if self._discovery_owner == current_thread:
60
+ return
61
+ self._discovery_condition.wait()
62
+ if self._discovery_state is _DiscoveryState.COMPLETE:
63
+ return
64
+ if self._discovery_state is _DiscoveryState.FAILED:
65
+ assert self._discovery_error is not None
66
+ raise self._discovery_error
67
+ self._discovery_state = _DiscoveryState.RUNNING
68
+ self._discovery_owner = current_thread
69
+
70
+ diagnostics: list[EncoderProviderDiagnostic] = []
71
+ try:
72
+ for entry_point in _installed_encoder_providers():
73
+ entries_before_provider = dict(self._entries)
74
+ try:
75
+ entry_point.load()()
76
+ except BaseException as error:
77
+ self._entries = entries_before_provider
78
+ message = " ".join(str(error).split()) or "No error message"
79
+ diagnostics.append(
80
+ EncoderProviderDiagnostic(
81
+ provider_key=entry_point.name,
82
+ provider=entry_point.value,
83
+ exception_type=type(error).__name__,
84
+ message=message,
85
+ )
86
+ )
87
+ except BaseException as error:
88
+ with self._discovery_condition:
89
+ self._discovery_state = _DiscoveryState.FAILED
90
+ self._discovery_owner = None
91
+ self._discovery_error = error
92
+ self._discovery_condition.notify_all()
93
+ raise
94
+ else:
95
+ with self._discovery_condition:
96
+ self._provider_diagnostics = tuple(diagnostics)
97
+ self._discovery_state = _DiscoveryState.COMPLETE
98
+ self._discovery_owner = None
99
+ self._discovery_condition.notify_all()
100
+
101
+ def _before_read(self) -> None:
102
+ self._ensure_plugins_discovered()
103
+
104
+ def provider_diagnostics(self) -> tuple[EncoderProviderDiagnostic, ...]:
105
+ """Return an immutable snapshot of skipped provider diagnostics."""
106
+ self._ensure_plugins_discovered()
107
+ return self._provider_diagnostics
108
+
109
+ def _missing_preset_error(self, name: str) -> KeyError:
110
+ available = ", ".join(sorted(self._entries)) or "(none)"
111
+ message = f"'{name}' not found in encoders registry. Available: {available}"
112
+ if self._provider_diagnostics:
113
+ failures = "; ".join(
114
+ f"'{item.provider_key}' ({item.concise()})"
115
+ for item in self._provider_diagnostics
116
+ )
117
+ message += f". Skipped Encoder providers: {failures}"
118
+ return KeyError(message)
119
+
120
+ def require(self, name: str) -> type:
121
+ """Retrieve an Encoder class, including provider context when it is missing."""
122
+ self._ensure_plugins_discovered()
123
+ if name not in self._entries:
124
+ raise self._missing_preset_error(name)
125
+ return self._entries[name].cls
126
+
127
+ def info(self, name: str) -> dict[str, Any]:
128
+ """Return Encoder metadata, including provider context when it is missing."""
129
+ self._ensure_plugins_discovered()
130
+ if name not in self._entries:
131
+ raise self._missing_preset_error(name)
132
+ entry = self._entries[name]
133
+ return {"name": name, **entry.metadata}
134
+
135
+
136
+ encoder_registry = EncoderRegistry()
137
+
138
+
139
+ def list_encoder_provider_diagnostics() -> tuple[EncoderProviderDiagnostic, ...]:
140
+ """List deterministic diagnostics for installed providers skipped at discovery."""
141
+ return encoder_registry.provider_diagnostics()
142
+
143
+
144
+ @dataclass(frozen=True)
145
+ class EncoderCapabilities:
146
+ """Resolved, metadata-only preflight view of one registered Encoder preset."""
147
+
148
+ name: str
149
+ level: str
150
+ pooled: bool
151
+ dense: bool
152
+ attention: bool
153
+ slide: bool
154
+ patient: bool
155
+ patch_size: tuple[int, int] | None
156
+ tile_encoder: str | None
157
+ tile_encoder_output_variant: str | None
158
+
159
+
160
+ def resolve_encoder_capabilities(encoder_name: str) -> EncoderCapabilities:
161
+ """Resolve supported extraction contracts without constructing the encoder."""
162
+ encoder_cls = encoder_registry.require(encoder_name)
163
+ metadata = encoder_registry.info(encoder_name)
164
+ _validate_encoder_capability_contract(encoder_name, encoder_cls, metadata)
165
+ level = resolve_encoder_level(encoder_name, metadata)
166
+ dense = all(_dense_class_contract(encoder_cls))
167
+ attention = _supports_attention(encoder_cls)
168
+ tile_encoder = None
169
+ tile_encoder_output_variant = None
170
+ if level in {"slide", "patient"}:
171
+ tile_encoder, tile_encoder_output_variant = (
172
+ _resolve_hierarchical_tile_dependency(encoder_name, metadata)
173
+ )
174
+ dependency_metadata = encoder_registry.info(tile_encoder)
175
+ dependency_level = resolve_encoder_level(tile_encoder, dependency_metadata)
176
+ if dependency_level != "tile":
177
+ raise ValueError(
178
+ f"Encoder '{encoder_name}' tile_encoder dependency '{tile_encoder}' "
179
+ f"must have level='tile', got level='{dependency_level}'."
180
+ )
181
+ dependency_output = resolve_tile_dependency_output(
182
+ encoder_name,
183
+ metadata=metadata,
184
+ )
185
+ tile_encoder_output_variant = str(dependency_output["output_variant"])
186
+ resolve_encoder_capabilities(tile_encoder)
187
+ return EncoderCapabilities(
188
+ name=encoder_name,
189
+ level=level,
190
+ pooled=issubclass(encoder_cls, TileEncoder),
191
+ dense=dense,
192
+ attention=attention,
193
+ slide=issubclass(encoder_cls, (SlideEncoder, PatientEncoder)),
194
+ patient=issubclass(encoder_cls, PatientEncoder),
195
+ patch_size=resolve_patch_size(encoder_name, metadata) if dense else None,
196
+ tile_encoder=tile_encoder,
197
+ tile_encoder_output_variant=tile_encoder_output_variant,
198
+ )
8
199
 
9
200
 
10
201
  def require_encoder_metadata_field(
@@ -31,6 +222,122 @@ def resolve_encoder_level(
31
222
  return level
32
223
 
33
224
 
225
+ _DENSE_CLASS_MEMBERS = (
226
+ "encode_tiles_dense",
227
+ "patch_size",
228
+ "get_normalization_transform",
229
+ )
230
+
231
+
232
+ def _dense_class_contract(encoder_cls: type) -> tuple[bool, ...]:
233
+ if not issubclass(encoder_cls, TileEncoder):
234
+ return (False,) * len(_DENSE_CLASS_MEMBERS)
235
+ return tuple(
236
+ getattr(encoder_cls, member) is not getattr(TileEncoder, member)
237
+ for member in _DENSE_CLASS_MEMBERS
238
+ )
239
+
240
+
241
+ def _supports_attention(encoder_cls: type) -> bool:
242
+ return (
243
+ issubclass(encoder_cls, TileEncoder)
244
+ and getattr(encoder_cls, "encode_tiles_attention")
245
+ is not TileEncoder.encode_tiles_attention
246
+ )
247
+
248
+
249
+ def _format_names(names: list[str]) -> str:
250
+ if len(names) == 1:
251
+ return names[0]
252
+ if len(names) == 2:
253
+ return " and ".join(names)
254
+ return ", ".join(names[:-1]) + f", and {names[-1]}"
255
+
256
+
257
+ def _validate_encoder_capability_contract(
258
+ encoder_name: str,
259
+ encoder_cls: type,
260
+ metadata: dict[str, Any],
261
+ ) -> None:
262
+ level = resolve_encoder_level(encoder_name, metadata)
263
+ required_base = {
264
+ "tile": TileEncoder,
265
+ "slide": SlideEncoder,
266
+ "patient": PatientEncoder,
267
+ }[level]
268
+ if not issubclass(encoder_cls, required_base):
269
+ raise ValueError(
270
+ f"Encoder '{encoder_name}' declares level='{level}', but class "
271
+ f"{encoder_cls.__name__} must subclass {required_base.__name__}."
272
+ )
273
+ if inspect.isabstract(encoder_cls):
274
+ missing = sorted(getattr(encoder_cls, "__abstractmethods__"))
275
+ raise ValueError(
276
+ f"Encoder '{encoder_name}' class {encoder_cls.__name__} is abstract; "
277
+ f"implement {_format_names(missing)} before registration."
278
+ )
279
+ if level in {"slide", "patient"}:
280
+ require_encoder_metadata_field(encoder_name, metadata, "tile_encoder")
281
+ require_encoder_metadata_field(
282
+ encoder_name,
283
+ metadata,
284
+ "tile_encoder_output_variant",
285
+ )
286
+ dense_members = _dense_class_contract(encoder_cls)
287
+ has_static_patch_size = metadata.get("patch_size") is not None
288
+ if any(dense_members) and not all(dense_members):
289
+ implemented = [
290
+ member
291
+ for member, is_implemented in zip(_DENSE_CLASS_MEMBERS, dense_members)
292
+ if is_implemented
293
+ ]
294
+ missing = [
295
+ member
296
+ for member, is_implemented in zip(_DENSE_CLASS_MEMBERS, dense_members)
297
+ if not is_implemented
298
+ ]
299
+ raise ValueError(
300
+ f"Encoder '{encoder_name}' has an incomplete dense class contract: "
301
+ f"{_format_names(implemented)} "
302
+ f"{'is' if len(implemented) == 1 else 'are'} overridden, but "
303
+ f"{_format_names(missing)} "
304
+ f"{'is' if len(missing) == 1 else 'are'} inherited as unsupported. "
305
+ "Override all three dense members together and declare patch_size metadata."
306
+ )
307
+ if has_static_patch_size and not all(dense_members):
308
+ raise ValueError(
309
+ f"Encoder '{encoder_name}' has an inconsistent dense contract: "
310
+ "patch_size metadata is declared, but the class must also override "
311
+ "encode_tiles_dense, patch_size, and get_normalization_transform."
312
+ )
313
+ if all(dense_members) and not has_static_patch_size:
314
+ raise ValueError(
315
+ f"Encoder '{encoder_name}' implements the dense class contract but does "
316
+ "not declare patch_size metadata. Add the encoder's static patch size "
317
+ "to @register_encoder."
318
+ )
319
+ if has_static_patch_size:
320
+ patch = metadata["patch_size"]
321
+ patch_values = patch if isinstance(patch, tuple) else (patch, patch)
322
+ valid_patch = (
323
+ len(patch_values) == 2
324
+ and all(type(value) is int and value > 0 for value in patch_values)
325
+ )
326
+ if not valid_patch:
327
+ raise ValueError(
328
+ f"Encoder '{encoder_name}' must declare patch_size as a positive int "
329
+ f"or pair of positive ints; got {patch!r}."
330
+ )
331
+ if _supports_attention(encoder_cls) and not (
332
+ all(dense_members) and has_static_patch_size
333
+ ):
334
+ raise ValueError(
335
+ f"Encoder '{encoder_name}' overrides encode_tiles_attention without a "
336
+ "complete dense contract. Attention maps require encode_tiles_dense, "
337
+ "patch_size, get_normalization_transform, and static patch_size metadata."
338
+ )
339
+
340
+
34
341
  def register_encoder(
35
342
  name: str,
36
343
  *,
@@ -66,9 +373,9 @@ def register_encoder(
66
373
  cache key can be resolved via :func:`resolve_patch_size` WITHOUT
67
374
  instantiating the (multi-GB) encoder; the model-load path asserts this
68
375
  static value still equals the loaded model's runtime ``patch_size``.
69
- level: Encoder output level ("tile" or "slide").
70
- tile_encoder: Registered tile encoder dependency for slide-level models.
71
- tile_encoder_output_variant: Fixed tile-encoder output variant for slide models.
376
+ level: Encoder output level ("tile", "slide", or "patient").
377
+ tile_encoder: Registered tile encoder dependency for slide/patient models.
378
+ tile_encoder_output_variant: Fixed tile output variant for slide/patient models.
72
379
  supported_spacing_um: The spacing(s) in µm/px the model was trained/validated
73
380
  for; :func:`validate_encoder_config` rejects requests outside this set
74
381
  unless ``allow_non_recommended_settings=True``. ``None`` marks a
@@ -111,7 +418,13 @@ def register_encoder(
111
418
  "precision": precision,
112
419
  "source": source,
113
420
  }
114
- return encoder_registry.register_decorator(name, metadata=metadata)
421
+
422
+ def decorator(encoder_cls: type) -> type:
423
+ _validate_encoder_capability_contract(name, encoder_cls, metadata)
424
+ encoder_registry.register(name, encoder_cls, metadata=metadata)
425
+ return encoder_cls
426
+
427
+ return decorator
115
428
 
116
429
 
117
430
  def resolve_variable_input_capability(
@@ -372,16 +685,8 @@ def resolve_tile_dependency_output(
372
685
  resolved["encoder_name"] = encoder_name
373
686
  return resolved
374
687
 
375
- # Both "slide" and "patient" declare tile_encoder / tile_encoder_output_variant.
376
- tile_encoder_name = str(
377
- require_encoder_metadata_field(encoder_name, info, "tile_encoder")
378
- )
379
- tile_encoder_output_variant = str(
380
- require_encoder_metadata_field(
381
- encoder_name,
382
- info,
383
- "tile_encoder_output_variant",
384
- )
688
+ tile_encoder_name, tile_encoder_output_variant = (
689
+ _resolve_hierarchical_tile_dependency(encoder_name, info)
385
690
  )
386
691
  tile_info = encoder_registry.info(tile_encoder_name)
387
692
  resolved = resolve_encoder_output(
@@ -391,3 +696,21 @@ def resolve_tile_dependency_output(
391
696
  )
392
697
  resolved["encoder_name"] = tile_encoder_name
393
698
  return resolved
699
+
700
+
701
+ def _resolve_hierarchical_tile_dependency(
702
+ encoder_name: str,
703
+ metadata: dict[str, Any],
704
+ ) -> tuple[str, str]:
705
+ """Read the fixed tile preset and output variant for a hierarchical encoder."""
706
+ tile_encoder_name = str(
707
+ require_encoder_metadata_field(encoder_name, metadata, "tile_encoder")
708
+ )
709
+ tile_encoder_output_variant = str(
710
+ require_encoder_metadata_field(
711
+ encoder_name,
712
+ metadata,
713
+ "tile_encoder_output_variant",
714
+ )
715
+ )
716
+ return tile_encoder_name, tile_encoder_output_variant