CoolFace
Apppublic

LEGENDFTW/image-filtering-explorer

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
processing.py118 linesDownload Raw Back to src
1"""processing.py — Filter implementations and noise generation."""2 3import numpy as np4import cv25from skimage.restoration import denoise_nl_means6 7# ── Human-readable descriptions ───────────────────────────────────────────────8FILTER_DESCRIPTIONS = {9    "Gaussian Blur": (10        "Convolves the image with a 2-D Gaussian kernel. "11        "Each output pixel is a weighted average of its neighbours, "12        "with closer pixels weighted more heavily. "13        "Excellent at removing Gaussian noise; blurs edges."14    ),15    "Median Filter": (16        "Replaces each pixel with the median of its neighbourhood. "17        "Non-linear — highly effective against salt-and-pepper noise "18        "because single outliers are outvoted by their neighbours. "19        "Preserves edges better than Gaussian blur."20    ),21    "Bilateral Filter": (22        "Like Gaussian blur but adds an intensity-range weight: "23        "pixels that differ strongly in colour are excluded from the average, "24        "even if they are spatially close. "25        "Smooths flat regions while keeping sharp edges intact."26    ),27    "Box (Mean) Filter": (28        "Simplest filter: each output pixel is the plain average of a square neighbourhood. "29        "Fast but blurs edges and smears salt-and-pepper noise. "30        "Useful baseline for comparison."31    ),32    "Non-local Means": (33        "Compares small image patches across a search window. "34        "Pixels whose surrounding patch looks similar contribute more to the output. "35        "State-of-the-art for Gaussian noise; very effective on textures. "36        "Computationally expensive."37    ),38}39 40 41# ── Filter dispatcher ─────────────────────────────────────────────────────────42def apply_filter(image: np.ndarray, filter_name: str, params: dict) -> np.ndarray:43    """Apply the selected filter and return the result as uint8 RGB."""44    img = image.copy()45    if img.dtype != np.uint8:46        img = np.clip(img, 0, 255).astype(np.uint8)47 48    if filter_name == "Gaussian Blur":49        ksize = _odd(params.get("ksize", 7))50        sigma = params.get("sigma", 1.5)51        out = cv2.GaussianBlur(img, (ksize, ksize), sigma)52 53    elif filter_name == "Median Filter":54        ksize = _odd(params.get("ksize", 5))55        out = cv2.medianBlur(img, ksize)56 57    elif filter_name == "Bilateral Filter":58        d           = params.get("d", 9)59        sigma_color = params.get("sigma_color", 75)60        sigma_space = params.get("sigma_space", 75)61        out = cv2.bilateralFilter(img, d, sigma_color, sigma_space)62 63    elif filter_name == "Box (Mean) Filter":64        ksize = _odd(params.get("ksize", 7))65        out = cv2.blur(img, (ksize, ksize))66 67    elif filter_name == "Non-local Means":68        h             = params.get("h", 10)69        template_size = _odd(params.get("template_size", 7))70        search_size   = _odd(params.get("search_size", 21))71        # cv2 NLM (float32, then back)72        img_f  = img.astype(np.float32) / 255.073        # Use skimage NLM for better results74        denoised = denoise_nl_means(75            img_f,76            h=h / 100.0,77            patch_size=template_size,78            patch_distance=search_size // 2,79            channel_axis=-1,80            fast_mode=True,81        )82        out = np.clip(denoised * 255, 0, 255).astype(np.uint8)83 84    else:85        raise ValueError(f"Unknown filter: {filter_name}")86 87    return out88 89 90# ── Noise addition ────────────────────────────────────────────────────────────91def add_synthetic_noise(image: np.ndarray, noise_type: str, level: int) -> np.ndarray:92    """Add synthetic noise to an image and return uint8."""93    img = image.astype(np.float32)94 95    if noise_type == "Gaussian":96        sigma = level97        noise = np.random.normal(0, sigma, img.shape)98        img = img + noise99 100    elif noise_type == "Salt & Pepper":101        prob = level / 1000.0      # e.g. level=25 → 2.5% corruption102        mask = np.random.random(img.shape[:2])103        img[mask < prob / 2]        = 0.0104        img[mask > 1 - prob / 2]    = 255.0105 106    elif noise_type == "Speckle":107        noise = np.random.normal(0, level / 100.0, img.shape)108        img = img + img * noise109 110    return np.clip(img, 0, 255).astype(np.uint8)111 112 113# ── Helpers ───────────────────────────────────────────────────────────────────114def _odd(n: int) -> int:115    """Ensure n is an odd positive integer (required by OpenCV kernels)."""116    n = max(3, int(n))117    return n if n % 2 == 1 else n + 1118