# ============================================================================================= #
# Author: Pavel Iakubovskii, ZFTurbo, ashawkey, Dominik Müller, #
# Samuel Šuľan, Lucia Hradecká, Filip Lux, Jakub Polonský #
# Copyright: albumentations: : https://github.com/albumentations-team #
# Pavel Iakubovskii : https://github.com/qubvel #
# ZFTurbo : https://github.com/ZFTurbo #
# ashawkey : https://github.com/ashawkey #
# Dominik Müller : https://github.com/muellerdo #
# Lucia Hradecká : lucia.d.hradecka@gmail.com #
# Filip Lux : lux.filip@gmail.com #
# Jakub Polonský #
# #
# Volumentations History: #
# - Original: https://github.com/albumentations-team/albumentations #
# - 3D Conversion: https://github.com/ashawkey/volumentations #
# - Continued Development: https://github.com/ZFTurbo/volumentations #
# - Enhancements: https://github.com/qubvel/volumentations #
# - Further Enhancements: https://github.com/muellerdo/volumentations #
# - Biomedical Enhancements: https://gitlab.fi.muni.cz/cbia/bio-volumentations #
# #
# MIT License. #
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy #
# of this software and associated documentation files (the "Software"), to deal #
# in the Software without restriction, including without limitation the rights #
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
# copies of the Software, and to permit persons to whom the Software is #
# furnished to do so, subject to the following conditions: #
# #
# The above copyright notice and this permission notice shall be included in all #
# copies or substantial portions of the Software. #
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
# SOFTWARE. #
# ============================================================================================= #
from warnings import warn
import numpy as np
from ..random_utils import random
from ..core.transforms_interface import DualTransform
from ..conversion import functional as FCT
from ..augmentations.utils import get_spatial_shape_from_image
from ..augmentations import functional_bbox as FB
[docs]
class Contiguous(DualTransform):
"""Transform the image data to a contiguous array.
Args:
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
image, mask, float mask
"""
def __init__(self, always_apply: bool = True, p: float = 1.0):
super().__init__(always_apply, p)
[docs]
def apply(self, image, **params):
return np.ascontiguousarray(image)
[docs]
def apply_to_mask(self, mask, **params):
return np.ascontiguousarray(mask)
def __repr__(self):
return f'Contiguous(always_apply={self.always_apply}, p={self.p})'
[docs]
class StandardizeDatatype(DualTransform):
"""Change image and float_mask datatype to ``np.float32`` without changing the intensities.
Change mask datatype to ``np.int32``.
Args:
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
image, mask, float mask
"""
def __init__(self, always_apply: bool = True, p: float = 1.0):
super().__init__(always_apply, p)
[docs]
def apply(self, image, **params):
return image.astype(np.float32)
[docs]
def apply_to_mask(self, mask, **params):
return mask.astype(np.int32)
[docs]
def apply_to_float_mask(self, mask, **params):
return mask.astype(np.float32)
def __repr__(self):
return f'StandardizeDatatype(always_apply={self.always_apply}, p={self.p})'
[docs]
class KeypointsFixDatatype(DualTransform):
"""Convert any 2D array-like format of keypoints into a list of tuples containing ints of floats.
The input can be a 2d numpy array, a list of lists, a list of tuples, etc., and individual coordinate values
can be of type int, float, or any numpy dtype.
Args:
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
key points
"""
def __init__(self, always_apply: bool = True, p: float = 1):
super().__init__(always_apply, p)
[docs]
def apply(self, volume, **params):
return volume
[docs]
def apply_to_keypoints(self, keypoints, keep_all=False, **params):
keypoints_ll = np.array(keypoints).tolist()
return [tuple(keypoint) for keypoint in keypoints_ll]
def __repr__(self):
return f'KeypointsFixDatatype({self.always_apply}, {self.p})'
[docs]
class NoConversion(DualTransform):
"""An identity transform.
Args:
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
image, mask
"""
def __init__(self, always_apply: bool = True, p: float = 1):
super().__init__(always_apply, p)
[docs]
def apply(self, volume, **params):
return volume
[docs]
def apply_to_mask(self, mask, **params):
return mask
[docs]
def apply_to_float_mask(self, mask, **params):
return mask
def __repr__(self):
return f'NoConversion({self.always_apply}, {self.p})'
[docs]
class ConvertToBBoxes(DualTransform):
"""Converts bounding boxes from 'raw data' to objects of BoundingBox class.
Args:
bbox_format (str): Format the bounding boxes are supplied in.
Supported formats: 'voc', 'coco', 'yolo', 'albumentations'.
Defaults to `'voc'`.
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
bounding_box
"""
def __init__(self, bbox_format: str = 'voc', always_apply: bool = True, p: float = 1.0):
super().__init__(always_apply, p)
self.bbox_format = bbox_format
[docs]
def apply(self, volume, **params):
return volume
[docs]
def apply_to_bboxes(self, bboxes, **params):
match (self.bbox_format):
case 'voc':
return [FB.BoundingBox(
min_point=np.asarray(bbox[0], float),
max_point=np.asarray(bbox[1], float),
time_point=bbox[2],
class_label=bbox[3] if len(bbox) == 4 else None,
bbox_format=self.bbox_format)
for bbox in bboxes]
case 'coco':
return [FB.BoundingBox(
min_point=np.asarray(bbox[0]),
max_point=np.asarray(bbox[0]) + np.asarray(bbox[1]),
time_point=bbox[2],
class_label=bbox[3] if len(bbox) == 4 else None,
bbox_format=self.bbox_format)
for bbox in bboxes]
case 'albumentations':
return [FB.BoundingBox(
min_point=np.asarray(bbox[0]) * params['shape'],
max_point=np.asarray(bbox[1]) * params['shape'],
time_point=bbox[2],
class_label=bbox[3] if len(bbox) == 4 else None,
shape=params['shape'],
bbox_format=self.bbox_format)
for bbox in bboxes]
case 'yolo':
return [FB.BoundingBox(
min_point=np.asarray([
(bbox[0][0] - (bbox[1][0] / 2)) * params['shape'][0],
(bbox[0][1] - (bbox[1][1] / 2)) * params['shape'][1],
(bbox[0][2] - (bbox[1][2] / 2)) * params['shape'][2]]),
max_point=np.asarray([
(bbox[0][0] + (bbox[1][0] / 2)) * params['shape'][0],
(bbox[0][1] + (bbox[1][1] / 2)) * params['shape'][1],
(bbox[0][2] + (bbox[1][2] / 2)) * params['shape'][2]]),
time_point=bbox[2],
class_label=bbox[3] if len(bbox) == 4 else None,
shape=params['shape'],
bbox_format=self.bbox_format)
for bbox in bboxes]
[docs]
def get_params(self, targets, **data):
shape = get_spatial_shape_from_image(data, targets)
return {
'shape': shape,
}
def __repr__(self):
return f'ConvertToBBoxes({self.bbox_format}, {self.always_apply}, {self.p})'
[docs]
class ConvertFromBBoxes(DualTransform):
"""Converts bounding boxes back to the 'raw data' format specified previously from the internal BoundingBox classes.
Args:
always_apply (bool, optional): Always apply this transformation in composition.
Defaults to ``True``.
p (float, optional): Probability of applying this transformation in composition.
Defaults to ``1``.
Targets:
bounding_box
"""
def __init__(self, always_apply: bool = True, p: float = 1.0):
super().__init__(always_apply, p)
[docs]
def apply(self, volume, **params):
return volume
[docs]
def apply_to_bboxes(self, bboxes, **params):
return [self.convert_back(bbox) for bbox in bboxes]
[docs]
def convert_back(self, bbox):
res = []
match bbox.bbox_format:
case 'voc':
res = [tuple(bbox.min.tolist()), tuple(bbox.max.tolist())]
case 'coco':
res = [tuple(bbox.min.tolist()), tuple((bbox.max - bbox.min).tolist())]
case 'albumentations':
res = [tuple((bbox.min / bbox.shape).tolist()), tuple((bbox.max / bbox.shape).tolist())]
case 'yolo':
center = (bbox.min + bbox.max) / 2
res = [tuple((center / bbox.shape).tolist()), tuple(((bbox.max - bbox.min) / bbox.shape).tolist())]
res.append(bbox.time_point)
if bbox.class_label is not None:
res.append(bbox.class_label)
return res
def __repr__(self):
return f'ConvertFromBBoxes({self.always_apply}, {self.p})'