Skip to content

⚡️ Speed up method MeanAveragePrecision._match_detection_batch by 16% #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

codeflash-ai[bot]
Copy link

@codeflash-ai codeflash-ai bot commented Feb 3, 2025

📄 16% (0.16x) speedup for MeanAveragePrecision._match_detection_batch in supervision/metrics/mean_average_precision.py

⏱️ Runtime : 14.6 milliseconds 12.6 milliseconds (best of 161 runs)

📝 Explanation and details

o3-mini
We replace the repeated use of np.where and np.stack/hstack with np.nonzero to obtain the target and prediction indices directly. We also inline some intermediate arrays and avoid unnecessary array stacking. This simplifies the loop (though it still loops over thresholds) and removes some overhead in the inner loop. The logic remains the same so that each function return value is identical.

Below is the optimized version:

Explanation of the changes made:
• Instead of using np.where to obtain a tuple and then stacking the resulting arrays, we use np.nonzero to get target and prediction indices directly.
• We compute the valid mask for each threshold once and then sort the matches by their iou values in descending order.
• We then use np.unique on the prediction indices (and then on the target indices) to remove duplicate matches.
• This avoids repeated array creation (via stacking) and improves the run‐time.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 22 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests Details
from __future__ import annotations

from typing import List

import numpy as np
# imports
import pytest  # used for our unit tests
from supervision.detection.core import Detections
from supervision.metrics.core import Metric, MetricTarget
from supervision.metrics.mean_average_precision import MeanAveragePrecision

# unit tests

# Basic Test Cases
def test_single_prediction_perfect_iou():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[1.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_single_prediction_no_iou():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

# Multiple Predictions and Targets
def test_multiple_predictions_mixed_iou():
    predictions_classes = np.array([1, 2, 3])
    target_classes = np.array([1, 2, 3])
    iou = np.array([[0.6, 0.3, 0.1], [0.2, 0.8, 0.4], [0.1, 0.2, 0.9]])
    iou_thresholds = np.array([0.5, 0.7])
    expected = np.array([[True, False], [True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_multiple_predictions_all_matches():
    predictions_classes = np.array([1, 2, 3])
    target_classes = np.array([1, 2, 3])
    iou = np.array([[0.9, 0.0, 0.0], [0.0, 0.9, 0.0], [0.0, 0.0, 0.9]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True], [True], [True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

# Edge Cases
def test_empty_predictions_and_targets():
    predictions_classes = np.array([])
    target_classes = np.array([])
    iou = np.array([[]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[]], dtype=bool)
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_no_iou_thresholds():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[0.9, 0.0], [0.0, 0.9]])
    iou_thresholds = np.array([])
    expected = np.zeros((2, 0), dtype=bool)
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_high_iou_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.6]])
    iou_thresholds = np.array([0.95])
    expected = np.array([[False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

# Overlapping Predictions
def test_multiple_predictions_single_target():
    predictions_classes = np.array([1, 1])
    target_classes = np.array([1])
    iou = np.array([[0.9], [0.8]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True], [False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_multiple_targets_single_prediction():
    predictions_classes = np.array([1])
    target_classes = np.array([1, 1])
    iou = np.array([[0.9, 0.8]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

# Large Scale Test Cases
def test_large_number_of_predictions_and_targets():
    predictions_classes = np.arange(1, 1001)
    target_classes = np.arange(1, 1001)
    iou = np.random.rand(1000, 1000)
    iou_thresholds = np.array([0.5, 0.75, 0.9])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

# Performance and Scalability
def test_stress_test_large_iou_matrix():
    predictions_classes = np.random.randint(1, 10, size=10000)
    target_classes = np.random.randint(1, 10, size=10000)
    iou = np.random.rand(10000, 10000)
    iou_thresholds = np.linspace(0.5, 0.95, 10)
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

from __future__ import annotations

from typing import List

import numpy as np
# imports
import pytest  # used for our unit tests
from supervision.detection.core import Detections
from supervision.metrics.core import Metric, MetricTarget
from supervision.metrics.mean_average_precision import MeanAveragePrecision

# unit tests

def test_single_prediction_and_target_above_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.8]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_single_prediction_and_target_below_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.4]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_multiple_predictions_and_targets():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[0.8, 0.2], [0.1, 0.9]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True], [True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_no_predictions_or_targets():
    predictions_classes = np.array([])
    target_classes = np.array([])
    iou = np.array([[]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[]], dtype=bool)
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_all_predictions_match_all_targets():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[1.0, 1.0], [1.0, 1.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_no_predictions_match_any_targets():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([3, 4])
    iou = np.array([[0.1, 0.2], [0.3, 0.4]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False, False], [False, False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_class_agnostic_mode():
    predictions_classes = np.array([1])
    target_classes = np.array([2])
    iou = np.array([[0.8]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_single_iou_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.8]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_multiple_iou_thresholds():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.8]])
    iou_thresholds = np.array([0.5, 0.7, 0.9])
    expected = np.array([[True, True, False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_large_scale():
    predictions_classes = np.random.randint(0, 10, 1000)
    target_classes = np.random.randint(0, 10, 1000)
    iou = np.random.rand(1000, 1000)
    iou_thresholds = np.array([0.5, 0.75])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)



def test_random_iou_distribution():
    predictions_classes = np.random.randint(0, 10, 10)
    target_classes = np.random.randint(0, 10, 10)
    iou = np.random.rand(10, 10)
    iou_thresholds = np.array([0.5, 0.75])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_exact_threshold_matches():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.5]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_just_below_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.49]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_just_above_threshold():
    predictions_classes = np.array([1])
    target_classes = np.array([1])
    iou = np.array([[0.51]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)


def test_very_high_iou_values():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[10.0, 5.0], [3.0, 8.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_negative_iou_values():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[-0.5, -0.2], [-0.1, -0.3]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False, False], [False, False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_floating_point_class_labels():
    predictions_classes = np.array([1.5, 2.5])
    target_classes = np.array([1.5, 2.5])
    iou = np.array([[0.8, 0.9], [0.7, 0.6]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_string_class_labels():
    predictions_classes = np.array(['cat', 'dog'])
    target_classes = np.array(['cat', 'dog'])
    iou = np.array([[0.8, 0.9], [0.7, 0.6]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_all_zero_iou_values():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[0.0, 0.0], [0.0, 0.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[False, False], [False, False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_all_one_iou_values():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[1.0, 1.0], [1.0, 1.0]])
    iou_thresholds = np.array([0.5])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_negative_iou_thresholds():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[0.8, 0.9], [0.7, 0.6]])
    iou_thresholds = np.array([-0.5, -0.1])
    expected = np.array([[True, True], [True, True]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

def test_iou_thresholds_greater_than_1():
    predictions_classes = np.array([1, 2])
    target_classes = np.array([1, 2])
    iou = np.array([[0.8, 0.9], [0.7, 0.6]])
    iou_thresholds = np.array([1.5, 2.0])
    expected = np.array([[False, False], [False, False]])
    codeflash_output = MeanAveragePrecision._match_detection_batch(predictions_classes, target_classes, iou, iou_thresholds)

Codeflash

o3-mini
We replace the repeated use of np.where and np.stack/hstack with np.nonzero to obtain the target and prediction indices directly. We also inline some intermediate arrays and avoid unnecessary array stacking. This simplifies the loop (though it still loops over thresholds) and removes some overhead in the inner loop. The logic remains the same so that each function return value is identical.

Below is the optimized version:

Explanation of the changes made:
• Instead of using np.where to obtain a tuple and then stacking the resulting arrays, we use np.nonzero to get target and prediction indices directly.
• We compute the valid mask for each threshold once and then sort the matches by their iou values in descending order.
• We then use np.unique on the prediction indices (and then on the target indices) to remove duplicate matches.
• This avoids repeated array creation (via stacking) and improves the run‐time.
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Feb 3, 2025
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 February 3, 2025 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚡️ codeflash Optimization PR opened by Codeflash AI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants