import numpy as np from nltk.tree import Tree import random class Compose(object): def __init__(self, transforms): self.transforms = transforms def __call__(self, coords_and_feats): for t in self.transforms: coords_and_feats = t(coords_and_feats) return coords_and_feats def __str__(self) -> str: sep = "\n\t" msg = "Compose of [" for t in self.transforms: msg += f"{sep}{t}," msg += "]\n" return msg def __repr__(self) -> str: sep = "\n\t" msg = "Compose of [" for t in self.transforms: msg += f"{sep}{t}" msg += "]\n" return msg class RandomDropSubTrees(object): def __init__(self, probs=[0.1, 0.2, 0.3, 0.4, 0.5], max_cnt=5): self.probs = probs self.max_cnt = max_cnt self.cnt = 0 def remove_subtrees(self, root, level_idx): reduced_root = Tree(root.label(), []) if len(root) == 0: return reduced_root p = np.random.uniform(0, 1, len(root)) # padding the dropping probabilities if level_idx >= len(self.probs): level_idx = len(self.probs) - 1 for child_idx in range(len(root)): if self.cnt > self.max_cnt: reduced_root.append(root[child_idx]) continue else: if p[child_idx] > self.probs[level_idx]: reduced_root.append( self.remove_subtrees(root[child_idx], level_idx + 1) ) else: self.cnt += 1 return reduced_root def __call__(self, tree): self.cnt = 0 return self.remove_subtrees(tree, level_idx=0) def __str__(self) -> str: return f"RandomDropSubTrees(probs={self.probs}, max_cnt={self.max_cnt})" def __repr__(self) -> str: return f"RandomDropSubTrees(probs={self.probs}, max_cnt={self.max_cnt})" class RandomSkipParentNode(object): def __init__(self, probs=[0.1], max_cnt=5): self.probs = probs self.max_cnt = max_cnt self.cnt = 0 def move_grandson_to_son(self, root, level_idx): if len(root) == 0 or len(root) == 1: return root p = np.random.uniform(0, 1, len(root)) # padding the dropping probabilities if level_idx >= len(self.probs): level_idx = len(self.probs) - 1 for child_idx in range(len(root)): if self.cnt >= self.max_cnt: break if p[child_idx] < self.probs[level_idx]: if len(root[child_idx]) == 0 or len(root[child_idx]) == 1: continue else: idx = random.randint(0, len(root[child_idx]) - 1) root[child_idx] = root[child_idx][idx] self.cnt += 1 else: root[child_idx] = self.move_grandson_to_son( root[child_idx], level_idx + 1 ) return root def __call__(self, tree): self.cnt = 0 return self.move_grandson_to_son(tree, level_idx=0) def __str__(self) -> str: return f"RandomSkipParentNode(probs={self.probs}, max_cnt={self.max_cnt})" def __repr__(self) -> str: return f"RandomSkipParentNode(probs={self.probs}, max_cnt={self.max_cnt})" class RandomSwapSiblingSubTrees(object): def __init__(self, probs=[0.1], max_cnt=5): self.probs = probs self.max_cnt = max_cnt self.cnt = 0 def swap_sibling_subtrees(self, root, level_idx): if len(root) < 2: return root p = np.random.uniform(0, 1, len(root)) # padding the dropping probabilities if level_idx >= len(self.probs): level_idx = len(self.probs) - 1 for child_idx in range(len(root)): if self.cnt >= self.max_cnt: break if p[child_idx] < self.probs[level_idx]: if len(root[child_idx]) < 2: continue else: my_subtree_idx = random.randint(0, len(root[child_idx]) - 1) sibling_idx = random.randint(0, len(root) - 1) if len(root[sibling_idx]) == 0: continue sibling_subtree_idx = random.randint(0, len(root[sibling_idx]) - 1) my_subtree = root[child_idx][my_subtree_idx].copy() sibling_subtree = root[sibling_idx][sibling_subtree_idx].copy() root[child_idx][my_subtree_idx] = sibling_subtree root[sibling_idx][sibling_subtree_idx] = my_subtree self.cnt += 1 else: root[child_idx] = self.swap_sibling_subtrees( root[child_idx], level_idx + 1 ) return root def __call__(self, tree): self.cnt = 0 return self.swap_sibling_subtrees(tree, level_idx=0) def __str__(self) -> str: return f"RandomSwapSiblingSubTrees(probs={self.probs}, max_cnt={self.max_cnt})" def __repr__(self) -> str: return f"RandomSwapSiblingSubTrees(probs={self.probs}, max_cnt={self.max_cnt})" class RandomRotateAligned(object): def __init__(self, p=0.5, axis=2): self.prob = p self.axis = axis def __call__(self, coords_and_feats): coord = coords_and_feats[:, :3] if np.random.rand() < self.prob: angle = np.random.uniform() * 2 * np.pi cos, sin = np.cos(angle), np.sin(angle) R_x = np.array([[1, 0, 0], [0, cos, -sin], [0, sin, cos]]) R_y = np.array([[cos, 0, sin], [0, 1, 0], [-sin, 0, cos]]) R_z = np.array([[cos, -sin, 0], [sin, cos, 0], [0, 0, 1]]) R = [R_x, R_y, R_z][self.axis] coord = np.dot(coord, R) coords_and_feats[:, :3] = coord return coords_and_feats def __str__(self) -> str: return f"RandomRotateAligned(p={self.prob},axis={self.axis})" def __repr__(self) -> str: return f"RandomRotateAligned(p={self.prob},axis={self.axis})" class RandomRotate(object): def __init__(self, sigma=0.03, clip=0.09, p=0.5): self.sigma = sigma self.clip = clip self.prob = p def __call__(self, coords_and_feats): coord = coords_and_feats[:, :3] if np.random.rand() < self.prob: angle_x = np.random.uniform() * 2 * np.pi angle_y = np.random.uniform() * 2 * np.pi angle_z = np.random.uniform() * 2 * np.pi cos_x, sin_x = np.cos(angle_x), np.sin(angle_x) cos_y, sin_y = np.cos(angle_y), np.sin(angle_y) cos_z, sin_z = np.cos(angle_z), np.sin(angle_z) R_x = np.array([[1, 0, 0], [0, cos_x, -sin_x], [0, sin_x, cos_x]]) R_y = np.array([[cos_y, 0, sin_y], [0, 1, 0], [-sin_y, 0, cos_y]]) R_z = np.array([[cos_z, -sin_z, 0], [sin_z, cos_z, 0], [0, 0, 1]]) R = np.dot(R_z, np.dot(R_y, R_x)) coord = np.dot(coord, R) coords_and_feats[:, :3] = coord return coords_and_feats def __str__(self) -> str: return f"RandomRotate(p={self.prob})" def __repr__(self) -> str: return f"RandomRotate(p={self.prob})" class RandomMaskFeats(object): def __init__(self, p=0.2): self.prob = p def __call__(self, coords_and_feats): if len(coords_and_feats[0]) > 5: feats = coords_and_feats[:, 5:] feats[ :, np.random.choice( np.arange(len(feats[0])), int(len(feats[0]) * self.prob) ), ] = 0 coords_and_feats[:, 5:] = feats return coords_and_feats def __str__(self) -> str: return f"RandomMaskFeats(p={self.prob})" def __repr__(self) -> str: return f"RandomMaskFeats(p={self.prob})" class RandomElasticate(object): def __init__(self, p=0.2, scales=[0.8, 1.2]): self.prob = p self.scales = scales def __call__(self, coords_and_feats): if len(coords_and_feats[0]) > 5: if np.random.rand() < self.prob: branches = coords_and_feats[:, 5:] scales = np.random.uniform( self.scales[0], self.scales[1], branches.shape ) branches *= scales coords_and_feats[:, 5:] = branches return coords_and_feats def __str__(self) -> str: return f"RandomElasticate(p={self.prob}, scales={self.scales})" def __repr__(self) -> str: return f"RandomElasticate(p={self.prob}, scales={self.scales})" class RandomScaleCoords(object): def __init__(self, scale=[0.8, 1.2], p=0.5): self.scale = scale self.prob = p def __call__(self, coords_and_feats): if np.random.rand() < self.prob: scale = np.random.uniform(self.scale[0], self.scale[1]) coords_and_feats[:, :4] *= scale if len(coords_and_feats[0]) > 5: coords_and_feats[:, 5:] *= scale return coords_and_feats def __str__(self) -> str: return f"RandomScaleCoords(p={self.prob}, scale={self.scale})" def __repr__(self) -> str: return f"RandomScaleCoords(p={self.prob}, scale={self.scale})" class RandomScaleCoordsTranslation(object): def __init__(self, scale=[0.5, 2], p=0.5): self.scale = scale self.prob = p def __call__(self, coords_and_feats): if np.random.rand() < self.prob: scale = np.random.uniform(self.scale[0], self.scale[1]) coord1 = coords_and_feats[:, :4] coord1 *= scale coords_and_feats[:, :4] = coord1 if len(coords_and_feats[0]) > 5: coord2 = coords_and_feats[:, 5:] coord2 *= scale coords_and_feats[:, 5:] = coord2 return coords_and_feats def __str__(self) -> str: return f"RandomScaleCoordsTranslation(p={self.prob}, scale={self.scale})" def __repr__(self) -> str: return f"RandomScaleCoordsTranslation(p={self.prob}, scale={self.scale})" class RandomScaleFeats(object): def __init__(self, scale=[0.5, 2], p=0.5): self.scale = scale self.prob = p def __call__(self, coords_and_feats): feats = coords_and_feats[:, 4:] if np.random.rand() < self.prob: scale = np.random.uniform(self.scale[0], self.scale[1]) feats *= scale coords_and_feats[:, 4:] = feats return coords_and_feats def __str__(self) -> str: return f"RandomScaleFeats(p={self.prob}, scale={self.scale})" def __repr__(self) -> str: return f"RandomScaleFeats(p={self.prob}, scale={self.scale})" class RandomShift(object): def __init__(self, shift=[5, 5, 5], p=0.5): self.shift = shift self.prob = p def __call__(self, coords_and_feats): coord = coords_and_feats[:, :3] if np.random.rand() < self.prob: shift_x = np.random.uniform(-self.shift[0], self.shift[0]) shift_y = np.random.uniform(-self.shift[1], self.shift[1]) shift_z = np.random.uniform(-self.shift[2], self.shift[2]) coord += [shift_x, shift_y, shift_z] coords_and_feats[:, :3] = coord return coords_and_feats def __str__(self) -> str: return f"RandomShift(p={self.prob}, shift={self.shift})" def __repr__(self) -> str: return f"RandomShift(p={self.prob}, shift={self.shift})" class RandomFlip(object): def __init__(self, p=0.5): self.prob = p def __call__(self, coords_and_feats): coord = coords_and_feats[:, :3] if np.random.rand() < self.prob: if np.random.rand() < 0.5: coord[:, 0] = -coord[:, 0] if np.random.rand() < 0.5: coord[:, 1] = -coord[:, 1] coords_and_feats[:, :3] = coord return coords_and_feats def __str__(self) -> str: return f"RandomFlip(p={self.prob})" def __repr__(self) -> str: return f"RandomFlip(p={self.prob})" class RandomJitter(object): def __init__(self, sigma=1, clip=5, p=0.5): self.sigma = sigma self.clip = clip self.prob = p def __call__(self, coords_and_feats): coord = coords_and_feats[:, :3] assert self.clip > 0 if np.random.rand() < self.prob: jitter = np.clip( self.sigma * np.random.randn(coord.shape[0], 3), -self.clip, self.clip ) coord += jitter coords_and_feats[:, :3] = coord return coords_and_feats def __str__(self) -> str: return f"RandomJitter(p={self.prob}, sigma={self.sigma}, clip={self.clip})" def __repr__(self) -> str: return f"RandomJitter(p={self.prob}, sigma={self.sigma}, clip={self.clip})" class RandomJitterLength(object): def __init__(self, sigma=0.1, clip=1, p=0.5): self.sigma = sigma self.clip = clip self.prob = p def __call__(self, coords_and_feats): feats1 = coords_and_feats[:, 3:4] assert self.clip > 0 if np.random.rand() < self.prob: jitter1 = np.clip( self.sigma * np.random.randn(*feats1.shape), -self.clip, self.clip ) feats1 += jitter1 if len(coords_and_feats[0]) > 5: feats2 = coords_and_feats[:, 5:] jitter2 = np.clip( self.sigma * np.random.randn(*feats2.shape), -self.clip, self.clip ) feats2 += jitter2 coords_and_feats[:, 5:] = feats2 coords_and_feats[:, 3:4] = feats1 return coords_and_feats def __str__(self) -> str: return ( f"RandomJitterLength(p={self.prob}, sigma={self.sigma}, clip={self.clip})" ) def __repr__(self) -> str: return ( f"RandomJitterLength(p={self.prob}, sigma={self.sigma}, clip={self.clip})" ) if __name__ == "__main__": transform = RandomScaleCoordsTranslation(p=1) coords_feats = np.random.rand(1024, 3) # coords_feats = np.concatenate([ # np.zeros((1,29)), # coords_feats # ]) print(coords_feats[-1]) transformed = transform(coords_feats) print(transformed[-1]) print(coords_feats[-1])