Spaces:
Sleeping
Sleeping
Download src/data/augmentor.py from Moni38/smartvision-ai: direct link, hf CLI and curl.
- Browser
- Download file 5.56 kB
-
https://huggingface.co/spaces/Moni38/smartvision-ai/resolve/main/src/data/augmentor.py
- Command line
-
hf download hf://spaces/Moni38/smartvision-ai/src/data/augmentor.py
-
curl -L -o augmentor.py https://huggingface.co/spaces/Moni38/smartvision-ai/resolve/main/src/data/augmentor.py
5.56 kB
| # src/data/augmentor.py | |
| # Augmentation pipelines for SmartVision CNN training. | |
| # | |
| # Pipeline design principles: | |
| # | |
| # PIL-space first (indices 0-4, before ToImage at index 5): | |
| # Geometric/color transforms on PIL input. PIL handles uint8 arithmetic | |
| # and boundary conditions correctly. Cost is CPU-bound but negligible | |
| # at 224x224 images. | |
| # | |
| # Float tensor space (indices 7-9, after ToDtype at index 6): | |
| # RandomZoomOut requires float32 input with fill values in [0.0, 1.0]. | |
| # fill=IMAGENET_MEAN: after Normalize, padded pixels become exactly 0.0. | |
| # Proof: (IMAGENET_MEAN - IMAGENET_MEAN) / IMAGENET_STD = [0, 0, 0] | |
| # fill=0.0: (0 - mean) / std = [-2.12, -2.04, -1.80] after Normalize (wrong) | |
| # fill=0.5: gives [+0.065, +0.196, +0.418] per channel (wrong, colored border) | |
| # | |
| # RandomPerspective border artifact (accepted tradeoff): | |
| # PIL RandomPerspective default fill=0 -> black border in uint8. | |
| # After full pipeline: (0/255 - mean) / std ~= [-2.12, -2.04, -1.80]. | |
| # At distortion_scale=0.2, p=0.3: border <5% of pixels on 30% of samples. | |
| # Net gradient impact is small. Mitigation if needed: move after ToDtype | |
| # and set fill=IMAGENET_MEAN. | |
| # | |
| # Why RandomGrayscale(p=0.05): | |
| # COCO household items (cup/bowl/bottle) are confused via memorized color | |
| # patterns from specific training images. 5% grayscale forces color-agnostic | |
| # feature learning occasionally without degrading vehicle/animal color signal. | |
| # | |
| # Why RandomPerspective (not RandomErasing): | |
| # COCO ground-truth boxes reflect real camera perspective variation. | |
| # Perspective augmentation models this directly. | |
| # RandomErasing destroys local texture patches -- the primary discriminator | |
| # for fine-grained categories. Do not add it. | |
| # | |
| # Round 3 changes from Round 2: | |
| # RandomRotation: 15 -> 20 degrees | |
| # ColorJitter brightness/contrast: 0.2 -> 0.3 | |
| # ColorJitter saturation: 0.1 -> 0.2 | |
| # RandomGrayscale(p=0.05): ADDED (PIL-space) | |
| # RandomPerspective(distortion_scale=0.2, p=0.3): ADDED (PIL-space) | |
| # RandomZoomOut side_range: (1.0, 1.3) -> (1.0, 1.4) | |
| import torch | |
| import torchvision.transforms.v2 as T | |
| IMAGENET_MEAN: list[float] = [0.485, 0.456, 0.406] | |
| IMAGENET_STD: list[float] = [0.229, 0.224, 0.225] | |
| def get_train_transforms(image_size: int = 224) -> T.Compose: | |
| """ | |
| Training augmentation pipeline -- 10 transforms. | |
| Ordered execution (order is load-bearing): | |
| [0] RandomHorizontalFlip(p=0.5) PIL geometric | |
| [1] RandomRotation(degrees=20) PIL geometric | |
| [2] ColorJitter(0.3, 0.3, 0.2, 0.05) PIL color | |
| [3] RandomGrayscale(p=0.05) PIL color NEW Round 3 | |
| [4] RandomPerspective(scale=0.2, p=0.3) PIL geometric NEW Round 3 | |
| [5] ToImage() PIL -> uint8 CHW tensor | |
| [6] ToDtype(torch.float32, scale=True) uint8 -> float32 [0, 1] | |
| [7] RandomZoomOut(fill=IMAGENET_MEAN, ...) float32 tensor | |
| [8] Resize((image_size, image_size)) | |
| [9] Normalize(IMAGENET_MEAN, IMAGENET_STD) | |
| Hard ordering constraints: | |
| [3], [4] < [5]: PIL ops must run before ToImage conversion | |
| [6] < [7]: ZoomOut fill is float32 [0, 1] space | |
| [9] = last: Normalize must be final transform | |
| """ | |
| return T.Compose( | |
| [ | |
| # PIL-space geometric | |
| T.RandomHorizontalFlip(p=0.5), | |
| T.RandomRotation(degrees=20), | |
| # PIL-space color | |
| T.ColorJitter( | |
| brightness=0.3, | |
| contrast=0.3, | |
| saturation=0.2, | |
| hue=0.05, | |
| ), | |
| T.RandomGrayscale(p=0.05), | |
| # PIL-space geometric (perspective) | |
| # fill=0 default: black border in uint8 -> ~[-2.12,-2.04,-1.80] after Normalize. | |
| # Accepted: borders are small (<5% of pixels) and infrequent (p=0.3). | |
| T.RandomPerspective(distortion_scale=0.2, p=0.3), | |
| # Convert to float32 tensor | |
| T.ToImage(), | |
| T.ToDtype(torch.float32, scale=True), | |
| # Float32 tensor augmentation | |
| # fill=IMAGENET_MEAN: padded pixels normalize to 0.0 per channel. | |
| T.RandomZoomOut( | |
| fill=IMAGENET_MEAN, | |
| side_range=(1.0, 1.4), | |
| p=0.3, | |
| ), | |
| # Finalize (single resize after all spatial augmentation) | |
| T.Resize((image_size, image_size), antialias=True), | |
| # Normalize last | |
| T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD), | |
| ] | |
| ) | |
| def get_eval_transforms(image_size: int = 224) -> T.Compose: | |
| """ | |
| Evaluation/test pipeline -- fully deterministic. | |
| Matches inference-time preprocessing exactly. | |
| Contains zero stochastic transforms. | |
| """ | |
| return T.Compose( | |
| [ | |
| T.ToImage(), | |
| T.ToDtype(torch.float32, scale=True), | |
| T.Resize((image_size, image_size), antialias=True), | |
| T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD), | |
| ] | |
| ) | |
| def denormalize(tensor: torch.Tensor) -> "np.ndarray": # noqa: F821 | |
| """Reverse ImageNet normalization. Returns HWC uint8 numpy array [0, 255].""" | |
| import numpy as np | |
| mean = torch.tensor(IMAGENET_MEAN, dtype=torch.float32).view(3, 1, 1) | |
| std = torch.tensor(IMAGENET_STD, dtype=torch.float32).view(3, 1, 1) | |
| img = tensor.clone().cpu() * std + mean | |
| return (img.clamp(0.0, 1.0).permute(1, 2, 0).numpy() * 255).astype(np.uint8) | |