File size: 3,465 Bytes
1a12965 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | """Thin, deterministic wrappers around Albumentations transforms.
Every wrapper collapses the transform's own randomized magnitude range down
to values derived from our ``strength in [0, 1]`` convention, and executes
via a freshly seeded ``A.Compose`` so results are fully reproducible under
:mod:`src.utils.random_utils` seed derivation rather than Albumentations'
global random state.
"""
from __future__ import annotations
import albumentations as A
import numpy as np
def _run(transform: "A.BasicTransform", image: np.ndarray, seed: int) -> np.ndarray:
pipeline = A.Compose([transform], seed=seed, p=1.0)
return pipeline(image=image)["image"]
def motion_blur(image: np.ndarray, strength: float, seed: int) -> np.ndarray:
"""Directional motion blur; ``strength`` controls kernel size (3..21 px)."""
k = int(round(3 + strength * 18))
k = k + 1 if k % 2 == 0 else k
k = max(3, min(k, 21))
return _run(A.MotionBlur(blur_limit=(k, k), p=1.0), image, seed)
def gaussian_blur(image: np.ndarray, strength: float, seed: int) -> tuple[np.ndarray, float]:
"""Isotropic Gaussian blur; ``strength`` controls sigma (0.2..4.0 px)."""
sigma = round(0.2 + strength * 3.8, 3)
out = _run(A.GaussianBlur(blur_limit=0, sigma_limit=(sigma, sigma), p=1.0), image, seed)
return out, sigma
def perspective_warp(image: np.ndarray, strength: float, seed: int) -> np.ndarray:
"""Subtle perspective distortion; ``strength`` controls corner-jitter scale."""
scale = round(0.01 + strength * 0.09, 4)
return _run(A.Perspective(scale=(scale, scale), keep_size=True, fit_output=False, p=1.0), image, seed)
def contrast_shift(image: np.ndarray, strength: float, seed: int) -> np.ndarray:
"""Global contrast shift; ``strength`` controls the max +/- magnitude."""
mag = round(strength * 0.45, 4)
t = A.RandomBrightnessContrast(brightness_limit=(0.0, 0.0), contrast_limit=(-mag, mag), p=1.0)
return _run(t, image, seed)
def gamma_shift(image: np.ndarray, strength: float, seed: int) -> tuple[np.ndarray, int]:
"""Gamma correction; ``strength`` controls deviation from gamma=100."""
delta = int(round(strength * 45))
lo, hi = max(100 - delta, 40), 100 + delta
out = _run(A.RandomGamma(gamma_limit=(lo, hi), p=1.0), image, seed)
return out, lo if lo != hi else lo
def jpeg_compression(image: np.ndarray, min_quality: int, max_quality: int, seed: int) -> tuple[np.ndarray, int]:
"""JPEG re-compression artifact simulation over ``[min_quality, max_quality]``."""
rng = np.random.default_rng(seed)
quality = int(rng.integers(min_quality, max_quality + 1))
out = _run(A.ImageCompression(compression_type="jpeg", quality_range=(quality, quality), p=1.0), image, seed)
return out, quality
def salt_and_pepper(image: np.ndarray, strength: float, seed: int) -> np.ndarray:
"""Salt-and-pepper impulse noise; ``strength`` controls the affected pixel fraction."""
amount = round(0.002 + strength * 0.09, 5)
t = A.SaltAndPepper(amount=(amount, amount), salt_vs_pepper=(0.45, 0.55), p=1.0)
return _run(t, image, seed)
def sensor_noise(image: np.ndarray, strength: float, seed: int) -> np.ndarray:
"""Gaussian sensor/scanner noise; ``strength`` controls the noise std as a fraction of range."""
std = round(0.01 + strength * 0.12, 5)
t = A.GaussNoise(std_range=(std, std), mean_range=(0.0, 0.0), per_channel=False, p=1.0)
return _run(t, image, seed)
|