@@ -279,6 +279,20 @@ def affine_segmentation_mask():
279
279
translate = (translate , translate ),
280
280
scale = scale ,
281
281
shear = (shear , shear ),
282
+
283
+
284
+ @register_kernel_info_from_sample_inputs_fn
285
+ def rotate_bounding_box ():
286
+ for bounding_box , angle , expand , center in itertools .product (
287
+ make_bounding_boxes (), [- 87 , 15 , 90 ], [True , False ], [None , [12 , 23 ]] # angle # expand # center
288
+ ):
289
+ yield SampleInput (
290
+ bounding_box ,
291
+ format = bounding_box .format ,
292
+ image_size = bounding_box .image_size ,
293
+ angle = angle ,
294
+ expand = expand ,
295
+ center = center ,
282
296
)
283
297
284
298
@@ -364,7 +378,7 @@ def _compute_expected_bbox(bbox, angle_, translate_, scale_, shear_, center_):
364
378
np .max (transformed_points [:, 1 ]),
365
379
]
366
380
out_bbox = features .BoundingBox (
367
- out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = ( 32 , 32 ) , dtype = torch .float32
381
+ out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = bbox . image_size , dtype = torch .float32
368
382
)
369
383
out_bbox = convert_bounding_box_format (
370
384
out_bbox , old_format = features .BoundingBoxFormat .XYXY , new_format = bbox .format , copy = False
@@ -379,25 +393,25 @@ def _compute_expected_bbox(bbox, angle_, translate_, scale_, shear_, center_):
379
393
],
380
394
extra_dims = ((4 ,),),
381
395
):
396
+ bboxes_format = bboxes .format
397
+ bboxes_image_size = bboxes .image_size
398
+
382
399
output_bboxes = F .affine_bounding_box (
383
400
bboxes ,
384
- bboxes . format ,
385
- image_size = image_size ,
401
+ bboxes_format ,
402
+ image_size = bboxes_image_size ,
386
403
angle = angle ,
387
404
translate = (translate , translate ),
388
405
scale = scale ,
389
406
shear = (shear , shear ),
390
407
center = center ,
391
408
)
409
+
392
410
if center is None :
393
- center = [s // 2 for s in image_size [::- 1 ]]
411
+ center = [s // 2 for s in bboxes_image_size [::- 1 ]]
394
412
395
- bboxes_format = bboxes .format
396
- bboxes_image_size = bboxes .image_size
397
413
if bboxes .ndim < 2 :
398
- bboxes = [
399
- bboxes ,
400
- ]
414
+ bboxes = [bboxes ]
401
415
402
416
expected_bboxes = []
403
417
for bbox in bboxes :
@@ -531,3 +545,147 @@ def test_correctness_affine_segmentation_mask_on_fixed_input(device):
531
545
out_mask = F .affine_segmentation_mask (mask , 90 , [0.0 , 0.0 ], 64.0 / 32.0 , [0.0 , 0.0 ])
532
546
533
547
torch .testing .assert_close (out_mask , expected_mask )
548
+
549
+
550
+ @pytest .mark .parametrize ("angle" , range (- 90 , 90 , 56 ))
551
+ @pytest .mark .parametrize ("expand" , [True , False ])
552
+ @pytest .mark .parametrize ("center" , [None , (12 , 14 )])
553
+ def test_correctness_rotate_bounding_box (angle , expand , center ):
554
+ def _compute_expected_bbox (bbox , angle_ , expand_ , center_ ):
555
+ affine_matrix = _compute_affine_matrix (angle_ , [0.0 , 0.0 ], 1.0 , [0.0 , 0.0 ], center_ )
556
+ affine_matrix = affine_matrix [:2 , :]
557
+
558
+ image_size = bbox .image_size
559
+ bbox_xyxy = convert_bounding_box_format (
560
+ bbox , old_format = bbox .format , new_format = features .BoundingBoxFormat .XYXY
561
+ )
562
+ points = np .array (
563
+ [
564
+ [bbox_xyxy [0 ].item (), bbox_xyxy [1 ].item (), 1.0 ],
565
+ [bbox_xyxy [2 ].item (), bbox_xyxy [1 ].item (), 1.0 ],
566
+ [bbox_xyxy [0 ].item (), bbox_xyxy [3 ].item (), 1.0 ],
567
+ [bbox_xyxy [2 ].item (), bbox_xyxy [3 ].item (), 1.0 ],
568
+ # image frame
569
+ [0.0 , 0.0 , 1.0 ],
570
+ [0.0 , image_size [0 ], 1.0 ],
571
+ [image_size [1 ], image_size [0 ], 1.0 ],
572
+ [image_size [1 ], 0.0 , 1.0 ],
573
+ ]
574
+ )
575
+ transformed_points = np .matmul (points , affine_matrix .T )
576
+ out_bbox = [
577
+ np .min (transformed_points [:4 , 0 ]),
578
+ np .min (transformed_points [:4 , 1 ]),
579
+ np .max (transformed_points [:4 , 0 ]),
580
+ np .max (transformed_points [:4 , 1 ]),
581
+ ]
582
+ if expand_ :
583
+ tr_x = np .min (transformed_points [4 :, 0 ])
584
+ tr_y = np .min (transformed_points [4 :, 1 ])
585
+ out_bbox [0 ] -= tr_x
586
+ out_bbox [1 ] -= tr_y
587
+ out_bbox [2 ] -= tr_x
588
+ out_bbox [3 ] -= tr_y
589
+
590
+ out_bbox = features .BoundingBox (
591
+ out_bbox , format = features .BoundingBoxFormat .XYXY , image_size = image_size , dtype = torch .float32
592
+ )
593
+ out_bbox = convert_bounding_box_format (
594
+ out_bbox , old_format = features .BoundingBoxFormat .XYXY , new_format = bbox .format , copy = False
595
+ )
596
+ return out_bbox .to (bbox .device )
597
+
598
+ image_size = (32 , 38 )
599
+
600
+ for bboxes in make_bounding_boxes (
601
+ image_sizes = [
602
+ image_size ,
603
+ ],
604
+ extra_dims = ((4 ,),),
605
+ ):
606
+ bboxes_format = bboxes .format
607
+ bboxes_image_size = bboxes .image_size
608
+
609
+ output_bboxes = F .rotate_bounding_box (
610
+ bboxes ,
611
+ bboxes_format ,
612
+ image_size = bboxes_image_size ,
613
+ angle = angle ,
614
+ expand = expand ,
615
+ center = center ,
616
+ )
617
+
618
+ if center is None :
619
+ center = [s // 2 for s in bboxes_image_size [::- 1 ]]
620
+
621
+ if bboxes .ndim < 2 :
622
+ bboxes = [bboxes ]
623
+
624
+ expected_bboxes = []
625
+ for bbox in bboxes :
626
+ bbox = features .BoundingBox (bbox , format = bboxes_format , image_size = bboxes_image_size )
627
+ expected_bboxes .append (_compute_expected_bbox (bbox , - angle , expand , center ))
628
+ if len (expected_bboxes ) > 1 :
629
+ expected_bboxes = torch .stack (expected_bboxes )
630
+ else :
631
+ expected_bboxes = expected_bboxes [0 ]
632
+ print ("input:" , bboxes )
633
+ print ("output_bboxes:" , output_bboxes )
634
+ print ("expected_bboxes:" , expected_bboxes )
635
+ torch .testing .assert_close (output_bboxes , expected_bboxes )
636
+
637
+
638
+ @pytest .mark .parametrize ("device" , cpu_and_gpu ())
639
+ @pytest .mark .parametrize ("expand" , [False ]) # expand=True does not match D2, analysis in progress
640
+ def test_correctness_rotate_bounding_box_on_fixed_input (device , expand ):
641
+ # Check transformation against known expected output
642
+ image_size = (64 , 64 )
643
+ # xyxy format
644
+ in_boxes = [
645
+ [1 , 1 , 5 , 5 ],
646
+ [1 , image_size [0 ] - 6 , 5 , image_size [0 ] - 2 ],
647
+ [image_size [1 ] - 6 , image_size [0 ] - 6 , image_size [1 ] - 2 , image_size [0 ] - 2 ],
648
+ [image_size [1 ] // 2 - 10 , image_size [0 ] // 2 - 10 , image_size [1 ] // 2 + 10 , image_size [0 ] // 2 + 10 ],
649
+ ]
650
+ in_boxes = features .BoundingBox (
651
+ in_boxes , format = features .BoundingBoxFormat .XYXY , image_size = image_size , dtype = torch .float64
652
+ ).to (device )
653
+ # Tested parameters
654
+ angle = 45
655
+ center = None if expand else [12 , 23 ]
656
+
657
+ # # Expected bboxes computed using Detectron2:
658
+ # from detectron2.data.transforms import RotationTransform, AugmentationList
659
+ # from detectron2.data.transforms import AugInput
660
+ # import cv2
661
+ # inpt = AugInput(im1, boxes=np.array(in_boxes, dtype="float32"))
662
+ # augs = AugmentationList([RotationTransform(*size, angle, expand=expand, center=center, interp=cv2.INTER_NEAREST), ])
663
+ # out = augs(inpt)
664
+ # print(inpt.boxes)
665
+ if expand :
666
+ expected_bboxes = [
667
+ [1.65937957 , 42.67157288 , 7.31623382 , 48.32842712 ],
668
+ [41.96446609 , 82.9766594 , 47.62132034 , 88.63351365 ],
669
+ [82.26955262 , 42.67157288 , 87.92640687 , 48.32842712 ],
670
+ [31.35786438 , 31.35786438 , 59.64213562 , 59.64213562 ],
671
+ ]
672
+ else :
673
+ expected_bboxes = [
674
+ [- 11.33452378 , 12.39339828 , - 5.67766953 , 18.05025253 ],
675
+ [28.97056275 , 52.69848481 , 34.627417 , 58.35533906 ],
676
+ [69.27564928 , 12.39339828 , 74.93250353 , 18.05025253 ],
677
+ [18.36396103 , 1.07968978 , 46.64823228 , 29.36396103 ],
678
+ ]
679
+
680
+ output_boxes = F .rotate_bounding_box (
681
+ in_boxes ,
682
+ in_boxes .format ,
683
+ in_boxes .image_size ,
684
+ angle ,
685
+ expand = expand ,
686
+ center = center ,
687
+ )
688
+
689
+ assert len (output_boxes ) == len (expected_bboxes )
690
+ for a_out_box , out_box in zip (expected_bboxes , output_boxes .cpu ()):
691
+ np .testing .assert_allclose (out_box .cpu ().numpy (), a_out_box )
0 commit comments