Hi,
The custom DefocusBlur in this fourth-place HuBMAP solution implements the transform that later became the built-in Defocus. This class made sense when it was added in 2021: its docstring links the still-open Albumentations feature tracker at that time. AlbumentationsX 2.3.5 now provides the same disk kernels directly, so the custom class and its three-channel loop can be removed. I am glad Albumentations was useful in this solution.
The current helper defines the disk kernel, converts the image to floating point, filters the three RGB channels separately, and then places the custom transform in OneOf:
def disk(radius, alias_blur=0.1, dtype=np.float32):
"""
From https://github.com/hendrycks/robustness/blob/master/ImageNet-C/create_c/make_imagenet_c.py
and https://github.com/albumentations-team/albumentations/issues/477
"""
if radius <= 8:
L = np.arange(-8, 8 + 1)
ksize = (3, 3)
else:
L = np.arange(-radius, radius + 1)
ksize = (5, 5)
X, Y = np.meshgrid(L, L)
aliased_disk = np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=dtype)
aliased_disk /= np.sum(aliased_disk)
# supersample disk to antialias
return cv2.GaussianBlur(aliased_disk, ksize=ksize, sigmaX=alias_blur)
class DefocusBlur(ImageOnlyTransform):
"""
From https://github.com/hendrycks/robustness/blob/master/ImageNet-C/create_c/make_imagenet_c.py
and https://github.com/albumentations-team/albumentations/issues/477
"""
def __init__(
self,
severity=1,
always_apply=False,
p=1.0,
):
super(DefocusBlur, self).__init__(always_apply, p)
self.severity = severity
self.radius, self.blur = [(3, 0.1), (4, 0.5), (6, 0.5), (8, 0.5), (10, 0.5)][
self.severity - 1
]
def apply(self, image, **params):
image = np.array(image) / 255.0
kernel = disk(radius=self.radius, alias_blur=self.blur)
channels = []
for d in range(3):
channels.append(cv2.filter2D(image[:, :, d], -1, kernel))
channels = np.array(channels).transpose((1, 2, 0))
return np.clip(channels, 0, 1) * 255
def get_transform_init_args_names(self):
return "severty"
def blur_transforms(p=0.5, blur_limit=5, gaussian_limit=(5, 7), severity=1):
return albu.OneOf(
[
DefocusBlur(severity=severity, always_apply=True),
albu.MotionBlur(blur_limit=blur_limit, always_apply=True),
albu.GaussianBlur(blur_limit=gaussian_limit, always_apply=True),
],
p=p,
)
This helper is part of the checked-in training path. HE_preprocess includes it after the geometric and color transforms:
if augment:
return albu.Compose(
[
albu.VerticalFlip(p=0.5),
albu.HorizontalFlip(p=0.5),
albu.ShiftScaleRotate(
scale_limit=0.1,
shift_limit=0.1,
rotate_limit=90,
p=0.5,
),
deformation_transform(p=0.5),
color_transforms(p=0.5),
blur_transforms(p=0.5),
normalizer,
]
)
The fold trainer passes that pipeline into the in-memory dataset:
in_mem_dataset = InMemoryTrainDataset(
train_img_names,
df_rle,
train_tile_size=config.tile_size,
reduce_factor=config.reduce_factor,
train_transfo=HE_preprocess(size=config.tile_size),
valid_transfo=HE_preprocess(augment=False, size=config.tile_size),
# ...
)
The dataset then applies it to each image and segmentation mask:
if self.transforms:
augmented = self.transforms(image=img, mask=mask)
img = augmented["image"]
mask = augmented["mask"]
With the 2.3.5 argument names, the complete replacement for blur_transforms is:
DEFOCUS_BY_SEVERITY = [
(3, 0.1),
(4, 0.5),
(6, 0.5),
(8, 0.5),
(10, 0.5),
]
def blur_transforms(p=0.5, blur_limit=5, gaussian_limit=(5, 7), severity=1):
radius, alias_blur = DEFOCUS_BY_SEVERITY[severity - 1]
return albu.OneOf(
[
albu.Defocus(
radius_range=(radius, radius),
alias_blur_range=(alias_blur, alias_blur),
p=1,
),
albu.MotionBlur(
blur_range=(3, blur_limit),
p=1,
),
albu.GaussianBlur(
blur_range=gaussian_limit,
p=1,
),
],
p=p,
)
The fixed one-value ranges preserve all five existing severity settings without introducing new parameter sampling. The three children still have equal weight inside OneOf, and the outer p keeps the existing probability.
I compared the custom disk() function with the published albumentationsx==2.3.5 implementation for (radius, alias_blur) equal to (3, 0.1), (4, 0.5), (6, 0.5), (8, 0.5), and (10, 0.5). All five kernels were exactly equal element by element. On a deterministic RGB uint8 image, the built-in output differed from the custom output by less than 0.5 on the 0..255 intensity scale because the built-in transform preserves uint8 and rounds the convolution result, while the custom transform returns floating-point values. The replacement preserved shape and dtype for every severity.
The repository does not pin an Albumentations version—the requirements section is still a TODO—so I did not open a dependency-change PR. This helper is an isolated 2.3.5 replacement; a complete migration also needs the old argument names in the rest of transforms.py updated and the full image/mask training path tested. AlbumentationsX 2.3.5 requires Python 3.10 or newer.
pip uninstall albumentations
pip install -U albumentationsx
The Python module name remains albumentations, so the existing import albumentations as albu import keeps the same module path.
The packages use different licenses: the legacy albumentations package used by this repository is MIT, while albumentationsx==2.3.5 is AGPL-3.0-only. The license guide explains the terms. Albumentations, LLC also offers commercial licenses with alternative terms.
If you have feedback, complaints, or proposals for AlbumentationsX, please open an issue. I read the tracker every day.
If this note is useful, a star or sponsorship would mean a lot.
Hi,
The custom
DefocusBlurin this fourth-place HuBMAP solution implements the transform that later became the built-inDefocus. This class made sense when it was added in 2021: its docstring links the still-open Albumentations feature tracker at that time. AlbumentationsX 2.3.5 now provides the same disk kernels directly, so the custom class and its three-channel loop can be removed. I am glad Albumentations was useful in this solution.The current helper defines the disk kernel, converts the image to floating point, filters the three RGB channels separately, and then places the custom transform in
OneOf:This helper is part of the checked-in training path.
HE_preprocessincludes it after the geometric and color transforms:The fold trainer passes that pipeline into the in-memory dataset:
The dataset then applies it to each image and segmentation mask:
With the 2.3.5 argument names, the complete replacement for
blur_transformsis:The fixed one-value ranges preserve all five existing severity settings without introducing new parameter sampling. The three children still have equal weight inside
OneOf, and the outerpkeeps the existing probability.I compared the custom
disk()function with the publishedalbumentationsx==2.3.5implementation for(radius, alias_blur)equal to(3, 0.1),(4, 0.5),(6, 0.5),(8, 0.5), and(10, 0.5). All five kernels were exactly equal element by element. On a deterministic RGBuint8image, the built-in output differed from the custom output by less than0.5on the0..255intensity scale because the built-in transform preservesuint8and rounds the convolution result, while the custom transform returns floating-point values. The replacement preserved shape and dtype for every severity.The repository does not pin an Albumentations version—the requirements section is still a TODO—so I did not open a dependency-change PR. This helper is an isolated 2.3.5 replacement; a complete migration also needs the old argument names in the rest of
transforms.pyupdated and the full image/mask training path tested. AlbumentationsX 2.3.5 requires Python 3.10 or newer.The Python module name remains
albumentations, so the existingimport albumentations as albuimport keeps the same module path.The packages use different licenses: the legacy
albumentationspackage used by this repository is MIT, whilealbumentationsx==2.3.5isAGPL-3.0-only. The license guide explains the terms. Albumentations, LLC also offers commercial licenses with alternative terms.If you have feedback, complaints, or proposals for AlbumentationsX, please open an issue. I read the tracker every day.
If this note is useful, a star or sponsorship would mean a lot.