def test_quat_grad_exists(self): """Quaternion calculations are differentiable.""" rotation = random_rotation() rotation.requires_grad = True modified = quaternion_to_matrix(matrix_to_quaternion(rotation)) [g] = torch.autograd.grad(modified.sum(), rotation) self.assertTrue(torch.isfinite(g).all())
def test_box_planar_dir(self): device = torch.device("cuda:0") box1 = torch.tensor( UNIT_BOX, dtype=torch.float32, device=device, ) n1 = torch.tensor( [ [0.0, 0.0, 1.0], [0.0, -1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], ], device=device, dtype=torch.float32, ) RR = random_rotation(dtype=torch.float32, device=device) TT = torch.rand((1, 3), dtype=torch.float32, device=device) box2 = box1 @ RR.transpose(0, 1) + TT n2 = n1 @ RR.transpose(0, 1) self.assertClose(box_planar_dir(box1), n1) self.assertClose(box_planar_dir(box2), n2)
def test_euler_grad_exists(self): """Euler angle calculations are differentiable.""" rotation = random_rotation(dtype=torch.float64, requires_grad=True) for convention in self._all_euler_angle_conventions(): euler_angles = matrix_to_euler_angles(rotation, convention) mdata = euler_angles_to_matrix(euler_angles, convention) [g] = torch.autograd.grad(mdata.sum(), rotation) self.assertTrue(torch.isfinite(g).all())
def test_box_volume(self): device = torch.device("cuda:0") box1 = torch.tensor( [ [3.1673, -2.2574, 0.4817], [4.6470, 0.2223, 2.4197], [5.2200, 1.1844, 0.7510], [3.7403, -1.2953, -1.1869], [-4.9316, 2.5724, 0.4856], [-3.4519, 5.0521, 2.4235], [-2.8789, 6.0142, 0.7549], [-4.3586, 3.5345, -1.1831], ], dtype=torch.float32, device=device, ) box2 = torch.tensor( [ [0.5623, 4.0647, 3.4334], [3.3584, 4.3191, 1.1791], [3.0724, -5.9235, -0.3315], [0.2763, -6.1779, 1.9229], [-2.0773, 4.6121, 0.2213], [0.7188, 4.8665, -2.0331], [0.4328, -5.3761, -3.5436], [-2.3633, -5.6305, -1.2893], ], dtype=torch.float32, device=device, ) box3 = torch.tensor( [ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1], ], dtype=torch.float32, device=device, ) RR = random_rotation(dtype=torch.float32, device=device) TT = torch.rand((1, 3), dtype=torch.float32, device=device) box4 = box3 @ RR.transpose(0, 1) + TT self.assertClose(box_volume(box1).cpu(), torch.tensor(65.899010), atol=1e-3) self.assertClose(box_volume(box2).cpu(), torch.tensor(156.386719), atol=1e-3) self.assertClose(box_volume(box3).cpu(), torch.tensor(1.0), atol=1e-3) self.assertClose(box_volume(box4).cpu(), torch.tensor(1.0), atol=1e-3)
def test_random_rotation_invariant(self): """The image of the x-axis isn't biased among quadrants.""" N = 1000 base = random_rotation() quadrants = list(itertools.product([False, True], repeat=3)) matrices = random_rotations(N) transformed = torch.matmul(base, matrices) transformed2 = torch.matmul(matrices, base) for k, results in enumerate([matrices, transformed, transformed2]): counts = {i: 0 for i in quadrants} for j in range(N): counts[tuple(i.item() > 0 for i in results[j, 0])] += 1 average = N / 8.0 counts_tensor = torch.tensor(list(counts.values())) chisquare_statistic = torch.sum( (counts_tensor - average) * (counts_tensor - average) / average ) # The 0.1 significance level for chisquare(8-1) is # scipy.stats.chi2(7).ppf(0.9) == 12.017. self.assertLess(chisquare_statistic, 12, (counts, chisquare_statistic, k))
def _test_iou(self, overlap_fn, device): box1 = torch.tensor( UNIT_BOX, dtype=torch.float32, device=device, ) # 1st test: same box, iou = 1.0 vol, iou = overlap_fn(box1[None], box1[None]) self.assertClose( vol, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) self.assertClose( iou, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) # 2nd test dd = random.random() box2 = box1 + torch.tensor([[0.0, dd, 0.0]], device=device) vol, iou = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # symmetry vol, iou = overlap_fn(box2[None], box1[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # 3rd test dd = random.random() box2 = box1 + torch.tensor([[dd, 0.0, 0.0]], device=device) vol, _ = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # symmetry vol, _ = overlap_fn(box2[None], box1[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # 4th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) vol, _ = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # symmetry vol, _ = overlap_fn(box2[None], box1[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # Also check IoU is 1 when computing overlap with the same shifted box vol, iou = overlap_fn(box2[None], box2[None]) self.assertClose( iou, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) # 5th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) RR = random_rotation(dtype=torch.float32, device=device) box1r = box1 @ RR.transpose(0, 1) box2r = box2 @ RR.transpose(0, 1) vol, _ = overlap_fn(box1r[None], box2r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # symmetry vol, _ = overlap_fn(box2r[None], box1r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # 6th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) RR = random_rotation(dtype=torch.float32, device=device) TT = torch.rand((1, 3), dtype=torch.float32, device=device) box1r = box1 @ RR.transpose(0, 1) + TT box2r = box2 @ RR.transpose(0, 1) + TT vol, _ = overlap_fn(box1r[None], box2r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), atol=1e-7, ) # symmetry vol, _ = overlap_fn(box2r[None], box1r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), atol=1e-7, ) # 7th test: hand coded example and test with meshlab output # Meshlab procedure to compute volumes of shapes # 1. Load a shape, then Filters # -> Remeshing, Simplification, Reconstruction -> Convex Hull # 2. Select the convex hull shape (This is important!) # 3. Then Filters -> Quality Measure and Computation -> Compute Geometric Measures # 3. Check for "Mesh Volume" in the stdout box1r = torch.tensor( [ [3.1673, -2.2574, 0.4817], [4.6470, 0.2223, 2.4197], [5.2200, 1.1844, 0.7510], [3.7403, -1.2953, -1.1869], [-4.9316, 2.5724, 0.4856], [-3.4519, 5.0521, 2.4235], [-2.8789, 6.0142, 0.7549], [-4.3586, 3.5345, -1.1831], ], device=device, ) box2r = torch.tensor( [ [0.5623, 4.0647, 3.4334], [3.3584, 4.3191, 1.1791], [3.0724, -5.9235, -0.3315], [0.2763, -6.1779, 1.9229], [-2.0773, 4.6121, 0.2213], [0.7188, 4.8665, -2.0331], [0.4328, -5.3761, -3.5436], [-2.3633, -5.6305, -1.2893], ], device=device, ) # from Meshlab: vol_inters = 33.558529 vol_box1 = 65.899010 vol_box2 = 156.386719 iou_mesh = vol_inters / (vol_box1 + vol_box2 - vol_inters) vol, iou = overlap_fn(box1r[None], box2r[None]) self.assertClose(vol, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(iou, torch.tensor([[iou_mesh]], device=device), atol=1e-1) # symmetry vol, iou = overlap_fn(box2r[None], box1r[None]) self.assertClose(vol, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(iou, torch.tensor([[iou_mesh]], device=device), atol=1e-1) # 8th test: compare with sampling # create box1 ctrs = torch.rand((2, 3), device=device) whl = torch.rand((2, 3), device=device) * 10.0 + 1.0 # box8a & box8b box8a = self.create_box(ctrs[0], whl[0]) box8b = self.create_box(ctrs[1], whl[1]) RR1 = random_rotation(dtype=torch.float32, device=device) TT1 = torch.rand((1, 3), dtype=torch.float32, device=device) RR2 = random_rotation(dtype=torch.float32, device=device) TT2 = torch.rand((1, 3), dtype=torch.float32, device=device) box1r = box8a @ RR1.transpose(0, 1) + TT1 box2r = box8b @ RR2.transpose(0, 1) + TT2 vol, iou = overlap_fn(box1r[None], box2r[None]) iou_sampling = self._box3d_overlap_sampling_batched(box1r[None], box2r[None], num_samples=10000) self.assertClose(iou, iou_sampling, atol=1e-2) # symmetry vol, iou = overlap_fn(box2r[None], box1r[None]) self.assertClose(iou, iou_sampling, atol=1e-2) # 9th test: non overlapping boxes, iou = 0.0 box2 = box1 + torch.tensor([[0.0, 100.0, 0.0]], device=device) vol, iou = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) self.assertClose( iou, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) # symmetry vol, iou = overlap_fn(box2[None], box1[None]) self.assertClose( vol, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) self.assertClose( iou, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) # 10th test: Non coplanar verts in a plane box10 = box1 + torch.rand((8, 3), dtype=torch.float32, device=device) msg = "Plane vertices are not coplanar" with self.assertRaisesRegex(ValueError, msg): overlap_fn(box10[None], box10[None]) # 11th test: Skewed bounding boxes but all verts are coplanar box_skew_1 = torch.tensor( [ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [-2, -2, 2], [2, -2, 2], [2, 2, 2], [-2, 2, 2], ], dtype=torch.float32, device=device, ) box_skew_2 = torch.tensor( [ [2.015995, 0.695233, 2.152806], [2.832533, 0.663448, 1.576389], [2.675445, -0.309592, 1.407520], [1.858907, -0.277806, 1.983936], [-0.413922, 3.161758, 2.044343], [2.852230, 3.034615, -0.261321], [2.223878, -0.857545, -0.936800], [-1.042273, -0.730402, 1.368864], ], dtype=torch.float32, device=device, ) vol1 = 14.000 vol2 = 14.000005 vol_inters = 5.431122 iou = vol_inters / (vol1 + vol2 - vol_inters) vols, ious = overlap_fn(box_skew_1[None], box_skew_2[None]) self.assertClose(vols, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(ious, torch.tensor([[iou]], device=device), atol=1e-1) # symmetry vols, ious = overlap_fn(box_skew_2[None], box_skew_1[None]) self.assertClose(vols, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(ious, torch.tensor([[iou]], device=device), atol=1e-1) # 12th test: Zero area bounding box (from GH issue #992) box12a = torch.tensor( [ [-1.0000, -1.0000, -0.5000], [1.0000, -1.0000, -0.5000], [1.0000, 1.0000, -0.5000], [-1.0000, 1.0000, -0.5000], [-1.0000, -1.0000, 0.5000], [1.0000, -1.0000, 0.5000], [1.0000, 1.0000, 0.5000], [-1.0000, 1.0000, 0.5000], ], device=device, dtype=torch.float32, ) box12b = torch.tensor( [ [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], ], device=device, dtype=torch.float32, ) msg = "Planes have zero areas" with self.assertRaisesRegex(ValueError, msg): overlap_fn(box12a[None], box12b[None]) # symmetry with self.assertRaisesRegex(ValueError, msg): overlap_fn(box12b[None], box12a[None]) # 13th test: From GH issue #992 # Zero area coplanar face after intersection ctrs = torch.tensor([[0.0, 0.0, 0.0], [-1.0, 1.0, 0.0]]) whl = torch.tensor([[2.0, 2.0, 2.0], [2.0, 2, 2]]) box13a = TestIoU3D.create_box(ctrs[0], whl[0]) box13b = TestIoU3D.create_box(ctrs[1], whl[1]) vol, iou = overlap_fn(box13a[None], box13b[None]) self.assertClose( vol, torch.tensor([[2.0]], device=vol.device, dtype=vol.dtype)) # 14th test: From GH issue #992 # Random rotation, same boxes, iou should be 1.0 corners = (torch.tensor( [ [-1.0, -1.0, -1.0], [1.0, -1.0, -1.0], [1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], [-1.0, -1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 1.0, 1.0], ], device=device, dtype=torch.float32, ) * 0.5) yaw = torch.tensor(0.185) Rot = torch.tensor( [ [torch.cos(yaw), 0.0, torch.sin(yaw)], [0.0, 1.0, 0.0], [-torch.sin(yaw), 0.0, torch.cos(yaw)], ], dtype=torch.float32, device=device, ) corners = (Rot.mm(corners.t())).t() vol, iou = overlap_fn(corners[None], corners[None]) self.assertClose(iou, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype), atol=1e-2) # 15th test: From GH issue #1082 box15a = torch.tensor( [ [-2.5629019, 4.13995749, -1.76344576], [1.92329434, 4.28127117, -1.86155124], [1.86994571, 5.97489644, -1.86155124], [-2.61625053, 5.83358276, -1.76344576], [-2.53123587, 4.14095496, -0.31397536], [1.95496037, 4.28226864, -0.41208084], [1.90161174, 5.97589391, -0.41208084], [-2.5845845, 5.83458023, -0.31397536], ], device=device, dtype=torch.float32, ) box15b = torch.tensor( [ [-2.6256125, 4.13036357, -1.82893437], [1.87201008, 4.25296695, -1.82893437], [1.82562476, 5.95458116, -1.82893437], [-2.67199782, 5.83197777, -1.82893437], [-2.6256125, 4.13036357, -0.40095884], [1.87201008, 4.25296695, -0.40095884], [1.82562476, 5.95458116, -0.40095884], [-2.67199782, 5.83197777, -0.40095884], ], device=device, dtype=torch.float32, ) vol, iou = overlap_fn(box15a[None], box15b[None]) self.assertClose(iou, torch.tensor([[0.91]], device=vol.device, dtype=vol.dtype), atol=1e-2)
def _test_iou(self, overlap_fn, device): box1 = torch.tensor( UNIT_BOX, dtype=torch.float32, device=device, ) # 1st test: same box, iou = 1.0 vol, iou = overlap_fn(box1[None], box1[None]) self.assertClose( vol, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) self.assertClose( iou, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) # 2nd test dd = random.random() box2 = box1 + torch.tensor([[0.0, dd, 0.0]], device=device) vol, iou = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # 3rd test dd = random.random() box2 = box1 + torch.tensor([[dd, 0.0, 0.0]], device=device) vol, _ = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[1 - dd]], device=vol.device, dtype=vol.dtype)) # 4th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) vol, _ = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # Also check IoU is 1 when computing overlap with the same shifted box vol, iou = overlap_fn(box2[None], box2[None]) self.assertClose( iou, torch.tensor([[1.0]], device=vol.device, dtype=vol.dtype)) # 5th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) RR = random_rotation(dtype=torch.float32, device=device) box1r = box1 @ RR.transpose(0, 1) box2r = box2 @ RR.transpose(0, 1) vol, _ = overlap_fn(box1r[None], box2r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), ) # 6th test ddx, ddy, ddz = random.random(), random.random(), random.random() box2 = box1 + torch.tensor([[ddx, ddy, ddz]], device=device) RR = random_rotation(dtype=torch.float32, device=device) TT = torch.rand((1, 3), dtype=torch.float32, device=device) box1r = box1 @ RR.transpose(0, 1) + TT box2r = box2 @ RR.transpose(0, 1) + TT vol, _ = overlap_fn(box1r[None], box2r[None]) self.assertClose( vol, torch.tensor( [[(1 - ddx) * (1 - ddy) * (1 - ddz)]], device=vol.device, dtype=vol.dtype, ), atol=1e-7, ) # 7th test: hand coded example and test with meshlab output # Meshlab procedure to compute volumes of shapes # 1. Load a shape, then Filters # -> Remeshing, Simplification, Reconstruction -> Convex Hull # 2. Select the convex hull shape (This is important!) # 3. Then Filters -> Quality Measure and Computation -> Compute Geometric Measures # 3. Check for "Mesh Volume" in the stdout box1r = torch.tensor( [ [3.1673, -2.2574, 0.4817], [4.6470, 0.2223, 2.4197], [5.2200, 1.1844, 0.7510], [3.7403, -1.2953, -1.1869], [-4.9316, 2.5724, 0.4856], [-3.4519, 5.0521, 2.4235], [-2.8789, 6.0142, 0.7549], [-4.3586, 3.5345, -1.1831], ], device=device, ) box2r = torch.tensor( [ [0.5623, 4.0647, 3.4334], [3.3584, 4.3191, 1.1791], [3.0724, -5.9235, -0.3315], [0.2763, -6.1779, 1.9229], [-2.0773, 4.6121, 0.2213], [0.7188, 4.8665, -2.0331], [0.4328, -5.3761, -3.5436], [-2.3633, -5.6305, -1.2893], ], device=device, ) # from Meshlab: vol_inters = 33.558529 vol_box1 = 65.899010 vol_box2 = 156.386719 iou_mesh = vol_inters / (vol_box1 + vol_box2 - vol_inters) vol, iou = overlap_fn(box1r[None], box2r[None]) self.assertClose(vol, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(iou, torch.tensor([[iou_mesh]], device=device), atol=1e-1) # 8th test: compare with sampling # create box1 ctrs = torch.rand((2, 3), device=device) whl = torch.rand((2, 3), device=device) * 10.0 + 1.0 # box8a & box8b box8a = self.create_box(ctrs[0], whl[0]) box8b = self.create_box(ctrs[1], whl[1]) RR1 = random_rotation(dtype=torch.float32, device=device) TT1 = torch.rand((1, 3), dtype=torch.float32, device=device) RR2 = random_rotation(dtype=torch.float32, device=device) TT2 = torch.rand((1, 3), dtype=torch.float32, device=device) box1r = box8a @ RR1.transpose(0, 1) + TT1 box2r = box8b @ RR2.transpose(0, 1) + TT2 vol, iou = overlap_fn(box1r[None], box2r[None]) iou_sampling = self._box3d_overlap_sampling_batched(box1r[None], box2r[None], num_samples=10000) self.assertClose(iou, iou_sampling, atol=1e-2) # 9th test: non overlapping boxes, iou = 0.0 box2 = box1 + torch.tensor([[0.0, 100.0, 0.0]], device=device) vol, iou = overlap_fn(box1[None], box2[None]) self.assertClose( vol, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) self.assertClose( iou, torch.tensor([[0.0]], device=vol.device, dtype=vol.dtype)) # 10th test: Non coplanar verts in a plane box10 = box1 + torch.rand((8, 3), dtype=torch.float32, device=device) msg = "Plane vertices are not coplanar" with self.assertRaisesRegex(ValueError, msg): overlap_fn(box10[None], box10[None]) # 11th test: Skewed bounding boxes but all verts are coplanar box_skew_1 = torch.tensor( [ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [-2, -2, 2], [2, -2, 2], [2, 2, 2], [-2, 2, 2], ], dtype=torch.float32, device=device, ) box_skew_2 = torch.tensor( [ [2.015995, 0.695233, 2.152806], [2.832533, 0.663448, 1.576389], [2.675445, -0.309592, 1.407520], [1.858907, -0.277806, 1.983936], [-0.413922, 3.161758, 2.044343], [2.852230, 3.034615, -0.261321], [2.223878, -0.857545, -0.936800], [-1.042273, -0.730402, 1.368864], ], dtype=torch.float32, device=device, ) vol1 = 14.000 vol2 = 14.000005 vol_inters = 5.431122 iou = vol_inters / (vol1 + vol2 - vol_inters) vols, ious = overlap_fn(box_skew_1[None], box_skew_2[None]) self.assertClose(vols, torch.tensor([[vol_inters]], device=device), atol=1e-1) self.assertClose(ious, torch.tensor([[iou]], device=device), atol=1e-1)