Source code for dicaugment.augmentations.geometric.functional

import math
from typing import List, Optional, Sequence, Tuple, Union

import cv2
import numpy as np
# import skimage.transform
import scipy.ndimage as ndimage
from functools import reduce

from dicaugment.augmentations.utils import (
    _maybe_process_in_chunks,
    _maybe_process_by_channel,
    angle_2pi_range,
    clipped,
    preserve_channel_dim,
    preserve_shape,
    SCIPY_MODE_TO_NUMPY_MODE
)

from ... import random_utils
from ...core.bbox_utils import denormalize_bbox, normalize_bbox
from ...core.transforms_interface import (
    BoxInternalType,
    FillValueType,
    ImageColorType,
    KeypointInternalType,
    DicomType,
    INTER_NEAREST,
    INTER_LINEAR,
    INTER_QUADRATIC,
    INTER_CUBIC,
    INTER_QUARTIC,
    INTER_QUINTIC
)

__all__ = [
    #"optical_distortion",
    #"elastic_transform_approx",
    #"grid_distortion",
    "pad",
    "pad_with_params",
    "bbox_rot90",
    "keypoint_rot90",
    "rotate",
    "bbox_rotate",
    "keypoint_rotate",
    "shift_scale_rotate",
    "keypoint_shift_scale_rotate",
    "bbox_shift_scale_rotate",
    #"elastic_transform",
    "resize",
    "scale",
    "keypoint_scale",
    "py3round",
    "_func_max_size",
    "longest_max_size",
    "smallest_max_size",
    #"perspective",
    #"perspective_bbox",
    #"rotation2DMatrixToEulerAngles",
    #"perspective_keypoint",
    #"_is_identity_matrix",
    #"warp_affine",
    #"keypoint_affine",
    #"bbox_affine",
    #"safe_rotate",
    #"bbox_safe_rotate",
    #"keypoint_safe_rotate",
    #"piecewise_affine",
    #"to_distance_maps",
    #"from_distance_maps",
    #"keypoint_piecewise_affine",
    #"bbox_piecewise_affine",
    "bbox_flip",
    "bbox_hflip",
    "bbox_transpose",
    "bbox_vflip",
    "bbox_zflip",
    "hflip",
    "vflip",
    "zflip",
    #"hflip_cv2",
    "transpose",
    "keypoint_flip",
    "keypoint_hflip",
    "keypoint_transpose",
    "keypoint_vflip",
    "keypoint_zflip",
]



[docs]def bbox_rot90(bbox: BoxInternalType, factor: int, axes: str, rows: int, cols: int, slices:int) -> BoxInternalType: # skipcq: PYL-W0613 """Rotates a bounding box by 90 degrees in the direction dicated by a right-handed coordinate system. i.e. from a top-level view of the xy plane: Rotation around the z-axis: counterclockwise rotation Rotation around the y-axis: left to right rotation Rotation around the x-axis: bottom to top rotation Args: bbox: A bounding box tuple (x_min, y_min, z_min, x_max, y_max, z_max). factor: Number of CCW rotations. Must be in set {0, 1, 2, 3} See np.rot90. axes: The axes that define the axis of rotation. Must be in {'xy','yz','xz'} rows: Image rows. cols: Image cols. slices: Image depth. Returns: tuple: A bounding box tuple (x_min, y_min, z_min, x_max, y_max, z_max). """ if factor not in {0, 1, 2, 3}: raise ValueError("Parameter n must be in set {0, 1, 2, 3}") if axes not in {"xy", "yz", "xz"}: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}") x_min, y_min, z_min, x_max, y_max, z_max = bbox[:6] if axes == 'xy': if factor == 1: bbox = y_min, 1 - x_max, z_min, y_max, 1 - x_min, z_max elif factor == 2: bbox = 1 - x_max, 1 - y_max, z_min, 1 - x_min, 1 - y_min, z_max elif factor == 3: bbox = 1 - y_max, x_min, z_min, 1 - y_min, x_max, z_max elif axes == 'xz': if factor == 1: bbox = 1 - z_max, y_min, x_min, 1 - z_min, y_max, z_min elif factor == 2: bbox = 1 - x_max, y_min, 1 - z_max, 1 - x_min, y_max, 1- z_min elif factor == 3: bbox = z_min, y_min, 1 - x_min, z_max, y_max, 1 - x_max elif axes == 'yz': if factor == 1: bbox = x_min, 1- z_max, y_min, x_max, 1 - z_min, y_max elif factor == 2: bbox = x_min, 1 - y_max, 1- z_max, x_max, 1 - y_min, 1 - z_min elif factor == 3: bbox = x_min, z_min, 1 - y_max, x_max, z_max, 1 - y_min return bbox
[docs]@angle_2pi_range def keypoint_rot90(keypoint: KeypointInternalType, factor: int, axes: str, rows: int, cols: int, slices: int, **params) -> KeypointInternalType: """Rotates a bounding box by 90 degrees in the direction dicated by a right-handed coordinate system. i.e. from a top-level view of the xy plane; * Rotation around the z-axis; counterclockwise rotation * Rotation around the y-axis; left to right rotation * Rotation around the x-axis; bottom to top rotation Args: keypoint: A keypoint `(x, y, z, angle, scale)`. factor: Number of CCW rotations. Must be in range [0;3] See np.rot90. axes: The axes that define the axis of rotation. Must be in {'xy','yz','xz'} rows: Image height. cols: Image width. Returns: tuple: A keypoint `(x, y, z, angle, scale)`. Raises: ValueError: if factor not in set {0, 1, 2, 3} """ x, y, z, angle, scale = keypoint[:5] if factor not in {0, 1, 2, 3}: raise ValueError("Parameter n must be in set {0, 1, 2, 3}") if axes not in {"xy", "yz", "xz"}: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}") if axes == 'xy': if factor == 1: x, y, z, angle = y, (cols - 1) - x, z, angle - math.pi / 2 elif factor == 2: x, y, z, angle = (cols - 1) - x, (rows - 1) - y, z, angle - math.pi elif factor == 3: x, y, z, angle = (rows - 1) - y, x, z, angle + math.pi / 2 if axes == 'xz': if factor == 1: x, y, z, angle = (slices - 1) - z, y, x, angle elif factor == 2: x, y, z, angle = (cols - 1) - x, y, (slices - 1) - z, angle elif factor == 3: x, y, z, angle = z, y, (cols - 1) - x, angle if axes == 'yz': if factor == 1: x, y, z, angle = x , (slices - 1) - z, y, angle elif factor == 2: x, y, z, angle = x, (rows - 1) - y, (slices - 1) - z, angle elif factor == 3: x, y, z, angle = x, z, (rows - 1) - y, angle return x, y, z, angle, scale
def _get_new_image_shape(rows, cols, slices, rot_mat, scale_x = 1, scale_y = 1, scale_z = 1): """ Finds the bounding box of the image transformation and provides the new shape that encapsulates the entire image """ rows /= 2 cols /= 2 slices /= 2 arr = np.array( [ [-rows, -cols, -slices], [-rows, -cols, slices], [-rows, cols, -slices], [-rows, cols, slices], [ rows, -cols, -slices], [ rows, -cols, slices], [ rows, cols, -slices], [ rows, cols, slices], ]).T arr = np.round(np.matmul(rot_mat, arr)) n_rows = int((np.max(arr[0]) - np.min(arr[0]))*scale_y) n_cols = int((np.max(arr[1]) - np.min(arr[1]))*scale_x) n_slices = int((np.max(arr[2]) - np.min(arr[2]))*scale_z) return n_rows, n_cols, n_slices def _get_image_center(shape): return (np.array(shape) - 1) / 2
[docs]@preserve_channel_dim def rotate( img: np.ndarray, angle: float, axes: str, crop_to_border: bool = False, interpolation: int = INTER_LINEAR, border_mode: int = "constant", value: Union[float,int] = 0, ): """ Rotates an image by angle degrees. Args: img: Target image. angle: Angle of rotation in degrees. axes: The axis of rotation. Must be one of `{'xy', 'xz', 'yz'}`. crop_to_border: If True, then the image is cropped or padded to fit the entire rotation. If False, then original image shape is maintained and some portions of the image may be cropped away. Default: False interpolation: scipy interpolation method (e.g. dicaugment.INTER_NEAREST). border_mode (str): scipy parameter to determine how the input image is extended during convolution to maintain image shape. Must be one of the following: * `reflect` (d c b a | a b c d | d c b a): The input is extended by reflecting about the edge of the last pixel. This mode is also sometimes referred to as half-sample symmetric. * `constant` (k k k k | a b c d | k k k k): The input is extended by filling all values beyond the edge with the same constant value, defined by the cval parameter. * `nearest` (a a a a | a b c d | d d d d): The input is extended by replicating the last pixel. * `mirror` (d c b | a b c d | c b a): The input is extended by reflecting about the center of the last pixel. This mode is also sometimes referred to as whole-sample symmetric. * `wrap` (a b c d | a b c d | a b c d): The input is extended by wrapping around to the opposite edge. Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.median_filter.html Default: `constant` value: The fill value when border_mode = `constant`. Default: 0 Returns: Image """ out_shape = in_shape = img.shape[:3] height, width, depth = out_shape in_center = _get_image_center(in_shape) out_center = _get_image_center(out_shape) angle = np.deg2rad(angle) rotation_matrix = _get_rotation_matrix(angle, axes, dir=-1) if crop_to_border: out_shape = _get_new_image_shape(height, width, depth, rotation_matrix) out_center = _get_image_center(out_shape) matrix = np.linalg.inv(rotation_matrix) offset = in_center - np.dot(matrix, out_center) warp_affine_fn = _maybe_process_by_channel( ndimage.affine_transform, matrix= matrix, offset = offset, order=interpolation, output_shape=out_shape, mode=border_mode, cval=value ) return warp_affine_fn(img)
[docs]def bbox_rotate(bbox: BoxInternalType, angle: float, method: str, axes: str, crop_to_border: bool, rows: int, cols: int, slices: int) -> BoxInternalType: """Rotates a bounding box by angle degrees. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_min)`. angle: Angle of rotation in degrees. axes: The axis of rotation. Must be one of `{'xy', 'xz', 'yz'}`. crop_to_border: If True, bbox is normalized to fit new image shape. See `rotate(crop_to_border=True)` method: Rotation method used. Should be one of: "largest_box", "ellipse". Default: "largest_box". rows: Image rows. cols: Image cols. slices: Image slices Returns: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_min)`. References: https://arxiv.org/abs/2109.13488 """ x_min, y_min, z_min, x_max, y_max, z_max = denormalize_bbox(list(map(lambda x: x - 0.5, bbox[:6])), rows, cols, slices) if method == "largest_box": bbox_points = np.array( [ [y_min, x_min, z_min], [y_min, x_min, z_max], [y_max, x_min, z_min], [y_max, x_min, z_max], [y_min, x_max, z_min], [y_min, x_max, z_max], [y_max, x_max, z_min], [y_max, x_max, z_max] ] ) elif method == "ellipse": w = (x_max - x_min) / 2 h = (y_max - y_min) / 2 d = (z_max - z_min) / 2 data = np.arange(0, 360, dtype=np.float32) if axes == "xy": x = np.tile(w * np.sin(np.radians(data)) + (w + x_min), 2) y = np.tile(h * np.cos(np.radians(data)) + (h + y_min), 2) z = np.concatenate((np.full((360,), z_min), np.full((360,), z_max))) elif axes == "xz": x = np.tile(w * np.sin(np.radians(data)) + (w + x_min), 2) y = np.concatenate((np.full((360,), y_min), np.full((360,), y_max))) z = np.tile(d * np.cos(np.radians(data)) + (d + z_min), 2) elif axes == "yz": x = np.concatenate((np.full((360,), x_min), np.full((360,), x_max))) y = np.tile(h * np.cos(np.radians(data)) + (h + y_min), 2) z = np.tile(d * np.sin(np.radians(data)) + (d + z_min), 2) else: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}") bbox_points = np.column_stack( [y,x,z], ) else: raise ValueError(f"Method {method} is not a valid rotation method.") angle = np.deg2rad(angle) dir = 1 if axes == 'xy': dir = -1 rotation_matrix = _get_rotation_matrix(angle, axes, dir=dir) bbox_points_t = np.matmul(rotation_matrix, bbox_points.T) x_min, x_max = np.min(bbox_points_t[1]), np.max(bbox_points_t[1]) y_min, y_max = np.min(bbox_points_t[0]), np.max(bbox_points_t[0]) z_min, z_max = np.min(bbox_points_t[2]), np.max(bbox_points_t[2]) bbox = x_min, y_min, z_min, x_max, y_max, z_max if crop_to_border: rows, cols, slices = _get_new_image_shape(rows, cols, slices, rotation_matrix) return list(map(lambda x: x + 0.5, normalize_bbox(bbox, rows, cols, slices)))
[docs]@angle_2pi_range def keypoint_rotate(keypoint, angle: float, axes: str, crop_to_border: bool, rows: int, cols: int, slices: int, **params): """Rotate a keypoint by angle. Args: keypoint (tuple): A keypoint `(x, y, z, angle, scale)`. angle (float): Rotation angle. axes: The axis of rotation. Must be one of `{'xy', 'xz', 'yz'}`. crop_to_border: If True, bbox is normalized to fit new image shape. See `rotate(crop_to_border=True)` rows (int): Image height. cols (int): Image width. slices: Image slices Returns: tuple: A keypoint `(x, y, z, angle, scale)`. """ angle = np.deg2rad(angle) if axes == "xz": axes = "yz" elif axes == "yz": axes = "xz" in_center = _get_image_center((rows, cols, slices)) out_center = in_center.copy() rotation_matrix = _get_rotation_matrix(angle, axes, dir=-1) if crop_to_border: out_shape = _get_new_image_shape(rows, cols, slices, rotation_matrix) out_center = _get_image_center(out_shape) x, y, z, a, s = keypoint[:5] p = np.array([[y,x,z]]) - in_center y,x,z = np.matmul(rotation_matrix, p.T).flatten() + out_center return x, y, z, a + (angle if axes=="xy" else 0), s
def _get_rotation_matrix(theta, axes, dir = 1): if axes == "xy": arr = np.array([ [ np.cos(theta), dir * np.sin(theta), 0], [dir * -np.sin(theta), np.cos(theta), 0], [ 0, 0, 1], ], dtype= np.float32) elif axes == "yz": arr = np.array([ [ np.cos(theta), 0, dir * -np.sin(theta)], [ 0, 1, 0], [dir * np.sin(theta), 0, np.cos(theta)], ], dtype= np.float32) elif axes == "xz": arr = np.array([ [1, 0, 0], [0, np.cos(theta), dir * -np.sin(theta)], [0, dir * np.sin(theta), np.cos(theta)], ], dtype= np.float32) else: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}") return arr # def _get_translation_matrix(x,y,z): # return np.array([ # [1, 0, 0, y], # [0, 1, 0, x], # [0, 0, 1, z], # [0, 0, 0, 1], # ], # dtype= np.float32) def _get_scale_matrix(dx,dy,dz): return np.array([ [dy, 0, 0], [ 0, dx, 0], [ 0, 0, dz], ], dtype= np.float32) def _get_shear_matrix(mode, sx = None, sy = None, sz = None): if sum(list(map(lambda x: x != None, [sx,sy,sz]))) != 2: raise ValueError("Expected two of (sx,sy,sz) to be non-null arguments. Got sx={}, sy={}, sz={}".format(sx,sy,sz)) if mode == "xy": assert sx != None and sy != None elif mode == "xz": assert sx != None and sz != None elif mode == "yz": assert sy != None and sz != None else: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}")
[docs]@preserve_channel_dim def shift_scale_rotate( img, angle, scale, dx, dy, dz, axes = "xy", crop_to_border = False, interpolation=INTER_LINEAR, border_mode="constant", value=0 ): out_shape = in_shape = img.shape[:3] height, width, depth = out_shape in_center = _get_image_center(in_shape) out_center = _get_image_center(out_shape) scale = (scale,)*3 angle = np.deg2rad(angle) rotation_matrix = _get_rotation_matrix(angle, axes, dir=-1) scale_matrix = _get_scale_matrix(*scale) if crop_to_border: out_shape = _get_new_image_shape(height, width, depth, rotation_matrix, *scale) out_center = _get_image_center(out_shape) matrix = np.linalg.inv(np.matmul(rotation_matrix, scale_matrix)) offset = in_center - np.dot(matrix, out_center) shift = np.array([dy,dx,dz]) * np.array(out_shape) warp_affine_fn = _maybe_process_by_channel( ndimage.affine_transform, matrix= matrix, offset = offset, order=interpolation, output_shape=out_shape, mode=border_mode, cval=value ) translate_fn = _maybe_process_by_channel( ndimage.shift, shift=shift, order=interpolation, mode=border_mode, cval=value ) return translate_fn(warp_affine_fn(img))
[docs]@angle_2pi_range def keypoint_shift_scale_rotate(keypoint, angle, scale, dx, dy, dz, axes = "xy", crop_to_border = False, rows=0, cols=0, slices=0,**params): out_shape = height, width, depth = rows, cols, slices in_center = np.array(out_shape) / 2 #_get_image_center((rows, cols, slices)) out_center = in_center.copy() scale = (scale,)*3 angle = np.deg2rad(angle) if axes == "xz": axes = "yz" elif axes == "yz": axes = "xz" rotation_matrix = _get_rotation_matrix(angle, axes, dir=-1) scale_matrix = _get_scale_matrix(*scale) if crop_to_border: out_shape = _get_new_image_shape(height, width, depth, rotation_matrix, *scale) out_center = _get_image_center(out_shape) matrix = np.matmul(rotation_matrix, scale_matrix) shift = np.array([dy,dx,dz]) * np.array(out_shape) x, y, z, a, s = keypoint[:5] p = np.array([[y,x,z]]) - in_center y,x,z = np.matmul(matrix, p.T).flatten() + out_center + shift return x, y, z, a + (angle if axes=="xy" else 0), s * scale[0]
[docs]def bbox_shift_scale_rotate(bbox, angle, scale, dx, dy, dz, axes="xy", crop_to_border=False, rotate_method="largest_box", rows=0, cols=0, slices=0, **kwargs): # skipcq: PYL-W0613 """Rotates, shifts and scales a bounding box. Rotation is made by angle degrees, scaling is made by scale factor and shifting is made by dx and dy. Args: bbox (tuple): A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. angle (int): Angle of rotation in degrees. scale (int): Scale factor. dx (int): Shift along x-axis. dy (int): Shift along y-axis. dz (int): Shift along z-axis. axes: The axis of rotation. Must be one of `{'xy', 'xz', 'yz'}`. crop_to_border: If True, bbox is normalized to fit new image shape. See `rotate(crop_to_border=True)` rotate_method(str): Rotation method used. Should be one of: "largest_box", "ellipse". Default: "largest_box". rows (int): Image rows. cols (int): Image cols. slices (int): Image slices Returns: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. """ x_min, y_min, z_min, x_max, y_max, z_max = denormalize_bbox(list(map(lambda x: x - 0.5, bbox[:6])), rows, cols, slices) if rotate_method == "largest_box": bbox_points = np.array( [ [y_min, x_min, z_min], [y_min, x_min, z_max], [y_max, x_min, z_min], [y_max, x_min, z_max], [y_min, x_max, z_min], [y_min, x_max, z_max], [y_max, x_max, z_min], [y_max, x_max, z_max] ] ) elif rotate_method == "ellipse": w = (x_max - x_min) / 2 h = (y_max - y_min) / 2 d = (z_max - z_min) / 2 data = np.arange(0, 360, dtype=np.float32) if axes == "xy": x = np.tile(w * np.sin(np.radians(data)) + (w + x_min), 2) y = np.tile(h * np.cos(np.radians(data)) + (h + y_min), 2) z = np.concatenate((np.full((360,), z_min), np.full((360,), z_max))) elif axes == "yz": x = np.tile(w * np.sin(np.radians(data)) + (w + x_min), 2) y = np.concatenate((np.full((360,), y_min), np.full((360,), y_max))) z = np.tile(d * np.cos(np.radians(data)) + (d + z_min), 2) elif axes == "xz": x = np.concatenate((np.full((360,), x_min), np.full((360,), x_max))) y = np.tile(h * np.cos(np.radians(data)) + (h + y_min), 2) z = np.tile(d * np.sin(np.radians(data)) + (d + z_min), 2) else: raise ValueError("Parameter axes must be one of {'xy','yz','xz'}") bbox_points = np.column_stack( [y,x,z], ) else: raise ValueError(f"Method {rotate_method} is not a valid rotation method.") angle = np.deg2rad(angle) scale = (scale,)*3 dir = 1 if axes == 'xy': dir = -1 rotation_matrix = _get_rotation_matrix(angle, axes, dir=dir) scale_matrix = _get_scale_matrix(*scale) shift = np.array([dx,dy,dz,dx,dy,dz]) if crop_to_border: rows, cols, slices = _get_new_image_shape(rows, cols, slices, rotation_matrix, *scale) matrix = np.matmul(rotation_matrix, scale_matrix) bbox_points_t = np.matmul(matrix, bbox_points.T) x_min, x_max = np.min(bbox_points_t[1]), np.max(bbox_points_t[1]) y_min, y_max = np.min(bbox_points_t[0]), np.max(bbox_points_t[0]) z_min, z_max = np.min(bbox_points_t[2]), np.max(bbox_points_t[2]) bbox = x_min, y_min, z_min, x_max, y_max, z_max # normalize points to assumed [0,1] range then apply translation return [p + s for s,p in zip(shift, map(lambda x: x + 0.5, normalize_bbox(bbox, rows, cols, slices)))]
# @preserve_shape # def elastic_transform( # img: np.ndarray, # alpha: float, # sigma: float, # alpha_affine: float, # interpolation: int = cv2.INTER_LINEAR, # border_mode: int = cv2.BORDER_REFLECT_101, # value: Optional[ImageColorType] = None, # random_state: Optional[np.random.RandomState] = None, # approximate: bool = False, # same_dxdy: bool = False, # ): # """Elastic deformation of images as described in [Simard2003]_ (with modifications). # Based on https://gist.github.com/ernestum/601cdf56d2b424757de5 # .. [Simard2003] Simard, Steinkraus and Platt, "Best Practices for # Convolutional Neural Networks applied to Visual Document Analysis", in # Proc. of the International Conference on Document Analysis and # Recognition, 2003. # """ # height, width = img.shape[:2] # # Random affine # center_square = np.array((height, width), dtype=np.float32) // 2 # square_size = min((height, width)) // 3 # alpha = float(alpha) # sigma = float(sigma) # alpha_affine = float(alpha_affine) # pts1 = np.array( # [ # center_square + square_size, # [center_square[0] + square_size, center_square[1] - square_size], # center_square - square_size, # ], # dtype=np.float32, # ) # pts2 = pts1 + random_utils.uniform(-alpha_affine, alpha_affine, size=pts1.shape, random_state=random_state).astype( # np.float32 # ) # matrix = cv2.getAffineTransform(pts1, pts2) # warp_fn = _maybe_process_in_chunks( # cv2.warpAffine, M=matrix, dsize=(width, height), flags=interpolation, borderMode=border_mode, borderValue=value # ) # img = warp_fn(img) # if approximate: # # Approximate computation smooth displacement map with a large enough kernel. # # On large images (512+) this is approximately 2X times faster # dx = random_utils.rand(height, width, random_state=random_state).astype(np.float32) * 2 - 1 # cv2.GaussianBlur(dx, (17, 17), sigma, dst=dx) # dx *= alpha # if same_dxdy: # # Speed up even more # dy = dx # else: # dy = random_utils.rand(height, width, random_state=random_state).astype(np.float32) * 2 - 1 # cv2.GaussianBlur(dy, (17, 17), sigma, dst=dy) # dy *= alpha # else: # dx = np.float32( # ndimage.gaussian_filter((random_utils.rand(height, width, random_state=random_state) * 2 - 1), sigma) * alpha # ) # if same_dxdy: # # Speed up # dy = dx # else: # dy = np.float32( # ndimage.gaussian_filter((random_utils.rand(height, width, random_state=random_state) * 2 - 1), sigma) * alpha # ) # x, y = np.meshgrid(np.arange(width), np.arange(height)) # map_x = np.float32(x + dx) # map_y = np.float32(y + dy) # remap_fn = _maybe_process_in_chunks( # cv2.remap, map1=map_x, map2=map_y, interpolation=interpolation, borderMode=border_mode, borderValue=value # ) # return remap_fn(img) def _resize(img, dsize, interpolation): img_height, img_width, img_depth = img.shape[:3] dst_height, dst_width, dst_depth = dsize scale_y = dst_height / img_height scale_x = dst_width / img_width scale_z = dst_depth / img_depth return ndimage.zoom(img, zoom = (scale_y,scale_x,scale_z), order=interpolation)
[docs]@preserve_channel_dim def resize(img, height, width, depth, interpolation=INTER_LINEAR): img_height, img_width, img_depth = img.shape[:3] if height == img_height and width == img_width and depth == img_depth: return img resize_fn = _maybe_process_by_channel(_resize, dsize=(height, width, depth), interpolation=interpolation) return resize_fn(img)
[docs]@preserve_channel_dim def scale(img: np.ndarray, scale: Union[float, Tuple[float]], interpolation: int = INTER_LINEAR) -> np.ndarray: scale_fn = _maybe_process_by_channel(ndimage.zoom, zoom = scale, order=interpolation) return scale_fn(img)
[docs]def keypoint_scale(keypoint: KeypointInternalType, scale_x: float, scale_y: float, scale_z: float) -> KeypointInternalType: """Scales a keypoint by scale_x and scale_y. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. scale_x: Scale coefficient x-axis. scale_y: Scale coefficient y-axis. scale_z: Scale coefficient y-axis. Returns: A keypoint `(x, y, z, angle, scale)`. """ x, y, z, angle, scale = keypoint[:5] return x * scale_x, y * scale_y, z * scale_z, angle, scale * max((scale_x, scale_y, scale_z))
[docs]def py3round(number): """Unified rounding in all python versions.""" if abs(round(number) - number) == 0.5: return int(2.0 * round(number / 2.0)) return int(round(number))
def _func_max_size(img, max_size, interpolation, func): height, width, depth = img.shape[:3] s = max_size / float(func((width, height, depth))) if s != 1.0: img = scale(img=img, scale=s, interpolation=interpolation) return img
[docs]@preserve_channel_dim def longest_max_size(img: np.ndarray, max_size: int, interpolation: int) -> np.ndarray: return _func_max_size(img, max_size, interpolation, max)
[docs]@preserve_channel_dim def smallest_max_size(img: np.ndarray, max_size: int, interpolation: int) -> np.ndarray: return _func_max_size(img, max_size, interpolation, min)
# @preserve_channel_dim # def perspective( # img: np.ndarray, # matrix: np.ndarray, # max_width: int, # max_height: int, # border_val: Union[int, float, List[int], List[float], np.ndarray], # border_mode: int, # keep_size: bool, # interpolation: int, # ): # h, w = img.shape[:2] # perspective_func = _maybe_process_in_chunks( # cv2.warpPerspective, # M=matrix, # dsize=(max_width, max_height), # borderMode=border_mode, # borderValue=border_val, # flags=interpolation, # ) # warped = perspective_func(img) # if keep_size: # return resize(warped, h, w, interpolation=interpolation) # return warped # def perspective_bbox( # bbox: BoxInternalType, # height: int, # width: int, # matrix: np.ndarray, # max_width: int, # max_height: int, # keep_size: bool, # ) -> BoxInternalType: # x1, y1, x2, y2 = denormalize_bbox(bbox, height, width)[:4] # points = np.array([[x1, y1], [x2, y1], [x2, y2], [x1, y2]], dtype=np.float32) # x1, y1, x2, y2 = float("inf"), float("inf"), 0, 0 # for pt in points: # pt = perspective_keypoint(pt.tolist() + [0, 0], height, width, matrix, max_width, max_height, keep_size) # x, y = pt[:2] # x1 = min(x1, x) # x2 = max(x2, x) # y1 = min(y1, y) # y2 = max(y2, y) # return normalize_bbox((x1, y1, x2, y2), height if keep_size else max_height, width if keep_size else max_width) # def rotation2DMatrixToEulerAngles(matrix: np.ndarray, y_up: bool = False) -> float: # """ # Args: # matrix (np.ndarray): Rotation matrix # y_up (bool): is Y axis looks up or down # """ # if y_up: # return np.arctan2(matrix[1, 0], matrix[0, 0]) # return np.arctan2(-matrix[1, 0], matrix[0, 0]) # @angle_2pi_range # def perspective_keypoint( # keypoint: KeypointInternalType, # height: int, # width: int, # matrix: np.ndarray, # max_width: int, # max_height: int, # keep_size: bool, # ) -> KeypointInternalType: # x, y, angle, scale = keypoint # keypoint_vector = np.array([x, y], dtype=np.float32).reshape([1, 1, 2]) # x, y = cv2.perspectiveTransform(keypoint_vector, matrix)[0, 0] # angle += rotation2DMatrixToEulerAngles(matrix[:2, :2], y_up=True) # scale_x = np.sign(matrix[0, 0]) * np.sqrt(matrix[0, 0] ** 2 + matrix[0, 1] ** 2) # scale_y = np.sign(matrix[1, 1]) * np.sqrt(matrix[1, 0] ** 2 + matrix[1, 1] ** 2) # scale *= max(scale_x, scale_y) # if keep_size: # scale_x = width / max_width # scale_y = height / max_height # return keypoint_scale((x, y, angle, scale), scale_x, scale_y) # return x, y, angle, scale # def _is_identity_matrix(matrix: skimage.transform.ProjectiveTransform) -> bool: # return np.allclose(matrix.params, np.eye(3, dtype=np.float32)) # @preserve_channel_dim # def warp_affine( # image: np.ndarray, # matrix: skimage.transform.ProjectiveTransform, # interpolation: int, # cval: Union[int, float, Sequence[int], Sequence[float]], # mode: int, # output_shape: Sequence[int], # ) -> np.ndarray: # if _is_identity_matrix(matrix): # return image # dsize = int(np.round(output_shape[1])), int(np.round(output_shape[0])) # warp_fn = _maybe_process_in_chunks( # cv2.warpAffine, M=matrix.params[:2], dsize=dsize, flags=interpolation, borderMode=mode, borderValue=cval # ) # tmp = warp_fn(image) # return tmp # @angle_2pi_range # def keypoint_affine( # keypoint: KeypointInternalType, # matrix: skimage.transform.ProjectiveTransform, # scale: dict, # ) -> KeypointInternalType: # if _is_identity_matrix(matrix): # return keypoint # x, y, a, s = keypoint[:4] # x, y = cv2.transform(np.array([[[x, y]]]), matrix.params[:2]).squeeze() # a += rotation2DMatrixToEulerAngles(matrix.params[:2]) # s *= np.max([scale["x"], scale["y"]]) # return x, y, a, s # def bbox_affine( # bbox: BoxInternalType, # matrix: skimage.transform.ProjectiveTransform, # rotate_method: str, # rows: int, # cols: int, # output_shape: Sequence[int], # ) -> BoxInternalType: # if _is_identity_matrix(matrix): # return bbox # x_min, y_min, x_max, y_max = denormalize_bbox(bbox, rows, cols)[:4] # if rotate_method == "largest_box": # points = np.array( # [ # [x_min, y_min], # [x_max, y_min], # [x_max, y_max], # [x_min, y_max], # ] # ) # elif rotate_method == "ellipse": # w = (x_max - x_min) / 2 # h = (y_max - y_min) / 2 # data = np.arange(0, 360, dtype=np.float32) # x = w * np.sin(np.radians(data)) + (w + x_min - 0.5) # y = h * np.cos(np.radians(data)) + (h + y_min - 0.5) # points = np.hstack([x.reshape(-1, 1), y.reshape(-1, 1)]) # else: # raise ValueError(f"Method {rotate_method} is not a valid rotation method.") # points = skimage.transform.matrix_transform(points, matrix.params) # x_min = np.min(points[:, 0]) # x_max = np.max(points[:, 0]) # y_min = np.min(points[:, 1]) # y_max = np.max(points[:, 1]) # return normalize_bbox((x_min, y_min, x_max, y_max), output_shape[0], output_shape[1]) # @preserve_channel_dim # def safe_rotate( # img: np.ndarray, # matrix: np.ndarray, # interpolation: int, # value: FillValueType = None, # border_mode: int = cv2.BORDER_REFLECT_101, # ) -> np.ndarray: # h, w = img.shape[:2] # warp_fn = _maybe_process_in_chunks( # cv2.warpAffine, # M=matrix, # dsize=(w, h), # flags=interpolation, # borderMode=border_mode, # borderValue=value, # ) # return warp_fn(img) # def bbox_safe_rotate(bbox: BoxInternalType, matrix: np.ndarray, cols: int, rows: int) -> BoxInternalType: # x1, y1, x2, y2 = denormalize_bbox(bbox, rows, cols)[:4] # points = np.array( # [ # [x1, y1, 1], # [x2, y1, 1], # [x2, y2, 1], # [x1, y2, 1], # ] # ) # points = points @ matrix.T # x1 = points[:, 0].min() # x2 = points[:, 0].max() # y1 = points[:, 1].min() # y2 = points[:, 1].max() # def fix_point(pt1: float, pt2: float, max_val: float) -> Tuple[float, float]: # # In my opinion, these errors should be very low, around 1-2 pixels. # if pt1 < 0: # return 0, pt2 + pt1 # if pt2 > max_val: # return pt1 - (pt2 - max_val), max_val # return pt1, pt2 # x1, x2 = fix_point(x1, x2, cols) # y1, y2 = fix_point(y1, y2, rows) # return normalize_bbox((x1, y1, x2, y2), rows, cols) # def keypoint_safe_rotate( # keypoint: KeypointInternalType, # matrix: np.ndarray, # angle: float, # scale_x: float, # scale_y: float, # cols: int, # rows: int, # ) -> KeypointInternalType: # x, y, a, s = keypoint[:4] # point = np.array([[x, y, 1]]) # x, y = (point @ matrix.T)[0] # # To avoid problems with float errors # x = np.clip(x, 0, cols - 1) # y = np.clip(y, 0, rows - 1) # a += angle # s *= max(scale_x, scale_y) # return x, y, a, s # @clipped # def piecewise_affine( # img: np.ndarray, # matrix: skimage.transform.PiecewiseAffineTransform, # interpolation: int, # mode: str, # cval: float, # ) -> np.ndarray: # return skimage.transform.warp( # img, matrix, order=interpolation, mode=mode, cval=cval, preserve_range=True, output_shape=img.shape # ) # def to_distance_maps( # keypoints: Sequence[Tuple[float, float]], height: int, width: int, inverted: bool = False # ) -> np.ndarray: # """Generate a ``(H,W,N)`` array of distance maps for ``N`` keypoints. # The ``n``-th distance map contains at every location ``(y, x)`` the # euclidean distance to the ``n``-th keypoint. # This function can be used as a helper when augmenting keypoints with a # method that only supports the augmentation of images. # Args: # keypoint: keypoint coordinates # height: image height # width: image width # inverted (bool): If ``True``, inverted distance maps are returned where each # distance value d is replaced by ``d/(d+1)``, i.e. the distance # maps have values in the range ``(0.0, 1.0]`` with ``1.0`` denoting # exactly the position of the respective keypoint. # Returns: # (H, W, N) ndarray # A ``float32`` array containing ``N`` distance maps for ``N`` # keypoints. Each location ``(y, x, n)`` in the array denotes the # euclidean distance at ``(y, x)`` to the ``n``-th keypoint. # If `inverted` is ``True``, the distance ``d`` is replaced # by ``d/(d+1)``. The height and width of the array match the # height and width in ``KeypointsOnImage.shape``. # """ # distance_maps = np.zeros((height, width, len(keypoints)), dtype=np.float32) # yy = np.arange(0, height) # xx = np.arange(0, width) # grid_xx, grid_yy = np.meshgrid(xx, yy) # for i, (x, y) in enumerate(keypoints): # distance_maps[:, :, i] = (grid_xx - x) ** 2 + (grid_yy - y) ** 2 # distance_maps = np.sqrt(distance_maps) # if inverted: # return 1 / (distance_maps + 1) # return distance_maps # def from_distance_maps( # distance_maps: np.ndarray, # inverted: bool, # if_not_found_coords: Optional[Union[Sequence[int], dict]], # threshold: Optional[float] = None, # ) -> List[Tuple[float, float]]: # """Convert outputs of ``to_distance_maps()`` to ``KeypointsOnImage``. # This is the inverse of `to_distance_maps`. # Args: # distance_maps (np.ndarray): The distance maps. ``N`` is the number of keypoints. # inverted (bool): Whether the given distance maps were generated in inverted mode # (i.e. :func:`KeypointsOnImage.to_distance_maps` was called with ``inverted=True``) or in non-inverted mode. # if_not_found_coords (tuple, list, dict or None, optional): # Coordinates to use for keypoints that cannot be found in `distance_maps`. # * If this is a ``list``/``tuple``, it must contain two ``int`` values. # * If it is a ``dict``, it must contain the keys ``x`` and ``y`` with each containing one ``int`` value. # * If this is ``None``, then the keypoint will not be added. # threshold (float): The search for keypoints works by searching for the # argmin (non-inverted) or argmax (inverted) in each channel. This # parameters contains the maximum (non-inverted) or minimum (inverted) value to accept in order to view a hit # as a keypoint. Use ``None`` to use no min/max. # nb_channels (None, int): Number of channels of the image on which the keypoints are placed. # Some keypoint augmenters require that information. If set to ``None``, the keypoint's shape will be set # to ``(height, width)``, otherwise ``(height, width, nb_channels)``. # """ # if distance_maps.ndim != 3: # raise ValueError( # f"Expected three-dimensional input, " # f"got {distance_maps.ndim} dimensions and shape {distance_maps.shape}." # ) # height, width, nb_keypoints = distance_maps.shape # drop_if_not_found = False # if if_not_found_coords is None: # drop_if_not_found = True # if_not_found_x = -1 # if_not_found_y = -1 # elif isinstance(if_not_found_coords, (tuple, list)): # if len(if_not_found_coords) != 2: # raise ValueError( # f"Expected tuple/list 'if_not_found_coords' to contain exactly two entries, " # f"got {len(if_not_found_coords)}." # ) # if_not_found_x = if_not_found_coords[0] # if_not_found_y = if_not_found_coords[1] # elif isinstance(if_not_found_coords, dict): # if_not_found_x = if_not_found_coords["x"] # if_not_found_y = if_not_found_coords["y"] # else: # raise ValueError( # f"Expected if_not_found_coords to be None or tuple or list or dict, got {type(if_not_found_coords)}." # ) # keypoints = [] # for i in range(nb_keypoints): # if inverted: # hitidx_flat = np.argmax(distance_maps[..., i]) # else: # hitidx_flat = np.argmin(distance_maps[..., i]) # hitidx_ndim = np.unravel_index(hitidx_flat, (height, width)) # if not inverted and threshold is not None: # found = distance_maps[hitidx_ndim[0], hitidx_ndim[1], i] < threshold # elif inverted and threshold is not None: # found = distance_maps[hitidx_ndim[0], hitidx_ndim[1], i] >= threshold # else: # found = True # if found: # keypoints.append((float(hitidx_ndim[1]), float(hitidx_ndim[0]))) # else: # if not drop_if_not_found: # keypoints.append((if_not_found_x, if_not_found_y)) # return keypoints # def keypoint_piecewise_affine( # keypoint: KeypointInternalType, # matrix: skimage.transform.PiecewiseAffineTransform, # h: int, # w: int, # keypoints_threshold: float, # ) -> KeypointInternalType: # x, y, a, s = keypoint[:4] # dist_maps = to_distance_maps([(x, y)], h, w, True) # dist_maps = piecewise_affine(dist_maps, matrix, 0, "constant", 0) # x, y = from_distance_maps(dist_maps, True, {"x": -1, "y": -1}, keypoints_threshold)[0] # return x, y, a, s # def bbox_piecewise_affine( # bbox: BoxInternalType, # matrix: skimage.transform.PiecewiseAffineTransform, # h: int, # w: int, # keypoints_threshold: float, # ) -> BoxInternalType: # x1, y1, x2, y2 = denormalize_bbox(bbox, h, w)[:4] # keypoints = [ # (x1, y1), # (x2, y1), # (x2, y2), # (x1, y2), # ] # dist_maps = to_distance_maps(keypoints, h, w, True) # dist_maps = piecewise_affine(dist_maps, matrix, 0, "constant", 0) # keypoints = from_distance_maps(dist_maps, True, {"x": -1, "y": -1}, keypoints_threshold) # keypoints = [i for i in keypoints if 0 <= i[0] < w and 0 <= i[1] < h] # keypoints_arr = np.array(keypoints) # x1 = keypoints_arr[:, 0].min() # y1 = keypoints_arr[:, 1].min() # x2 = keypoints_arr[:, 0].max() # y2 = keypoints_arr[:, 1].max() # return normalize_bbox((x1, y1, x2, y2), h, w)
[docs]def vflip(img: np.ndarray) -> np.ndarray: return np.ascontiguousarray(img[::-1, ...])
[docs]def hflip(img: np.ndarray) -> np.ndarray: return np.ascontiguousarray(img[:, ::-1, ...])
[docs]def zflip(img: np.ndarray) -> np.ndarray: return np.ascontiguousarray(img[:, :, ::-1, ...])
def hflip_cv2(img: np.ndarray) -> np.ndarray: return cv2.flip(img, 1) @preserve_shape def random_flip(img: np.ndarray, d: int) -> np.ndarray: if d == 0: img = vflip(img) elif d == 1: img = hflip(img) elif d == 2: img = zflip(img) elif d == -1: img = hflip(img) img = vflip(img) img = zflip(img) else: raise ValueError("Invalid d value {}. Valid values are -1, 0, 1, and 2".format(d)) return img
[docs]def transpose(img: np.ndarray) -> np.ndarray: return img.transpose(1, 0, 2, 3) if len(img.shape) > 3 else img.transpose(1, 0, 2)
def rot90(img: np.ndarray, factor: int, axes: Tuple = (0,1)) -> np.ndarray: img = np.rot90(img, factor, axes) return np.ascontiguousarray(img)
[docs]def bbox_vflip(bbox: BoxInternalType, rows: int, cols: int, slices: int) -> BoxInternalType: # skipcq: PYL-W0613 """Flip a bounding box vertically around the x-axis. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. rows: Image rows. cols: Image cols. slices: Image slices Returns: tuple: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. """ x_min, y_min, z_min, x_max, y_max, z_max = bbox[:6] return x_min, 1 - y_max, z_min, x_max, 1 - y_min, z_max
[docs]def bbox_hflip(bbox: BoxInternalType, rows: int, cols: int, slices: int) -> BoxInternalType: # skipcq: PYL-W0613 """Flip a bounding box horizontally around the y-axis. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. rows: Image rows. cols: Image cols. slices: Image slices Returns: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. """ x_min, y_min, z_min, x_max, y_max, z_max = bbox[:6] return 1 - x_max, y_min, z_min, 1 - x_min, y_max, z_max
[docs]def bbox_zflip(bbox: BoxInternalType, rows: int, cols: int, slices: int) -> BoxInternalType: # skipcq: PYL-W0613 """Flip a bounding box on the z-axis. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. rows: Image rows. cols: Image cols. slices: Image slices Returns: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. """ x_min, y_min, z_min, x_max, y_max, z_max = bbox[:6] return x_min, y_min, 1 - z_max, x_max, y_max, 1 - z_min
[docs]def bbox_flip(bbox: BoxInternalType, d: int, rows: int, cols: int, slices: int) -> BoxInternalType: """Flip a bounding box either vertically, horizontally, along the slice axis, or all depending on the value of `d`. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. d: dimension. 0 for vertical flip, 1 for horizontal, 2 for z-axis, -1 for transpose rows: Image rows. cols: Image cols. slices: Image slices Returns: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. Raises: ValueError: if value of `d` is not -1, 0, 1, 2. """ if d == 0: bbox = bbox_vflip(bbox, rows, cols, slices) elif d == 1: bbox = bbox_hflip(bbox, rows, cols, slices) elif d == 2: bbox = bbox_zflip(bbox, rows, cols, slices) elif d == -1: bbox = bbox_hflip(bbox, rows, cols, slices) bbox = bbox_vflip(bbox, rows, cols, slices) bbox = bbox_zflip(bbox, rows, cols, slices) else: raise ValueError("Invalid d value {}. Valid values are -1, 0, 1, and 2".format(d)) return bbox
[docs]def bbox_transpose( bbox: KeypointInternalType, axis: int, rows: int, cols: int, slices: int ) -> KeypointInternalType: # skipcq: PYL-W0613 """Transposes a bounding box along given axis. Args: bbox: A bounding box `(x_min, y_min, z_min, x_max, y_max, z_max)`. axis: 0 - main axis, 1 - secondary axis. rows: Image rows. cols: Image cols. Returns: A bounding box tuple `(x_min, y_min, z_min, x_max, y_max, z_max)`. Raises: ValueError: If axis not equal to 0 or 1. """ x_min, y_min, z_min, x_max, y_max, z_max = bbox[:6] if axis not in {0,1}: raise ValueError("Parameter axes must be one of {0,1}") if axis == 0: bbox = (y_min, x_min, z_min, y_max, x_max, z_max) if axis == 1: bbox = (1 - y_max, 1 - x_max, z_min, 1 - y_min, 1 - x_min, z_max) return bbox
[docs]@angle_2pi_range def keypoint_vflip(keypoint: KeypointInternalType, rows: int, cols: int, slices: int) -> KeypointInternalType: """Flip a keypoint vertically around the x-axis. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. rows: Image height. cols: Image width. slices: Image depth Returns: tuple: A keypoint `(x, y, z, angle, scale)`. """ x, y, z, angle, scale = keypoint[:5] angle = -angle return x, (rows - 1) - y, z, angle, scale
[docs]@angle_2pi_range def keypoint_hflip(keypoint: KeypointInternalType, rows: int, cols: int, slices: int) -> KeypointInternalType: """Flip a keypoint horizontally around the y-axis. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. rows: Image height. cols: Image width. slices: Image depth Returns: A keypoint `(x, y, z, angle, scale)`. """ x, y, z, angle, scale = keypoint[:5] angle = math.pi - angle return (cols - 1) - x, y, z, angle, scale
[docs]@angle_2pi_range def keypoint_zflip(keypoint: KeypointInternalType, rows: int, cols: int, slices: int) -> KeypointInternalType: """Flip a keypoint along the z-axis. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. rows: Image height. cols: Image width. slices: Image depth Returns: A keypoint `(x, y, z, angle, scale)`. """ x, y, z, angle, scale = keypoint[:5] return x, y, (slices-1) - z, angle, scale
[docs]def keypoint_flip(keypoint: KeypointInternalType, d: int, rows: int, cols: int, slices: int) -> KeypointInternalType: """Flip a keypoint either vertically, horizontally, along the slice axis, or all depending on the value of `d`. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. d: Number of flip. Must be -1, 0 or 1: * 0 - vertical flip, * 1 - horizontal flip, * 2 - z-axis flip, * -1 - vertical, horizontal, and z-axis flip. rows: Image height. cols: Image width. slices: Image depth Returns: A keypoint `(x, y, z, angle, scale)`. Raises: ValueError: if value of `d` is not -1, 0, 1 or 2. """ if d == 0: keypoint = keypoint_vflip(keypoint, rows, cols, slices) elif d == 1: keypoint = keypoint_hflip(keypoint, rows, cols, slices) elif d == 2: keypoint = keypoint_zflip(keypoint, rows, cols, slices) elif d == -1: keypoint = keypoint_hflip(keypoint, rows, cols, slices) keypoint = keypoint_vflip(keypoint, rows, cols, slices) keypoint = keypoint_zflip(keypoint, rows, cols, slices) else: raise ValueError(f"Invalid d value {d}. Valid values are -1, 0, 1, and 2") return keypoint
[docs]def keypoint_transpose(keypoint: KeypointInternalType) -> KeypointInternalType: """Rotate a keypoint by angle. Args: keypoint: A keypoint `(x, y, z, angle, scale)`. Returns: A keypoint `(x, y, z, angle, scale)`. """ x, y, z, angle, scale = keypoint[:5] if angle <= np.pi: angle = np.pi - angle else: angle = 3 * np.pi - angle return y, x, z, angle, scale
[docs]@preserve_channel_dim def pad( img: np.ndarray, min_height: int, min_width: int, min_depth: int, border_mode: int = "constant", value: Union[float,int] = 0, ) -> np.ndarray: height, width, depth = img.shape[:3] if height < min_height: h_pad_top = int((min_height - height) / 2.0) h_pad_bottom = min_height - height - h_pad_top else: h_pad_top = 0 h_pad_bottom = 0 if width < min_width: w_pad_left = int((min_width - width) / 2.0) w_pad_right = min_width - width - w_pad_left else: w_pad_left = 0 w_pad_right = 0 if depth < min_depth: d_pad_close = int((min_depth - depth) / 2.0) d_pad_far = min_depth - depth - d_pad_close else: d_pad_close = 0 d_pad_far = 0 img = pad_with_params(img, h_pad_top, h_pad_bottom, w_pad_left, w_pad_right, d_pad_close, d_pad_far, border_mode, value) if img.shape[:3] != (max(min_height, height), max(min_width, width), max(min_depth, depth)): raise RuntimeError( "Invalid result shape. Got: {}. Expected: {}".format( img.shape[:3], (max(min_height, height), max(min_width, width), max(min_depth, depth)) ) ) return img
def _pad( img: np.ndarray, pad_width: Tuple[Tuple,Tuple,Tuple], border_mode: str = 'constant', value: Union[float,int] = 0 ) -> np.ndarray: return np.pad(img, pad_width=pad_width, mode=SCIPY_MODE_TO_NUMPY_MODE[border_mode], constant_values = value)
[docs]@preserve_channel_dim def pad_with_params( img: np.ndarray, h_pad_top: int, h_pad_bottom: int, w_pad_left: int, w_pad_right: int, d_pad_front: int, d_pad_back: int, border_mode: str = 'constant', value: Union[float,int] = None, ) -> np.ndarray: pad_fn = _maybe_process_by_channel( _pad, pad_width=((h_pad_top, h_pad_bottom),(w_pad_left, w_pad_right),(d_pad_front, d_pad_back)), border_mode=border_mode, value=value) return pad_fn(img)
# @preserve_shape # def optical_distortion( # img: np.ndarray, # k: int = 0, # dx: int = 0, # dy: int = 0, # interpolation: int = cv2.INTER_LINEAR, # border_mode: int = cv2.BORDER_REFLECT_101, # value: Optional[ImageColorType] = None, # ) -> np.ndarray: # """Barrel / pincushion distortion. Unconventional augment. # Reference: # | https://stackoverflow.com/questions/6199636/formulas-for-barrel-pincushion-distortion # | https://stackoverflow.com/questions/10364201/image-transformation-in-opencv # | https://stackoverflow.com/questions/2477774/correcting-fisheye-distortion-programmatically # | http://www.coldvision.io/2017/03/02/advanced-lane-finding-using-opencv/ # """ # height, width = img.shape[:2] # fx = width # fy = height # cx = width * 0.5 + dx # cy = height * 0.5 + dy # camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32) # distortion = np.array([k, k, 0, 0, 0], dtype=np.float32) # map1, map2 = cv2.initUndistortRectifyMap(camera_matrix, distortion, None, None, (width, height), cv2.CV_32FC1) # return cv2.remap(img, map1, map2, interpolation=interpolation, borderMode=border_mode, borderValue=value) # @preserve_shape # def grid_distortion( # img: np.ndarray, # num_steps: int = 10, # xsteps: Tuple = (), # ysteps: Tuple = (), # interpolation: int = cv2.INTER_LINEAR, # border_mode: int = cv2.BORDER_REFLECT_101, # value: Optional[ImageColorType] = None, # ) -> np.ndarray: # """Perform a grid distortion of an input image. # Reference: # http://pythology.blogspot.sg/2014/03/interpolation-on-regular-distorted-grid.html # """ # height, width = img.shape[:2] # x_step = width // num_steps # xx = np.zeros(width, np.float32) # prev = 0 # for idx in range(num_steps + 1): # x = idx * x_step # start = int(x) # end = int(x) + x_step # if end > width: # end = width # cur = width # else: # cur = prev + x_step * xsteps[idx] # xx[start:end] = np.linspace(prev, cur, end - start) # prev = cur # y_step = height // num_steps # yy = np.zeros(height, np.float32) # prev = 0 # for idx in range(num_steps + 1): # y = idx * y_step # start = int(y) # end = int(y) + y_step # if end > height: # end = height # cur = height # else: # cur = prev + y_step * ysteps[idx] # yy[start:end] = np.linspace(prev, cur, end - start) # prev = cur # map_x, map_y = np.meshgrid(xx, yy) # map_x = map_x.astype(np.float32) # map_y = map_y.astype(np.float32) # remap_fn = _maybe_process_in_chunks( # cv2.remap, # map1=map_x, # map2=map_y, # interpolation=interpolation, # borderMode=border_mode, # borderValue=value, # ) # return remap_fn(img) # @preserve_shape # def elastic_transform_approx( # img: np.ndarray, # alpha: float, # sigma: float, # alpha_affine: float, # interpolation: int = cv2.INTER_LINEAR, # border_mode: int = cv2.BORDER_REFLECT_101, # value: Optional[ImageColorType] = None, # random_state: Optional[np.random.RandomState] = None, # ) -> np.ndarray: # """Elastic deformation of images as described in [Simard2003]_ (with modifications for speed). # Based on https://gist.github.com/ernestum/601cdf56d2b424757de5 # .. [Simard2003] Simard, Steinkraus and Platt, "Best Practices for # Convolutional Neural Networks applied to Visual Document Analysis", in # Proc. of the International Conference on Document Analysis and # Recognition, 2003. # """ # height, width = img.shape[:2] # # Random affine # center_square = np.array((height, width), dtype=np.float32) // 2 # square_size = min((height, width)) // 3 # alpha = float(alpha) # sigma = float(sigma) # alpha_affine = float(alpha_affine) # pts1 = np.array( # [ # center_square + square_size, # [center_square[0] + square_size, center_square[1] - square_size], # center_square - square_size, # ], # dtype=np.float32, # ) # pts2 = pts1 + random_utils.uniform(-alpha_affine, alpha_affine, size=pts1.shape, random_state=random_state).astype( # np.float32 # ) # matrix = cv2.getAffineTransform(pts1, pts2) # warp_fn = _maybe_process_in_chunks( # cv2.warpAffine, # M=matrix, # dsize=(width, height), # flags=interpolation, # borderMode=border_mode, # borderValue=value, # ) # img = warp_fn(img) # dx = random_utils.rand(height, width, random_state=random_state).astype(np.float32) * 2 - 1 # cv2.GaussianBlur(dx, (17, 17), sigma, dst=dx) # dx *= alpha # dy = random_utils.rand(height, width, random_state=random_state).astype(np.float32) * 2 - 1 # cv2.GaussianBlur(dy, (17, 17), sigma, dst=dy) # dy *= alpha # x, y = np.meshgrid(np.arange(width), np.arange(height)) # map_x = np.float32(x + dx) # map_y = np.float32(y + dy) # remap_fn = _maybe_process_in_chunks( # cv2.remap, # map1=map_x, # map2=map_y, # interpolation=interpolation, # borderMode=border_mode, # borderValue=value, # ) # return remap_fn(img)