Venket / src /utils /image_io.py
venki101's picture
Add files using upload-large-folder tool
97b64b9 verified
Raw History Blame Contribute Delete
2.29 kB
"""Image I/O helpers built on Pillow + NumPy.
Pillow is used for decoding (broad format support: PNG/JPG/JPEG/TIFF/BMP)
while all pixel manipulation downstream is done with NumPy/OpenCV arrays in
RGB(A) uint8 layout for consistency across the pipeline.
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
from PIL import Image
SUPPORTED_IMAGE_EXTENSIONS: tuple[str, ...] = (
".png", ".jpg", ".jpeg", ".tif", ".tiff", ".bmp",
)
def load_image_rgb(path: "Path | str") -> np.ndarray:
"""Load an image file as an ``(H, W, 3)`` uint8 RGB array."""
with Image.open(path) as im:
return np.array(im.convert("RGB"))
def load_image_any(path: "Path | str") -> np.ndarray:
"""Load an image preserving its native mode (grayscale, RGB, or RGBA) as uint8.
Used for signature source images, which may be grayscale scans, palette
images, or already have an alpha channel.
"""
with Image.open(path) as im:
if im.mode in ("L", "RGB", "RGBA"):
return np.array(im)
if im.mode == "LA":
return np.array(im.convert("RGBA"))
# Palette / CMYK / other -> normalize to RGB
return np.array(im.convert("RGB"))
def save_image_rgb(path: "Path | str", array: np.ndarray) -> None:
"""Save an ``(H, W, 3)`` or ``(H, W, 4)`` uint8 array as PNG/JPEG depending on extension."""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
mode = "RGBA" if array.ndim == 3 and array.shape[2] == 4 else "RGB"
Image.fromarray(array.astype(np.uint8), mode=mode).save(path)
def save_image_gray(path: "Path | str", array: np.ndarray) -> None:
"""Save a single-channel uint8 array as a grayscale image."""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
Image.fromarray(array.astype(np.uint8), mode="L").save(path)
def discover_images(root: "Path | str", extensions: tuple[str, ...] = SUPPORTED_IMAGE_EXTENSIONS) -> list[Path]:
"""Recursively discover image files under ``root``, sorted deterministically."""
root = Path(root)
if not root.exists():
return []
found = [p for p in root.rglob("*") if p.is_file() and p.suffix.lower() in extensions]
return sorted(found, key=lambda p: p.as_posix().lower())