| """Visual enhancement modes for the displayed cutout (apoyo al revisor). |
| |
| Same transforms and parameters as the static review site (docs/app.js), ported |
| to numpy/skimage. These are cosmetic, applied to the 8-bit RGB only — they do |
| NOT touch the image the model sees (that stays the raw Lupton composite). |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| from skimage.exposure import equalize_adapthist |
| from skimage.filters import unsharp_mask |
|
|
| MODES = ["Original", "Percentil", "Unsharp", "Asinh", "CLAHE", "Estructuras"] |
|
|
|
|
| def percentile_stretch(rgb: np.ndarray, p_lo: float = 1.0, p_hi: float = 99.5) -> np.ndarray: |
| out = rgb.astype(np.float32) |
| for c in range(3): |
| lo, hi = np.percentile(out[..., c], [p_lo, p_hi]) |
| rng = (hi - lo) or 1.0 |
| out[..., c] = np.clip((out[..., c] - lo) / rng * 255.0, 0, 255) |
| return out.astype(np.uint8) |
|
|
|
|
| def unsharp(rgb: np.ndarray, radius: float = 6.0, amount: float = 1.5) -> np.ndarray: |
| f = rgb.astype(np.float32) / 255.0 |
| out = unsharp_mask(f, radius=radius, amount=amount, channel_axis=-1) |
| return (np.clip(out, 0, 1) * 255).astype(np.uint8) |
|
|
|
|
| def asinh_stretch(rgb: np.ndarray, a: float = 1.5) -> np.ndarray: |
| f = rgb.astype(np.float32) / 255.0 |
| out = np.arcsinh(a * f) / np.arcsinh(a) |
| return (np.clip(out, 0, 1) * 255).astype(np.uint8) |
|
|
|
|
| def clahe(rgb: np.ndarray, clip_limit: float = 0.01) -> np.ndarray: |
| f = rgb.astype(np.float32) / 255.0 |
| out = np.zeros_like(f) |
| for c in range(3): |
| out[..., c] = equalize_adapthist(f[..., c], clip_limit=clip_limit) |
| return (np.clip(out, 0, 1) * 255).astype(np.uint8) |
|
|
|
|
| def estructuras(rgb: np.ndarray, amount: float = 1.5) -> np.ndarray: |
| x = percentile_stretch(rgb, 1.0, 99.5) |
| x = unsharp(x, 6.0, amount) |
| x = asinh_stretch(x, amount * 0.5) |
| return x |
|
|
|
|
| def to_gray(rgb: np.ndarray) -> np.ndarray: |
| lum = (0.2126 * rgb[..., 0] + 0.7152 * rgb[..., 1] + 0.0722 * rgb[..., 2]).astype(np.uint8) |
| return np.dstack([lum, lum, lum]) |
|
|
|
|
| def apply(rgb: np.ndarray, mode: str = "Original", gray: bool = False) -> np.ndarray: |
| """Apply the chosen enhancement mode (and optional grayscale) to an RGB.""" |
| if mode == "Percentil": |
| rgb = percentile_stretch(rgb) |
| elif mode == "Unsharp": |
| rgb = unsharp(rgb) |
| elif mode == "Asinh": |
| rgb = asinh_stretch(rgb) |
| elif mode == "CLAHE": |
| rgb = clahe(rgb) |
| elif mode == "Estructuras": |
| rgb = estructuras(rgb) |
| if gray: |
| rgb = to_gray(rgb) |
| return rgb |
|
|