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

letterbox_demo

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=114 is the YOLO convention).
  • position controls 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 sizeChannelsBeforeAfterSpeedup
256 × 25610.61 ms0.17 ms3.6 ×
256 × 25631.60 ms0.36 ms4.4 ×
256 × 25694.73 ms0.98 ms4.8 ×
512 × 51211.92 ms0.35 ms5.5 ×
512 × 51235.45 ms0.63 ms8.6 ×
512 × 512916.75 ms1.64 ms10.2 ×
1024 × 102417.42 ms1.05 ms7.1 ×
1024 × 1024321.82 ms1.72 ms12.7 ×
1024 × 1024965.70 ms4.64 ms14.2 ×

Normalize(normalization="image_per_channel") — per-channel normalization

Image sizeChannelsBeforeAfterSpeedup
256 × 25610.57 ms0.17 ms3.4 ×
256 × 25637.81 ms1.00 ms7.8 ×
512 × 51211.95 ms0.39 ms5.1 ×
512 × 512330.84 ms3.33 ms9.3 ×
1024 × 102417.34 ms0.87 ms8.5 ×
1024 × 10243123.37 ms12.30 ms10.0 ×

RandomBrightnessContrast(brightness_by_max=False) — mean-based brightness/contrast

Uses mean and multiply_add from albucore:

Image sizeChannelsBeforeAfterSpeedup
256 × 25610.21 ms0.12 ms1.8 ×
256 × 25630.46 ms0.22 ms2.1 ×
256 × 25691.40 ms0.47 ms3.0 ×
512 × 51231.88 ms0.60 ms3.1 ×
512 × 51295.45 ms1.63 ms3.3 ×
1024 × 102437.09 ms1.79 ms4.0 ×
1024 × 1024921.63 ms4.63 ms4.7 ×

Halftone — mean-based luminance computation per cell

Image sizeChannelsBeforeAfterSpeedup
256 × 256150.9 ms37.2 ms1.37 ×
256 × 256367.5 ms60.8 ms1.11 ×
512 × 5121206 ms155 ms1.33 ×
512 × 5123269 ms247 ms1.09 ×
1024 × 10241820 ms607 ms1.35 ×
1024 × 102431073 ms980 ms1.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:

ModuleContents
pixel/weather.pyRandomSnow, RandomGravel, RandomRain, RandomFog, RandomSunFlare, RandomShadow, Spatter, AtmosphericFog
pixel/channel.pyChannelShuffle (+ new channel_order param), ChannelSwap
pixel/noise.pyGaussNoise, ISONoise, MultiplicativeNoise, ShotNoise, AdditiveNoise, SaltAndPepper, FilmGrain
pixel/color.py23 color / tone / brightness transforms
pixel/compression.pyImageCompression, Downscale
pixel/transforms.pyNormalize, 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