@@ -111,6 +111,11 @@ def _test_iou(self, overlap_fn, device):
111111 self .assertClose (
112112 vol , torch .tensor ([[1 - dd ]], device = vol .device , dtype = vol .dtype )
113113 )
114+ # symmetry
115+ vol , iou = overlap_fn (box2 [None ], box1 [None ])
116+ self .assertClose (
117+ vol , torch .tensor ([[1 - dd ]], device = vol .device , dtype = vol .dtype )
118+ )
114119
115120 # 3rd test
116121 dd = random .random ()
@@ -119,6 +124,11 @@ def _test_iou(self, overlap_fn, device):
119124 self .assertClose (
120125 vol , torch .tensor ([[1 - dd ]], device = vol .device , dtype = vol .dtype )
121126 )
127+ # symmetry
128+ vol , _ = overlap_fn (box2 [None ], box1 [None ])
129+ self .assertClose (
130+ vol , torch .tensor ([[1 - dd ]], device = vol .device , dtype = vol .dtype )
131+ )
122132
123133 # 4th test
124134 ddx , ddy , ddz = random .random (), random .random (), random .random ()
@@ -132,6 +142,16 @@ def _test_iou(self, overlap_fn, device):
132142 dtype = vol .dtype ,
133143 ),
134144 )
145+ # symmetry
146+ vol , _ = overlap_fn (box2 [None ], box1 [None ])
147+ self .assertClose (
148+ vol ,
149+ torch .tensor (
150+ [[(1 - ddx ) * (1 - ddy ) * (1 - ddz )]],
151+ device = vol .device ,
152+ dtype = vol .dtype ,
153+ ),
154+ )
135155
136156 # Also check IoU is 1 when computing overlap with the same shifted box
137157 vol , iou = overlap_fn (box2 [None ], box2 [None ])
@@ -152,6 +172,16 @@ def _test_iou(self, overlap_fn, device):
152172 dtype = vol .dtype ,
153173 ),
154174 )
175+ # symmetry
176+ vol , _ = overlap_fn (box2r [None ], box1r [None ])
177+ self .assertClose (
178+ vol ,
179+ torch .tensor (
180+ [[(1 - ddx ) * (1 - ddy ) * (1 - ddz )]],
181+ device = vol .device ,
182+ dtype = vol .dtype ,
183+ ),
184+ )
155185
156186 # 6th test
157187 ddx , ddy , ddz = random .random (), random .random (), random .random ()
@@ -170,6 +200,17 @@ def _test_iou(self, overlap_fn, device):
170200 ),
171201 atol = 1e-7 ,
172202 )
203+ # symmetry
204+ vol , _ = overlap_fn (box2r [None ], box1r [None ])
205+ self .assertClose (
206+ vol ,
207+ torch .tensor (
208+ [[(1 - ddx ) * (1 - ddy ) * (1 - ddz )]],
209+ device = vol .device ,
210+ dtype = vol .dtype ,
211+ ),
212+ atol = 1e-7 ,
213+ )
173214
174215 # 7th test: hand coded example and test with meshlab output
175216
@@ -214,6 +255,10 @@ def _test_iou(self, overlap_fn, device):
214255 vol , iou = overlap_fn (box1r [None ], box2r [None ])
215256 self .assertClose (vol , torch .tensor ([[vol_inters ]], device = device ), atol = 1e-1 )
216257 self .assertClose (iou , torch .tensor ([[iou_mesh ]], device = device ), atol = 1e-1 )
258+ # symmetry
259+ vol , iou = overlap_fn (box2r [None ], box1r [None ])
260+ self .assertClose (vol , torch .tensor ([[vol_inters ]], device = device ), atol = 1e-1 )
261+ self .assertClose (iou , torch .tensor ([[iou_mesh ]], device = device ), atol = 1e-1 )
217262
218263 # 8th test: compare with sampling
219264 # create box1
@@ -232,14 +277,20 @@ def _test_iou(self, overlap_fn, device):
232277 iou_sampling = self ._box3d_overlap_sampling_batched (
233278 box1r [None ], box2r [None ], num_samples = 10000
234279 )
235-
280+ self .assertClose (iou , iou_sampling , atol = 1e-2 )
281+ # symmetry
282+ vol , iou = overlap_fn (box2r [None ], box1r [None ])
236283 self .assertClose (iou , iou_sampling , atol = 1e-2 )
237284
238285 # 9th test: non overlapping boxes, iou = 0.0
239286 box2 = box1 + torch .tensor ([[0.0 , 100.0 , 0.0 ]], device = device )
240287 vol , iou = overlap_fn (box1 [None ], box2 [None ])
241288 self .assertClose (vol , torch .tensor ([[0.0 ]], device = vol .device , dtype = vol .dtype ))
242289 self .assertClose (iou , torch .tensor ([[0.0 ]], device = vol .device , dtype = vol .dtype ))
290+ # symmetry
291+ vol , iou = overlap_fn (box2 [None ], box1 [None ])
292+ self .assertClose (vol , torch .tensor ([[0.0 ]], device = vol .device , dtype = vol .dtype ))
293+ self .assertClose (iou , torch .tensor ([[0.0 ]], device = vol .device , dtype = vol .dtype ))
243294
244295 # 10th test: Non coplanar verts in a plane
245296 box10 = box1 + torch .rand ((8 , 3 ), dtype = torch .float32 , device = device )
@@ -284,6 +335,56 @@ def _test_iou(self, overlap_fn, device):
284335 vols , ious = overlap_fn (box_skew_1 [None ], box_skew_2 [None ])
285336 self .assertClose (vols , torch .tensor ([[vol_inters ]], device = device ), atol = 1e-1 )
286337 self .assertClose (ious , torch .tensor ([[iou ]], device = device ), atol = 1e-1 )
338+ # symmetry
339+ vols , ious = overlap_fn (box_skew_2 [None ], box_skew_1 [None ])
340+ self .assertClose (vols , torch .tensor ([[vol_inters ]], device = device ), atol = 1e-1 )
341+ self .assertClose (ious , torch .tensor ([[iou ]], device = device ), atol = 1e-1 )
342+
343+ # 12th test: Zero area bounding box (from GH issue #992)
344+ box12a = torch .tensor (
345+ [
346+ [- 1.0000 , - 1.0000 , - 0.5000 ],
347+ [1.0000 , - 1.0000 , - 0.5000 ],
348+ [1.0000 , 1.0000 , - 0.5000 ],
349+ [- 1.0000 , 1.0000 , - 0.5000 ],
350+ [- 1.0000 , - 1.0000 , 0.5000 ],
351+ [1.0000 , - 1.0000 , 0.5000 ],
352+ [1.0000 , 1.0000 , 0.5000 ],
353+ [- 1.0000 , 1.0000 , 0.5000 ],
354+ ],
355+ device = device ,
356+ dtype = torch .float32 ,
357+ )
358+
359+ box12b = torch .tensor (
360+ [
361+ [0.0 , 0.0 , 0.0 ],
362+ [0.0 , 0.0 , 0.0 ],
363+ [0.0 , 0.0 , 0.0 ],
364+ [0.0 , 0.0 , 0.0 ],
365+ [0.0 , 0.0 , 0.0 ],
366+ [0.0 , 0.0 , 0.0 ],
367+ [0.0 , 0.0 , 0.0 ],
368+ [0.0 , 0.0 , 0.0 ],
369+ ],
370+ device = device ,
371+ dtype = torch .float32 ,
372+ )
373+ msg = "Planes have zero areas"
374+ with self .assertRaisesRegex (ValueError , msg ):
375+ overlap_fn (box12a [None ], box12b [None ])
376+ # symmetry
377+ with self .assertRaisesRegex (ValueError , msg ):
378+ overlap_fn (box12b [None ], box12a [None ])
379+
380+ # 13th test: From GH issue #992
381+ # Zero area coplanar face after intersection
382+ ctrs = torch .tensor ([[0.0 , 0.0 , 0.0 ], [- 1.0 , 1.0 , 0.0 ]])
383+ whl = torch .tensor ([[2.0 , 2.0 , 2.0 ], [2.0 , 2 , 2 ]])
384+ box13a = TestIoU3D .create_box (ctrs [0 ], whl [0 ])
385+ box13b = TestIoU3D .create_box (ctrs [1 ], whl [1 ])
386+ vol , iou = overlap_fn (box13a [None ], box13b [None ])
387+ self .assertClose (vol , torch .tensor ([[2.0 ]], device = vol .device , dtype = vol .dtype ))
287388
288389 def _test_real_boxes (self , overlap_fn , device ):
289390 data_filename = "./real_boxes.pkl"
@@ -577,6 +678,13 @@ def box_planar_dir(box: torch.Tensor, eps=1e-4) -> torch.Tensor:
577678 msg = "Plane vertices are not coplanar"
578679 raise ValueError (msg )
579680
681+ # Check all faces have non zero area
682+ area1 = torch .cross (v1 - v0 , v2 - v0 , dim = - 1 ).norm (dim = - 1 ) / 2
683+ area2 = torch .cross (v3 - v0 , v2 - v0 , dim = - 1 ).norm (dim = - 1 ) / 2
684+ if (area1 < eps ).any ().item () or (area2 < eps ).any ().item ():
685+ msg = "Planes have zero areas"
686+ raise ValueError (msg )
687+
580688 # We can write: `ctr = v0 + a * e0 + b * e1 + c * n`, (1).
581689 # With <e0, n> = 0 and <e1, n> = 0, where <.,.> refers to the dot product,
582690 # since that e0 is orthogonal to n. Same for e1.
@@ -607,6 +715,27 @@ def box_planar_dir(box: torch.Tensor, eps=1e-4) -> torch.Tensor:
607715 return n
608716
609717
718+ def tri_verts_area (tri_verts : torch .Tensor ) -> torch .Tensor :
719+ """
720+ Computes the area of the triangle faces in tri_verts
721+ Args:
722+ tri_verts: tensor of shape (T, 3, 3)
723+ Returns:
724+ areas: the area of the triangles (T, 1)
725+ """
726+ add_dim = False
727+ if tri_verts .ndim == 2 :
728+ tri_verts = tri_verts .unsqueeze (0 )
729+ add_dim = True
730+
731+ v0 , v1 , v2 = tri_verts .unbind (1 )
732+ areas = torch .cross (v1 - v0 , v2 - v0 , dim = - 1 ).norm (dim = - 1 ) / 2.0
733+
734+ if add_dim :
735+ areas = areas [0 ]
736+ return areas
737+
738+
610739def box_volume (box : torch .Tensor ) -> torch .Tensor :
611740 """
612741 Computes the volume of each box in boxes.
@@ -988,7 +1117,10 @@ def box3d_overlap_naive(box1: torch.Tensor, box2: torch.Tensor):
9881117 keep2 = torch .ones ((tri_verts2 .shape [0 ],), device = device , dtype = torch .bool )
9891118 for i1 in range (tri_verts1 .shape [0 ]):
9901119 for i2 in range (tri_verts2 .shape [0 ]):
991- if coplanar_tri_faces (tri_verts1 [i1 ], tri_verts2 [i2 ]):
1120+ if (
1121+ coplanar_tri_faces (tri_verts1 [i1 ], tri_verts2 [i2 ])
1122+ and tri_verts_area (tri_verts1 [i1 ]) > 1e-4
1123+ ):
9921124 keep2 [i2 ] = 0
9931125 keep2 = keep2 .nonzero ()[:, 0 ]
9941126 tri_verts2 = tri_verts2 [keep2 ]
0 commit comments