LEGENDFTW/image-filtering-explorer
0
1"""utils.py — I/O helpers and visualisation utilities."""2 3import os4import io5import numpy as np6import cv27from PIL import Image8 9SAMPLE_DIR = os.path.join(os.path.dirname(__file__), "sample_images")10 11 12def load_sample_image(filename: str) -> np.ndarray:13 """Load a built-in sample image as an RGB uint8 array."""14 path = os.path.join(SAMPLE_DIR, filename)15 if not os.path.exists(path):16 raise FileNotFoundError(f"Sample image not found: {path}")17 pil = Image.open(path).convert("RGB")18 return np.array(pil)19 20 21def image_to_bytes(image: np.ndarray, fmt: str = "PNG") -> bytes:22 """Convert an RGB uint8 numpy array to image bytes."""23 pil = Image.fromarray(image.astype(np.uint8))24 buf = io.BytesIO()25 pil.save(buf, format=fmt)26 return buf.getvalue()27 28 29def overlay_noise_heatmap(original: np.ndarray, filtered: np.ndarray) -> np.ndarray:30 """31 Generate a colourmap heatmap of per-pixel difference magnitude32 overlaid (blended) on the original image.33 Returns an RGB uint8 image.34 """35 diff = np.abs(original.astype(np.int32) - filtered.astype(np.int32))36 mag = diff.mean(axis=-1).astype(np.float32) # (H, W)37 38 # Normalise 0→25539 vmax = mag.max()40 if vmax > 0:41 mag_norm = (mag / vmax * 255).astype(np.uint8)42 else:43 mag_norm = np.zeros_like(mag, dtype=np.uint8)44 45 # Apply JET colourmap (BGR → RGB)46 heatmap_bgr = cv2.applyColorMap(mag_norm, cv2.COLORMAP_JET)47 heatmap_rgb = cv2.cvtColor(heatmap_bgr, cv2.COLOR_BGR2RGB)48 49 # Blend with original for context (30 % heatmap, 70 % original)50 blended = cv2.addWeighted(original, 0.5, heatmap_rgb, 0.5, 0)51 return blended.astype(np.uint8)52 