5cbd3f29e3
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Generate and publish ONNX docs / build (push) Has been cancelled
Generate and publish ONNX docs / deploy (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
476 lines
16 KiB
Python
476 lines
16 KiB
Python
# Copyright (c) ONNX Project Contributors
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
import onnx
|
|
from onnx.backend.test.case.base import Base
|
|
from onnx.backend.test.case.node import expect
|
|
|
|
|
|
class NonMaxSuppression(Base):
|
|
@staticmethod
|
|
def export_nonmaxsuppression_suppress_by_IOU() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 3], [0, 0, 0], [0, 0, 5]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_suppress_by_IOU",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_suppress_by_IOU_and_scores() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.4]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 3], [0, 0, 0]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_suppress_by_IOU_and_scores",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_flipped_coordinates() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[1.0, 1.0, 0.0, 0.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, 0.9, 1.0, -0.1],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[1.0, 10.1, 0.0, 11.1],
|
|
[1.0, 101.0, 0.0, 100.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 3], [0, 0, 0], [0, 0, 5]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_flipped_coordinates",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_limit_output_size() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([2]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 3], [0, 0, 0]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_limit_output_size",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_single_box() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array([[[0.0, 0.0, 1.0, 1.0]]]).astype(np.float32)
|
|
scores = np.array([[[0.9]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 0]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_single_box",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_identical_boxes() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array(
|
|
[[[0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]]]
|
|
).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 0]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_identical_boxes",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_center_point_box_format() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
center_point_box=1,
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.5, 0.5, 1.0, 1.0],
|
|
[0.5, 0.6, 1.0, 1.0],
|
|
[0.5, 0.4, 1.0, 1.0],
|
|
[0.5, 10.5, 1.0, 1.0],
|
|
[0.5, 10.6, 1.0, 1.0],
|
|
[0.5, 100.5, 1.0, 1.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array([[0, 0, 3], [0, 0, 0], [0, 0, 5]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_center_point_box_format",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_two_classes() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array(
|
|
[[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3], [0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]
|
|
).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([2]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array(
|
|
[[0, 0, 3], [0, 0, 0], [0, 1, 3], [0, 1, 0]]
|
|
).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_two_classes",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_two_batches() -> None:
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
],
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[0.0, 0.1, 1.0, 1.1],
|
|
[0.0, -0.1, 1.0, 0.9],
|
|
[0.0, 10.0, 1.0, 11.0],
|
|
[0.0, 10.1, 1.0, 11.1],
|
|
[0.0, 100.0, 1.0, 101.0],
|
|
],
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array(
|
|
[[[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]], [[0.9, 0.75, 0.6, 0.95, 0.5, 0.3]]]
|
|
).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([2]).astype(np.int64)
|
|
iou_threshold = np.array([0.5]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
selected_indices = np.array(
|
|
[[0, 0, 3], [0, 0, 0], [1, 0, 3], [1, 0, 0]]
|
|
).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_two_batches",
|
|
)
|
|
|
|
@staticmethod
|
|
def export_nonmaxsuppression_iou_threshold_boundary() -> None:
|
|
"""Test boundary condition where IoU exactly equals threshold.
|
|
|
|
This test verifies that the comparison is strict (>), not inclusive (>=).
|
|
When IoU exactly equals the threshold, boxes should be KEPT, not suppressed.
|
|
This follows PyTorch's NMS implementation.
|
|
"""
|
|
node = onnx.helper.make_node(
|
|
"NonMaxSuppression",
|
|
inputs=[
|
|
"boxes",
|
|
"scores",
|
|
"max_output_boxes_per_class",
|
|
"iou_threshold",
|
|
"score_threshold",
|
|
],
|
|
outputs=["selected_indices"],
|
|
)
|
|
# Two boxes with 50% overlap in each dimension
|
|
# box1=[0,0,1,1], box2=[0.5,0.5,1.5,1.5]
|
|
# Intersection area = 0.5 * 0.5 = 0.25
|
|
# Union area = 1.0 + 1.0 - 0.25 = 1.75
|
|
# IoU = 0.25 / 1.75 (exact value computed below as float32)
|
|
boxes = np.array(
|
|
[
|
|
[
|
|
[0.0, 0.0, 1.0, 1.0], # box 0
|
|
[0.5, 0.5, 1.5, 1.5], # box 1 - overlaps box 0
|
|
]
|
|
]
|
|
).astype(np.float32)
|
|
scores = np.array([[[0.9, 0.8]]]).astype(np.float32)
|
|
max_output_boxes_per_class = np.array([3]).astype(np.int64)
|
|
# Compute the exact IoU value and use it as threshold
|
|
# This ensures the threshold exactly equals the IoU
|
|
exact_iou = np.float32(0.25 / 1.75)
|
|
iou_threshold = np.array([exact_iou]).astype(np.float32)
|
|
score_threshold = np.array([0.0]).astype(np.float32)
|
|
# Both boxes should be selected because IoU == threshold (not > threshold)
|
|
selected_indices = np.array([[0, 0, 0], [0, 0, 1]]).astype(np.int64)
|
|
|
|
expect(
|
|
node,
|
|
inputs=[
|
|
boxes,
|
|
scores,
|
|
max_output_boxes_per_class,
|
|
iou_threshold,
|
|
score_threshold,
|
|
],
|
|
outputs=[selected_indices],
|
|
name="test_nonmaxsuppression_iou_threshold_boundary",
|
|
)
|