Repository: Lightning-AI/torchmetrics

Issue:
`ValueError: 0.5/0.75 is not in list` - when passing a custom set of IoU thresholds to `MeanAveragePrecision`
## 🐛 Bug

When I try to use `MeanAveragePrecision` to compute the mAP over a custom range of IoU thresholds, a value error is thrown if `0.5` and `0.75` are not included in the list.

This is due to these values being hardcoded [here](https://github.com/PyTorchLightning/metrics/blob/c494f3575f542e7c264555a71fb97e8676f64700/torchmetrics/detection/mean_ap.py#L656).

### To Reproduce

Steps to reproduce the behavior...

```
Traceback (most recent call last):
 line 27, in <module>
    print(map.compute())
  File "C:\Users\hughesc\Anaconda3\envs\accelerated-dev\lib\site-packages\torchmetrics\metric.py", line 440, in wrapped_func
    value = compute(*args, **kwargs)
  File "C:\Users\hughesc\Anaconda3\envs\accelerated-dev\lib\site-packages\torchmetrics\detection\mean_ap.py", line 766, in compute
    map_val, mar_val = self._summarize_results(precisions, recalls)
  File "C:\Users\hughesc\Anaconda3\envs\accelerated-dev\lib\site-packages\torchmetrics\detection\mean_ap.py", line 656, in _summarize_results
    map_metrics.map_50 = self._summarize(results, True, iou_threshold=0.5, max_dets=last_max_det_thr)
  File "C:\Users\hughesc\Anaconda3\envs\accelerated-dev\lib\site-packages\torchmetrics\detection\mean_ap.py", line 570, in _summarize
    thr = self.iou_thresholds.index(iou_threshold)
ValueError: 0.5 is not in list
```

#### Code sample
```py
import torch
from torchmetrics.detection import MeanAveragePrecision

if __name__ == '__main__':

    device = torch.device('cuda:0')

    preds = [
        dict( boxes=torch.tensor([[1.5, 2., 3., 4.]], device=device),
        scores=torch.tensor([0.6], device=device),
        labels=torch.tensor([1], device=device),)
    ]

    targets = [
        dict(boxes=torch.tensor([[1., 2., 3., 4.]], device=device),
             scores=torch.tensor([0.8], device=device),
             labels=torch.tensor([1], device=device), )
    ]

    map = MeanAveragePrecision(class_metrics=True,
                               iou_thresholds=[0.1, 0.2],
                               ).to(device)

    map.update(preds, targets)

    print(map.compute())
```

### Expected behavior

Ideally, I would like to be able to calculate mAP using any custom range of IoU values, or even a single value if it is passed as a list (e.g. `[0.4]`). Although this is a departure from the pycoctools implementation, this would make this metric well suited to use cases outside of the coco competition.

At a minimum, this restriction should be documented, and the error should be handled more gracefully. 

### Environment

- OS (e.g., Linux): Windows, Ubuntu
- Python & PyTorch Version (e.g., 1.0): Python 3.8 & 3.9, PyTorch 1.10
- How you installed PyTorch (`conda`, `pip`, build command if you used source): conda
- Any other relevant information:




Discussion:
I am facing the same issue when passing a single threshold, which I need to do some error analysis.

The easiest solution could be to return `-1` for either of these thresholds if they are not present in the specified thresholds.

A more neat approach could be to have an optional argument at the beginning set by default at `[0.5, 0.75]` to indicate the desired thresholds to be reported? Then would be possible to iterate over the asked thresholds and report them. 

I can try and implement the solution and submit a PR. Ideally I would do the former (with the -1) as I am a bit budgeted in time but if you think the later is what should be there I can try to give it a go too.

Resolving patch:
diff --git a/torchmetrics/detection/mean_ap.py b/torchmetrics/detection/mean_ap.py
--- a/torchmetrics/detection/mean_ap.py
+++ b/torchmetrics/detection/mean_ap.py
@@ -653,8 +653,14 @@ def _summarize_results(self, precisions: Tensor, recalls: Tensor) -> Tuple[MAPMe
         map_metrics = MAPMetricResults()
         map_metrics.map = self._summarize(results, True)
         last_max_det_thr = self.max_detection_thresholds[-1]
-        map_metrics.map_50 = self._summarize(results, True, iou_threshold=0.5, max_dets=last_max_det_thr)
-        map_metrics.map_75 = self._summarize(results, True, iou_threshold=0.75, max_dets=last_max_det_thr)
+        if 0.5 in self.iou_thresholds:
+            map_metrics.map_50 = self._summarize(results, True, iou_threshold=0.5, max_dets=last_max_det_thr)
+        else:
+            map_metrics.map_50 = torch.tensor([-1])
+        if 0.75 in self.iou_thresholds:
+            map_metrics.map_75 = self._summarize(results, True, iou_threshold=0.75, max_dets=last_max_det_thr)
+        else:
+            map_metrics.map_75 = torch.tensor([-1])
         map_metrics.map_small = self._summarize(results, True, area_range="small", max_dets=last_max_det_thr)
         map_metrics.map_medium = self._summarize(results, True, area_range="medium", max_dets=last_max_det_thr)
         map_metrics.map_large = self._summarize(results, True, area_range="large", max_dets=last_max_det_thr)
@@ -750,8 +756,6 @@ def compute(self) -> dict:
             dict containing
 
             - map: ``torch.Tensor``
-            - map_50: ``torch.Tensor``
-            - map_75: ``torch.Tensor``
             - map_small: ``torch.Tensor``
             - map_medium: ``torch.Tensor``
             - map_large: ``torch.Tensor``
@@ -761,6 +765,8 @@ def compute(self) -> dict:
             - mar_small: ``torch.Tensor``
             - mar_medium: ``torch.Tensor``
             - mar_large: ``torch.Tensor``
+            - map_50: ``torch.Tensor`` (-1 if 0.5 not in the list of iou thresholds)
+            - map_75: ``torch.Tensor`` (-1 if 0.75 not in the list of iou thresholds)
             - map_per_class: ``torch.Tensor`` (-1 if class metrics are disabled)
             - mar_100_per_class: ``torch.Tensor`` (-1 if class metrics are disabled)
         """


Test patch:
diff --git a/tests/detection/test_map.py b/tests/detection/test_map.py
--- a/tests/detection/test_map.py
+++ b/tests/detection/test_map.py
@@ -313,6 +313,19 @@ def test_map_gpu(inputs):
     metric.compute()
 
 
+@pytest.mark.skipif(_pytest_condition, reason="test requires that torchvision=>0.8.0 is installed")
+@pytest.mark.skipif(_gpu_test_condition, reason="test requires CUDA availability")
+def test_map_with_custom_thresholds():
+    """Test that map works with custom iou thresholds."""
+    metric = MeanAveragePrecision(iou_thresholds=[0.1, 0.2])
+    metric = metric.to("cuda")
+    for preds, targets in zip(_inputs.preds, _inputs.target):
+        metric.update(_move_to_gpu(preds), _move_to_gpu(targets))
+    res = metric.compute()
+    assert res["map_50"].item() == -1
+    assert res["map_75"].item() == -1
+
+
 @pytest.mark.skipif(_pytest_condition, reason="test requires that pycocotools and torchvision=>0.8.0 is installed")
 def test_empty_metric():
     """Test empty metric."""