Repository: Lightning-AI/torchmetrics

Issue:
Result of IoU is Nan when there is empty item in preds.
## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->

### To Reproduce

The `target` is:
```
[
 {'boxes': tensor([[ 86.0000,  15.0000, 214.0000, 224.0000],
                            [201.0000, 109.0000, 294.0000, 141.0000]], device='cuda:2'), 'labels': tensor([ 6, 10], device='cuda:2')}, 
 {'boxes': tensor([[124.,  79., 399., 237.],
                            [  0.,  82.,  66., 156.]], device='cuda:2'), 'labels': tensor([5, 6], device='cuda:2')}, 
 {'boxes': tensor([[238.0000,  74.0000, 343.0000, 275.0000]], device='cuda:2'), 'labels': tensor([6], device='cuda:2')}, 
 {'boxes': tensor([[  8.0000,  70.0000,  76.0000, 110.0000],
                            [247.0000, 131.0000, 315.0000, 175.0000],
                            [361.0000, 177.0000, 395.0000, 203.0000]], device='cuda:2'), 'labels': tensor([0, 0, 0], device='cuda:2')}
]
```
The `preds` is:
```
[
{'boxes': tensor([[ 69.0789,  32.9795, 237.3270, 223.9482],
        [192.3514, 108.7430, 293.3430, 144.3972],
        [194.6256, 108.4384, 291.3083, 143.7163],
        [195.6599, 113.8255, 291.3373, 144.4118]], device='cuda:2'), 'labels': tensor([ 6,  5, 10, 13], device='cuda:2'), 'scores': tensor([0.9997, 0.8557, 0.6551, 0.1529], device='cuda:2')}, 
{'boxes': tensor([[116.1356,  70.3358, 353.5793, 214.7886],
        [  7.3007,  98.0802,  69.1378, 163.0365],
        [305.5327,  27.2811, 322.0698,  39.8957],
        [112.4302, 208.0613, 173.9218, 292.1096]], device='cuda:2'), 'labels': tensor([5, 6, 5, 6], device='cuda:2'), 'scores': tensor([1.0000, 0.9450, 0.5993, 0.1131], device='cuda:2')}, 
{'boxes': tensor([[222.7714,  74.0670, 365.3134, 249.2353],
        [201.2384,   4.7298, 230.6123,  16.7820],
        [322.8268, 151.7757, 400.0000, 187.3730],
        [140.8015, 170.1787, 227.3417, 291.6501],
        [100.7026,  64.1443, 367.1241, 231.8164],
        [ 17.2630,  38.2540,  74.0354, 209.2324],
        [  0.0000, 155.9979,  78.3138, 208.8021],
        [  3.1740, 149.3114,  73.7855, 210.7626]], device='cuda:2'), 'labels': tensor([ 6,  5,  5,  6,  6,  6, 13,  6], device='cuda:2'), 'scores': tensor([0.9951, 0.9907, 0.9418, 0.8937, 0.7300, 0.2960, 0.1127, 0.1039],
       device='cuda:2')}, 
{'boxes': tensor([], device='cuda:2', size=(0, 4)), 'labels': tensor([], device='cuda:2', dtype=torch.int64), 'scores': tensor([], device='cuda:2')}]
```
The last item of preds is empty, when I using torchmetrics.detection.iou.IntersectionOverUnion to calculate IoU, the result is nan.

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

<details>
  <summary>Code sample</summary>

```python
import torch
from torch import tensor
from torch import empty
from torchmetrics.detection import IntersectionOverUnion

def calc_iou(preds: list, target: list):
    metric = IntersectionOverUnion()
    metric.update(preds, target)
    result = metric.compute()
    return result

target = [
 {'boxes': tensor([[ 86.0000,  15.0000, 214.0000, 224.0000],
                   [201.0000, 109.0000, 294.0000, 141.0000]]), 'labels': tensor([ 6, 10])}, 
 {'boxes': tensor([[124.,  79., 399., 237.],
                   [  0.,  82.,  66., 156.]]), 'labels': tensor([5, 6])}, 
 {'boxes': tensor([[238.0000,  74.0000, 343.0000, 275.0000]]), 'labels': tensor([6])}, 
 {'boxes': tensor([[  8.0000,  70.0000,  76.0000, 110.0000],
                   [247.0000, 131.0000, 315.0000, 175.0000],
                   [361.0000, 177.0000, 395.0000, 203.0000]]), 'labels': tensor([0, 0, 0])}
]
preds = [
{'boxes': tensor([[ 69.0789,  32.9795, 237.3270, 223.9482],
        [192.3514, 108.7430, 293.3430, 144.3972],
        [194.6256, 108.4384, 291.3083, 143.7163],
        [195.6599, 113.8255, 291.3373, 144.4118]]), 'labels': tensor([ 6,  5, 10, 13]), 'scores': tensor([0.9997, 0.8557, 0.6551, 0.1529])}, 
{'boxes': tensor([[116.1356,  70.3358, 353.5793, 214.7886],
        [  7.3007,  98.0802,  69.1378, 163.0365],
        [305.5327,  27.2811, 322.0698,  39.8957],
        [112.4302, 208.0613, 173.9218, 292.1096]]), 'labels': tensor([5, 6, 5, 6]), 'scores': tensor([1.0000, 0.9450, 0.5993, 0.1131])}, 
{'boxes': tensor([[222.7714,  74.0670, 365.3134, 249.2353],
        [201.2384,   4.7298, 230.6123,  16.7820],
        [322.8268, 151.7757, 400.0000, 187.3730],
        [140.8015, 170.1787, 227.3417, 291.6501],
        [100.7026,  64.1443, 367.1241, 231.8164],
        [ 17.2630,  38.2540,  74.0354, 209.2324],
        [  0.0000, 155.9979,  78.3138, 208.8021],
        [  3.1740, 149.3114,  73.7855, 210.7626]]), 'labels': tensor([ 6,  5,  5,  6,  6,  6, 13,  6]), 'scores': tensor([0.9951, 0.9907, 0.9418, 0.8937, 0.7300, 0.2960, 0.1127, 0.1039])}, 
{'boxes': empty(size=(0, 4)), 'labels': tensor([], dtype=torch.int64), 'scores': tensor([])}]

calc_iou(preds, target)
```
```
{'iou': tensor(nan)}
```
</details>

### Expected behavior
A correct iou value.

<!-- A clear and concise description of what you expected to happen. -->

### Environment

- TorchMetrics version (and how you installed TM, e.g. `conda`, `pip`, build from source): I installed TM by `pip`, the version is `1.0.0`.
- Python & PyTorch Version (e.g., 1.0): python version: `3.10.9`, pytorch version: `1.13.1`
- Any other relevant information such as OS (e.g., Linux): Ubuntu, "20.04.6 LTS (Focal Fossa)"

### Additional context

<!-- Add any other context about the problem here. -->



Discussion:
Hi! thanks for your contribution!, great first issue!

Resolving patch:
diff --git a/src/torchmetrics/functional/detection/iou.py b/src/torchmetrics/functional/detection/iou.py
--- a/src/torchmetrics/functional/detection/iou.py
+++ b/src/torchmetrics/functional/detection/iou.py
@@ -38,7 +38,7 @@ def _iou_update(
 def _iou_compute(iou: torch.Tensor, labels_eq: bool = True) -> torch.Tensor:
     if labels_eq:
         return iou.diag().mean()
-    return iou.mean()
+    return iou.mean() if iou.numel() > 0 else torch.tensor(0.0)
 
 
 def intersection_over_union(


Test patch:
diff --git a/tests/unittests/detection/test_iou.py b/tests/unittests/detection/test_iou.py
--- a/tests/unittests/detection/test_iou.py
+++ b/tests/unittests/detection/test_iou.py
@@ -15,7 +15,7 @@
 
 import pytest
 import torch
-from torch import Tensor
+from torch import Tensor, tensor
 from torchmetrics.detection.iou import IntersectionOverUnion
 from torchmetrics.functional.detection.iou import intersection_over_union
 from torchmetrics.metric import Metric
@@ -56,3 +56,16 @@ class TestIntersectionOverUnion(MetricTester, BaseTestIntersectionOverUnion):
     }
     metric_class: ClassVar[Metric] = IntersectionOverUnion
     metric_fn: ClassVar[Callable[[Tensor, Tensor, bool, float], Tensor]] = intersection_over_union
+
+
+def test_corner_case():
+    """Test corner case where preds is empty for a given target.
+
+    See this issue: https://github.com/Lightning-AI/torchmetrics/issues/1889
+    """
+    target = [{"boxes": tensor([[238.0000, 74.0000, 343.0000, 275.0000]]), "labels": tensor([6])}]
+    preds = [{"boxes": tensor([[], [], [], []]).T, "labels": tensor([], dtype=torch.int64), "scores": tensor([])}]
+    metric = IntersectionOverUnion()
+    metric.update(preds, target)
+    result = metric.compute()
+    assert result["iou"] == tensor(0.0)