slide2vec 6.3.0__tar.gz → 6.3.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-6.3.0 → slide2vec-6.3.2}/PKG-INFO +1 -1
- {slide2vec-6.3.0 → slide2vec-6.3.2}/pyproject.toml +2 -2
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/__init__.py +1 -1
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/api.py +38 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/data/tile_reader.py +11 -22
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/feature_identity.py +36 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/SOURCES.txt +1 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_hs2p5_integration.py +28 -0
- slide2vec-6.3.2/tests/test_pooled_identity_api.py +283 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/LICENSE +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/README.md +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/setup.cfg +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/__main__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/_cufile.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/artifacts.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/cli.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/configs/resources.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/cufile.json +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/data/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/data/dataset.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/dense_image_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/dense_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/image_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/pin_gpu.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/distributed/worker_entry.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/base.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/dinov2.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/dinov3.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/genbio.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/gpfm.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/isight.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/mettle.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/mstar.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/prism2.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/rudolfv2.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/models/waiv.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/inference.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/progress.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_encode.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_encoder_input.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_reading.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_recipe.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_image_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_regions.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_sliding.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/dense_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/distributed.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/effective_encoder_input.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/encoder_input_contract.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/image_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/image_specs.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/image_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/pooled_encoder_input.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/preprocessing.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/sharding.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/types.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/config.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec/utils/utils.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_attention_extraction.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_benchmark_tooling.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_cli_noninteractive.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_cufile_compat_mode.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_encode_kit.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_encoder_input.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_extraction.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_image_reading.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_image_resume.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_image_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_image_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_regions.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_sliding.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_source_spacing.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dense_worker.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dinov2_natimage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_dinov3_natimage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_capabilities.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_input_contract.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_plugins.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_preprocessing.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_provider_failures.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_encoder_registry.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_feature_identity.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_gigapath.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_gpfm_genbio_heavy.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_image_shard.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_image_stage.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_inference_benchmark.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_isight.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_load_model_hf_auth.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_mascaret.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_mettle.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_on_slide_persisted.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_output_consistency.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_patch_size_metadata.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_patient_annotation_sampling.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_patient_manifest.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_phaet.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_pooled_encoder_input.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_pooled_geometry.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_prism.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_prism2.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_process_list_performance.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_progress.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_regression_core.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_regression_inference.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_regression_models.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_rudolfv2.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_runtime_batching.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_sharding.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_slide_coordinate_preparation.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_slide_latents.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_soma_migration.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_tile_store.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_tiling_pipeline.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_titan.py +0 -0
- {slide2vec-6.3.0 → slide2vec-6.3.2}/tests/test_worker_gpu_pinning.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "6.3.
|
|
7
|
+
version = "6.3.2"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -180,7 +180,7 @@ no_implicit_reexport = true
|
|
|
180
180
|
max-line-length = 160
|
|
181
181
|
|
|
182
182
|
[tool.bumpver]
|
|
183
|
-
current_version = "6.3.
|
|
183
|
+
current_version = "6.3.2"
|
|
184
184
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
185
185
|
commit = false
|
|
186
186
|
tag = false
|
|
@@ -688,6 +688,44 @@ class Model:
|
|
|
688
688
|
# Construction fact, not an encode: see _load_backend_without_transform.
|
|
689
689
|
return int(self._load_backend_without_transform().feature_dim)
|
|
690
690
|
|
|
691
|
+
def pooled_identity_differences(
|
|
692
|
+
self,
|
|
693
|
+
recorded: Mapping[str, Any],
|
|
694
|
+
*,
|
|
695
|
+
preprocessing: PreprocessingConfig | None,
|
|
696
|
+
execution: ExecutionOptions | None = None,
|
|
697
|
+
) -> dict[str, tuple[Any, Any]]:
|
|
698
|
+
"""Compare a pooled sidecar's ``compatibility`` block with the current recipe.
|
|
699
|
+
|
|
700
|
+
Returns ``{field: (recorded, current)}`` for differing fields held by both
|
|
701
|
+
identities. Missing fields retain the extraction resume semantics: they
|
|
702
|
+
cannot be verified and are accepted. The caller owns the policy for an
|
|
703
|
+
unrecorded identity or a difference.
|
|
704
|
+
|
|
705
|
+
Explicitly pass ``preprocessing=None`` for pre-cropped images, whose shipped
|
|
706
|
+
transform determines geometry. Pass a :class:`PreprocessingConfig` for
|
|
707
|
+
tiles read from slides; its unset fields resolve as in pooled extraction.
|
|
708
|
+
``execution`` defaults to the model's recommended precision and needs no
|
|
709
|
+
output directory.
|
|
710
|
+
|
|
711
|
+
Only a recorded ``transform`` loads weights, on an isolated CPU model.
|
|
712
|
+
Slide and patient models load their registered tile encoder and tile output
|
|
713
|
+
variant, without loading aggregation weights. This method does not change
|
|
714
|
+
this model's input contract or loaded backend, encode pixels, or write files.
|
|
715
|
+
"""
|
|
716
|
+
from slide2vec.runtime.feature_identity import pooled_identity_differences
|
|
717
|
+
|
|
718
|
+
resolved_execution = _coerce_execution_options(execution, model=self)
|
|
719
|
+
resolved_preprocessing = (
|
|
720
|
+
None if preprocessing is None else _resolve_direct_api_preprocessing(self, preprocessing)
|
|
721
|
+
)
|
|
722
|
+
return pooled_identity_differences(
|
|
723
|
+
self,
|
|
724
|
+
dict(recorded),
|
|
725
|
+
execution=resolved_execution,
|
|
726
|
+
preprocessing=resolved_preprocessing,
|
|
727
|
+
)
|
|
728
|
+
|
|
691
729
|
def prepare_dense_encoder(
|
|
692
730
|
self,
|
|
693
731
|
*,
|
|
@@ -40,31 +40,20 @@ class SuperTileBatchSampler:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def _open_wsi_backend(image_path: str, backend: str, gpu_decode: bool):
|
|
43
|
-
"""Open a
|
|
44
|
-
if backend == "cucim":
|
|
45
|
-
from hs2p.wsi.backends.cucim import CuCIMReader
|
|
46
|
-
return CuCIMReader(image_path, gpu_decode=gpu_decode)
|
|
47
|
-
elif backend == "openslide":
|
|
48
|
-
from hs2p.wsi.backends.openslide import OpenSlideReader
|
|
49
|
-
return OpenSlideReader(image_path)
|
|
50
|
-
elif backend == "vips":
|
|
51
|
-
from hs2p.wsi.backends.vips import VIPSReader
|
|
52
|
-
return VIPSReader(image_path)
|
|
53
|
-
elif backend == "asap":
|
|
54
|
-
from hs2p.wsi.backends.asap import ASAPReader
|
|
55
|
-
with suppress_c_stderr():
|
|
56
|
-
return ASAPReader(image_path)
|
|
57
|
-
else:
|
|
58
|
-
raise ValueError(
|
|
59
|
-
f"Unknown backend: {backend!r}. "
|
|
60
|
-
"Choose from: cucim, openslide, vips, asap"
|
|
61
|
-
)
|
|
43
|
+
"""Open a slide with the backend tiling resolved for it and return the reader.
|
|
62
44
|
|
|
45
|
+
hs2p's ``open_slide`` knows every backend tiling can resolve, ``pil`` for flat
|
|
46
|
+
PNG/JPEG slides included. Reads address pyramid levels and level-0 pixels only, so a
|
|
47
|
+
slide without spacing metadata opens without it.
|
|
48
|
+
"""
|
|
49
|
+
from hs2p.wsi.reader import open_slide
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
with suppress_c_stderr() if backend == "asap" else nullcontext():
|
|
52
|
+
return open_slide(image_path, backend, gpu_decode=gpu_decode, require_spacing=False)
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
|
|
55
|
+
class WSITileReader:
|
|
56
|
+
"""Random-access tile reader over any slide backend hs2p can open.
|
|
68
57
|
|
|
69
58
|
When ``use_supertiles=True``, tiles are grouped into 8×8/4×4/2×2 blocks so
|
|
70
59
|
that one region read covers multiple tiles. ``num_cucim_workers`` and
|
|
@@ -136,6 +136,42 @@ def differing_fields(
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def pooled_identity_differences(
|
|
140
|
+
model,
|
|
141
|
+
recorded: dict[str, Any],
|
|
142
|
+
*,
|
|
143
|
+
execution,
|
|
144
|
+
preprocessing=None,
|
|
145
|
+
) -> dict[str, tuple[Any, Any]]:
|
|
146
|
+
"""Compare a recorded pooled identity with the resolved current request."""
|
|
147
|
+
|
|
148
|
+
def resolve_transform() -> dict[str, Any]:
|
|
149
|
+
from slide2vec.api import Model
|
|
150
|
+
|
|
151
|
+
encoder_name, output_variant = model.name, model._output_variant
|
|
152
|
+
if model.level != "tile":
|
|
153
|
+
info = encoder_registry.info(model.name)
|
|
154
|
+
encoder_name = info["tile_encoder"]
|
|
155
|
+
output_variant = info["tile_encoder_output_variant"]
|
|
156
|
+
source = Model.from_preset(
|
|
157
|
+
encoder_name,
|
|
158
|
+
device="cpu",
|
|
159
|
+
output_variant=output_variant,
|
|
160
|
+
allow_non_recommended_settings=model.allow_non_recommended_settings,
|
|
161
|
+
)
|
|
162
|
+
if preprocessing is None:
|
|
163
|
+
source._declare_given_encoder_input(emit_run_info=False)
|
|
164
|
+
else:
|
|
165
|
+
source._declare_encoder_input(preprocessing, emit_run_info=False)
|
|
166
|
+
return transform_record(source._load_backend().transforms)
|
|
167
|
+
|
|
168
|
+
return differing_fields(
|
|
169
|
+
recorded,
|
|
170
|
+
pooled_feature_identity(model, execution=execution, preprocessing=preprocessing),
|
|
171
|
+
resolve_transform=resolve_transform,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
139
175
|
class PooledResumeCheck:
|
|
140
176
|
"""Compare the completed pooled artifacts of one resume with the run's feature identity.
|
|
141
177
|
|
|
@@ -160,6 +160,7 @@ tests/test_patient_manifest.py
|
|
|
160
160
|
tests/test_phaet.py
|
|
161
161
|
tests/test_pooled_encoder_input.py
|
|
162
162
|
tests/test_pooled_geometry.py
|
|
163
|
+
tests/test_pooled_identity_api.py
|
|
163
164
|
tests/test_prism.py
|
|
164
165
|
tests/test_prism2.py
|
|
165
166
|
tests/test_process_list_performance.py
|
|
@@ -90,6 +90,34 @@ def test_tile_archive_is_written_when_requested_or_not_reading_on_the_fly(
|
|
|
90
90
|
assert Path(pd.read_csv(process_list).iloc[0]["tiles_tar_path"]) == archive_path
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
@pytest.mark.parametrize("use_supertiles", [True, False], ids=["supertiles", "single-tiles"])
|
|
94
|
+
def test_flat_slide_tiles_are_read_on_the_fly_with_the_backend_tiling_resolved(
|
|
95
|
+
tmp_path, use_supertiles
|
|
96
|
+
):
|
|
97
|
+
from slide2vec.data.tile_reader import OnTheFlyBatchTileCollator
|
|
98
|
+
|
|
99
|
+
slide = _flat_slide(tmp_path, np.ones((32, 32), dtype=np.uint8))
|
|
100
|
+
pixels = np.random.default_rng(0).integers(0, 256, (128, 128, 3), dtype=np.uint8)
|
|
101
|
+
Image.fromarray(pixels).save(slide.image_path)
|
|
102
|
+
|
|
103
|
+
_, (result,), _ = prepare_tiled_slides(
|
|
104
|
+
[slide], _preprocessing(), output_dir=tmp_path / "out", num_workers=1
|
|
105
|
+
)
|
|
106
|
+
collator = OnTheFlyBatchTileCollator(
|
|
107
|
+
image_path=slide.image_path,
|
|
108
|
+
tiling_result=result,
|
|
109
|
+
backend=result.backend,
|
|
110
|
+
use_supertiles=use_supertiles,
|
|
111
|
+
)
|
|
112
|
+
indices, tiles, _ = collator(list(range(len(result.x))))
|
|
113
|
+
|
|
114
|
+
assert result.backend == "pil"
|
|
115
|
+
assert tiles.shape == (16, 3, 32, 32)
|
|
116
|
+
for index, tile in zip(indices.tolist(), tiles):
|
|
117
|
+
x, y = int(result.x[index]), int(result.y[index])
|
|
118
|
+
np.testing.assert_array_equal(tile.permute(1, 2, 0).numpy(), pixels[y : y + 32, x : x + 32])
|
|
119
|
+
|
|
120
|
+
|
|
93
121
|
@pytest.mark.parametrize("output_mode", ["per_annotation", "merged"])
|
|
94
122
|
def test_spacingless_annotation_mask_groups_values_and_preserves_bag_identity(tmp_path, output_mode):
|
|
95
123
|
labels = np.full((32, 32), 2, dtype=np.uint8)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""The public comparison of recorded pooled identities with the current extraction recipe.
|
|
2
|
+
|
|
3
|
+
Failure modes: wrong metadata differences, eager or GPU model loading, using the
|
|
4
|
+
wrong input regime or tile dependency, changing the caller's model, and requiring
|
|
5
|
+
artifact writes for a metadata comparison. Expected differences are literal records.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import torch
|
|
10
|
+
from PIL import Image
|
|
11
|
+
from torchvision.transforms import v2
|
|
12
|
+
|
|
13
|
+
from slide2vec import ExecutionOptions, ImageSpec, Model, PreprocessingConfig
|
|
14
|
+
from slide2vec.encoders import PatientEncoder, SlideEncoder, TileEncoder, encoder_registry, register_encoder
|
|
15
|
+
|
|
16
|
+
TILE_ENCODER = "identity-api-tile"
|
|
17
|
+
SHIPPED_TRANSFORM = {
|
|
18
|
+
"normalize": {"mean": [0.5, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
|
|
19
|
+
"resize": {"size": [40], "interpolation": "bicubic"},
|
|
20
|
+
"center_crop": {"size": [32, 32]},
|
|
21
|
+
}
|
|
22
|
+
OLD_TRANSFORM = {
|
|
23
|
+
"normalize": {"mean": [0.25, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
|
|
24
|
+
"resize": {"size": [40], "interpolation": "bicubic"},
|
|
25
|
+
"center_crop": {"size": [32, 32]},
|
|
26
|
+
}
|
|
27
|
+
NORMALIZATION_ONLY = {
|
|
28
|
+
"normalize": {"mean": [0.5, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
|
|
29
|
+
"resize": None,
|
|
30
|
+
"center_crop": None,
|
|
31
|
+
}
|
|
32
|
+
ALTERNATE_NORMALIZATION = {
|
|
33
|
+
"normalize": {"mean": [0.25, 0.5, 0.5], "std": [0.5, 0.5, 0.5]},
|
|
34
|
+
"resize": None,
|
|
35
|
+
"center_crop": None,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def tile_encoder(monkeypatch):
|
|
41
|
+
"""A weight-free provider with known transforms and observable device selection."""
|
|
42
|
+
monkeypatch.setattr(encoder_registry, "_entries", dict(encoder_registry._entries))
|
|
43
|
+
|
|
44
|
+
@register_encoder(
|
|
45
|
+
TILE_ENCODER,
|
|
46
|
+
output_variants={"default": {"encode_dim": 3}, "alternate": {"encode_dim": 3}},
|
|
47
|
+
default_output_variant="default",
|
|
48
|
+
input_size=32,
|
|
49
|
+
supports_variable_input_size=True,
|
|
50
|
+
supported_spacing_um=[0.5],
|
|
51
|
+
default_spacing_um=0.5,
|
|
52
|
+
precision="fp16",
|
|
53
|
+
)
|
|
54
|
+
class TestTileEncoder(TileEncoder):
|
|
55
|
+
variants = []
|
|
56
|
+
devices = []
|
|
57
|
+
encoded_images = 0
|
|
58
|
+
|
|
59
|
+
def __init__(self, *, output_variant=None, allow_non_recommended_settings=False):
|
|
60
|
+
self._device = torch.device("cpu")
|
|
61
|
+
self.output_variant = output_variant or "default"
|
|
62
|
+
type(self).variants.append(self.output_variant)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def encode_dim(self):
|
|
66
|
+
return 3
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def device(self):
|
|
70
|
+
return self._device
|
|
71
|
+
|
|
72
|
+
def to(self, device):
|
|
73
|
+
self._device = torch.device(device)
|
|
74
|
+
type(self).devices.append(str(self._device))
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def get_normalization_transform(self):
|
|
78
|
+
mean = [0.25, 0.5, 0.5] if self.output_variant == "alternate" else [0.5, 0.5, 0.5]
|
|
79
|
+
return v2.Compose([
|
|
80
|
+
v2.ToImage(),
|
|
81
|
+
v2.ToDtype(torch.float32, scale=True),
|
|
82
|
+
v2.Normalize(mean=mean, std=[0.5, 0.5, 0.5]),
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
def get_transform(self):
|
|
86
|
+
return v2.Compose([
|
|
87
|
+
v2.Resize(40, interpolation=v2.InterpolationMode.BICUBIC),
|
|
88
|
+
v2.CenterCrop(32),
|
|
89
|
+
self.get_normalization_transform(),
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
def encode_tiles(self, batch):
|
|
93
|
+
type(self).encoded_images += int(batch.shape[0])
|
|
94
|
+
return batch.mean(dim=(-1, -2))
|
|
95
|
+
|
|
96
|
+
return TestTileEncoder
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture
|
|
100
|
+
def aggregate_encoders(tile_encoder):
|
|
101
|
+
class AggregationLifecycle:
|
|
102
|
+
def __init__(self, **kwargs):
|
|
103
|
+
raise AssertionError("Identity comparison must not load aggregation weights")
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def encode_dim(self):
|
|
107
|
+
return 3
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def device(self):
|
|
111
|
+
return torch.device("cpu")
|
|
112
|
+
|
|
113
|
+
def to(self, device):
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def encode_slide(self, tile_features, coordinates=None, *, tile_size_lv0=None):
|
|
117
|
+
return tile_features.mean(dim=0)
|
|
118
|
+
|
|
119
|
+
class TestSlideEncoder(AggregationLifecycle, SlideEncoder):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
class TestPatientEncoder(AggregationLifecycle, PatientEncoder):
|
|
123
|
+
def encode_patient(self, slide_embeddings):
|
|
124
|
+
return slide_embeddings.mean(dim=0)
|
|
125
|
+
|
|
126
|
+
names = {"slide": "identity-api-slide", "patient": "identity-api-patient"}
|
|
127
|
+
for level, cls in (("slide", TestSlideEncoder), ("patient", TestPatientEncoder)):
|
|
128
|
+
register_encoder(
|
|
129
|
+
names[level],
|
|
130
|
+
level=level,
|
|
131
|
+
tile_encoder=TILE_ENCODER,
|
|
132
|
+
tile_encoder_output_variant="alternate",
|
|
133
|
+
output_variants={"default": {"encode_dim": 3}},
|
|
134
|
+
default_output_variant="default",
|
|
135
|
+
supported_spacing_um=[0.5],
|
|
136
|
+
default_spacing_um=0.5,
|
|
137
|
+
precision="fp32",
|
|
138
|
+
)(cls)
|
|
139
|
+
return names
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_pooled_identity_comparison_reports_recorded_and_current_metadata():
|
|
143
|
+
model = Model.from_preset("virchow2", device="cuda")
|
|
144
|
+
recorded = {
|
|
145
|
+
"encoder_name": "older-encoder",
|
|
146
|
+
"output_variant": "cls",
|
|
147
|
+
"precision": "bf16",
|
|
148
|
+
"feature_dtype": "fp16",
|
|
149
|
+
"older_field": "accepted",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
differences = model.pooled_identity_differences(
|
|
153
|
+
recorded,
|
|
154
|
+
preprocessing=None,
|
|
155
|
+
execution=ExecutionOptions(precision="fp32", output_dtype="fp32", num_gpus=1),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
assert differences == {
|
|
159
|
+
"encoder_name": ("older-encoder", "virchow2"),
|
|
160
|
+
"output_variant": ("cls", "cls_patch_mean"),
|
|
161
|
+
"precision": ("bf16", "fp32"),
|
|
162
|
+
"feature_dtype": ("fp16", "fp32"),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_pooled_identity_comparison_reads_the_shipped_transform_on_cpu(tile_encoder):
|
|
167
|
+
model = Model.from_preset(TILE_ENCODER, device="cuda")
|
|
168
|
+
|
|
169
|
+
differences = model.pooled_identity_differences(
|
|
170
|
+
{"transform": OLD_TRANSFORM},
|
|
171
|
+
preprocessing=None,
|
|
172
|
+
execution=ExecutionOptions(precision="fp32", num_gpus=1),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
assert differences == {"transform": (OLD_TRANSFORM, SHIPPED_TRANSFORM)}
|
|
176
|
+
assert tile_encoder.devices == ["cpu"]
|
|
177
|
+
assert tile_encoder.encoded_images == 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_pooled_identity_comparison_reads_the_declared_transform(tile_encoder):
|
|
181
|
+
differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
|
|
182
|
+
{"transform": SHIPPED_TRANSFORM},
|
|
183
|
+
preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
|
|
184
|
+
execution=ExecutionOptions(precision="fp32", num_gpus=1),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert differences == {"transform": (SHIPPED_TRANSFORM, NORMALIZATION_ONLY)}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.parametrize("level", ["slide", "patient"])
|
|
191
|
+
def test_pooled_identity_comparison_uses_the_registered_tile_dependency(
|
|
192
|
+
tile_encoder, aggregate_encoders, level
|
|
193
|
+
):
|
|
194
|
+
model = Model.from_preset(aggregate_encoders[level], device="cuda")
|
|
195
|
+
|
|
196
|
+
differences = model.pooled_identity_differences(
|
|
197
|
+
{"tile_encoder_output_variant": "default", "transform": NORMALIZATION_ONLY},
|
|
198
|
+
preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
|
|
199
|
+
execution=ExecutionOptions(precision="fp32", num_gpus=1),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
assert differences == {
|
|
203
|
+
"tile_encoder_output_variant": ("default", "alternate"),
|
|
204
|
+
"transform": (NORMALIZATION_ONLY, ALTERNATE_NORMALIZATION),
|
|
205
|
+
}
|
|
206
|
+
assert tile_encoder.variants == ["alternate"]
|
|
207
|
+
assert tile_encoder.devices == ["cpu"]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_pooled_identity_comparison_accepts_missing_fields_without_loading_weights(tile_encoder):
|
|
211
|
+
differences = Model.from_preset(TILE_ENCODER, device="cuda").pooled_identity_differences(
|
|
212
|
+
{},
|
|
213
|
+
preprocessing=None,
|
|
214
|
+
execution=ExecutionOptions(precision="fp32", num_gpus=1),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
assert differences == {}
|
|
218
|
+
assert tile_encoder.variants == []
|
|
219
|
+
assert tile_encoder.devices == []
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@pytest.mark.parametrize("execution", [None, ExecutionOptions(num_gpus=1)])
|
|
223
|
+
def test_pooled_identity_comparison_resolves_the_extraction_precision_default(tile_encoder, execution):
|
|
224
|
+
differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
|
|
225
|
+
{"precision": "fp32", "feature_dtype": "fp32"},
|
|
226
|
+
preprocessing=None,
|
|
227
|
+
execution=execution,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
assert differences == {"precision": ("fp32", "fp16"), "feature_dtype": ("fp32", "fp16")}
|
|
231
|
+
assert tile_encoder.variants == []
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_pooled_identity_comparison_resolves_hierarchical_geometry(tile_encoder):
|
|
235
|
+
differences = Model.from_preset(TILE_ENCODER).pooled_identity_differences(
|
|
236
|
+
{
|
|
237
|
+
"requested_tile_size_px": 64,
|
|
238
|
+
"encoder_input_size_px": 64,
|
|
239
|
+
"region_tile_multiple": 3,
|
|
240
|
+
"requested_region_size_px": 96,
|
|
241
|
+
},
|
|
242
|
+
preprocessing=PreprocessingConfig(region_tile_multiple=2),
|
|
243
|
+
execution=ExecutionOptions(precision="fp32", num_gpus=1),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
assert differences == {
|
|
247
|
+
"requested_tile_size_px": (64, 32),
|
|
248
|
+
"encoder_input_size_px": (64, 32),
|
|
249
|
+
"region_tile_multiple": (3, 2),
|
|
250
|
+
"requested_region_size_px": (96, 64),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_pooled_identity_comparison_preserves_a_model_used_for_image_extraction(tmp_path, tile_encoder):
|
|
255
|
+
image_path = tmp_path / "image.png"
|
|
256
|
+
Image.new("RGB", (48, 48), color=(255, 255, 255)).save(image_path)
|
|
257
|
+
model = Model.from_preset(TILE_ENCODER, device="cpu")
|
|
258
|
+
execution = ExecutionOptions(
|
|
259
|
+
output_dir=tmp_path / "artifacts",
|
|
260
|
+
precision="fp32",
|
|
261
|
+
num_gpus=1,
|
|
262
|
+
num_workers_per_gpu=0,
|
|
263
|
+
)
|
|
264
|
+
(artifact,) = model.embed_images([ImageSpec(sample_id="first", image_path=image_path)], execution=execution)
|
|
265
|
+
sidecar_before = artifact.metadata_path.read_bytes()
|
|
266
|
+
|
|
267
|
+
differences = model.pooled_identity_differences(
|
|
268
|
+
artifact.metadata["compatibility"],
|
|
269
|
+
preprocessing=PreprocessingConfig(requested_tile_size_px=32, requested_spacing_um=0.5),
|
|
270
|
+
execution=execution,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
assert differences == {"transform": (SHIPPED_TRANSFORM, NORMALIZATION_ONLY)}
|
|
274
|
+
assert tile_encoder.encoded_images == 1
|
|
275
|
+
assert artifact.metadata_path.read_bytes() == sidecar_before
|
|
276
|
+
|
|
277
|
+
(next_artifact,) = model.embed_images([ImageSpec(sample_id="second", image_path=image_path)], execution=execution)
|
|
278
|
+
|
|
279
|
+
# One original backend and one comparison copy; extraction reuses the original.
|
|
280
|
+
assert tile_encoder.variants == ["default", "default"]
|
|
281
|
+
assert tile_encoder.encoded_images == 2
|
|
282
|
+
assert next_artifact.metadata["compatibility"]["transform"] == SHIPPED_TRANSFORM
|
|
283
|
+
assert torch.equal(torch.load(next_artifact.path, weights_only=True), torch.tensor([1.0, 1.0, 1.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
|
|
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
|