# depth_estimator.py placeholder """ WHAT: This module defines the Depth Estimator, the third stage of the Multiverse AI pipeline. WHY: Video generation models often need a sense of 3D space to create realistic camera movements (like panning or zooming). By extracting a grayscale depth map from our flat 2D generated image, we give the downstream video model the geometry it needs to distinguish foreground from background. HOW: It implements the `BaseModel` interface. It uses the Hugging Face `transformers` library to load the 'Depth-Anything' model, processes the PIL Image, and returns a new depth-mapped PIL Image. """ import gc import torch from PIL import Image # Import the pipeline utility from transformers from transformers import pipeline # Import our base interface and global configurations from .base import BaseModel from ..config import MODEL_IDS, DEVICE, MOCK_INFERENCE class DepthEstimator(BaseModel): """ Wrapper for the Depth Estimation model (Depth-Anything). Takes a 2D PIL Image and generates a grayscale depth map (also a PIL Image). """ def __init__(self): self.pipe = None self.model_id = MODEL_IDS["depth_estimation"] def initialize(self) -> None: """ WHAT: Loads the Depth-Anything model weights into memory. WHY: We delay loading until this exact stage in the pipeline to conserve VRAM. If we loaded it at application startup alongside SDXL, we would crash immediately. HOW: Uses `transformers.pipeline` for the 'depth-estimation' task. We map the global config.DEVICE string to ensure it runs on the GPU if available. Falls back to Mock Mode if weights are missing or fails to load. """ if MOCK_INFERENCE: print("[DepthEstimator] Running in MOCK mode. Bypassing depth model load.") return try: self.pipe = pipeline( task="depth-estimation", model=self.model_id, device=DEVICE ) except Exception as e: print(f"[DepthEstimator Warning] Failed to load local weights: {e}. Degrading gracefully to MOCK fallback.") self.pipe = None def generate(self, **kwargs) -> Image.Image: """ WHAT: Analyzes the provided 2D image and calculates the distance of objects from the camera. WHY: To create the 3D geometry asset required by the upcoming video generation stage. HOW: Extracts the 'image' argument passed from the orchestration layer (pipeline.py), runs it through the depth pipeline, and extracts the resulting PIL Image. """ # The orchestration layer passes the output of the Image Generator as 'image' input_image = kwargs.get("image") if not input_image: raise ValueError("DepthEstimator requires a valid PIL Image passed as 'image' in kwargs.") if not isinstance(input_image, Image.Image): raise ValueError("The provided 'image' must be a PIL Image object.") if MOCK_INFERENCE or self.pipe is None: print("[DepthEstimator] Generating mock depth map image...") w, h = input_image.size # Create a simple grayscale gradient representing depth depth_img = Image.new("L", (w, h)) for y in range(h): # Darker (deeper) at the top, lighter (closer) at the bottom val = int((y / h) * 255) # Quick scanline write depth_img.paste(val, (0, y, w, y + 1)) return depth_img try: # Run the inference # The depth-estimation pipeline returns a dictionary containing the depth map and raw tensor. result = self.pipe(input_image) # 'depth' is the key containing the finalized PIL Image (a grayscale depth map) depth_map = result["depth"] return depth_map except Exception as e: raise RuntimeError(f"Depth estimation failed: {str(e)}") from e def cleanup(self) -> None: """ WHAT: Completely removes the depth model from memory and clears the hardware cache. WHY: VRAM MANAGEMENT IS CRITICAL. Even though Depth-Anything-Small is lighter than SDXL, it still consumes precious gigabytes of VRAM. Because the pipeline runs sequentially, this model is no longer needed once the depth map is created. By deleting it now, we ensure the GPU is completely empty and ready to load the heavy Video/Audio models next. HOW: We delete the Python reference to the pipeline, force Python's garbage collector to run, and strictly call `torch.cuda.empty_cache()` to free the allocated blocks on the GPU. """ if MOCK_INFERENCE: return if self.pipe is not None: # 1. Delete the Python object referencing the model weights del self.pipe self.pipe = None # 2. Force Python to run garbage collection gc.collect() # 3. If using a GPU, force PyTorch to release the freed memory back to the OS if DEVICE == "cuda": torch.cuda.empty_cache()