File size: 2,401 Bytes
f2ec79c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Image helpers: loading, resizing and cheap (non-AI) augmentation."""

from __future__ import annotations

import hashlib
import random
import re
from pathlib import Path
from typing import List

from PIL import Image, ImageEnhance, ImageOps


def slugify(text: str, max_len: int = 80) -> str:
    text = str(text).strip().lower()
    text = re.sub(r"[^a-z0-9]+", "_", text)
    text = re.sub(r"_+", "_", text).strip("_")
    return (text or "item")[:max_len]


def stable_hash(text: str, length: int = 12) -> str:
    return hashlib.sha1(text.encode("utf-8")).hexdigest()[:length]


def load_rgb(path: str | Path) -> Image.Image:
    return Image.open(path).convert("RGB")


def snap_size(width: int, height: int, longest_side: int) -> tuple[int, int]:
    """Scale so the longest side == longest_side, snapped to multiples of 8."""
    if width >= height:
        new_w = longest_side
        new_h = int(round(longest_side * height / width))
    else:
        new_h = longest_side
        new_w = int(round(longest_side * width / height))
    new_w = max(8, (new_w // 8) * 8)
    new_h = max(8, (new_h // 8) * 8)
    return new_w, new_h


def fit_image(image: Image.Image, longest_side: int) -> Image.Image:
    w, h = image.size
    new_w, new_h = snap_size(w, h, longest_side)
    return image.resize((new_w, new_h), Image.LANCZOS)


def save_jpeg(image: Image.Image, path: Path, quality: int = 92) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    image.convert("RGB").save(path, format="JPEG", quality=quality)


def plain_augment(image: Image.Image, rng: random.Random) -> Image.Image:
    """Light, label-preserving augmentation using only Pillow."""
    out = image
    if rng.random() < 0.5:
        out = ImageOps.mirror(out)
    # Brightness / contrast / colour jitter.
    out = ImageEnhance.Brightness(out).enhance(rng.uniform(0.85, 1.15))
    out = ImageEnhance.Contrast(out).enhance(rng.uniform(0.9, 1.1))
    out = ImageEnhance.Color(out).enhance(rng.uniform(0.9, 1.1))
    # Small rotation with edge-replicating fill.
    angle = rng.uniform(-6.0, 6.0)
    if abs(angle) > 0.5:
        out = out.rotate(angle, resample=Image.BICUBIC, expand=False)
    return out


def read_images_from_dir(directory: str | Path) -> List[Path]:
    exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
    return sorted(p for p in Path(directory).iterdir() if p.suffix.lower() in exts)