AlbumentationsX 2.1.1
Pipeline replay: save exact sampled params and reproduce any augmentation
Every transform now tracks the exact scalar values that were actually used in a run — not the ranges, but the concrete numbers sampled from them. Enable with save_applied_params=True on Compose, then replay or log them:
import albumentations as A
import numpy as np
transform = A.Compose([
A.RandomBrightnessContrast(brightness_limit=(-0.3, 0.3), p=1),
A.GaussNoise(std_range=(0.1, 0.4), p=0.9),
A.HorizontalFlip(p=0.5),
], save_applied_params=True)
result = transform(image=image)
# Which transforms ran, and with what exact values?
print(result["applied_transforms"])
# [
# ("RandomBrightnessContrast",
# {"brightness_limit": 0.21, "contrast_limit": -0.08, ...}),
# ("GaussNoise",
# {"std_range": 0.27, "mean_range": 0.0, ...}),
# ]
# Reconstruct a deterministic p=1.0 pipeline that reproduces the same effect:
replay = A.Compose.from_applied_transforms(result["applied_transforms"])
result2 = replay(image=image) # same pixel output for constructor-level randomness
What applied_config contains: all constructor parameters merged with the concrete sampled overrides — suitable for logging, debugging, or writing to a database alongside training annotations.
Scope: from_applied_transforms fixes constructor-level randomness (brightness factor, noise sigma, etc.). Transforms with internal spatial randomness (random crop offsets, dropout mask positions) may still vary — those depend on RNG state beyond the constructor params.
New transform: LetterBox
Scale-to-fit with padding — the standard YOLO preprocessing step, equivalent to LongestMaxSize + PadIfNeeded in a single transform.
transform = A.Compose([
A.LetterBox(size=(480, 640), fill=114, fill_mask=0, p=1.0)
], bbox_params=A.BboxParams(coord_format="pascal_voc", label_fields=["labels"]))
# Wide image (800×600) → fits width, pads top/bottom
result = transform(image=image, mask=mask, bboxes=bboxes, labels=labels)
# result["image"].shape == (480, 640, 3)
# Tall image (600×800) → fits height, pads left/right
result2 = transform(image=tall_image, mask=tall_mask, bboxes=tall_bboxes, labels=labels)
# result2["image"].shape == (480, 640, 3)
- Preserves aspect ratio; pads the short side with a constant color (
fill=114is the YOLO convention). positioncontrols where the resized image is placed on the canvas:"center"(default),"top_left","top_right","bottom_left","bottom_right", or"random".- Full target support: images, masks, HBB and OBB bounding boxes, keypoints, volumes.
Performance: albucore 0.1.2 speeds up mean/std-based normalization and brightness/contrast
albucore 0.1.2 replaces numpy.mean / numpy.std calls in the hot path with optimized C++ reductions (albucore.mean, albucore.std), and adds faster implementations of add_weighted, multiply_by_constant, add_vector, normalize, power, and multiply_by_vector.
The impact on real transforms (measured against albucore 0.0.41 on identical hardware):
Normalize(normalization="image") — global-mean normalization
| Image size | Channels | Before | After | Speedup |
|---|---|---|---|---|
| 256 × 256 | 1 | 0.61 ms | 0.17 ms | 3.6 × |
| 256 × 256 | 3 | 1.60 ms | 0.36 ms | 4.4 × |
| 256 × 256 | 9 | 4.73 ms | 0.98 ms | 4.8 × |
| 512 × 512 | 1 | 1.92 ms | 0.35 ms | 5.5 × |
| 512 × 512 | 3 | 5.45 ms | 0.63 ms | 8.6 × |
| 512 × 512 | 9 | 16.75 ms | 1.64 ms | 10.2 × |
| 1024 × 1024 | 1 | 7.42 ms | 1.05 ms | 7.1 × |
| 1024 × 1024 | 3 | 21.82 ms | 1.72 ms | 12.7 × |
| 1024 × 1024 | 9 | 65.70 ms | 4.64 ms | 14.2 × |
Normalize(normalization="image_per_channel") — per-channel normalization
| Image size | Channels | Before | After | Speedup |
|---|---|---|---|---|
| 256 × 256 | 1 | 0.57 ms | 0.17 ms | 3.4 × |
| 256 × 256 | 3 | 7.81 ms | 1.00 ms | 7.8 × |
| 512 × 512 | 1 | 1.95 ms | 0.39 ms | 5.1 × |
| 512 × 512 | 3 | 30.84 ms | 3.33 ms | 9.3 × |
| 1024 × 1024 | 1 | 7.34 ms | 0.87 ms | 8.5 × |
| 1024 × 1024 | 3 | 123.37 ms | 12.30 ms | 10.0 × |
RandomBrightnessContrast(brightness_by_max=False) — mean-based brightness/contrast
Uses mean and multiply_add from albucore:
| Image size | Channels | Before | After | Speedup |
|---|---|---|---|---|
| 256 × 256 | 1 | 0.21 ms | 0.12 ms | 1.8 × |
| 256 × 256 | 3 | 0.46 ms | 0.22 ms | 2.1 × |
| 256 × 256 | 9 | 1.40 ms | 0.47 ms | 3.0 × |
| 512 × 512 | 3 | 1.88 ms | 0.60 ms | 3.1 × |
| 512 × 512 | 9 | 5.45 ms | 1.63 ms | 3.3 × |
| 1024 × 1024 | 3 | 7.09 ms | 1.79 ms | 4.0 × |
| 1024 × 1024 | 9 | 21.63 ms | 4.63 ms | 4.7 × |
Halftone — mean-based luminance computation per cell
| Image size | Channels | Before | After | Speedup |
|---|---|---|---|---|
| 256 × 256 | 1 | 50.9 ms | 37.2 ms | 1.37 × |
| 256 × 256 | 3 | 67.5 ms | 60.8 ms | 1.11 × |
| 512 × 512 | 1 | 206 ms | 155 ms | 1.33 × |
| 512 × 512 | 3 | 269 ms | 247 ms | 1.09 × |
| 1024 × 1024 | 1 | 820 ms | 607 ms | 1.35 × |
| 1024 × 1024 | 3 | 1073 ms | 980 ms | 1.10 × |
Internal refactoring: pixel module split
The 7 800-line pixel/transforms.py was split into focused modules — no public API change, all names continue to import from albumentations as before:
| Module | Contents |
|---|---|
pixel/weather.py | RandomSnow, RandomGravel, RandomRain, RandomFog, RandomSunFlare, RandomShadow, Spatter, AtmosphericFog |
pixel/channel.py | ChannelShuffle (+ new channel_order param), ChannelSwap |
pixel/noise.py | GaussNoise, ISONoise, MultiplicativeNoise, ShotNoise, AdditiveNoise, SaltAndPepper, FilmGrain |
pixel/color.py | 23 color / tone / brightness transforms |
pixel/compression.py | ImageCompression, Downscale |
pixel/transforms.py | Normalize, InvertImg, Sharpen, Emboss, Superpixels, RingingOvershoot, UnsharpMask, Dithering, Halftone, LensFlare |
ChannelShuffle gains an optional channel_order parameter — pass a tuple to pin a fixed permutation. ChannelSwap is now a thin subclass that requires it.
Upgrade
pip install -U albumentationsx