Repository: keras-team/keras-cv

Issue:
BoxCOCOMetrics do not handle Ragged prediction inputs properly
```
def print_metrics(metrics):
    maxlen = max([len(key) for key in result.keys()])
    print("Metrics:")
    print("-" * (maxlen + 1))
    for k, v in metrics.items():
        print(f"{k.ljust(maxlen+1)}: {v.numpy():0.2f}")

coco_metrics.reset_state()

for images, y_true in tqdm.tqdm(iter(eval_ds)):
  y_pred = model.predict(images, verbose=0)
  coco_metrics.update_state(y_true, y_pred)

result = coco_metrics.result(force=True)
print_metrics(result)
```

is broken, but by simply inserting:

```
  y_pred = keras_cv.bounding_box.to_dense(y_pred, max_boxes=32)
```
after the y_pred line, it works again.  This indicates that the box metrics dont know how to handle ragged inputs.

Discussion:
LukeWood: /gcbrun

Patch:
diff --git a/keras_cv/metrics/coco/pycoco_wrapper.py b/keras_cv/metrics/coco/pycoco_wrapper.py
index f630350..60c94a2 100644
--- a/keras_cv/metrics/coco/pycoco_wrapper.py
+++ b/keras_cv/metrics/coco/pycoco_wrapper.py
@@ -125,18 +125,18 @@ def _convert_predictions_to_coco_annotations(predictions):
     coco_predictions = []
     num_batches = len(predictions["source_id"])
     for i in range(num_batches):
-        predictions["detection_boxes"][i] = _yxyx_to_xywh(
-            predictions["detection_boxes"][i]
-        )
         batch_size = predictions["source_id"][i].shape[0]
         for j in range(batch_size):
             max_num_detections = predictions["num_detections"][i][j]
+            predictions["detection_boxes"][i][j] = _yxyx_to_xywh(
+                predictions["detection_boxes"][i][j]
+            )
             for k in range(max_num_detections):
                 ann = {}
                 ann["image_id"] = predictions["source_id"][i][j]
-                ann["category_id"] = predictions["detection_classes"][i][j, k]
-                ann["bbox"] = predictions["detection_boxes"][i][j, k]
-                ann["score"] = predictions["detection_scores"][i][j, k]
+                ann["category_id"] = predictions["detection_classes"][i][j][k]
+                ann["bbox"] = predictions["detection_boxes"][i][j][k]
+                ann["score"] = predictions["detection_scores"][i][j][k]
                 coco_predictions.append(ann)
 
     for i, ann in enumerate(coco_predictions):
@@ -162,17 +162,17 @@ def _convert_groundtruths_to_coco_dataset(groundtruths, label_map=None):
                 ann = {}
                 ann["image_id"] = groundtruths["source_id"][i][j]
                 ann["iscrowd"] = 0
-                ann["category_id"] = int(groundtruths["classes"][i][j, k])
+                ann["category_id"] = int(groundtruths["classes"][i][j][k])
                 boxes = groundtruths["boxes"][i]
                 ann["bbox"] = [
-                    float(boxes[j, k, 1]),
-                    float(boxes[j, k, 0]),
-                    float(boxes[j, k, 3] - boxes[j, k, 1]),
-                    float(boxes[j, k, 2] - boxes[j, k, 0]),
+                    float(boxes[j][k][1]),
+                    float(boxes[j][k][0]),
+                    float(boxes[j][k][3] - boxes[j][k][1]),
+                    float(boxes[j][k][2] - boxes[j][k][0]),
                 ]
                 ann["area"] = float(
-                    (boxes[j, k, 3] - boxes[j, k, 1])
-                    * (boxes[j, k, 2] - boxes[j, k, 0])
+                    (boxes[j][k][3] - boxes[j][k][1])
+                    * (boxes[j][k][2] - boxes[j][k][0])
                 )
                 gt_annotations.append(ann)
 


Test patch:
diff --git a/keras_cv/metrics/object_detection/box_coco_metrics_test.py b/keras_cv/metrics/object_detection/box_coco_metrics_test.py
index 331965b..9638d9e 100644
--- a/keras_cv/metrics/object_detection/box_coco_metrics_test.py
+++ b/keras_cv/metrics/object_detection/box_coco_metrics_test.py
@@ -137,3 +137,43 @@ class BoxCOCOMetricsTest(tf.test.TestCase):
             self.assertAlmostEqual(
                 metrics["coco_metrics_" + metric], golden_metrics[metric]
             )
+
+    def test_coco_metric_suite_ragged_prediction(self):
+        bounding_boxes = {
+            "boxes": tf.ragged.constant(
+                [
+                    [[10, 10, 20, 20], [100, 100, 150, 150]],  # small, medium
+                    [
+                        [200, 200, 400, 400],  # large
+                    ],
+                ],
+                dtype=tf.float32,
+            ),
+            "classes": tf.ragged.constant(
+                [
+                    [0, 1],
+                    [2],
+                ],
+                dtype=tf.float32,
+            ),
+            "confidence": tf.ragged.constant(
+                [
+                    [0.7, 0.8],
+                    [0.9],
+                ],
+                dtype=tf.float32,
+            ),
+        }
+        dense_bounding_boxes = bounding_box.to_dense(bounding_boxes)
+        ragged_bounding_boxes = bounding_box.to_ragged(dense_bounding_boxes)
+        suite = BoxCOCOMetrics(bounding_box_format="xyxy", evaluate_freq=1)
+        y_true = dense_bounding_boxes
+        y_pred = ragged_bounding_boxes
+
+        suite.update_state(y_true, y_pred)
+        metrics = suite.result(force=True)
+
+        for metric in metrics:
+            # The metrics will be all 1.0 because the prediction and ground
+            # truth is identical.
+            self.assertAllEqual(metrics[metric], 1.0)