@@ -477,3 +477,130 @@ def call(self, inputs: Any) -> Any:
477
477
axis = self .axis ,
478
478
output_type = self .output_type ,
479
479
name = self .name )
480
+
481
+
482
+ _or = tf .maximum
483
+ _and = tf .minimum
484
+ _reduce_or = tf .reduce_max
485
+
486
+
487
+ def _tensor_sum_vectors (a , b ):
488
+ return tf .reshape (a , [1 , 1 , 1 , - 1 ]) + tf .reshape (b , [1 , 1 , - 1 , 1 ])
489
+
490
+
491
+ def _tensor_product_iou (boxes ):
492
+ """Computes pairwise IOU.
493
+
494
+ Reason to use 4-D tensors is to follow TPU compiler preference.
495
+
496
+ Args:
497
+ boxes: A 2-D float `Tensor` of shape `[num_boxes, 4]`.
498
+
499
+ Returns:
500
+ A 4-D float `Tensor` of shape `[1, 1, num_boxes, num_boxes]` containing
501
+ pairwise IOU.
502
+ """
503
+ boxes = tf .reshape (boxes , [- 1 , 4 ])
504
+ boxes = tf .transpose (boxes , [1 , 0 ])
505
+ bottom , left , top , right = tf .split (boxes , 4 , 0 )
506
+ height , width = top - bottom , right - left
507
+ area = height * width
508
+ area_sum = _tensor_sum_vectors (area , area )
509
+ bottom_pad , left_pad , top_pad , right_pad = (
510
+ tf .nn .relu (_tensor_sum_vectors (x , - x ))
511
+ for x in (- bottom , - left , top , right ))
512
+ height_pad , width_pad = bottom_pad + top_pad , left_pad + right_pad
513
+ intersection = tf .nn .relu (height - height_pad ) * tf .nn .relu (width - width_pad )
514
+ union = area_sum - intersection
515
+ iou = tf .math .divide (intersection , union + _same (union ))
516
+ return iou
517
+
518
+
519
+ def _greater (x ):
520
+ """Avoid non lowerable layers in boolean comparison.
521
+
522
+ Logical operation results in tensor of boolean type. However in serving such
523
+ a tensors cannot be cast to values because of NNAPI specs.
524
+ `tf.where` operation result in `select` instruction lowering, which not runs
525
+ well on all generations of edge-tpus.
526
+
527
+ Args:
528
+ x: any numeric tensor.
529
+
530
+ Returns:
531
+ tf.where(x > tf.zero_like(x), tf.one_like(x), tf.zero_like(x))
532
+ """
533
+ x_clip = tf .minimum (tf .nn .relu (x ), tf .constant (1 , dtype = x .dtype ))
534
+ return - tf .math .floor (- x_clip )
535
+
536
+
537
+ def _same (x ):
538
+ """Avoid non lowerable layers in boolean equality.
539
+
540
+ Logical operation results in tensor of boolean type. However in serving such
541
+ a tensors cannot be cast to values because of NNAPI specs.
542
+ `tf.where` operation result in `select` instruction lowering, which not runs
543
+ well on all generations of edge-tpus.
544
+
545
+ Args:
546
+ x: any numeric tensor.
547
+
548
+ Returns:
549
+ tf.where(x == tf.zero_like(x), tf.one_like(x), tf.zero_like(x))
550
+ """
551
+ x_clip = tf .minimum (tf .abs (x ), tf .constant (1 , dtype = x .dtype ))
552
+ return tf .constant (1 , dtype = x .dtype ) + tf .math .floor (- x_clip )
553
+
554
+
555
+ def non_max_suppression_padded (boxes : tf .Tensor ,
556
+ scores : tf .Tensor ,
557
+ output_size : int ,
558
+ iou_threshold : float = 0.5 ) -> tf .Tensor :
559
+ """Selects a subset of boxes which have highest score among IOU-similar boxes.
560
+
561
+ Prunes away boxes that have high intersection-over-union (IOU) overlap
562
+ with boxes having higher score. Boxes are supplied as `[y1, x1, y2, x2]`,
563
+ where `(y1, x1)` and `(y2, x2)` are the coordinates of any diagonal pair of
564
+ box corners. Note that this algorithm is agnostic to the coordinate system.
565
+ Thus translating or reflections of the coordinate system result in the same
566
+ boxes being selected by the algorithm. The output of this operation is a
567
+ set of integers indexing into the input collection of bounding boxes
568
+ representing the selected boxes.
569
+
570
+ Set will be returned padded on the right with `-1` values. The bounding
571
+ box coordinates corresponding to the selected indices can then be obtained
572
+ using the `tf.gather` operation. For example:
573
+ ```python
574
+ selected_indices = vision.modeling.layers.non_max_suppression_padded(
575
+ boxes, scores, max_output_size, iou_threshold)
576
+ selected_boxes = tf.gather(boxes, selected_indices)
577
+ ```
578
+
579
+ Args:
580
+ boxes: A 2-D float `Tensor` of shape `[num_boxes, 4]`.
581
+ scores: A 1-D float `Tensor` of shape `[num_boxes]` representing a single
582
+ score corresponding to each box (each row of boxes).
583
+ output_size: A scalar integer `Tensor` representing the maximum number of
584
+ boxes to be selected by non-max suppression.
585
+ iou_threshold: A 0-D float tensor representing the threshold for deciding
586
+ whether boxes overlap too much with respect to IOU.
587
+
588
+ Returns:
589
+ selected_indices: A 1-D integer `Tensor` of shape `[output_size]`
590
+ representing the selected indices from the boxes tensor and `-1` values
591
+ for the padding.
592
+ """
593
+ order = tf .range (tf .size (scores ), dtype = tf .float32 )
594
+ relative_order = _tensor_sum_vectors (order , - order )
595
+ relative_scores = _tensor_sum_vectors (scores , - scores )
596
+ similar = _greater (_tensor_product_iou (boxes ) - iou_threshold )
597
+ worse = _greater (relative_scores )
598
+ same_later = _and (_same (relative_scores ), _greater (relative_order ))
599
+ similar_worse_or_same_later = _and (similar , _or (worse , same_later ))
600
+ prunable = _reduce_or (similar_worse_or_same_later , axis = - 1 )
601
+ remaining = (tf .constant (1. ) - prunable )
602
+ # top_k runs on TPU cores, let it happen, TPU tiles implementation is slower.
603
+ top_k = tf .math .top_k (remaining * tf .exp (scores ), output_size )
604
+ return tf .squeeze (
605
+ tf .cast (top_k .indices , top_k .values .dtype ) * _greater (top_k .values ) -
606
+ _same (top_k .values ))
0 commit comments