Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| """ | |
| Image mapping utilities for product catalog. | |
| This module provides functions to map product image filenames to their | |
| actual file paths in the product_samples directory structure, with | |
| fallback handling for missing images and caching for performance. | |
| """ | |
| import os | |
| from pathlib import Path | |
| from typing import Dict, Optional | |
| from functools import lru_cache | |
| # Base path for product images (relative to project root) | |
| PRODUCT_SAMPLES_BASE = "search_personalization/product_samples" | |
| PLACEHOLDER_IMAGE = "search_personalization/product_samples/product_image_coming_soon.png" | |
| THUMBNAIL_DIR = Path("search_personalization/product_samples/.thumbnails") | |
| # In-memory cache for image path existence checks | |
| _image_cache: Dict[str, str] = {} | |
| def get_thumbnail_path(product: Dict) -> str: | |
| """ | |
| Get a small thumbnail for fast UI rendering (~200px wide, ~70% quality). | |
| Generates on first access, then serves from disk cache. | |
| Args: | |
| product: Product dictionary containing 'category' and 'image' fields | |
| Returns: | |
| Path to the thumbnail image, or placeholder if original doesn't exist. | |
| """ | |
| original = get_image_path(product) | |
| if original == PLACEHOLDER_IMAGE: | |
| return PLACEHOLDER_IMAGE | |
| rel = Path(original).relative_to(PRODUCT_SAMPLES_BASE) | |
| thumb = THUMBNAIL_DIR / rel | |
| if thumb.exists(): | |
| return str(thumb) | |
| # Generate thumbnail | |
| try: | |
| from PIL import Image | |
| thumb.parent.mkdir(parents=True, exist_ok=True) | |
| img = Image.open(original) | |
| img.thumbnail((200, 200)) | |
| img.save(thumb, "JPEG", quality=70) | |
| return str(thumb) | |
| except Exception: | |
| return original | |
| def get_image_path(product: Dict, use_cache: bool = True) -> str: | |
| """ | |
| Map product image filename to actual file path in product_samples directory. | |
| This function constructs the full path to a product image based on the | |
| product's category and image filename. If the image file doesn't exist, | |
| it returns the path to a placeholder image. | |
| Args: | |
| product: Product dictionary containing 'category' and 'image' fields | |
| use_cache: Whether to use cached image path results (default: True) | |
| Returns: | |
| String path to the image file, either the actual product image or | |
| the placeholder image if the product image is not found | |
| Examples: | |
| >>> product = {"category": "accessories", "image": "abc123.jpg"} | |
| >>> get_image_path(product) | |
| 'product_samples/accessories/abc123.jpg' | |
| >>> product = {"category": "apparel", "image": "missing.jpg"} | |
| >>> get_image_path(product) # If missing.jpg doesn't exist | |
| 'product_samples/product_image_coming_soon.png' | |
| Requirements: 9.4 | |
| """ | |
| # Extract category and image from product | |
| category = product.get('category', '') | |
| image_filename = product.get('image', '') | |
| # If no image filename provided, return placeholder | |
| if not image_filename: | |
| return PLACEHOLDER_IMAGE | |
| # If no category provided, return placeholder | |
| if not category: | |
| return PLACEHOLDER_IMAGE | |
| # Create cache key | |
| cache_key = f"{category}/{image_filename}" | |
| # Check cache first if enabled | |
| if use_cache and cache_key in _image_cache: | |
| return _image_cache[cache_key] | |
| # Construct the expected image path | |
| image_path = os.path.join(PRODUCT_SAMPLES_BASE, category, image_filename) | |
| # Check if the image file exists | |
| if os.path.exists(image_path) and os.path.isfile(image_path): | |
| # Cache the result | |
| if use_cache: | |
| _image_cache[cache_key] = image_path | |
| return image_path | |
| # Image doesn't exist, return placeholder | |
| if use_cache: | |
| _image_cache[cache_key] = PLACEHOLDER_IMAGE | |
| return PLACEHOLDER_IMAGE | |
| def get_image_path_cached(category: str, image_filename: str) -> str: | |
| """ | |
| Cached version of image path lookup using functools.lru_cache. | |
| This function provides an alternative caching mechanism using Python's | |
| built-in LRU cache decorator. It's more efficient for repeated lookups | |
| of the same category/image combinations. | |
| Args: | |
| category: Product category (e.g., 'accessories', 'apparel', 'footwear') | |
| image_filename: Image filename (e.g., 'abc123.jpg') | |
| Returns: | |
| String path to the image file or placeholder | |
| Note: | |
| This function uses @lru_cache which caches up to 1000 unique | |
| category/filename combinations. The cache persists for the | |
| lifetime of the application. | |
| """ | |
| # If no image filename provided, return placeholder | |
| if not image_filename: | |
| return PLACEHOLDER_IMAGE | |
| # If no category provided, return placeholder | |
| if not category: | |
| return PLACEHOLDER_IMAGE | |
| # Construct the expected image path | |
| image_path = os.path.join(PRODUCT_SAMPLES_BASE, category, image_filename) | |
| # Check if the image file exists | |
| if os.path.exists(image_path) and os.path.isfile(image_path): | |
| return image_path | |
| # Image doesn't exist, return placeholder | |
| return PLACEHOLDER_IMAGE | |
| def clear_image_cache() -> None: | |
| """ | |
| Clear the in-memory image path cache. | |
| This function clears both the manual cache dictionary and the | |
| LRU cache used by get_image_path_cached. Useful for testing | |
| or when the file system changes. | |
| """ | |
| global _image_cache | |
| _image_cache.clear() | |
| get_image_path_cached.cache_clear() | |
| def get_cache_info() -> Dict[str, any]: | |
| """ | |
| Get information about the current cache state. | |
| Returns: | |
| Dictionary containing cache statistics: | |
| - manual_cache_size: Number of entries in the manual cache | |
| - lru_cache_info: Statistics from the LRU cache | |
| """ | |
| return { | |
| 'manual_cache_size': len(_image_cache), | |
| 'lru_cache_info': get_image_path_cached.cache_info()._asdict() | |
| } | |
| def preload_image_cache(products: list) -> int: | |
| """ | |
| Preload the image cache with paths for a list of products. | |
| This function can be used to warm up the cache when loading | |
| products from OpenSearch, improving performance for subsequent | |
| image path lookups. | |
| Args: | |
| products: List of product dictionaries | |
| Returns: | |
| Number of image paths successfully cached | |
| Example: | |
| >>> products = [ | |
| ... {"category": "accessories", "image": "img1.jpg"}, | |
| ... {"category": "apparel", "image": "img2.jpg"} | |
| ... ] | |
| >>> count = preload_image_cache(products) | |
| >>> print(f"Cached {count} image paths") | |
| """ | |
| cached_count = 0 | |
| for product in products: | |
| category = product.get('category', '') | |
| image_filename = product.get('image', '') | |
| if category and image_filename: | |
| # Use the cached version to populate the LRU cache | |
| get_image_path_cached(category, image_filename) | |
| cached_count += 1 | |
| return cached_count | |
| def validate_placeholder_exists() -> bool: | |
| """ | |
| Check if the placeholder image file exists. | |
| Returns: | |
| True if placeholder image exists, False otherwise | |
| """ | |
| return os.path.exists(PLACEHOLDER_IMAGE) and os.path.isfile(PLACEHOLDER_IMAGE) | |