"""Decision-linked localization heatmap for the A-EYE backend (model 54+ family). The heatmap is the PatchGuard patch-probability map, refined for localization: * computed at higher resolution (24x24) for a finer, less blocky map, * sharpened by a noise-residual map (insertions disturb camera noise), * per-image normalized and focused to the single strongest connected region (kills scattered noise so only the suspected insert lights up), * intensity-gated by the calibrated image confidence, so weak/uncertain maps stay faint instead of painting the photo with noise. Pure numpy / PIL / scipy. New file; nothing existing is modified. """ from __future__ import annotations import numpy as np from PIL import Image from scipy.ndimage import gaussian_filter, label, uniform_filter def jet(values: np.ndarray) -> np.ndarray: v = np.clip(values, 0.0, 1.0) r = np.clip(1.5 - np.abs(4.0 * v - 3.0), 0.0, 1.0) g = np.clip(1.5 - np.abs(4.0 * v - 2.0), 0.0, 1.0) b = np.clip(1.5 - np.abs(4.0 * v - 1.0), 0.0, 1.0) return np.stack([r, g, b], axis=-1) def residual_var(image: Image.Image, grid: int, win: int = 10) -> np.ndarray: """Local noise-residual variance, block-reduced to grid x grid. Camera regions carry consistent sensor noise; AI-inserted regions usually break it, so this helps pin the patch map onto the real seam.""" g = np.asarray(image.convert("L").resize((288, 288)), np.float32) res = g - gaussian_filter(g, 2) m2 = uniform_filter(res * res, win) m1 = uniform_filter(res, win) var = np.maximum(m2 - m1 * m1, 0.0) h, w = var.shape bh, bw = h // grid, w // grid return var[: bh * grid, : bw * grid].reshape(grid, bh, grid, bw).mean(axis=(1, 3)) def _focus(loc: np.ndarray) -> np.ndarray: """Per-image normalize, then keep only the strongest connected blob.""" rng = float(np.ptp(loc)) if rng < 1e-6: return np.zeros_like(loc) n = (loc - loc.min()) / (rng + 1e-6) binary = n > 0.55 lab, k = label(binary) if k > 1: sums = [float((loc * (lab == i)).sum()) for i in range(1, k + 1)] keep = 1 + int(np.argmax(sums)) n = n * (lab == keep) elif k == 0: n = n * 0.0 return n def _big(focus_map: np.ndarray, size: tuple[int, int]) -> np.ndarray: """Upsample the small focus map to image size, feathered for clean edges.""" img = Image.fromarray((np.clip(focus_map, 0.0, 1.0) * 255).astype(np.uint8)) big = np.asarray(img.resize(size, Image.Resampling.BICUBIC), np.float32) / 255.0 return gaussian_filter(big, max(1.0, size[0] / 130.0)) def pure_heatmap(loc: np.ndarray, size: tuple[int, int], conf: float = 1.0, blanket: bool = False) -> Image.Image: """Standalone jet heatmap (no original image).""" if blanket: big = np.full((size[1], size[0]), 0.9, np.float32) else: big = _big(_focus(loc), size) * float(np.clip(conf, 0.0, 1.0)) return Image.fromarray((jet(big) * 255).astype(np.uint8)) def overlay( image: Image.Image, loc: np.ndarray, conf: float = 1.0, blanket: bool = False, floor: float = 0.30, max_alpha: float = 0.92, gamma: float = 0.55, ) -> Image.Image: """Jet overlay on the photo. The hot region is rendered VIVID (the per-image map is normalized so its peak is full red at max_alpha). `blanket=True` paints the whole image red. `conf` is kept for API compatibility but the caller passes 1.0 so the color is always strong, per product preference.""" rgb = np.asarray(image.convert("RGB"), np.float32) / 255.0 h, w = rgb.shape[:2] if blanket: big = np.full((h, w), 0.9, np.float32) c = 1.0 else: big = _big(_focus(loc), (w, h)) c = float(np.clip(conf, 0.0, 1.0)) norm = np.clip((big - floor) / (1.0 - floor), 0.0, 1.0) alpha = max_alpha * c * (norm ** gamma) blended = rgb * (1.0 - alpha[..., None]) + jet(big) * alpha[..., None] return Image.fromarray((np.clip(blended, 0.0, 1.0) * 255).astype(np.uint8))