Spaces:
Running
on
Zero
Running
on
Zero
| """Pointwise transformations.""" | |
| from __future__ import annotations | |
| from typing import TypedDict | |
| import numpy as np | |
| from vis4d.common.typing import NDArrayFloat | |
| from vis4d.data.const import CommonKeys as K | |
| from .base import Transform | |
| class GenPcBounds: | |
| """Extracts the max and min values of the loaded points.""" | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Extracts the max and min values of the pointcloud.""" | |
| coordinates = coordinates_list[0] | |
| pc_bounds = [np.stack([coordinates.min(0), coordinates.max(0)])] * len( | |
| coordinates_list | |
| ) | |
| return pc_bounds | |
| class NormalizeByMaxBounds: | |
| """Normalizes the pointcloud by the max bounds.""" | |
| def __init__(self, axes: tuple[int, int, int] = (0, 1, 2)) -> None: | |
| """Creates an instance of the class. | |
| Args: | |
| axes (tuple[int, int, int]): Over which axes to apply | |
| normalization. | |
| """ | |
| self.axes = axes | |
| def __call__( | |
| self, | |
| coords_list: list[NDArrayFloat], | |
| pc_bounds_list: list[NDArrayFloat], | |
| ) -> list[NDArrayFloat]: | |
| """Applies the normalization.""" | |
| for i, (coords, pc_bounds) in enumerate( | |
| zip(coords_list, pc_bounds_list) | |
| ): | |
| max_bound = np.max(np.abs(pc_bounds), axis=0) | |
| for ax in self.axes: | |
| coords[:, ax] = coords[:, ax] / max_bound[ax] | |
| coords_list[i] = coords | |
| return coords_list | |
| class CenterAndNormalize: | |
| """Centers and normalizes the pointcloud.""" | |
| def __init__(self, centering: bool = True, normalize: bool = True) -> None: | |
| """Creates an instance of the class. | |
| Args: | |
| centering (bool): Whether to center the pointcloud | |
| normalize (bool): Whether to normalize the pointcloud | |
| """ | |
| self.centering = centering | |
| self.normalize = normalize | |
| def __call__(self, coords_list: list[NDArrayFloat]) -> list[NDArrayFloat]: | |
| """Applies the Center and Normalization operations.""" | |
| for i, coords in enumerate(coords_list): | |
| if self.centering: | |
| coords = coords - np.mean(coords, axis=0) | |
| if self.normalize: | |
| coords = coords / np.max(np.sqrt(np.sum(coords**2, axis=-1))) | |
| coords_list[i] = coords | |
| return coords_list | |
| class AddGaussianNoise: | |
| """Adds random normal distributed noise with given std to the data. | |
| Args: | |
| std (float): Standard Deviation of the noise | |
| """ | |
| def __init__(self, noise_level: float = 0.01): | |
| """Creates an instance of the class. | |
| Args: | |
| noise_level (float): The noise level. Standard deviation for | |
| the gaussian noise. | |
| """ | |
| self.noise_level = noise_level | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Adds gaussian noise to the coordiantes.""" | |
| for i, coordinates in enumerate(coordinates_list): | |
| coordinates[i] = ( | |
| coordinates | |
| + np.random.randn(*coordinates.shape) * self.noise_level | |
| ) | |
| return coordinates_list | |
| class AddUniformNoise: | |
| """Adds random normal distributed noise with given std to the data. | |
| Args: | |
| std (float): Standard Deviation of the noise | |
| """ | |
| def __init__(self, noise_level: float = 0.01): | |
| """Creates an instance of the class. | |
| Args: | |
| noise_level (float): The noise level. Half the range of the | |
| uniform noise. The noise is sampled from | |
| [-noise_level, noise_level]. | |
| """ | |
| self.noise_level = noise_level | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Adds uniform noise to the coordinates.""" | |
| for i, coordinates in enumerate(coordinates_list): | |
| coordinates_list[i] = coordinates + np.random.uniform( | |
| -self.noise_level, self.noise_level, coordinates.shape | |
| ) | |
| return coordinates_list | |
| class SE3Transform(TypedDict): | |
| """Parameters for Resize.""" | |
| translation: NDArrayFloat | |
| rotation: NDArrayFloat | |
| def _gen_random_se3_transform( | |
| translation_min: NDArrayFloat, | |
| translation_max: NDArrayFloat, | |
| rotation_min: NDArrayFloat, | |
| rotation_max: NDArrayFloat, | |
| ) -> SE3Transform: | |
| """Creates a random SE3 Transforms. | |
| The transform is generated by sampling a random translation and | |
| rotation from a uniform distribution. | |
| """ | |
| angle = np.random.uniform(rotation_min, rotation_max) | |
| translation = np.random.uniform(translation_min, translation_max) | |
| cos_x, sin_x = np.cos(angle[0]), np.sin(angle[0]) | |
| cos_y, sin_y = np.cos(angle[1]), np.sin(angle[1]) | |
| cos_z, sin_z = np.cos(angle[2]), np.sin(angle[2]) | |
| rotx = np.array([[1, 0, 0], [0, cos_x, -sin_x], [0, sin_x, cos_x]]) | |
| roty = np.array([[cos_y, 0, sin_y], [0, 1, 0], [-sin_y, 0, cos_y]]) | |
| rotz = np.array([[cos_z, -sin_z, 0], [sin_z, cos_z, 0], [0, 0, 1]]) | |
| rot = np.dot(rotz, np.dot(roty, rotx)) | |
| return SE3Transform(translation=translation, rotation=rot) | |
| class ApplySE3Transform: | |
| """Applies a given SE3 Transform to the data.""" | |
| def __init__( | |
| self, | |
| translation_min: tuple[float, float, float] = (0.0, 0.0, 0.0), | |
| translation_max: tuple[float, float, float] = (0.0, 0.0, 0.0), | |
| rotation_min: tuple[float, float, float] = (0.0, 0.0, 0.0), | |
| rotation_max: tuple[float, float, float] = (0.0, 0.0, 0.0), | |
| ) -> None: | |
| """Creates an instance of the class. | |
| Args: | |
| translation_min (tuple[float, float, float]): Minimum translation. | |
| translation_max (tuple[float, float, float]): Maximum translation. | |
| rotation_min (tuple[float, float, float]): Minimum euler rotation | |
| angles [rad]. Applied in the order rot_x -> rot_y -> rot_z. | |
| rotation_max (tuple[float, float, float]): Maximum euler rotation | |
| angles [rad]. Applied in the order rot_x -> rot_y -> rot_z. | |
| """ | |
| self.translation_min = np.asarray(translation_min) | |
| self.translation_max = np.asarray(translation_max) | |
| self.rotation_min = np.asarray(rotation_min) | |
| self.rotation_max = np.asarray(rotation_max) | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Applies a SE3 Transform.""" | |
| for i, coordinates in enumerate(coordinates_list): | |
| transform = _gen_random_se3_transform( | |
| self.translation_min, | |
| self.translation_max, | |
| self.rotation_min, | |
| self.rotation_max, | |
| ) | |
| if coordinates.shape[-1] == 3: | |
| coordinates_list[i] = ( | |
| transform["rotation"] @ coordinates.T | |
| ).T + transform["translation"] | |
| elif coordinates.shape[-2] == 3: | |
| coordinates_list[i] = ( | |
| transform["rotation"] @ coordinates | |
| ).T + transform["translation"] | |
| else: | |
| raise ValueError( | |
| f"Invalid shape for coordinates: {coordinates.shape}" | |
| ) | |
| return coordinates_list | |
| class ApplySO3Transform(ApplySE3Transform): | |
| """Applies a given SO3 Transform to the data.""" | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Applies a given SO3 Transform to the data.""" | |
| for i, coordinates in enumerate(coordinates_list): | |
| transform = _gen_random_se3_transform( | |
| self.translation_min, | |
| self.translation_max, | |
| self.rotation_min, | |
| self.rotation_max, | |
| )["rotation"] | |
| if coordinates.shape[-1] == 3: | |
| coordinates_list[i] = (transform @ coordinates.T).T | |
| elif coordinates.shape[-2] == 3: | |
| coordinates_list[i] = (transform @ coordinates).T | |
| else: | |
| raise ValueError( | |
| f"Invalid shape for coordinates: {coordinates.shape}" | |
| ) | |
| return coordinates_list | |
| class TransposeChannels: | |
| """Transposes some predifined channels.""" | |
| def __init__(self, channels: tuple[int, int] = (-1, -2)): | |
| """Creates an instance of the class. | |
| Args: | |
| channels (tuple[int, int]): Tuple of channels to transpose | |
| """ | |
| self.channels = channels | |
| def __call__( | |
| self, coordinates_list: list[NDArrayFloat] | |
| ) -> list[NDArrayFloat]: | |
| """Transposes some predifined channels.""" | |
| for i, coordinates in enumerate(coordinates_list): | |
| coordinates_list[i] = coordinates.transpose(*self.channels) | |
| return coordinates_list | |