Backend pipelines¶
Compose accepts registered transform objects from Kornia, TorchVision, and Albumentations, but it does not inherit every native container's input or numerical contract. The common safe interface is a batch-first BCHW torch tensor.
Native builder¶
Use from_params when you need rotation, scale, shear, translation, horizontal or vertical flips, brightness, or contrast without an optional augmentation backend:
from fuse_augmentations import Compose, ReorderPolicy
augment = Compose.from_params(
rotation=(-20.0, 20.0),
shear_x=(-5.0, 5.0),
brightness=0.1,
reorder=ReorderPolicy.NONE,
)
PyTorch is still mandatory; “native” means independent of an augmentation backend, not framework-free.
Kornia¶
import kornia.augmentation as K
import torch
from fuse_augmentations import Compose, ReorderPolicy
augment = Compose(
[
K.RandomRotation(degrees=15.0, p=0.8),
K.RandomAffine(degrees=0.0, scale=(0.9, 1.1), p=0.7),
K.RandomHorizontalFlip(p=0.5),
],
reorder=ReorderPolicy.NONE,
)
output = augment(torch.rand(8, 3, 224, 224))
Kornia has the broadest declarative capability matrix in the current package. Registered transforms still use package-level segment interpolation and padding semantics.
TorchVision¶
import torch
import torchvision.transforms.v2 as T
from fuse_augmentations import Compose
augment = Compose(
[
T.RandomRotation(degrees=15.0),
T.RandomAffine(degrees=0.0, scale=(0.9, 1.1)),
T.RandomHorizontalFlip(p=0.5),
],
)
output = augment(torch.rand(8, 3, 224, 224))
The fused path requires BCHW input. A native TorchVision Compose may accept CHW tensors or PIL images; this package does not generally do so. RandomRotation(expand=True) is explicitly unsupported, and center/fill/interpolation behavior is not pixel-equivalent to native TorchVision.
Albumentations¶
The cross-backend tensor path accepts BCHW float tensors:
import albumentations as A
import torch
from fuse_augmentations import Compose
augment = Compose(
[A.Rotate(limit=15, p=0.8), A.HorizontalFlip(p=0.5)],
execution="cv2",
)
output = augment(torch.rand(4, 3, 224, 224))
execution="cv2" is the default CPU path. execution="torch" applies registered geometry through a batched torch sampling grid and can remain on a torch device, but its border and subpixel numerics differ from OpenCV. An opaque CPU-only Albumentations passthrough still transfers an accelerator tensor to the host and back; execution="torch" keeps the fused warp on-device but cannot make that native operation device-native. execution="auto" resolves per call — host data to cv2, accelerator data to torch — and pipeline.resolved_execution reports which one ran; it trades cross-environment bit-reproducibility for that convenience, so Reproducibility covers when it is the wrong choice.
An HWC NumPy path also exists. Without data_keys it transforms the image only:
import numpy as np
image = np.zeros((224, 224, 3), dtype=np.uint8)
result = augment(image=image)
assert result["image"].shape == image.shape
Declare data_keys and the same call carries masks, boxes, keypoints and rotated boxes:
with_boxes = Compose(
[A.Affine(rotate=(-10.0, 10.0), p=1.0)],
data_keys=["input", "bbox_xyxy"],
)
out = with_boxes(image=image, bboxes=np.zeros((4, 4), dtype=np.float32))
assert out["image"].dtype == np.uint8
A NumPy image comes back in the dtype it was passed in, as Albumentations returns it. Routed NumPy masks are labels rather than image intensities: their dtype and values are preserved through the round trip, and they are never normalized to [0, 1]; nearest sampling is the hard-label default. Labels and Albumentations' own processor behaviour are still not replicated — filtering instances after a warp is the caller's, and Auxiliary targets covers the helpers for it. Tensor input is a separate contract: images stay float32 [0, 1] throughout, while native Albumentations transforms that expect uint8 ranges can behave incorrectly on image tensors.
Mixed backends¶
import kornia.augmentation as K
import torchvision.transforms.v2 as T
from fuse_augmentations import Compose
augment = Compose(
[
T.RandomRotation(10.0),
K.RandomHorizontalFlip(p=0.5),
K.RandomBrightness(brightness=(0.9, 1.1), p=1.0),
],
)
Each transform is routed to its registered adapter. A backend change is a hard segment boundary: matrices from different backends are not combined into one warp. Inspect fusion_plan_descriptors to make the cost visible.
Passthrough warnings are contracts¶
An unregistered transform becomes a passthrough barrier when the adapter can safely call it. That does not mean the operation is transparent or target-safe.
Danger
With data_keys, an unknown or unclassified spatial transform is refused before any segment executes, so it cannot silently leave auxiliary targets stale. Image-only calls may still execute native passthrough; replace the transform with a registered operation or route targets through a native target-aware pipeline when you need explicit semantics.