Spaces:
Sleeping
Sleeping
| """ | |
| Embedding cache utilities for pre-computed features. | |
| Handles saving/loading embeddings to disk for fast retrieval. | |
| """ | |
| import torch | |
| from pathlib import Path | |
| from typing import Tuple, List, Optional, Dict | |
| import json | |
| def save_embeddings( | |
| embeddings: torch.Tensor, | |
| metadata: Dict, | |
| output_dir: str, | |
| filename: Optional[str] = None | |
| ) -> Path: | |
| """ | |
| Save embeddings and metadata to disk. | |
| Args: | |
| embeddings: Tensor of shape (N, embed_dim) | |
| metadata: Dict with keys like 'modality', 'sample_ids', 'class_labels' | |
| output_dir: Directory to save to | |
| filename: Optional custom filename (without extension) | |
| Returns: | |
| Path to saved file | |
| """ | |
| output_dir = Path(output_dir) | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| # Generate filename if not provided | |
| if filename is None: | |
| modality = metadata.get("modality", "unknown") | |
| n_samples = embeddings.shape[0] | |
| filename = f"{modality}_embeddings_{n_samples}" | |
| # Save embeddings tensor | |
| embeddings_path = output_dir / f"{filename}.pt" | |
| torch.save(embeddings, embeddings_path) | |
| # Save metadata as JSON | |
| metadata_path = output_dir / f"{filename}_metadata.json" | |
| # Convert tensors in metadata to lists for JSON serialization | |
| serializable_metadata = {} | |
| for key, value in metadata.items(): | |
| if isinstance(value, torch.Tensor): | |
| serializable_metadata[key] = value.tolist() | |
| else: | |
| serializable_metadata[key] = value | |
| with open(metadata_path, "w") as f: | |
| json.dump(serializable_metadata, f, indent=2) | |
| return embeddings_path | |
| def load_embeddings( | |
| cache_path: str | |
| ) -> Tuple[torch.Tensor, Dict]: | |
| """ | |
| Load embeddings and metadata from disk. | |
| Args: | |
| cache_path: Path to .pt embeddings file | |
| Returns: | |
| (embeddings tensor, metadata dict) | |
| """ | |
| cache_path = Path(cache_path) | |
| # Load embeddings | |
| embeddings = torch.load(cache_path, weights_only=True) | |
| # Load metadata if exists | |
| metadata_path = cache_path.with_name( | |
| cache_path.stem + "_metadata.json" | |
| ) | |
| metadata = {} | |
| if metadata_path.exists(): | |
| with open(metadata_path, "r") as f: | |
| metadata = json.load(f) | |
| return embeddings, metadata | |
| def get_cache_path( | |
| output_dir: str, | |
| modality: str, | |
| split: str = "gallery", | |
| embed_dim: int = 768 | |
| ) -> Path: | |
| """ | |
| Generate standard cache file path. | |
| Args: | |
| output_dir: Base output directory | |
| modality: Modality type | |
| split: Dataset split (query/gallery) | |
| embed_dim: Embedding dimension | |
| Returns: | |
| Path object for cache file | |
| """ | |
| output_dir = Path(output_dir) | |
| filename = f"{modality}_{split}_embeddings.pt" | |
| return output_dir / filename | |
| def verify_embeddings( | |
| embeddings: torch.Tensor, | |
| expected_dim: Optional[int] = None, | |
| l2_normalized: bool = True | |
| ) -> bool: | |
| """ | |
| Verify embeddings are valid. | |
| Args: | |
| embeddings: Embedding tensor | |
| expected_dim: Expected embedding dimension | |
| l2_normalized: Whether embeddings should be L2-normalized | |
| Returns: | |
| True if valid | |
| """ | |
| if embeddings.dim() != 2: | |
| print(f"Expected 2D tensor, got {embeddings.dim()}D") | |
| return False | |
| if expected_dim is not None and embeddings.shape[1] != expected_dim: | |
| print(f"Expected dim {expected_dim}, got {embeddings.shape[1]}") | |
| return False | |
| if l2_normalized: | |
| norms = torch.norm(embeddings, dim=1) | |
| # Check if norms are close to 1 (allowing for floating point) | |
| if not torch.allclose(norms, torch.ones_like(norms), atol=1e-3): | |
| print(f"Embeddings not L2-normalized. Norms: {norms[:5]}") | |
| return False | |
| return True | |
| # Self-check | |
| if __name__ == "__main__": | |
| import tempfile | |
| print("Testing embedding cache utilities...") | |
| # Create dummy embeddings | |
| embeddings = torch.randn(100, 768) | |
| embeddings = torch.nn.functional.normalize(embeddings, dim=1) # L2 normalize | |
| metadata = { | |
| "modality": "optical", | |
| "sample_ids": list(range(100)), | |
| "class_labels": [i % 10 for i in range(100)], | |
| } | |
| # Test save/load roundtrip | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| # Save | |
| save_path = save_embeddings(embeddings, metadata, tmpdir, "test") | |
| print(f"Saved to: {save_path}") | |
| # Load | |
| loaded_embeddings, loaded_metadata = load_embeddings(save_path) | |
| print(f"Loaded embeddings shape: {loaded_embeddings.shape}") | |
| print(f"Loaded metadata keys: {list(loaded_metadata.keys())}") | |
| # Verify | |
| assert torch.allclose(embeddings, loaded_embeddings), "Embeddings mismatch!" | |
| assert loaded_metadata["modality"] == "optical", "Metadata mismatch!" | |
| # Test verify | |
| assert verify_embeddings(loaded_embeddings, expected_dim=768), "Verification failed!" | |
| print("\nEmbedding cache test passed!") | |