@@ -248,6 +248,21 @@ def affine_bounding_box():
248
248
)
249
249
250
250
251
+ @register_kernel_info_from_sample_inputs_fn
252
+ def rotate_bounding_box ():
253
+ for bounding_box , angle , expand , center in itertools .product (
254
+ make_bounding_boxes (), [- 87 , 15 , 90 ], [True , False ], [None , [12 , 23 ]] # angle # expand # center
255
+ ):
256
+ yield SampleInput (
257
+ bounding_box ,
258
+ format = bounding_box .format ,
259
+ image_size = bounding_box .image_size ,
260
+ angle = angle ,
261
+ expand = expand ,
262
+ center = center ,
263
+ )
264
+
265
+
251
266
@pytest .mark .parametrize (
252
267
"kernel" ,
253
268
[
@@ -330,7 +345,7 @@ def _compute_expected_bbox(bbox, angle_, translate_, scale_, shear_, center_):
330
345
np .max (transformed_points [:, 1 ]),
331
346
]
332
347
out_bbox = features .BoundingBox (
333
- out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = ( 32 , 32 ) , dtype = torch .float32
348
+ out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = bbox . image_size , dtype = torch .float32
334
349
)
335
350
out_bbox = convert_bounding_box_format (
336
351
out_bbox , old_format = features .BoundingBoxFormat .XYXY , new_format = bbox .format , copy = False
@@ -345,25 +360,25 @@ def _compute_expected_bbox(bbox, angle_, translate_, scale_, shear_, center_):
345
360
],
346
361
extra_dims = ((4 ,),),
347
362
):
363
+ bboxes_format = bboxes .format
364
+ bboxes_image_size = bboxes .image_size
365
+
348
366
output_bboxes = F .affine_bounding_box (
349
367
bboxes ,
350
- bboxes . format ,
351
- image_size = image_size ,
368
+ bboxes_format ,
369
+ image_size = bboxes_image_size ,
352
370
angle = angle ,
353
371
translate = (translate , translate ),
354
372
scale = scale ,
355
373
shear = (shear , shear ),
356
374
center = center ,
357
375
)
376
+
358
377
if center is None :
359
- center = [s // 2 for s in image_size [::- 1 ]]
378
+ center = [s // 2 for s in bboxes_image_size [::- 1 ]]
360
379
361
- bboxes_format = bboxes .format
362
- bboxes_image_size = bboxes .image_size
363
380
if bboxes .ndim < 2 :
364
- bboxes = [
365
- bboxes ,
366
- ]
381
+ bboxes = [bboxes ]
367
382
368
383
expected_bboxes = []
369
384
for bbox in bboxes :
@@ -427,3 +442,147 @@ def test_correctness_affine_bounding_box_on_fixed_input(device):
427
442
assert len (output_boxes ) == len (expected_bboxes )
428
443
for a_out_box , out_box in zip (expected_bboxes , output_boxes .cpu ()):
429
444
np .testing .assert_allclose (out_box .cpu ().numpy (), a_out_box )
445
+
446
+
447
+ @pytest .mark .parametrize ("angle" , range (- 90 , 90 , 56 ))
448
+ @pytest .mark .parametrize ("expand" , [True , False ])
449
+ @pytest .mark .parametrize ("center" , [None , (12 , 14 )])
450
+ def test_correctness_rotate_bounding_box (angle , expand , center ):
451
+ def _compute_expected_bbox (bbox , angle_ , expand_ , center_ ):
452
+ affine_matrix = _compute_affine_matrix (angle_ , [0.0 , 0.0 ], 1.0 , [0.0 , 0.0 ], center_ )
453
+ affine_matrix = affine_matrix [:2 , :]
454
+
455
+ image_size = bbox .image_size
456
+ bbox_xyxy = convert_bounding_box_format (
457
+ bbox , old_format = bbox .format , new_format = features .BoundingBoxFormat .XYXY
458
+ )
459
+ points = np .array (
460
+ [
461
+ [bbox_xyxy [0 ].item (), bbox_xyxy [1 ].item (), 1.0 ],
462
+ [bbox_xyxy [2 ].item (), bbox_xyxy [1 ].item (), 1.0 ],
463
+ [bbox_xyxy [0 ].item (), bbox_xyxy [3 ].item (), 1.0 ],
464
+ [bbox_xyxy [2 ].item (), bbox_xyxy [3 ].item (), 1.0 ],
465
+ # image frame
466
+ [0.0 , 0.0 , 1.0 ],
467
+ [0.0 , image_size [0 ], 1.0 ],
468
+ [image_size [1 ], image_size [0 ], 1.0 ],
469
+ [image_size [1 ], 0.0 , 1.0 ],
470
+ ]
471
+ )
472
+ transformed_points = np .matmul (points , affine_matrix .T )
473
+ out_bbox = [
474
+ np .min (transformed_points [:4 , 0 ]),
475
+ np .min (transformed_points [:4 , 1 ]),
476
+ np .max (transformed_points [:4 , 0 ]),
477
+ np .max (transformed_points [:4 , 1 ]),
478
+ ]
479
+ if expand_ :
480
+ tr_x = np .min (transformed_points [4 :, 0 ])
481
+ tr_y = np .min (transformed_points [4 :, 1 ])
482
+ out_bbox [0 ] -= tr_x
483
+ out_bbox [1 ] -= tr_y
484
+ out_bbox [2 ] -= tr_x
485
+ out_bbox [3 ] -= tr_y
486
+
487
+ out_bbox = features .BoundingBox (
488
+ out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = image_size , dtype = torch .float32
489
+ )
490
+ out_bbox = convert_bounding_box_format (
491
+ out_bbox , old_format = features .BoundingBoxFormat .XYXY , new_format = bbox .format , copy = False
492
+ )
493
+ return out_bbox .to (bbox .device )
494
+
495
+ image_size = (32 , 38 )
496
+
497
+ for bboxes in make_bounding_boxes (
498
+ image_sizes = [
499
+ image_size ,
500
+ ],
501
+ extra_dims = ((4 ,),),
502
+ ):
503
+ bboxes_format = bboxes .format
504
+ bboxes_image_size = bboxes .image_size
505
+
506
+ output_bboxes = F .rotate_bounding_box (
507
+ bboxes ,
508
+ bboxes_format ,
509
+ image_size = bboxes_image_size ,
510
+ angle = angle ,
511
+ expand = expand ,
512
+ center = center ,
513
+ )
514
+
515
+ if center is None :
516
+ center = [s // 2 for s in bboxes_image_size [::- 1 ]]
517
+
518
+ if bboxes .ndim < 2 :
519
+ bboxes = [bboxes ]
520
+
521
+ expected_bboxes = []
522
+ for bbox in bboxes :
523
+ bbox = features .BoundingBox (bbox , format = bboxes_format , image_size = bboxes_image_size )
524
+ expected_bboxes .append (_compute_expected_bbox (bbox , - angle , expand , center ))
525
+ if len (expected_bboxes ) > 1 :
526
+ expected_bboxes = torch .stack (expected_bboxes )
527
+ else :
528
+ expected_bboxes = expected_bboxes [0 ]
529
+ print ("input:" , bboxes )
530
+ print ("output_bboxes:" , output_bboxes )
531
+ print ("expected_bboxes:" , expected_bboxes )
532
+ torch .testing .assert_close (output_bboxes , expected_bboxes )
533
+
534
+
535
+ @pytest .mark .parametrize ("device" , cpu_and_gpu ())
536
+ @pytest .mark .parametrize ("expand" , [False ]) # expand=True does not match D2, analysis in progress
537
+ def test_correctness_rotate_bounding_box_on_fixed_input (device , expand ):
538
+ # Check transformation against known expected output
539
+ image_size = (64 , 64 )
540
+ # xyxy format
541
+ in_boxes = [
542
+ [1 , 1 , 5 , 5 ],
543
+ [1 , image_size [0 ] - 6 , 5 , image_size [0 ] - 2 ],
544
+ [image_size [1 ] - 6 , image_size [0 ] - 6 , image_size [1 ] - 2 , image_size [0 ] - 2 ],
545
+ [image_size [1 ] // 2 - 10 , image_size [0 ] // 2 - 10 , image_size [1 ] // 2 + 10 , image_size [0 ] // 2 + 10 ],
546
+ ]
547
+ in_boxes = features .BoundingBox (
548
+ in_boxes , format = features .BoundingBoxFormat .XYXY , image_size = image_size , dtype = torch .float64
549
+ ).to (device )
550
+ # Tested parameters
551
+ angle = 45
552
+ center = None if expand else [12 , 23 ]
553
+
554
+ # # Expected bboxes computed using Detectron2:
555
+ # from detectron2.data.transforms import RotationTransform, AugmentationList
556
+ # from detectron2.data.transforms import AugInput
557
+ # import cv2
558
+ # inpt = AugInput(im1, boxes=np.array(in_boxes, dtype="float32"))
559
+ # augs = AugmentationList([RotationTransform(*size, angle, expand=expand, center=center, interp=cv2.INTER_NEAREST), ])
560
+ # out = augs(inpt)
561
+ # print(inpt.boxes)
562
+ if expand :
563
+ expected_bboxes = [
564
+ [1.65937957 , 42.67157288 , 7.31623382 , 48.32842712 ],
565
+ [41.96446609 , 82.9766594 , 47.62132034 , 88.63351365 ],
566
+ [82.26955262 , 42.67157288 , 87.92640687 , 48.32842712 ],
567
+ [31.35786438 , 31.35786438 , 59.64213562 , 59.64213562 ],
568
+ ]
569
+ else :
570
+ expected_bboxes = [
571
+ [- 11.33452378 , 12.39339828 , - 5.67766953 , 18.05025253 ],
572
+ [28.97056275 , 52.69848481 , 34.627417 , 58.35533906 ],
573
+ [69.27564928 , 12.39339828 , 74.93250353 , 18.05025253 ],
574
+ [18.36396103 , 1.07968978 , 46.64823228 , 29.36396103 ],
575
+ ]
576
+
577
+ output_boxes = F .rotate_bounding_box (
578
+ in_boxes ,
579
+ in_boxes .format ,
580
+ in_boxes .image_size ,
581
+ angle ,
582
+ expand = expand ,
583
+ center = center ,
584
+ )
585
+
586
+ assert len (output_boxes ) == len (expected_bboxes )
587
+ for a_out_box , out_box in zip (expected_bboxes , output_boxes .cpu ()):
588
+ np .testing .assert_allclose (out_box .cpu ().numpy (), a_out_box )
0 commit comments