diff --git a/tools/mo/automation/package_BOM.txt b/tools/mo/automation/package_BOM.txt index b9bc64d1c8b..2e56196c3bc 100644 --- a/tools/mo/automation/package_BOM.txt +++ b/tools/mo/automation/package_BOM.txt @@ -934,6 +934,7 @@ openvino/tools/mo/ops/mxfft.py openvino/tools/mo/ops/mxrepeat.py openvino/tools/mo/ops/mxreshape.py openvino/tools/mo/ops/NextIteration.py +openvino/tools/mo/ops/nms_rotated.py openvino/tools/mo/ops/non_max_suppression.py openvino/tools/mo/ops/non_zero.py openvino/tools/mo/ops/normalize.py diff --git a/tools/mo/openvino/tools/mo/ops/nms_rotated.py b/tools/mo/openvino/tools/mo/ops/nms_rotated.py new file mode 100644 index 00000000000..d845373d4c5 --- /dev/null +++ b/tools/mo/openvino/tools/mo/ops/nms_rotated.py @@ -0,0 +1,79 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import logging as log + +import numpy as np + +from openvino.tools.mo.front.common.partial_infer.utils import shape_array, dynamic_dimension_value +from openvino.tools.mo.front.extractor import bool_to_str +from openvino.tools.mo.graph.graph import Node, Graph +from openvino.tools.mo.middle.passes.convert_data_type import np_data_type_to_destination_type +from openvino.tools.mo.ops.op import Op +from openvino.tools.mo.utils.error import Error + + +class NMSRotated(Op): + op = 'NMSRotated' + enabled = False + + def __init__(self, graph: Graph, attrs: dict): + mandatory_props = { + 'type': self.op, + 'op': self.op, + 'version': 'opset13', + + 'infer': self.infer, + 'type_infer': self.type_infer, + + 'sort_result_descending': True, + 'output_type': np.int64, + 'clockwise': True, + + 'in_ports_count': 5, + 'out_ports_count': 3, + } + super().__init__(graph, mandatory_props, attrs) + + def backend_attrs(self): + return [('sort_result_descending', lambda node: bool_to_str(node, 'sort_result_descending')), + 'output_type', + ('clockwise', lambda node: bool_to_str(node, 'clockwise'))] + + def supported_attrs(self): + return [ + 'sort_result_descending', + 'output_type', + 'clockwise', + ] + + @staticmethod + def infer(node: Node): + num_of_inputs = len(node.in_ports()) + opset = node.get_opset() + required_num_inputs = 5 + input_msg_fmt = 'NMSRotated node {} from {} must have {} inputs' + node_name = node.soft_get('name', node.id) + inputs_msg = input_msg_fmt.format( + node_name, opset, required_num_inputs) + assert num_of_inputs == required_num_inputs, inputs_msg + + node.out_port(0).data.set_shape( + shape_array([dynamic_dimension_value, 3])) + num_of_outputs = len( + [port for port in node.out_ports().values() if not port.disconnected()]) + if num_of_outputs >= 2 and node.has_port('out', 1): + node.out_port(1).data.set_shape( + shape_array([dynamic_dimension_value, 3])) + if num_of_outputs >= 3 and node.has_port('out', 2): + node.out_port(2).data.set_shape(shape_array([1])) + + @staticmethod + def type_infer(node: Node): + node.out_port(1).set_data_type(np.float32) + if node.has_valid('output_type') and node['output_type'].lower() == 'i32': + node.out_port(0).set_data_type(np.int32) + node.out_port(2).set_data_type(np.int32) + else: + node.out_port(0).set_data_type(np.int64) + node.out_port(2).set_data_type(np.int64) diff --git a/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py b/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py index 3e5b35ef62f..fefc2653c64 100644 --- a/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py +++ b/tools/mo/unit_tests/mo/utils/ir_reader/ops_test.py @@ -357,3 +357,103 @@ class TestOps(unittest.TestCase): self.assertEqual(loaded_model.get_output_element_type(0), Type.i64) self.assertEqual(loaded_model.get_output_partial_shape( 0), PartialShape([2, 3])) + + def test_nms_rotated_13_attrs_false_i32(self): + boxes_shape = [1, 100, 5] + scores_shape = [1, 2, 100] + max_output_boxes_val = 5 + iou_threshold_val = 0.5 + score_threshold_val = 0.4 + + boxes_parameter = opset13.parameter( + boxes_shape, name="Boxes", dtype=np.float32) + scores_parameter = opset13.parameter( + scores_shape, name="Scores", dtype=np.float32) + + max_output_boxes = opset13.constant([max_output_boxes_val], np.int64) + iou_threshold = opset13.constant([iou_threshold_val], np.float32) + score_threshold = opset13.constant([score_threshold_val], np.float32) + + sort_result_descending = False + output_type = "i32" + clockwise = False + + node = opset13.nms_rotated(boxes_parameter, scores_parameter, max_output_boxes, iou_threshold, + score_threshold, sort_result_descending, output_type, clockwise) + + model = Model(node, [boxes_parameter, scores_parameter]) + graph, loaded_model = TestOps.check_graph_can_save( + model, 'nms_rotated_model_1') + ir_node = graph.get_op_nodes(op="NMSRotated")[0] + + self.assertListEqual(ir_node.out_port( + 0).data.get_shape().tolist(), [None, 3]) + self.assertListEqual(ir_node.out_port( + 1).data.get_shape().tolist(), [None, 3]) + self.assertListEqual(ir_node.out_port( + 2).data.get_shape().tolist(), [1]) + + self.assertEqual(ir_node["version"], "opset13") + self.assertEqual(ir_node['sort_result_descending'], False) + self.assertEqual(ir_node['output_type'], "i32") + self.assertEqual(ir_node['clockwise'], False) + self.assertEqual(loaded_model.get_output_element_type(0), Type.i32) + self.assertEqual(loaded_model.get_output_element_type(1), Type.f32) + self.assertEqual(loaded_model.get_output_element_type(2), Type.i32) + + self.assertEqual(loaded_model.get_output_partial_shape( + 0), PartialShape([Dimension(-1, 10), 3])) + self.assertEqual(loaded_model.get_output_partial_shape( + 1), PartialShape([Dimension(-1, 10), 3])) + self.assertEqual(loaded_model.get_output_partial_shape( + 2), PartialShape([1])) + + def test_nms_rotated_13_attrs_true_i64(self): + boxes_shape = [1, 100, 5] + scores_shape = [1, 3, 100] + max_output_boxes_val = 5 + iou_threshold_val = 0.5 + score_threshold_val = 0.4 + + boxes_parameter = opset13.parameter( + boxes_shape, name="Boxes", dtype=np.float32) + scores_parameter = opset13.parameter( + scores_shape, name="Scores", dtype=np.float32) + + max_output_boxes = opset13.constant([max_output_boxes_val], np.int64) + iou_threshold = opset13.constant([iou_threshold_val], np.float32) + score_threshold = opset13.constant([score_threshold_val], np.float32) + + sort_result_descending = True + output_type = "i64" + clockwise = True + + node = opset13.nms_rotated(boxes_parameter, scores_parameter, max_output_boxes, iou_threshold, + score_threshold, sort_result_descending, output_type, clockwise) + + model = Model(node, [boxes_parameter, scores_parameter]) + graph, loaded_model = TestOps.check_graph_can_save( + model, 'nms_rotated_model_2') + ir_node = graph.get_op_nodes(op="NMSRotated")[0] + + self.assertListEqual(ir_node.out_port( + 0).data.get_shape().tolist(), [None, 3]) + self.assertListEqual(ir_node.out_port( + 1).data.get_shape().tolist(), [None, 3]) + self.assertListEqual(ir_node.out_port( + 2).data.get_shape().tolist(), [1]) + + self.assertEqual(ir_node["version"], "opset13") + self.assertEqual(ir_node['sort_result_descending'], True) + self.assertEqual(ir_node['output_type'], "i64") + self.assertEqual(ir_node['clockwise'], True) + self.assertEqual(loaded_model.get_output_element_type(0), Type.i64) + self.assertEqual(loaded_model.get_output_element_type(1), Type.f32) + self.assertEqual(loaded_model.get_output_element_type(2), Type.i64) + + self.assertEqual(loaded_model.get_output_partial_shape( + 0), PartialShape([Dimension(-1, 15), 3])) + self.assertEqual(loaded_model.get_output_partial_shape( + 1), PartialShape([Dimension(-1, 15), 3])) + self.assertEqual(loaded_model.get_output_partial_shape( + 2), PartialShape([1]))