NMS-4 op support (#1115)
authorEvgeny Lazarev <evgeny.lazarev@intel.com>
Tue, 30 Jun 2020 11:04:31 +0000 (14:04 +0300)
committerGitHub <noreply@github.com>
Tue, 30 Jun 2020 11:04:31 +0000 (14:04 +0300)
* Specification for the NMS-4 operation (updated shape infer function)

* Enabled NMS-4 in the Model Optimizer

* Changed opset version for NMS with dynamic outputs and namespace to be "dynamic"

* Added NMS-4

* Added opset4 to the nGraph

* Added unit tests for NMS-4 type infer

* Renamed UpgradeNMS3ToNMS4 to UpgradeNMS3ToNMSDynamic. Added stub for ConvertNMS4ToLegacy

* Make IE aware of opset4 ops

* Updated NMSIE to have different shape infer function based on the NMS it was converted from. Implemented NMS4->NMSIE conversion

* Apply code style

* Updated StaticShapeNonMaximumSuppression op in the VPU

* Introduced new version of NMSIE operation with shape infer function from v4::NMS

* Fixed dynamicToStaticNonMaxSuppression transformation

* Added new version of NMSIE op with updated shape infer function

* Fixed NMS4 to NMSIE2 transformation

* Fixed constructors for nGraph ops v4::NM and dynamic::NMS

* Updated text in the opset4 specification document

* Code style fixes

* Fixed constructors for StaticShapeNMS + fixed test

* Minor change to the NMS op in the MO

* Fixed typo in the dynamic_to_static_shape_non_max_suppression transformation

* Removed redundant checks

* Refactored NMS infer and validate functions

* Added more checks to the validate_and_infer_types functions for NMS-3 and NMS-4

* Fixed compilation issue on Windows for op NMS

* Code style fixes

* Fixed typos in the NMSIE and NMSIE2 to CNNLayer op conversion

* Fixed typo in the ie_cnn_layer_builder_ngraph.cpp

* Fixed the NMSToLegacyNMS transformation. Added unit tests

* Apply code review comments

* Refactored NMSIE to use visitors

* Removed calling ConvertNMS4ToLegacy in the common optimizations

* Moved NMS4ToNMSLegacy to convert1_to_legacy group of transformations

* Removed useless include statement

* Removed copy-paste issue

Co-authored-by: Evgeny Lazarev <elazarev.nnov@gmail.com>
36 files changed:
docs/ops/opset.md
docs/ops/opset4.md [new file with mode: 0644]
docs/ops/sort/NonMaxSuppression_4.md [new file with mode: 0644]
inference-engine/src/inference_engine/ie_core.cpp
inference-engine/src/legacy_api/src/convert_function_to_cnn_network.cpp
inference-engine/src/legacy_api/src/ie_cnn_layer_builder_ngraph.cpp
inference-engine/src/readers/ir_reader/ie_ir_parser.cpp
inference-engine/src/transformations/include/ngraph_ops/nms_ie.hpp
inference-engine/src/transformations/include/transformations/common_optimizations/common_optimizations_tbl.hpp
inference-engine/src/transformations/include/transformations/convert_nms_4_to_nms_dynamic.hpp [moved from inference-engine/src/transformations/include/transformations/convert_nms_3_to_nms_v4.hpp with 53% similarity]
inference-engine/src/transformations/include/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp [new file with mode: 0644]
inference-engine/src/transformations/include/transformations/convert_opset1_to_legacy/convert_opset1_to_legacy_tbl.hpp
inference-engine/src/transformations/src/ngraph_ops/nms_ie.cpp
inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp
inference-engine/src/transformations/src/transformations/convert_nms_3_to_nms_4.cpp [deleted file]
inference-engine/src/transformations/src/transformations/convert_nms_4_to_nms_dynamic.cpp [new file with mode: 0644]
inference-engine/src/transformations/src/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.cpp [new file with mode: 0644]
inference-engine/src/transformations/src/transformations/convert_opset1_to_legacy/convert_opset1_to_legacy.cpp
inference-engine/src/vpu/common/include/vpu/ngraph/operations/static_shape_non_maximum_suppression.hpp
inference-engine/src/vpu/common/src/ngraph/operations/static_shape_non_maximum_suppression.cpp
inference-engine/src/vpu/common/src/ngraph/transformations/dynamic_to_static_shape.cpp
inference-engine/src/vpu/common/src/ngraph/transformations/dynamic_to_static_shape_non_max_suppression.cpp
inference-engine/tests/functional/inference_engine/transformations/convert_nms4_test.cpp [new file with mode: 0644]
inference-engine/tests/functional/plugin/myriad/ngraph/transformations/dynamic_to_static_shape_non_max_suppression.cpp
inference-engine/tests/functional/plugin/myriad/subgraph_tests/dsr_non_max_suppression.cpp
model-optimizer/extensions/ops/non_max_suppression.py
model-optimizer/extensions/ops/non_max_suppression_test.py
ngraph/python/src/ngraph/utils/node_factory.py
ngraph/python/src/pyngraph/node_factory.cpp
ngraph/src/ngraph/op/non_max_suppression.cpp
ngraph/src/ngraph/op/non_max_suppression.hpp
ngraph/src/ngraph/opsets/opset.cpp
ngraph/src/ngraph/opsets/opset.hpp
ngraph/src/ngraph/opsets/opset4.hpp [new file with mode: 0644]
ngraph/src/ngraph/opsets/opset4_tbl.hpp [new file with mode: 0644]
ngraph/test/type_prop/non_max_suppression.cpp

index 08ca7ae..dcf30b6 100644 (file)
@@ -6,6 +6,7 @@ This topic provides a complete list of available sets of operations supported in
 
 | OpenVINO™ Version | Actual Operations Set            | 
 | :---------------- | :------------------------------- | 
+| 2021.1            | [opset4](opset4.md)   | 
 | 2020.4            | [opset3](opset3.md)   |
 | 2020.3            | [opset2](opset2.md)   |
 | 2020.2            | [opset2](opset2.md)   | 
diff --git a/docs/ops/opset4.md b/docs/ops/opset4.md
new file mode 100644 (file)
index 0000000..91e4d8b
--- /dev/null
@@ -0,0 +1,143 @@
+# Operation Set `opset4` Specification
+
+This specification document describes `opset4` operation set supported in OpenVINO.
+Support for each particular operation from the list below depends on the capabilities available in a inference plugin
+and may vary among different hardware platforms and devices. Examples of operation instances are expressed as IR V10 xml
+snippets. Such IR is generated by the Model Optimizer. The semantics match corresponding nGraph operation classes
+declared in `namespace opset4`.
+
+
+## Table of Contents <a name="toc"></a>
+
+* [Abs](arithmetic/Abs_1.md)
+* [Acos](arithmetic/Acos_1.md)
+* [Acosh](arithmetic/Acosh_1.md)
+* [Add](arithmetic/Add_1.md)
+* [Asin](arithmetic/Asin_1.md)
+* [Asinh](arithmetic/Asinh_1.md)
+* [Assign](infrastructure/Assign_3.md)
+* [Atan](arithmetic/Atan_1.md)
+* [Atanh](arithmetic/Atanh_1.md)
+* [AvgPool](pooling/AvgPool_1.md)
+* [BatchNormInference](normalization/BatchNormInference_1.md)
+* [BatchToSpace](movement/BatchToSpace_2.md)
+* [BinaryConvolution](convolution/BinaryConvolution_1.md)
+* [Broadcast](movement/Broadcast_3.md)
+* [Bucketize](condition/Bucketize_3.md)
+* [CTCGreedyDecoder](sequence/CTCGreedyDecoder_1.md)
+* [Ceiling](arithmetic/Ceiling_1.md)
+* [Clamp](activation/Clamp_1.md)
+* [Concat](movement/Concat_1.md)
+* [Constant](infrastructure/Constant_1.md)
+* [Convert](type/Convert_1.md)
+* [ConvertLike](type/ConvertLike_1.md)
+* [Convolution](convolution/Convolution_1.md)
+* [ConvolutionBackpropData](convolution/ConvolutionBackpropData_1.md)
+* [Cos](arithmetic/Cos_1.md)
+* [Cosh](arithmetic/Cosh_1.md)
+* [CumSum](arithmetic/CumSum_3.md)
+* [DeformableConvolution](convolution/DeformableConvolution_1.md)
+* [DeformablePSROIPooling](detection/DeformablePSROIPooling_1.md)
+* [DepthToSpace](movement/DepthToSpace_1.md)
+* [DetectionOutput](detection/DetectionOutput_1.md)
+* [Divide](arithmetic/Divide_1.md)
+* [Elu](activation/Elu_1.md)
+* [EmbeddingBagOffsetsSum](sparse/EmbeddingBagOffsetsSum_3.md)
+* [EmbeddingBagPackedSum](sparse/EmbeddingBagPackedSum_3.md)
+* [EmbeddingSegmentsSum](sparse/EmbeddingSegmentsSum_3.md)
+* [Equal](comparison/Equal_1.md)
+* [Erf](arithmetic/Erf_1.md)
+* [Exp](activation/Exp_1.md)
+* [ExtractImagePatches](movement/ExtractImagePatches_3.md)
+* [FakeQuantize](quantization/FakeQuantize_1.md)
+* [Floor](arithmetic/Floor_1.md)
+* [FloorMod](arithmetic/FloorMod_1.md)
+* [Gather](movement/Gather_1.md)
+* [GatherTree](movement/GatherTree_1.md)
+* [Gelu](activation/GELU_2.md)
+* [Greater](comparison/Greater_1.md)
+* [GreaterEqual](comparison/GreaterEqual_1.md)
+* [GRN](normalization/GRN_1.md)
+* [GroupConvolution](convolution/GroupConvolution_1.md)
+* [GroupConvolutionBackpropData](convolution/GroupConvolutionBackpropData_1.md)
+* [GRUCell](sequence/GRUCell_3.md)
+* [HardSigmoid](activation/HardSigmoid_1.md)
+* [Interpolate](image/Interpolate_1.md)
+* [Less](comparison/Less_1.md)
+* [LessEqual](comparison/LessEqual_1.md)
+* [Log](arithmetic/Log_1.md)
+* [LogicalAnd](logical/LogicalAnd_1.md)
+* [LogicalNot](logical/LogicalNot_1.md)
+* [LogicalOr](logical/LogicalOr_1.md)
+* [LogicalXor](logical/LogicalXor_1.md)
+* [LRN](normalization/LRN_1.md)
+* [LSTMCell](sequence/LSTMCell_1.md)
+* [LSTMSequence](sequence/LSTMSequence_1.md)
+* [MatMul](matrix/MatMul_1.md)
+* [MaxPool](pooling/MaxPool_1.md)
+* [Maximum](arithmetic/Maximum_1.md)
+* [Minimum](arithmetic/Minimum_1.md)
+* [Mod](arithmetic/Mod_1.md)
+* [MVN](normalization/MVN_1.md)
+* [Multiply](arithmetic/Multiply_1.md)
+* [Negative](arithmetic/Negative_1.md)
+* [NonMaxSuppression](sort/NonMaxSuppression_4.md)
+* [NonZero](condition/NonZero_3.md)
+* [NormalizeL2](normalization/NormalizeL2_1.md)
+* [NotEqual](comparison/NotEqual_1.md)
+* [OneHot](sequence/OneHot_1.md)
+* [Pad](movement/Pad_1.md)
+* [Parameter](infrastructure/Parameter_1.md)
+* [Power](arithmetic/Power_1.md)
+* [PReLU](activation/PReLU_1.md)
+* [PriorBoxClustered](detection/PriorBoxClustered_1.md)
+* [PriorBox](detection/PriorBox_1.md)
+* [Proposal](detection/Proposal_1.md)
+* [PSROIPooling](detection/PSROIPooling_1.md)
+* [Range](generation/Range_1.md)
+* [ReLU](activation/ReLU_1.md)
+* [ReadValue](infrastructure/ReadValue_3.md)
+* [ReduceLogicalAnd](reduction/ReduceLogicalAnd_1.md)
+* [ReduceLogicalOr](reduction/ReduceLogicalOr_1.md)
+* [ReduceMax](reduction/ReduceMax_1.md)
+* [ReduceMean](reduction/ReduceMean_1.md)
+* [ReduceMin](reduction/ReduceMin_1.md)
+* [ReduceProd](reduction/ReduceProd_1.md)
+* [ReduceSum](reduction/ReduceSum_1.md)
+* [RegionYolo](detection/RegionYolo_1.md)
+* [ReorgYolo](detection/ReorgYolo_1.md)
+* [Reshape](shape/Reshape_1.md)
+* [Result](infrastructure/Result_1.md)
+* [Reverse](movement/Reverse_1.md)
+* [ReverseSequence](movement/ReverseSequence_1.md)
+* [RNNCell](sequence/RNNCell_3.md)
+* [ROIAlign](detection/ROIAlign_3.md)
+* [ROIPooling](detection/ROIPooling_1.md)
+* [ScatterElementsUpdate](movement/ScatterElementsUpdate_3.md)
+* [ScatterNDUpdate](movement/ScatterNDUpdate_3.md)
+* [ScatterUpdate](movement/ScatterUpdate_3.md)
+* [Select](condition/Select_1.md)
+* [Selu](arithmetic/Selu_1.md)
+* [ShapeOf](shape/ShapeOf_3.md)
+* [ShuffleChannels](movement/ShuffleChannels_1.md)
+* [Sigmoid](activation/Sigmoid_1.md)
+* [Sign](arithmetic/Sign_1.md)
+* [Sin](arithmetic/Sin_1.md)
+* [Sinh](arithmetic/Sinh_1.md)
+* [SoftMax](activation/SoftMax_1.md)
+* [SpaceToBatch](movement/SpaceToBatch_2.md)
+* [SpaceToDepth](movement/SpaceToDepth_1.md)
+* [Split](movement/Split_1.md)
+* [Sqrt](arithmetic/Sqrt_1.md)
+* [SquaredDifference](arithmetic/SquaredDifference_1.md)
+* [Squeeze](shape/Squeeze_1.md)
+* [StridedSlice](movement/StridedSlice_1.md)
+* [Subtract](arithmetic/Subtract_1.md)
+* [Tan](arithmetic/Tan_1.md)
+* [Tanh](arithmetic/Tanh_1.md)
+* [TensorIterator](infrastructure/TensorIterator_1.md)
+* [Tile](movement/Tile_1.md)
+* [TopK](sort/TopK_3.md)
+* [Transpose](movement/Transpose_1.md)
+* [Unsqueeze](shape/Unsqueeze_1.md)
+* [VariadicSplit](movement/VariadicSplit_1.md)
diff --git a/docs/ops/sort/NonMaxSuppression_4.md b/docs/ops/sort/NonMaxSuppression_4.md
new file mode 100644 (file)
index 0000000..22cb360
--- /dev/null
@@ -0,0 +1,104 @@
+## NonMaxSuppression<a name="NonMaxSuppression"></a>
+
+**Versioned name**: *NonMaxSuppression-4*
+
+**Category**: *Sorting and maximization*
+
+**Short description**: *NonMaxSuppression* performs non maximum suppression of the boxes with predicted scores.
+
+**Detailed description**: *NonMaxSuppression* performs non maximum suppression algorithm as described below:
+
+1.  Take the box with highest score. If the score is less than `score_threshold` then stop. Otherwise add the box to the
+output and continue to the next step.
+2.  For each input box, calculate the IOU (intersection over union) with the box added during the previous step. If the 
+value is greater than the `iou_threshold` threshold then remove the input box from further consideration.  
+3.  Return to step 1.
+
+This algorithm is applied independently to each class of each batch element. The total number of output boxes for each
+class must not exceed `max_output_boxes_per_class`. 
+
+**Attributes**:
+
+* *box_encoding*
+
+  * **Description**: *box_encoding* specifies the format of boxes data encoding.
+  * **Range of values**: "corner" or "center"
+    * *corner* - the box data is supplied as `[y1, x1, y2, x2]` where `(y1, x1)` and `(y2, x2)` are the coordinates of any diagonal pair of box corners.
+    * *center* - the box data is supplied as `[x_center, y_center, width, height]`.
+  * **Type**: string
+  * **Default value**: "corner"
+  * **Required**: *no*
+
+* *sort_result_descending*
+
+  * **Description**: *sort_result_descending* is a flag that specifies whenever it is necessary to sort selected boxes across batches or not.
+  * **Range of values**: True of False
+    * *True* - sort selected boxes across batches.
+    * *False* - do not sort selected boxes across batches (boxes are sorted per class).
+  * **Type**: boolean
+  * **Default value**: True
+  * **Required**: *no*
+  
+* *output_type*
+
+  * **Description**: the output tensor type
+  * **Range of values**: "i64" or "i32"
+  * **Type**: string
+  * **Default value**: "i64"
+  * **Required**: *No*
+
+**Inputs**:
+
+*   **1**: `boxes` - tensor of type *T* and shape `[num_batches, num_boxes, 4]` with box coordinates. Required.
+
+*   **2**: `scores` - tensor of type *T* and shape `[num_batches, num_classes, num_boxes]` with box scores. Required.
+
+*   **3**: `max_output_boxes_per_class` - scalar tensor of type *T_MAX_BOXES* specifying maximum number of boxes to be selected per class. Optional with default value 0 meaning select no boxes.
+
+*   **4**: `iou_threshold` - scalar tensor of type *T_THRESHOLDS* specifying intersection over union threshold. Optional with default value 0 meaning keep all boxes.
+
+*   **5**: `score_threshold` - scalar tensor of type *T_THRESHOLDS* specifying minimum score to consider box for the processing. Optional with default value 0.
+
+**Outputs**:
+
+*   **1**: `selected_indices` - tensor of type *T_IND* and shape `[min(num_boxes, max_output_boxes_per_class) * num_batches * num_classes, 3]` containing information about selected boxes as triplets `[batch_index, class_index, box_index]`.
+The output tensor is filled with 0s for output tensor elements if the total number of selected boxes is less than the output tensor size.
+
+**Types**
+
+* *T*: floating point type.
+
+* *T_MAX_BOXES*: integer type.
+
+* *T_THRESHOLDS*: floating point type.
+
+* *T_IND*: `int64` or `int32`.
+
+**Example**
+
+```xml
+<layer ... type="NonMaxSuppression" ... >
+    <data box_encoding="corner" sort_result_descending="1" output_type="i64"/>
+    <input>
+        <port id="0">
+            <dim>3</dim>
+            <dim>100</dim>
+            <dim>4</dim>
+        </port>
+        <port id="1">
+            <dim>3</dim>
+            <dim>5</dim>
+            <dim>100</dim>
+        </port>
+        <port id="2"/> <!-- 10 -->
+        <port id="3"/>
+        <port id="4"/>
+    </input>
+    <output>
+        <port id="5" precision="I64">
+            <dim>150</dim> <!-- min(100, 10) * 3 * 5 -->
+            <dim>3</dim>
+        </port>
+    </output>
+</layer>
+```
index 4b41aa1..a1d6a47 100644 (file)
@@ -550,6 +550,7 @@ Core::Impl::Impl() {
     opsetNames.insert("opset1");
     opsetNames.insert("opset2");
     opsetNames.insert("opset3");
+    opsetNames.insert("opset4");
 }
 
 Core::Impl::~Impl() {}
index a9838f9..c6fc8f7 100644 (file)
@@ -487,6 +487,14 @@ InferenceEngine::details::CNNLayerCreator::CNNLayerCreator(const std::shared_ptr
                            << " should be replaced by constant during constant folding.";
         return nullptr;
     });
+
+    addSpecificCreator({"NonMaxSuppressionIE"}, [](const std::shared_ptr<::ngraph::Node>& node,
+                                                 const std::map<std::string, std::string> params) -> CNNLayerPtr {
+        LayerParams attrs = {node->get_friendly_name(), "NonMaxSuppression", details::convertPrecision(node->get_output_element_type(0))};
+        auto res = std::make_shared<InferenceEngine::NonMaxSuppressionLayer>(attrs);
+        res->params = params;
+        return res;
+    });
 }
 
 CNNLayerPtr InferenceEngine::details::CNNLayerCreator::create() {
@@ -560,7 +568,6 @@ std::shared_ptr<CNNNetworkImpl> convertFunctionToICNNNetwork(const std::shared_p
                 std::make_shared<Builder::NodeConverter<::ngraph::op::v1::Minimum>>(),
                 std::make_shared<Builder::NodeConverter<::ngraph::op::v1::Multiply>>(),
                 std::make_shared<Builder::NodeConverter<::ngraph::op::v1::NonMaxSuppression>>(),
-                std::make_shared<Builder::NodeConverter<::ngraph::op::NonMaxSuppressionIE>>(),
                 std::make_shared<Builder::NodeConverter<::ngraph::op::NormalizeL2>>(),
                 std::make_shared<Builder::NodeConverter<::ngraph::op::NormalizeIE>>(),
                 std::make_shared<Builder::NodeConverter<::ngraph::op::OneHotIE>>(),
index d74d47f..7d5a999 100644 (file)
@@ -2100,18 +2100,5 @@ CNNLayer::Ptr NodeConverter<ngraph::op::v1::NonMaxSuppression>::createLayer(cons
     THROW_IE_EXCEPTION << "NonMaxSuppression operation must be converted to NonMaxSuppressionIE operation.";
 }
 
-template <>
-CNNLayer::Ptr NodeConverter<ngraph::op::NonMaxSuppressionIE>::createLayer(const std::shared_ptr<ngraph::Node>& layer) const {
-    LayerParams params = {layer->get_friendly_name(), "NonMaxSuppression", Precision::I32};
-
-    auto castedLayer = std::dynamic_pointer_cast<ngraph::op::NonMaxSuppressionIE>(layer);
-    if (castedLayer == nullptr) THROW_IE_EXCEPTION << "Cannot get " << params.type << " layer " << params.name;
-
-    auto res = std::make_shared<InferenceEngine::NonMaxSuppressionLayer>(params);
-    res->params["sort_result_descending"] = std::to_string(castedLayer->m_sort_result_descending);
-    res->params["center_point_box"] = std::to_string(castedLayer->m_sort_result_descending);
-    return res;
-}
-
 }  // namespace Builder
 }  // namespace InferenceEngine
index 1120fd4..6dbabad 100644 (file)
@@ -116,6 +116,7 @@ V10Parser::V10Parser(const std::vector<IExtensionPtr>& exts) {
     opsets["opset1"] = ngraph::get_opset1();
     opsets["opset2"] = ngraph::get_opset2();
     opsets["opset3"] = ngraph::get_opset3();
+    opsets["opset4"] = ngraph::get_opset4();
 
     // Load custom opsets
     for (const auto& ext : exts) {
index e9924ed..7095aa5 100644 (file)
@@ -29,6 +29,29 @@ public:
 
     void validate_and_infer_types() override;
 
+    bool visit_attributes(AttributeVisitor& visitor) override;
+
+    std::shared_ptr<Node> clone_with_new_inputs(const OutputVector & new_args) const override;
+
+    int m_center_point_box;
+    bool m_sort_result_descending = true;
+};
+
+class TRANSFORMATIONS_API NonMaxSuppressionIE2 : public NonMaxSuppressionIE {
+public:
+    static constexpr NodeTypeInfo type_info{"NonMaxSuppressionIE", 2};
+    const NodeTypeInfo& get_type_info() const override { return type_info; }
+
+    NonMaxSuppressionIE2(const Output<Node>& boxes,
+                        const Output<Node>& scores,
+                        const Output<Node>& max_output_boxes_per_class,
+                        const Output<Node>& iou_threshold,
+                        const Output<Node>& score_threshold,
+                        int center_point_box,
+                        bool sort_result_descending);
+
+    void validate_and_infer_types() override;
+
     std::shared_ptr<Node> clone_with_new_inputs(const OutputVector & new_args) const override;
 
     int m_center_point_box;
index 4de8358..a1190f2 100644 (file)
@@ -17,7 +17,7 @@
 // This pass must be called first in pipeline
 NGRAPH_PASS(InitNodeInfo, ::ngraph::pass)
 NGRAPH_PASS(RemoveFilteringBoxesBySize, ::ngraph::pass) // Resolves dynamism (replaces NonZero), CF needed
-NGRAPH_PASS(UpgradeNMS3ToNMS4, ::ngraph::pass) // replaces v3::NMS with v4::NMS(always dyn output shape) if function has opset3::NonZero operation
+NGRAPH_PASS(UpgradeNMS4ToNMSDynamic, ::ngraph::pass) // replaces v4::NMS with dynamic::NMS(always dyn output shape) if function has opset3::NonZero operation
 NGRAPH_PASS(ConstantFolding, ::ngraph::pass)
 NGRAPH_PASS(StridedSliceOptimization, ::ngraph::pass) // depends on CF
 NGRAPH_PASS(NopElimination, ::ngraph::pass) // may introduce fake dynamism
 namespace ngraph {
 namespace pass {
 
-class TRANSFORMATIONS_API UpgradeNMS3ToNMS4;
+class TRANSFORMATIONS_API UpgradeNMS4ToNMSDynamic;
 
 }  // namespace pass
 }  // namespace ngraph
 
 /*
  * Description:
- *      UpgradeNMS3ToNMS4 transformation upgrades NonMaxSuppression operations from v3 version to v4
+ *      UpgradeNMS4ToNMSDynamic transformation upgrades NonMaxSuppression operations from v3 version to dynamic
  *      in case function has at least one NonZero operation (dynamism marker).
- *      NMS of version v4 always has dynamic output
+ *      NMS dynamic always has dynamic output
  */
 
 
-class ngraph::pass::UpgradeNMS3ToNMS4: public ngraph::pass::FunctionPass {
+class ngraph::pass::UpgradeNMS4ToNMSDynamic: public ngraph::pass::GraphRewrite {
 public:
+    UpgradeNMS4ToNMSDynamic() : GraphRewrite() {
+        upgrade_nms4_to_nms_dynamic();
+    }
     bool run_on_function(std::shared_ptr<ngraph::Function> f) override;
+
+private:
+    void upgrade_nms4_to_nms_dynamic();
 };
 
diff --git a/inference-engine/src/transformations/include/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp b/inference-engine/src/transformations/include/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp
new file mode 100644 (file)
index 0000000..e9bccaa
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (C) 2018-2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#pragma once
+
+#include <vector>
+#include <memory>
+
+#include <transformations_visibility.hpp>
+
+#include <ngraph/pass/graph_rewrite.hpp>
+
+namespace ngraph {
+namespace pass {
+
+    class TRANSFORMATIONS_API ConvertNMS4ToLegacy;
+
+}  // namespace pass
+}  // namespace ngraph
+
+/*
+ * Description:
+ *      Convert NMS-4 directly to legacy NMS because NMS-3 and NMS-1 have different shape infer function
+ */
+
+
+class ngraph::pass::ConvertNMS4ToLegacy: public ngraph::pass::GraphRewrite {
+public:
+    ConvertNMS4ToLegacy() : GraphRewrite() {
+        convert_nms4_to_legacy();
+    }
+private:
+    void convert_nms4_to_legacy();
+};
+
index c7f696c..dbd050b 100644 (file)
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include <ngraph/opsets/opset1.hpp>
+#include <ngraph/opsets/opset4.hpp>
 
 using namespace std;
 using namespace ngraph;
@@ -20,32 +21,73 @@ op::NonMaxSuppressionIE::NonMaxSuppressionIE(const Output<Node> &boxes,
                                              const Output<Node> &score_threshold,
                                              int center_point_box,
                                              bool sort_result_descending)
-        : Op({boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold})
-        , m_center_point_box(center_point_box)
-        , m_sort_result_descending(sort_result_descending) {
+        : Op({boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold}),
+          m_center_point_box(center_point_box), m_sort_result_descending(sort_result_descending) {
     constructor_validate_and_infer_types();
 }
 
 
-std::shared_ptr<Node> op::NonMaxSuppressionIE::clone_with_new_inputs(const ngraph::OutputVector & new_args) const {
+std::shared_ptr<Node> op::NonMaxSuppressionIE::clone_with_new_inputs(const ngraph::OutputVector &new_args) const {
     check_new_args_count(this, new_args);
     return make_shared<NonMaxSuppressionIE>(new_args.at(0), new_args.at(1), new_args.at(2), new_args.at(3),
                                             new_args.at(4), m_center_point_box, m_sort_result_descending);
 }
 
 void op::NonMaxSuppressionIE::validate_and_infer_types() {
-    auto squeeze_input = [](const Output<Node> & input) -> std::shared_ptr<Node> {
+    auto squeeze_input = [](const Output<Node> &input) -> std::shared_ptr<Node> {
         return std::make_shared<opset1::Squeeze>(input, opset1::Constant::create(element::i64, Shape{1}, {0}));
     };
 
     // Calculate output shape using opset1::NonMaxSuppression
     auto max_output_boxes_per_class = std::dynamic_pointer_cast<opset1::Constant>(input_value(2).get_node_shared_ptr());
-    auto nms = std::make_shared<opset1::NonMaxSuppression>(input_value(0),
-            input_value(1),
+    auto nms = std::make_shared<opset1::NonMaxSuppression>(input_value(0), input_value(1),
             /* second input is used for output calculation and only if it's Constant output shape won't be dynamic */
-            max_output_boxes_per_class ? opset1::Constant::create(element::i64, Shape{}, max_output_boxes_per_class->cast_vector<int64_t>()) :
-            squeeze_input(input_value(2)),
-            squeeze_input(input_value(3)),
-            squeeze_input(input_value(4)));
+                                                           max_output_boxes_per_class ? opset1::Constant::create(element::i64, Shape{},
+                                                                   max_output_boxes_per_class->cast_vector<int64_t>())
+                                                                                      : squeeze_input(input_value(2)),
+                                                           squeeze_input(input_value(3)), squeeze_input(input_value(4)));
+    set_output_type(0, nms->output(0).get_element_type(), nms->output(0).get_partial_shape());
+}
+
+bool op::NonMaxSuppressionIE::visit_attributes(AttributeVisitor& visitor) {
+    visitor.on_attribute("center_point_box", m_center_point_box);
+    visitor.on_attribute("sort_result_descending", m_sort_result_descending);
+    return true;
+}
+
+// The second version of the operation is different just in the shape infer function (uses v4::NMS)
+constexpr NodeTypeInfo op::NonMaxSuppressionIE2::type_info;
+
+op::NonMaxSuppressionIE2::NonMaxSuppressionIE2(const Output<Node> &boxes,
+                                               const Output<Node> &scores,
+                                               const Output<Node> &max_output_boxes_per_class,
+                                               const Output<Node> &iou_threshold,
+                                               const Output<Node> &score_threshold,
+                                               int center_point_box,
+                                               bool sort_result_descending)
+        : op::NonMaxSuppressionIE(boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold, center_point_box, sort_result_descending) {
+    constructor_validate_and_infer_types();
+}
+
+
+std::shared_ptr<Node> op::NonMaxSuppressionIE2::clone_with_new_inputs(const ngraph::OutputVector &new_args) const {
+    check_new_args_count(this, new_args);
+    return make_shared<NonMaxSuppressionIE2>(new_args.at(0), new_args.at(1), new_args.at(2), new_args.at(3),
+                                             new_args.at(4), m_center_point_box, m_sort_result_descending);
+}
+
+void op::NonMaxSuppressionIE2::validate_and_infer_types() {
+    auto squeeze_input = [](const Output<Node> &input) -> std::shared_ptr<Node> {
+        return std::make_shared<opset1::Squeeze>(input, opset1::Constant::create(element::i64, Shape{1}, {0}));
+    };
+
+    // Calculate output shape using opset4::NonMaxSuppression
+    auto max_output_boxes_per_class = std::dynamic_pointer_cast<opset1::Constant>(input_value(2).get_node_shared_ptr());
+    auto nms = std::make_shared<opset4::NonMaxSuppression>(input_value(0), input_value(1),
+            /* second input is used for output calculation and only if it's Constant output shape won't be dynamic */
+                                                           max_output_boxes_per_class ? opset1::Constant::create(element::i64, Shape{},
+                                                                   max_output_boxes_per_class->cast_vector<int64_t>())
+                                                                                      : squeeze_input(input_value(2)), squeeze_input(input_value(3)),
+                                                           squeeze_input(input_value(4)));
     set_output_type(0, nms->output(0).get_element_type(), nms->output(0).get_partial_shape());
 }
index add05ce..7bf85ca 100644 (file)
@@ -9,7 +9,7 @@
 #include "transformations/optimize_strided_slice.hpp"
 #include "transformations/convert_scatter_elements_to_scatter.hpp"
 #include "transformations/remove_filtering_boxes_by_size.hpp"
-#include "transformations/convert_nms_3_to_nms_v4.hpp"
+#include "transformations/convert_nms_4_to_nms_dynamic.hpp"
 #include "transformations/init_node_info.hpp"
 
 #include <ngraph/pass/manager.hpp>
diff --git a/inference-engine/src/transformations/src/transformations/convert_nms_3_to_nms_4.cpp b/inference-engine/src/transformations/src/transformations/convert_nms_3_to_nms_4.cpp
deleted file mode 100644 (file)
index 1ec47d1..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2018-2020 Intel Corporation
-// SPDX-License-Identifier: Apache-2.0
-//
-
-#include <memory>
-#include <vector>
-
-#include <ngraph/graph_util.hpp>
-#include <ngraph/opsets/opset3.hpp>
-#include <ngraph/rt_info.hpp>
-#include <transformations/utils/utils.hpp>
-
-#include "transformations/convert_nms_3_to_nms_v4.hpp"
-
-bool ngraph::pass::UpgradeNMS3ToNMS4::run_on_function(std::shared_ptr<ngraph::Function> f) {
-    bool rewritten = false;
-    if (!ngraph::op::util::has_op_with_type<ngraph::opset3::NonZero>(f)) {
-        return rewritten;
-    }
-
-    for (auto &node : f->get_ops()) {
-        auto nms_3 = ngraph::as_type_ptr<opset3::NonMaxSuppression>(node);
-        if (!nms_3)
-            continue;
-
-        const auto box_encoding = static_cast<const op::v4::NonMaxSuppression::BoxEncodingType>(nms_3->get_box_encoding());
-        const auto new_args = nms_3->input_values();
-        NODE_VALIDATION_CHECK(nms_3.get(),
-                              new_args.size() >= 2 && new_args.size() <= 5,
-                              "Number of inputs must be 2, 3, 4 or 5");
-        const auto& arg2 = new_args.size() > 2 ? new_args.at(2) : ngraph::opset3::Constant::create(element::i32, Shape{}, {0});
-        const auto& arg3 = new_args.size() > 3 ? new_args.at(3) : ngraph::opset3::Constant::create(element::f32, Shape{}, {.0f});
-        const auto& arg4 = new_args.size() > 4 ? new_args.at(4) : ngraph::opset3::Constant::create(element::f32, Shape{}, {.0f});
-
-        const auto nms_4 = std::make_shared<op::v4::NonMaxSuppression>(
-                new_args.at(0),
-                new_args.at(1),
-                arg2,
-                arg3,
-                arg4,
-                box_encoding,
-                nms_3->get_sort_result_descending(),
-                nms_3->get_output_type());
-
-        nms_4->set_friendly_name(nms_3->get_friendly_name());
-        ngraph::copy_runtime_info(nms_3, nms_4);
-        ngraph::replace_node(nms_3, nms_4);
-        rewritten = true;
-    }
-    return rewritten;
-}
diff --git a/inference-engine/src/transformations/src/transformations/convert_nms_4_to_nms_dynamic.cpp b/inference-engine/src/transformations/src/transformations/convert_nms_4_to_nms_dynamic.cpp
new file mode 100644 (file)
index 0000000..c365f4f
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) 2018-2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <memory>
+#include <vector>
+
+#include <ngraph/graph_util.hpp>
+#include <ngraph/opsets/opset4.hpp>
+#include <ngraph/rt_info.hpp>
+#include <transformations/utils/utils.hpp>
+
+#include "transformations/convert_nms_4_to_nms_dynamic.hpp"
+
+void ngraph::pass::UpgradeNMS4ToNMSDynamic::upgrade_nms4_to_nms_dynamic() {
+    auto boxes = std::make_shared<pattern::op::Label>(element::f32, Shape{1, 1000, 4});
+    auto scores = std::make_shared<pattern::op::Label>(element::f32, Shape{1, 1, 1000});
+    auto max_output_boxes_per_class = ngraph::opset4::Constant::create(element::i64, Shape{}, {10});
+    auto iou_threshold = ngraph::opset4::Constant::create(element::f32, Shape{}, {0.75});
+    auto score_threshold = ngraph::opset4::Constant::create(element::f32, Shape{}, {0.7});
+    auto nms = std::make_shared<ngraph::opset4::NonMaxSuppression>(boxes, scores, max_output_boxes_per_class,
+                                                                   iou_threshold, score_threshold);
+
+    ngraph::graph_rewrite_callback callback = [](pattern::Matcher &m) {
+        auto nms_4 = std::dynamic_pointer_cast<ngraph::opset4::NonMaxSuppression>(m.get_match_root());
+        if (!nms_4) {
+            return false;
+        }
+
+        const auto box_encoding = static_cast<const op::dynamic::NonMaxSuppression::BoxEncodingType>(nms_4->get_box_encoding());
+        const auto new_args = nms_4->input_values();
+        const auto& arg2 = new_args.size() > 2 ? new_args.at(2) : ngraph::opset4::Constant::create(element::i32, Shape{}, {0});
+        const auto& arg3 = new_args.size() > 3 ? new_args.at(3) : ngraph::opset4::Constant::create(element::f32, Shape{}, {.0f});
+        const auto& arg4 = new_args.size() > 4 ? new_args.at(4) : ngraph::opset4::Constant::create(element::f32, Shape{}, {.0f});
+
+        const auto nms_dynamic = std::make_shared<op::dynamic::NonMaxSuppression>(
+                new_args.at(0),
+                new_args.at(1),
+                arg2,
+                arg3,
+                arg4,
+                box_encoding,
+                nms_4->get_sort_result_descending(),
+                nms_4->get_output_type());
+
+        nms_dynamic->set_friendly_name(nms_4->get_friendly_name());
+        ngraph::copy_runtime_info(nms_4, nms_dynamic);
+        ngraph::replace_node(nms_4, nms_dynamic);
+        return true;
+    };
+
+    auto m = std::make_shared<ngraph::pattern::Matcher>(nms, "UpgradeNMS4ToDynamic");
+    this->add_matcher(m, callback, PassProperty::CHANGE_DYNAMIC_STATE);
+}
+
+bool ngraph::pass::UpgradeNMS4ToNMSDynamic::run_on_function(std::shared_ptr<ngraph::Function> f) {
+    if (ngraph::op::util::has_op_with_type<ngraph::opset4::NonZero>(f)) {
+        return GraphRewrite::run_on_function(f);
+    }
+    return false;
+}
diff --git a/inference-engine/src/transformations/src/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.cpp b/inference-engine/src/transformations/src/transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.cpp
new file mode 100644 (file)
index 0000000..65cf67e
--- /dev/null
@@ -0,0 +1,119 @@
+// Copyright (C) 2018-2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <memory>
+#include <vector>
+
+#include <ngraph/graph_util.hpp>
+#include <ngraph/opsets/opset1.hpp>
+#include <ngraph/opsets/opset4.hpp>
+#include <ngraph_ops/nms_ie.hpp>
+#include <ngraph/rt_info.hpp>
+#include <transformations/utils/utils.hpp>
+
+#include "transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp"
+
+void ngraph::pass::ConvertNMS4ToLegacy::convert_nms4_to_legacy() {
+    auto boxes = std::make_shared<pattern::op::Label>(element::f32, Shape{1, 1000, 4});
+    auto scores = std::make_shared<pattern::op::Label>(element::f32, Shape{1, 1, 1000});
+    auto max_output_boxes_per_class = ngraph::opset4::Constant::create(element::i64, Shape{}, {10});
+    auto iou_threshold = ngraph::opset4::Constant::create(element::f32, Shape{}, {0.75});
+    auto score_threshold = ngraph::opset4::Constant::create(element::f32, Shape{}, {0.7});
+    auto nms = std::make_shared<ngraph::opset4::NonMaxSuppression>(boxes, scores, max_output_boxes_per_class,
+                                                                   iou_threshold, score_threshold);
+
+    ngraph::graph_rewrite_callback callback = [](pattern::Matcher &m) {
+        auto nms_4 = std::dynamic_pointer_cast<ngraph::opset4::NonMaxSuppression>(m.get_match_root());
+        if (!nms_4) {
+            return false;
+        }
+
+        const auto new_args = nms_4->input_values();
+        const auto& arg2 = new_args.size() > 2 ? new_args.at(2) : ngraph::opset4::Constant::create(element::i32, Shape{}, {0});
+        const auto& arg3 = new_args.size() > 3 ? new_args.at(3) : ngraph::opset4::Constant::create(element::f32, Shape{}, {.0f});
+        const auto& arg4 = new_args.size() > 4 ? new_args.at(4) : ngraph::opset4::Constant::create(element::f32, Shape{}, {.0f});
+
+        const auto max_output_boxes_per_class_rank = arg2.get_partial_shape().rank();
+        const auto iou_threshold_rank = arg3.get_partial_shape().rank();
+        const auto score_threshold_rank = arg4.get_partial_shape().rank();
+
+        // Check that required ranks are not dynamic
+        if (max_output_boxes_per_class_rank.is_dynamic() ||
+            iou_threshold_rank.is_dynamic() ||
+            score_threshold_rank.is_dynamic()) {
+            return false;
+        }
+
+        if (max_output_boxes_per_class_rank.get_length() == 1 &&
+            iou_threshold_rank.get_length() == 1 &&
+            score_threshold_rank.get_length() == 1) {
+            return false;
+        }
+
+        // vector of new nGraph operations
+        NodeVector new_ops;
+
+        auto new_max_per_class = arg2;
+        if (max_output_boxes_per_class_rank.get_length() == 0) {
+            // WA: we need to create Constant manually because it requires by NMS shape inference
+            //     otherwise we will get dynamic shape until first CF is executed. It can be resolved
+            //     if CF will be executed right after transformation and before Validate pass.
+            if (auto new_max_per_class_const = std::dynamic_pointer_cast<opset1::Constant>(new_max_per_class.get_node_shared_ptr())) {
+                new_max_per_class = opset1::Constant::create(element::i64, Shape{1}, new_max_per_class_const->cast_vector<int64_t>());
+            } else {
+                new_max_per_class = std::make_shared<ngraph::op::Unsqueeze>(arg2, opset1::Constant::create(element::i64, Shape{1}, {0}));
+                new_ops.push_back(new_max_per_class.get_node_shared_ptr());
+            }
+        }
+        auto new_iou_threshold = arg3;
+        if (iou_threshold_rank.get_length() == 0) {
+            new_iou_threshold = std::make_shared<ngraph::op::Unsqueeze>(arg3, opset1::Constant::create(element::i64, Shape{1}, {0}));
+            new_ops.push_back(new_iou_threshold.get_node_shared_ptr());
+        }
+        auto new_score_threshold = arg4;
+        if (score_threshold_rank.get_length() == 0) {
+            new_score_threshold = std::make_shared<ngraph::op::Unsqueeze>(arg4, opset1::Constant::create(element::i64, Shape{1}, {0}));
+            new_ops.push_back(new_score_threshold.get_node_shared_ptr());
+        }
+
+        int center_point_box = 0;
+        switch (nms_4->get_box_encoding()) {
+            case ::ngraph::opset4::NonMaxSuppression::BoxEncodingType::CENTER:
+                center_point_box = 1;
+                break;
+            case ::ngraph::opset4::NonMaxSuppression::BoxEncodingType::CORNER:
+                center_point_box = 0;
+                break;
+            default:
+                throw ngraph_error("NonMaxSuppression layer " + nms_4->get_friendly_name() +
+                                   " has unsupported box encoding");
+        }
+        const auto nms_legacy = std::make_shared<op::NonMaxSuppressionIE2>(
+                new_args.at(0),
+                new_args.at(1),
+                new_max_per_class,
+                new_iou_threshold,
+                new_score_threshold,
+                center_point_box,
+                nms_4->get_sort_result_descending());
+        new_ops.push_back(nms_legacy);
+
+        Output<Node> last;
+        // if the output is the i32 then it matches behavior of the v1::NonMaxSuppression otherwise need to insert Convert
+        if (nms_4->get_output_type() == element::i32) {
+            last = nms_legacy;
+        } else {
+            last = std::make_shared<ngraph::opset4::Convert>(nms_legacy, nms_4->get_output_type());
+            new_ops.push_back(last.get_node_shared_ptr());
+        }
+
+        last.get_node_shared_ptr()->set_friendly_name(nms_4->get_friendly_name());
+        ngraph::copy_runtime_info(nms_4, new_ops);
+        ngraph::replace_node(nms_4, last.get_node_shared_ptr());
+        return true;
+    };
+
+    auto m = std::make_shared<ngraph::pattern::Matcher>(nms, "ConvertNMS4ToNMSLegacy");
+    this->add_matcher(m, callback, PassProperty::CHANGE_DYNAMIC_STATE);
+}
index 2a23c39..76bb223 100644 (file)
@@ -20,6 +20,7 @@
 #include <transformations/convert_opset1_to_legacy/convert_mul_or_add_finally.hpp>
 #include <transformations/convert_negative.hpp>
 #include <transformations/convert_opset1_to_legacy/convert_nms_to_nms_ie.hpp>
+#include <transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp>
 #include <transformations/convert_opset1_to_legacy/convert_normalizel2_to_normalize_ie.hpp>
 #include <transformations/convert_opset1_to_legacy/convert_one_hot_to_one_hot_ie.hpp>
 #include <transformations/convert_opset1_to_legacy/convert_pad_to_pad_ie.hpp>
index 13cc7d0..9f21ffb 100644 (file)
@@ -12,7 +12,7 @@
 
 namespace ngraph { namespace vpu { namespace op {
 
-class StaticShapeNonMaxSuppression : public ngraph::op::v3::NonMaxSuppression {
+class StaticShapeNonMaxSuppression : public ngraph::op::v4::NonMaxSuppression {
 public:
     static constexpr NodeTypeInfo type_info{"StaticShapeStaticShapeNonMaxSuppression", 0};
     const NodeTypeInfo& get_type_info() const override { return type_info; }
index 9c60b8e..97e2d46 100644 (file)
@@ -20,9 +20,9 @@ StaticShapeNonMaxSuppression::StaticShapeNonMaxSuppression(
         const StaticShapeNonMaxSuppression::BoxEncodingType box_encoding,
         const bool sort_result_descending,
         const element::Type& output_type)
-        : ngraph::op::v3::NonMaxSuppression({
+        : ngraph::op::v4::NonMaxSuppression(
             boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold,
-            box_encoding, sort_result_descending, output_type}) {
+            box_encoding, sort_result_descending, output_type) {
     constructor_validate_and_infer_types();
 }
 
@@ -32,12 +32,12 @@ StaticShapeNonMaxSuppression::StaticShapeNonMaxSuppression(
         const StaticShapeNonMaxSuppression::BoxEncodingType box_encoding,
         const bool sort_result_descending,
         const element::Type& output_type)
-        : ngraph::op::v3::NonMaxSuppression({boxes,
+        : ngraph::op::v4::NonMaxSuppression(boxes,
               scores,
               ngraph::opset3::Constant::create(element::i64, Shape{}, {0}),
               ngraph::opset3::Constant::create(element::f32, Shape{}, {.0f}),
               ngraph::opset3::Constant::create(element::f32, Shape{}, {.0f}),
-              box_encoding, sort_result_descending, output_type}) {
+              box_encoding, sort_result_descending, output_type) {
     constructor_validate_and_infer_types();
 }
 
@@ -64,7 +64,7 @@ StaticShapeNonMaxSuppression::clone_with_new_inputs(const OutputVector& new_args
 }
 
 void StaticShapeNonMaxSuppression::validate_and_infer_types() {
-    ngraph::op::v3::NonMaxSuppression::validate_and_infer_types();
+    ngraph::op::v4::NonMaxSuppression::validate_and_infer_types();
 
     const auto out_shape = this->get_output_partial_shape(0);
     NODE_VALIDATION_CHECK(this, out_shape.is_static(),
index ca1d961..9c7aa5f 100644 (file)
@@ -66,7 +66,7 @@ const Transformations& getDefaultTransformations() {
         {ngraph::opset3::Equal::type_info, dynamicToStaticShapeBinaryEltwise},
         {ngraph::opset3::Greater::type_info, dynamicToStaticShapeBinaryEltwise},
         {ngraph::opset3::Power::type_info, dynamicToStaticShapeBinaryEltwise},
-        {ngraph::op::v4::NonMaxSuppression::type_info, dynamicToStaticNonMaxSuppression},
+        {ngraph::op::dynamic::NonMaxSuppression::type_info, dynamicToStaticNonMaxSuppression},
         {ngraph::opset3::NonZero::type_info,   dynamicToStaticShapeNonZero},
         {ngraph::opset3::TopK::type_info, dynamicToStaticShapeTopK},
         {ngraph::opset3::Transpose::type_info, dynamicToStaticShapeTranspose},
index 64f88b2..a2bff37 100644 (file)
 namespace vpu {
 
 void dynamicToStaticNonMaxSuppression(std::shared_ptr<ngraph::Node> node) {
-    auto nms_4 = std::dynamic_pointer_cast<ngraph::op::v4::NonMaxSuppression>(node);
-    VPU_THROW_UNLESS(nms_4, "dynamicToStaticNonMaxSuppression transformation for {} of type {} expects {} as node for replacement",
-                     node->get_friendly_name(), node->get_type_info(), ngraph::op::v4::NonMaxSuppression::type_info);
+    auto nms_dynamic = std::dynamic_pointer_cast<ngraph::op::dynamic::NonMaxSuppression>(node);
+    VPU_THROW_UNLESS(nms_dynamic, "dynamicToStaticNonMaxSuppression transformation for {} of type {} expects {} as node for replacement",
+                     node->get_friendly_name(), node->get_type_info(), ngraph::op::dynamic::NonMaxSuppression::type_info);
 
     auto staticShapeNMS = std::make_shared<ngraph::vpu::op::StaticShapeNonMaxSuppression>(
-            nms_4->input_value(0),
-            nms_4->input_value(1),
-            nms_4->input_value(2),
-            nms_4->input_value(3),
-            nms_4->input_value(4),
-            nms_4->get_box_encoding(),
-            nms_4->get_sort_result_descending(),
-            nms_4->get_output_type());
+            nms_dynamic->input_value(0),
+            nms_dynamic->input_value(1),
+            nms_dynamic->input_value(2),
+            nms_dynamic->input_value(3),
+            nms_dynamic->input_value(4),
+            nms_dynamic->get_box_encoding(),
+            nms_dynamic->get_sort_result_descending(),
+            nms_dynamic->get_output_type());
 
     auto dynamicShapeResolver = std::make_shared<ngraph::vpu::op::DynamicShapeResolver>(
             staticShapeNMS->output(0), staticShapeNMS->output(1));
-    dynamicShapeResolver->set_friendly_name(nms_4->get_friendly_name());
+    dynamicShapeResolver->set_friendly_name(nms_dynamic->get_friendly_name());
 
-    ngraph::replace_node(std::move(nms_4), std::move(dynamicShapeResolver));
+    ngraph::replace_node(std::move(nms_dynamic), std::move(dynamicShapeResolver));
 }
 
 }  // namespace vpu
diff --git a/inference-engine/tests/functional/inference_engine/transformations/convert_nms4_test.cpp b/inference-engine/tests/functional/inference_engine/transformations/convert_nms4_test.cpp
new file mode 100644 (file)
index 0000000..cf54611
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <memory>
+
+#include <ngraph/function.hpp>
+#include <ngraph/opsets/opset4.hpp>
+#include <ngraph_ops/nms_ie.hpp>
+#include <transformations/convert_opset1_to_legacy/convert_nms_4_to_legacy.hpp>
+#include <transformations/init_node_info.hpp>
+#include <transformations/utils/utils.hpp>
+
+#include "ngraph_test_utils.hpp"
+
+using namespace testing;
+using namespace ngraph;
+
+TEST(TransformationTests, ConvertNMS4ToNMSIEStatic) {
+    std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, Shape{1, 1000, 4});
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, Shape{1, 1, 1000});
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<opset4::NonMaxSuppression>(boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold,
+                                                               opset4::NonMaxSuppression::BoxEncodingType::CORNER, true);
+
+        f = std::make_shared<Function>(NodeVector{nms}, ParameterVector{boxes, scores});
+
+        const auto &orig_shape = f->get_output_partial_shape(0);
+        pass::InitNodeInfo().run_on_function(f);
+        pass::ConvertNMS4ToLegacy().run_on_function(f);
+        ASSERT_NO_THROW(check_rt_info(f));
+        ASSERT_TRUE(f->get_output_partial_shape(0).is_static()) << "Shape " << f->get_output_partial_shape(0) << " should be static";
+    }
+
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, Shape{1, 1000, 4});
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, Shape{1, 1, 1000});
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{1}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<op::NonMaxSuppressionIE2>(boxes, scores, max_output_boxes_per_class,
+                                                              std::make_shared<opset4::Unsqueeze>(iou_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              std::make_shared<opset4::Unsqueeze>(score_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              0, true);
+        auto convert = std::make_shared<ngraph::opset4::Convert>(nms, element::i64);
+        convert->set_friendly_name("nms");
+
+        f_ref = std::make_shared<Function>(NodeVector{convert}, ParameterVector{boxes, scores});
+        ASSERT_TRUE(f_ref->get_output_partial_shape(0).is_static()) << "Shape " << f_ref->get_output_partial_shape(0) << " should be static";
+    }
+
+    auto res = compare_functions(f, f_ref);
+    ASSERT_TRUE(res.first) << res.second;
+}
+
+TEST(TransformationTests, ConvertNMS4ToNMSIEDynamic1) {
+    std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, PartialShape::dynamic());
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, PartialShape::dynamic());
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<opset4::NonMaxSuppression>(boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold,
+                                                               opset4::NonMaxSuppression::BoxEncodingType::CORNER, true, element::i32);
+
+        f = std::make_shared<Function>(NodeVector{nms}, ParameterVector{boxes, scores});
+
+        pass::InitNodeInfo().run_on_function(f);
+        pass::ConvertNMS4ToLegacy().run_on_function(f);
+        f->validate_nodes_and_infer_types();
+        ASSERT_NO_THROW(check_rt_info(f));
+    }
+
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, PartialShape::dynamic());
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, PartialShape::dynamic());
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{1}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<op::NonMaxSuppressionIE2>(boxes, scores, max_output_boxes_per_class,
+                                                              std::make_shared<opset4::Unsqueeze>(iou_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              std::make_shared<opset4::Unsqueeze>(score_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              0, true);
+
+        f_ref = std::make_shared<Function>(NodeVector{nms}, ParameterVector{boxes, scores});
+    }
+
+    auto res = compare_functions(f, f_ref);
+    ASSERT_TRUE(res.first) << res.second;
+}
+
+TEST(TransformationTests, ConvertNMS4ToNMSIEDynamic2) {
+    std::shared_ptr<Function> f(nullptr), f_ref(nullptr);
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, PartialShape{DYN, 1000, 4});
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, PartialShape{DYN, 1, 1000});
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<opset4::NonMaxSuppression>(boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold,
+                                                               opset4::NonMaxSuppression::BoxEncodingType::CORNER, true, element::i32);
+
+        f = std::make_shared<Function>(NodeVector{nms}, ParameterVector{boxes, scores});
+
+        pass::InitNodeInfo().run_on_function(f);
+        pass::ConvertNMS4ToLegacy().run_on_function(f);
+        f->validate_nodes_and_infer_types();
+        ASSERT_NO_THROW(check_rt_info(f));
+    }
+
+    {
+        auto boxes = std::make_shared<opset4::Parameter>(element::f32, PartialShape{DYN, 1000, 4});
+        auto scores = std::make_shared<opset4::Parameter>(element::f32, PartialShape{DYN, 1, 1000});
+        auto max_output_boxes_per_class = opset4::Constant::create(element::i64, Shape{1}, {10});
+        auto iou_threshold = opset4::Constant::create(element::f32, Shape{}, {0.75});
+        auto score_threshold = opset4::Constant::create(element::f32, Shape{}, {0.7});
+        auto nms = std::make_shared<op::NonMaxSuppressionIE2>(boxes, scores, max_output_boxes_per_class,
+                                                              std::make_shared<opset4::Unsqueeze>(iou_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              std::make_shared<opset4::Unsqueeze>(score_threshold,
+                                                                                                  opset4::Constant::create(element::i64, Shape{1}, {0})),
+                                                              0, true);
+
+        f_ref = std::make_shared<Function>(NodeVector{nms}, ParameterVector{boxes, scores});
+    }
+
+    auto res = compare_functions(f, f_ref);
+    ASSERT_TRUE(res.first) << res.second;
+}
index 597b32e..06318cb 100644 (file)
@@ -56,7 +56,7 @@ protected:
         const auto dims = std::make_shared<ngraph::opset3::Parameter>(ngraph::element::i64, ngraph::Shape{3});
         const auto dsr = std::make_shared<ngraph::vpu::op::DynamicShapeResolver>(scores, dims);
 
-        const auto node = std::make_shared<ngraph::op::v4::NonMaxSuppression>(
+        const auto node = std::make_shared<ngraph::op::dynamic::NonMaxSuppression>(
                 boxes, dsr, max_output_boxes_per_class, iou_threshold, score_threshold);
 
         auto outputShape = node->get_output_partial_shape(0);
index 131b4bf..d46edbd 100644 (file)
@@ -48,12 +48,12 @@ protected:
         const auto dims = std::make_shared<ngraph::opset3::Parameter>(ngraph::element::i64, ngraph::Shape{3});
         const auto dsr = std::make_shared<ngraph::vpu::op::DynamicShapeResolver>(scores, dims);
 
-        const auto node = std::make_shared<ngraph::op::v4::NonMaxSuppression>(
+        const auto node = std::make_shared<ngraph::op::dynamic::NonMaxSuppression>(
                 boxes, dsr, max_output_boxes_per_class, iou_threshold, score_threshold);
 
         const auto result = std::make_shared<ngraph::opset3::Result>(node);
         function = std::make_shared<ngraph::Function>(ngraph::ResultVector{result},
-                ngraph::ParameterVector{boxes, scores, dims}, "DSR-v4::NMS");
+                ngraph::ParameterVector{boxes, scores, dims}, "DSR-dynamic::NMS");
     }
 };
 
index f161b8f..fcbe0a0 100644 (file)
@@ -29,10 +29,10 @@ class NonMaxSuppression(Op):
 
     def __init__(self, graph: Graph, attrs: dict):
         mandatory_props = {
-            'type': __class__.op,
-            'op': __class__.op,
-            'version': 'opset3',
-            'infer': __class__.infer,
+            'type': self.op,
+            'op': self.op,
+            'version': 'opset4',
+            'infer': self.infer,
             'output_type': np.int64,
             'center_point_box': 0,
             'box_encoding': 'corner',
@@ -45,18 +45,15 @@ class NonMaxSuppression(Op):
         }
         super().__init__(graph, mandatory_props, attrs)
 
-    def supported_attrs(self):
-        if self.ir_version < 10:
-            return ['center_point_box']
+    def backend_attrs(self):
+        version = self.get_opset()
+        if version in ['opset3', 'opset4']:
+            return ['sort_result_descending', 'box_encoding',
+                    ('output_type', lambda node: np_data_type_to_destination_type(node.output_type))]
+        elif version == 'opset1':
+            return ['sort_result_descending', 'box_encoding']
         else:
-            version = self.get_opset()
-            if version == 'opset3':
-                return ['sort_result_descending', 'box_encoding',
-                        ('output_type', lambda node: np_data_type_to_destination_type(node.output_type))]
-            elif version == 'opset1':
-                return ['sort_result_descending', 'box_encoding']
-            else:
-                raise Error('Unsupported operation opset version "{}"'.format(version))
+            raise Error('Unsupported operation opset version "{}"'.format(version))
 
     @staticmethod
     def infer(node: Node):
@@ -76,12 +73,15 @@ class NonMaxSuppression(Op):
         num_input_boxes = boxes_shape[1]
         assert scores_shape[2] == num_input_boxes, 'Number of boxes mismatch'
 
-        max_number_of_boxes = min(num_input_boxes, boxes_shape[0] * max_output_boxes_per_class * num_classes)
+        if node.get_opset() == 'opset4':
+            max_number_of_boxes = min(num_input_boxes, max_output_boxes_per_class) * boxes_shape[0] * num_classes
+        else:
+            max_number_of_boxes = min(num_input_boxes, boxes_shape[0] * max_output_boxes_per_class * num_classes)
         node.out_port(0).data.set_shape(int64_array([max_number_of_boxes, 3]))
 
     @staticmethod
     def type_infer(node):
-        if node.get_opset() == 'opset3':
+        if node.get_opset() in ['opset3', 'opset4']:
             node.out_port(0).set_data_type(node.output_type)
         else:
             node.out_port(0).set_data_type(np.int64)
index bfbb015..b41aef0 100644 (file)
@@ -21,8 +21,7 @@ import numpy as np
 from extensions.ops.non_max_suppression import NonMaxSuppression
 from mo.front.common.partial_infer.utils import int64_array
 from mo.graph.graph import Node
-from mo.utils.unittest.graph import build_graph, regular_op_with_shaped_data, valued_const_with_data, result, \
-    connect, FakeAttr
+from mo.utils.unittest.graph import build_graph, regular_op_with_shaped_data, valued_const_with_data, result, connect
 
 
 class TestNonMaxSuppressionInfer(unittest.TestCase):
@@ -30,7 +29,7 @@ class TestNonMaxSuppressionInfer(unittest.TestCase):
         nodes = {
             **regular_op_with_shaped_data('boxes', [10, 100, 4], {'type': 'Parameter'}),
             **regular_op_with_shaped_data('scores', [10, 5, 100], {'type': 'Parameter'}),
-            **valued_const_with_data('max_output_per_class', int64_array(10)),
+            **valued_const_with_data('max_output_per_class', int64_array(7)),
             **regular_op_with_shaped_data('nms', None, {'op': 'NonMaxSuppression', 'type': 'NonMaxSuppression',
                                                         'name': 'nms'}),
             **result('output'),
@@ -43,9 +42,7 @@ class TestNonMaxSuppressionInfer(unittest.TestCase):
             *connect('nms', 'output'),
         ], nodes_with_edges_only=True)
 
-    def test_nms_infer_v10_opset1(self):
-        self.graph.graph['cmd_params'] = FakeAttr(ir_version=10)
-
+    def test_nms_infer_opset1(self):
         nms_node = Node(self.graph, 'nms')
         nms_node['version'] = 'opset1'
         NonMaxSuppression.infer(nms_node)
@@ -54,9 +51,7 @@ class TestNonMaxSuppressionInfer(unittest.TestCase):
         self.assertTrue(np.array_equal(nms_node.out_port(0).data.get_shape(), [100, 3]))
         self.assertTrue(nms_node.out_port(0).get_data_type() == np.int64)
 
-    def test_nms_infer_v10_i64_opset3(self):
-        self.graph.graph['cmd_params'] = FakeAttr(ir_version=10)
-
+    def test_nms_infer_i64_opset3(self):
         nms_node = Node(self.graph, 'nms')
         nms_node['version'] = 'opset3'
         nms_node['output_type'] = np.int64
@@ -66,9 +61,7 @@ class TestNonMaxSuppressionInfer(unittest.TestCase):
         self.assertTrue(np.array_equal(nms_node.out_port(0).data.get_shape(), [100, 3]))
         self.assertTrue(nms_node.out_port(0).get_data_type() == np.int64)
 
-    def test_nms_infer_v10_i32_opset3(self):
-        self.graph.graph['cmd_params'] = FakeAttr(ir_version=10)
-
+    def test_nms_infer_i32_opset3(self):
         nms_node = Node(self.graph, 'nms')
         nms_node['version'] = 'opset3'
         nms_node['output_type'] = np.int32
@@ -77,3 +70,23 @@ class TestNonMaxSuppressionInfer(unittest.TestCase):
 
         self.assertTrue(np.array_equal(nms_node.out_port(0).data.get_shape(), [100, 3]))
         self.assertTrue(nms_node.out_port(0).get_data_type() == np.int32)
+
+    def test_nms_infer_i32_opset4(self):
+        nms_node = Node(self.graph, 'nms')
+        nms_node['version'] = 'opset4'
+        nms_node['output_type'] = np.int32
+        NonMaxSuppression.infer(nms_node)
+        NonMaxSuppression.type_infer(nms_node)
+
+        self.assertTrue(np.array_equal(nms_node.out_port(0).data.get_shape(), [10 * 5 * 7, 3]))
+        self.assertTrue(nms_node.out_port(0).get_data_type() == np.int32)
+
+    def test_nms_infer_i64_opset4(self):
+        nms_node = Node(self.graph, 'nms')
+        nms_node['version'] = 'opset4'
+        nms_node['output_type'] = np.int64
+        NonMaxSuppression.infer(nms_node)
+        NonMaxSuppression.type_infer(nms_node)
+
+        self.assertTrue(np.array_equal(nms_node.out_port(0).data.get_shape(), [10 * 5 * 7, 3]))
+        self.assertTrue(nms_node.out_port(0).get_data_type() == np.int64)
index d07ac3d..60fcbeb 100644 (file)
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional
 from _pyngraph import NodeFactory as _NodeFactory
 from ngraph.impl import Node
 
-DEFAULT_OPSET = "opset3"
+DEFAULT_OPSET = "opset4"
 
 
 class NodeFactory(object):
index d7cfca7..4c294bd 100644 (file)
@@ -90,6 +90,7 @@ namespace
                 {"opset1", OpsetFunction(ngraph::get_opset1)},
                 {"opset2", OpsetFunction(ngraph::get_opset2)},
                 {"opset3", OpsetFunction(ngraph::get_opset3)},
+                {"opset4", OpsetFunction(ngraph::get_opset4)},
             };
 
             auto it = s_opsets.find(opset_ver);
index 2557724..57d5836 100644 (file)
@@ -178,7 +178,6 @@ void op::v1::NonMaxSuppression::validate_and_infer_types()
 
         out_shape[0] = std::min(num_boxes, max_output_boxes_per_class * num_classes);
     }
-    set_output_size(1);
     set_output_type(0, output_element_type, out_shape);
 }
 
@@ -291,7 +290,7 @@ bool ngraph::op::v3::NonMaxSuppression::visit_attributes(AttributeVisitor& visit
     return true;
 }
 
-void op::v3::NonMaxSuppression::validate_and_infer_types()
+void op::v3::NonMaxSuppression::validate()
 {
     const auto boxes_ps = get_input_partial_shape(0);
     const auto scores_ps = get_input_partial_shape(1);
@@ -300,13 +299,8 @@ void op::v3::NonMaxSuppression::validate_and_infer_types()
                           m_output_type == element::i64 || m_output_type == element::i32,
                           "Output type must be i32 or i64");
 
-    // NonMaxSuppression produces triplets
-    // that have the following format: [batch_index, class_index, box_index]
-    PartialShape out_shape = {Dimension::dynamic(), 3};
-
     if (boxes_ps.is_dynamic() || scores_ps.is_dynamic())
     {
-        set_output_type(0, m_output_type, out_shape);
         return;
     }
 
@@ -372,16 +366,32 @@ void op::v3::NonMaxSuppression::validate_and_infer_types()
                           boxes_ps[2].is_static() && boxes_ps[2].get_length() == 4u,
                           "The last dimension of the 'boxes' input must be equal to 4. Got:",
                           boxes_ps[2]);
+}
 
-    const auto max_output_boxes_per_class = input_value(2).get_node_shared_ptr();
-    if (num_boxes_boxes.is_static() && scores_ps[1].is_static() &&
-        max_output_boxes_per_class->is_constant())
-    {
-        const auto num_boxes = num_boxes_boxes.get_length();
-        const auto max_output_boxes_per_class = max_boxes_output_from_input();
-        const auto num_classes = scores_ps[1].get_length();
+void op::v3::NonMaxSuppression::validate_and_infer_types()
+{
+    const auto boxes_ps = get_input_partial_shape(0);
+    const auto scores_ps = get_input_partial_shape(1);
 
-        out_shape[0] = std::min(num_boxes, max_output_boxes_per_class * num_classes);
+    // NonMaxSuppression produces triplets
+    // that have the following format: [batch_index, class_index, box_index]
+    PartialShape out_shape = {Dimension::dynamic(), 3};
+
+    validate();
+
+    if (boxes_ps.rank().is_static() && scores_ps.rank().is_static())
+    {
+        const auto num_boxes_boxes = boxes_ps[1];
+        const auto max_output_boxes_per_class_node = input_value(2).get_node_shared_ptr();
+        if (num_boxes_boxes.is_static() && scores_ps[1].is_static() &&
+            max_output_boxes_per_class_node->is_constant())
+        {
+            const auto num_boxes = num_boxes_boxes.get_length();
+            const auto num_classes = scores_ps[1].get_length();
+            const auto max_output_boxes_per_class = max_boxes_output_from_input();
+
+            out_shape[0] = std::min(num_boxes, max_output_boxes_per_class * num_classes);
+        }
     }
     set_output_type(0, m_output_type, out_shape);
 }
@@ -433,14 +443,14 @@ op::v4::NonMaxSuppression::NonMaxSuppression(
     const op::v4::NonMaxSuppression::BoxEncodingType box_encoding,
     const bool sort_result_descending,
     const element::Type& output_type)
-    : op::v3::NonMaxSuppression({boxes,
-                                 scores,
-                                 max_output_boxes_per_class,
-                                 iou_threshold,
-                                 score_threshold,
-                                 box_encoding,
-                                 sort_result_descending,
-                                 output_type})
+    : op::v3::NonMaxSuppression(boxes,
+                                scores,
+                                max_output_boxes_per_class,
+                                iou_threshold,
+                                score_threshold,
+                                box_encoding,
+                                sort_result_descending,
+                                output_type)
 {
     constructor_validate_and_infer_types();
 }
@@ -451,14 +461,14 @@ op::v4::NonMaxSuppression::NonMaxSuppression(
     const op::v4::NonMaxSuppression::BoxEncodingType box_encoding,
     const bool sort_result_descending,
     const element::Type& output_type)
-    : op::v3::NonMaxSuppression({boxes,
-                                 scores,
-                                 op::Constant::create(element::i64, Shape{}, {0}),
-                                 op::Constant::create(element::f32, Shape{}, {.0f}),
-                                 op::Constant::create(element::f32, Shape{}, {.0f}),
-                                 box_encoding,
-                                 sort_result_descending,
-                                 output_type})
+    : op::v3::NonMaxSuppression(boxes,
+                                scores,
+                                op::Constant::create(element::i64, Shape{}, {0}),
+                                op::Constant::create(element::f32, Shape{}, {.0f}),
+                                op::Constant::create(element::f32, Shape{}, {.0f}),
+                                box_encoding,
+                                sort_result_descending,
+                                output_type)
 {
     constructor_validate_and_infer_types();
 }
@@ -493,6 +503,106 @@ shared_ptr<Node>
 
 void op::v4::NonMaxSuppression::validate_and_infer_types()
 {
+    const auto boxes_ps = get_input_partial_shape(0);
+    const auto scores_ps = get_input_partial_shape(1);
+
+    // NonMaxSuppression produces triplets
+    // that have the following format: [batch_index, class_index, box_index]
+    PartialShape out_shape = {Dimension::dynamic(), 3};
+
+    op::v3::NonMaxSuppression::validate();
+
+    if (boxes_ps.rank().is_static() && scores_ps.rank().is_static())
+    {
+        const auto num_boxes_boxes = boxes_ps[1];
+        const auto max_output_boxes_per_class_node = input_value(2).get_node_shared_ptr();
+        if (num_boxes_boxes.is_static() && scores_ps[0].is_static() && scores_ps[1].is_static() &&
+            max_output_boxes_per_class_node->is_constant())
+        {
+            const auto num_boxes = num_boxes_boxes.get_length();
+            const auto num_classes = scores_ps[1].get_length();
+            const auto max_output_boxes_per_class = max_boxes_output_from_input();
+
+            out_shape[0] = std::min(num_boxes, max_output_boxes_per_class) * num_classes *
+                           scores_ps[0].get_length();
+        }
+    }
+    set_output_type(0, m_output_type, out_shape);
+}
+
+// ------------------------------ dynamic ------------------------------
+
+constexpr NodeTypeInfo op::dynamic::NonMaxSuppression::type_info;
+
+op::dynamic::NonMaxSuppression::NonMaxSuppression(
+    const Output<Node>& boxes,
+    const Output<Node>& scores,
+    const Output<Node>& max_output_boxes_per_class,
+    const Output<Node>& iou_threshold,
+    const Output<Node>& score_threshold,
+    const op::dynamic::NonMaxSuppression::BoxEncodingType box_encoding,
+    const bool sort_result_descending,
+    const element::Type& output_type)
+    : op::v3::NonMaxSuppression(boxes,
+                                scores,
+                                max_output_boxes_per_class,
+                                iou_threshold,
+                                score_threshold,
+                                box_encoding,
+                                sort_result_descending,
+                                output_type)
+{
+    constructor_validate_and_infer_types();
+}
+
+op::dynamic::NonMaxSuppression::NonMaxSuppression(
+    const Output<Node>& boxes,
+    const Output<Node>& scores,
+    const op::dynamic::NonMaxSuppression::BoxEncodingType box_encoding,
+    const bool sort_result_descending,
+    const element::Type& output_type)
+    : op::v3::NonMaxSuppression(boxes,
+                                scores,
+                                op::Constant::create(element::i64, Shape{}, {0}),
+                                op::Constant::create(element::f32, Shape{}, {.0f}),
+                                op::Constant::create(element::f32, Shape{}, {.0f}),
+                                box_encoding,
+                                sort_result_descending,
+                                output_type)
+{
+    constructor_validate_and_infer_types();
+}
+
+shared_ptr<Node>
+    op::dynamic::NonMaxSuppression::clone_with_new_inputs(const OutputVector& new_args) const
+{
+    check_new_args_count(this, new_args);
+    NODE_VALIDATION_CHECK(this,
+                          new_args.size() >= 2 && new_args.size() <= 5,
+                          "Number of inputs must be 2, 3, 4 or 5");
+
+    const auto& arg2 = new_args.size() > 2
+                           ? new_args.at(2)
+                           : ngraph::op::Constant::create(element::i32, Shape{}, {0});
+    const auto& arg3 = new_args.size() > 3
+                           ? new_args.at(3)
+                           : ngraph::op::Constant::create(element::f32, Shape{}, {.0f});
+    const auto& arg4 = new_args.size() > 4
+                           ? new_args.at(4)
+                           : ngraph::op::Constant::create(element::f32, Shape{}, {.0f});
+
+    return std::make_shared<op::dynamic::NonMaxSuppression>(new_args.at(0),
+                                                            new_args.at(1),
+                                                            arg2,
+                                                            arg3,
+                                                            arg4,
+                                                            m_box_encoding,
+                                                            m_sort_result_descending,
+                                                            m_output_type);
+}
+
+void op::dynamic::NonMaxSuppression::validate_and_infer_types()
+{
     op::v3::NonMaxSuppression::validate_and_infer_types();
 
     // NonMaxSuppression produces triplets
index 398fc60..fd71feb 100644 (file)
@@ -177,8 +177,7 @@ namespace ngraph
                 BoxEncodingType m_box_encoding = BoxEncodingType::CORNER;
                 bool m_sort_result_descending = true;
                 ngraph::element::Type m_output_type = ngraph::element::i64;
-
-            private:
+                void validate();
                 int64_t max_boxes_output_from_input() const;
             };
         } // namespace v3
@@ -236,6 +235,60 @@ namespace ngraph
                     clone_with_new_inputs(const OutputVector& new_args) const override;
             };
         } // namespace v4
+
+        namespace dynamic
+        {
+            /// \brief NonMaxSuppression operation
+            ///
+            class NGRAPH_API NonMaxSuppression : public op::v3::NonMaxSuppression
+            {
+            public:
+                static constexpr NodeTypeInfo type_info{"NonMaxSuppression", 99};
+                const NodeTypeInfo& get_type_info() const override { return type_info; }
+                NonMaxSuppression() = default;
+
+                /// \brief Constructs a NonMaxSuppression operation.
+                ///
+                /// \param boxes Node producing the box coordinates
+                /// \param scores Node producing the box scores
+                /// \param max_output_boxes_per_class Node producing maximum number of boxes to be
+                /// selected per class
+                /// \param iou_threshold Node producing intersection over union threshold
+                /// \param score_threshold Node producing minimum score threshold
+                /// \param box_encoding Specifies the format of boxes data encoding
+                /// \param sort_result_descending Specifies whether it is necessary to sort selected
+                /// boxes across batches
+                /// \param output_type Specifies the output tensor type
+                NonMaxSuppression(const Output<Node>& boxes,
+                                  const Output<Node>& scores,
+                                  const Output<Node>& max_output_boxes_per_class,
+                                  const Output<Node>& iou_threshold,
+                                  const Output<Node>& score_threshold,
+                                  const BoxEncodingType box_encoding = BoxEncodingType::CORNER,
+                                  const bool sort_result_descending = true,
+                                  const ngraph::element::Type& output_type = ngraph::element::i64);
+
+                /// \brief Constructs a NonMaxSuppression operation with default values for the last
+                ///        3 inputs
+                ///
+                /// \param boxes Node producing the box coordinates
+                /// \param scores Node producing the box coordinates
+                /// \param box_encoding Specifies the format of boxes data encoding
+                /// \param sort_result_descending Specifies whether it is necessary to sort selected
+                /// boxes across batches
+                /// \param output_type Specifies the output tensor type
+                NonMaxSuppression(const Output<Node>& boxes,
+                                  const Output<Node>& scores,
+                                  const BoxEncodingType box_encoding = BoxEncodingType::CORNER,
+                                  const bool sort_result_descending = true,
+                                  const ngraph::element::Type& output_type = ngraph::element::i64);
+
+                void validate_and_infer_types() override;
+
+                std::shared_ptr<Node>
+                    clone_with_new_inputs(const OutputVector& new_args) const override;
+            };
+        } // namespace dynamic
     }     // namespace op
 
     NGRAPH_API
index 8ef6b39..ba38c6e 100644 (file)
@@ -118,6 +118,25 @@ const ngraph::OpSet& ngraph::get_opset3()
     return opset;
 }
 
+const ngraph::OpSet& ngraph::get_opset4()
+{
+    static std::mutex init_mutex;
+    static bool opset_is_initialized = false;
+    static OpSet opset;
+    if (!opset_is_initialized)
+    {
+        std::lock_guard<std::mutex> guard(init_mutex);
+        if (!opset_is_initialized)
+        {
+#define NGRAPH_OP(NAME, NAMESPACE) opset.insert<NAMESPACE::NAME>();
+#include "ngraph/opsets/opset4_tbl.hpp"
+#undef NGRAPH_OP
+            opset_is_initialized = true;
+        }
+    }
+    return opset;
+}
+
 const ngraph::OpSet& ngraph::get_ie_opset()
 {
     static std::mutex init_mutex;
@@ -132,6 +151,7 @@ const ngraph::OpSet& ngraph::get_ie_opset()
 #include "ngraph/opsets/opset1_tbl.hpp"
 #include "ngraph/opsets/opset2_tbl.hpp"
 #include "ngraph/opsets/opset3_tbl.hpp"
+#include "ngraph/opsets/opset4_tbl.hpp"
 #undef NGRAPH_OP
             opset_is_initialized = true;
         }
index 0dacf1c..1b1194f 100644 (file)
@@ -132,6 +132,7 @@ namespace ngraph
     const NGRAPH_API OpSet& get_opset1();
     const NGRAPH_API OpSet& get_opset2();
     const NGRAPH_API OpSet& get_opset3();
+    const NGRAPH_API OpSet& get_opset4();
     // Every op after opset0
     const NGRAPH_API OpSet& get_ie_opset();
 }
diff --git a/ngraph/src/ngraph/opsets/opset4.hpp b/ngraph/src/ngraph/opsets/opset4.hpp
new file mode 100644 (file)
index 0000000..6e8ce40
--- /dev/null
@@ -0,0 +1,29 @@
+//*****************************************************************************
+// Copyright 2017-2020 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//*****************************************************************************
+
+#pragma once
+
+#include "ngraph/ops.hpp"
+
+namespace ngraph
+{
+    namespace opset4
+    {
+#define NGRAPH_OP(a, b) using b::a;
+#include "ngraph/opsets/opset4_tbl.hpp"
+#undef NGRAPH_OP
+    }
+}
diff --git a/ngraph/src/ngraph/opsets/opset4_tbl.hpp b/ngraph/src/ngraph/opsets/opset4_tbl.hpp
new file mode 100644 (file)
index 0000000..ad304aa
--- /dev/null
@@ -0,0 +1,154 @@
+//*****************************************************************************
+// Copyright 2017-2020 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//*****************************************************************************
+
+#ifndef NGRAPH_OP
+#warning "NGRAPH_OP not defined"
+#define NGRAPH_OP(x, y)
+#endif
+
+NGRAPH_OP(Abs, ngraph::op::v0)
+NGRAPH_OP(Acos, ngraph::op::v0)
+NGRAPH_OP(Add, ngraph::op::v1)
+NGRAPH_OP(Asin, ngraph::op::v0)
+NGRAPH_OP(Atan, ngraph::op::v0)
+NGRAPH_OP(AvgPool, ngraph::op::v1)
+NGRAPH_OP(BatchNormInference, ngraph::op::v0)
+NGRAPH_OP(BinaryConvolution, ngraph::op::v1)
+NGRAPH_OP(Broadcast, ngraph::op::v3)
+NGRAPH_OP(Bucketize, ngraph::op::v3)
+NGRAPH_OP(CTCGreedyDecoder, ngraph::op::v0)
+NGRAPH_OP(Ceiling, ngraph::op::v0)
+NGRAPH_OP(Clamp, ngraph::op::v0)
+NGRAPH_OP(Concat, ngraph::op::v0)
+NGRAPH_OP(Constant, ngraph::op)
+NGRAPH_OP(Convert, ngraph::op::v0)
+NGRAPH_OP(ConvertLike, ngraph::op::v1)
+NGRAPH_OP(Convolution, ngraph::op::v1)
+NGRAPH_OP(ConvolutionBackpropData, ngraph::op::v1)
+NGRAPH_OP(Cos, ngraph::op::v0)
+NGRAPH_OP(Cosh, ngraph::op::v0)
+NGRAPH_OP(CumSum, ngraph::op::v0)
+NGRAPH_OP(DeformableConvolution, ngraph::op::v1)
+NGRAPH_OP(DeformablePSROIPooling, ngraph::op::v1)
+NGRAPH_OP(DepthToSpace, ngraph::op::v0)
+NGRAPH_OP(DetectionOutput, ngraph::op::v0)
+NGRAPH_OP(Divide, ngraph::op::v1)
+NGRAPH_OP(Elu, ngraph::op::v0)
+NGRAPH_OP(Erf, ngraph::op::v0)
+NGRAPH_OP(Equal, ngraph::op::v1)
+NGRAPH_OP(Exp, ngraph::op::v0)
+NGRAPH_OP(ExtractImagePatches, ngraph::op::v3)
+NGRAPH_OP(FakeQuantize, ngraph::op::v0)
+NGRAPH_OP(Floor, ngraph::op::v0)
+NGRAPH_OP(FloorMod, ngraph::op::v1)
+NGRAPH_OP(Gather, ngraph::op::v1)
+NGRAPH_OP(GatherTree, ngraph::op::v1)
+NGRAPH_OP(Greater, ngraph::op::v1)
+NGRAPH_OP(GreaterEqual, ngraph::op::v1)
+NGRAPH_OP(GroupConvolution, ngraph::op::v1)
+NGRAPH_OP(GroupConvolutionBackpropData, ngraph::op::v1)
+NGRAPH_OP(GRN, ngraph::op::v0)
+NGRAPH_OP(HardSigmoid, ngraph::op::v0)
+NGRAPH_OP(Interpolate, ngraph::op::v0)
+NGRAPH_OP(Less, ngraph::op::v1)
+NGRAPH_OP(LessEqual, ngraph::op::v1)
+NGRAPH_OP(Log, ngraph::op::v0)
+NGRAPH_OP(LogicalAnd, ngraph::op::v1)
+NGRAPH_OP(LogicalNot, ngraph::op::v1)
+NGRAPH_OP(LogicalOr, ngraph::op::v1)
+NGRAPH_OP(LogicalXor, ngraph::op::v1)
+NGRAPH_OP(LRN, ngraph::op::v0)
+NGRAPH_OP(LSTMCell, ngraph::op::v0)
+NGRAPH_OP(LSTMSequence, ngraph::op::v0)
+NGRAPH_OP(MatMul, ngraph::op::v0)
+NGRAPH_OP(MaxPool, ngraph::op::v1)
+NGRAPH_OP(Maximum, ngraph::op::v1)
+NGRAPH_OP(Minimum, ngraph::op::v1)
+NGRAPH_OP(Mod, ngraph::op::v1)
+NGRAPH_OP(Multiply, ngraph::op::v1)
+NGRAPH_OP(MVN, ngraph::op::v0)
+NGRAPH_OP(Negative, ngraph::op::v0)
+NGRAPH_OP(NormalizeL2, ngraph::op::v0)
+NGRAPH_OP(NotEqual, ngraph::op::v1)
+NGRAPH_OP(OneHot, ngraph::op::v1)
+NGRAPH_OP(PRelu, ngraph::op::v0)
+NGRAPH_OP(PSROIPooling, ngraph::op::v0)
+NGRAPH_OP(Pad, ngraph::op::v1)
+NGRAPH_OP(Parameter, ngraph::op::v0)
+NGRAPH_OP(Power, ngraph::op::v1)
+NGRAPH_OP(PriorBox, ngraph::op::v0)
+NGRAPH_OP(PriorBoxClustered, ngraph::op::v0)
+NGRAPH_OP(Proposal, ngraph::op::v0)
+NGRAPH_OP(Range, ngraph::op::v0)
+NGRAPH_OP(Relu, ngraph::op::v0)
+NGRAPH_OP(ReduceMax, ngraph::op::v1)
+NGRAPH_OP(ReduceLogicalAnd, ngraph::op::v1)
+NGRAPH_OP(ReduceLogicalOr, ngraph::op::v1)
+NGRAPH_OP(ReduceMean, ngraph::op::v1)
+NGRAPH_OP(ReduceMin, ngraph::op::v1)
+NGRAPH_OP(ReduceProd, ngraph::op::v1)
+NGRAPH_OP(ReduceSum, ngraph::op::v1)
+NGRAPH_OP(RegionYolo, ngraph::op::v0)
+NGRAPH_OP(ReorgYolo, ngraph::op::v0)
+NGRAPH_OP(Reshape, ngraph::op::v1)
+NGRAPH_OP(Result, ngraph::op::v0)
+NGRAPH_OP(ReverseSequence, ngraph::op::v0)
+NGRAPH_OP(ROIPooling, ngraph::op::v0)
+NGRAPH_OP(Select, ngraph::op::v1)
+NGRAPH_OP(Selu, ngraph::op::v0)
+NGRAPH_OP(Sign, ngraph::op::v0)
+NGRAPH_OP(Sigmoid, ngraph::op::v0)
+NGRAPH_OP(Sin, ngraph::op::v0)
+NGRAPH_OP(Sinh, ngraph::op::v0)
+NGRAPH_OP(Softmax, ngraph::op::v1)
+NGRAPH_OP(Sqrt, ngraph::op::v0)
+NGRAPH_OP(SpaceToDepth, ngraph::op::v0)
+NGRAPH_OP(Split, ngraph::op::v1)
+NGRAPH_OP(SquaredDifference, ngraph::op::v0)
+NGRAPH_OP(Squeeze, ngraph::op::v0)
+NGRAPH_OP(StridedSlice, ngraph::op::v1)
+NGRAPH_OP(Subtract, ngraph::op::v1)
+NGRAPH_OP(Tan, ngraph::op::v0)
+NGRAPH_OP(Tanh, ngraph::op::v0)
+NGRAPH_OP(TensorIterator, ngraph::op::v0)
+NGRAPH_OP(Tile, ngraph::op::v0)
+NGRAPH_OP(Transpose, ngraph::op::v1)
+NGRAPH_OP(Unsqueeze, ngraph::op::v0)
+NGRAPH_OP(VariadicSplit, ngraph::op::v1)
+
+// New operations added in opset2
+NGRAPH_OP(Gelu, ngraph::op::v0)
+NGRAPH_OP(BatchToSpace, ngraph::op::v1)
+NGRAPH_OP(SpaceToBatch, ngraph::op::v1)
+
+// New operations added in opset3
+NGRAPH_OP(EmbeddingBagPackedSum, ngraph::op::v3)
+NGRAPH_OP(EmbeddingSegmentsSum, ngraph::op::v3)
+NGRAPH_OP(EmbeddingBagOffsetsSum, ngraph::op::v3)
+NGRAPH_OP(GRUCell, ngraph::op::v3)
+NGRAPH_OP(NonZero, ngraph::op::v3)
+NGRAPH_OP(RNNCell, ngraph::op::v0)
+NGRAPH_OP(ROIAlign, ngraph::op::v3)
+NGRAPH_OP(ScatterElementsUpdate, ngraph::op::v3)
+NGRAPH_OP(ScatterUpdate, ngraph::op::v3)
+NGRAPH_OP(ShuffleChannels, ngraph::op::v0)
+NGRAPH_OP(ShapeOf, ngraph::op::v3)
+NGRAPH_OP(Assign, ngraph::op::v3)
+NGRAPH_OP(ReadValue, ngraph::op::v3)
+NGRAPH_OP(TopK, ngraph::op::v3)
+
+// New operations added in opset4
+NGRAPH_OP(NonMaxSuppression, ngraph::op::v4)
index 5d7e150..405a384 100644 (file)
@@ -364,3 +364,186 @@ TEST(type_prop, nms_v3_dynamic_boxes_and_scores)
     ASSERT_TRUE(
         nms->get_output_partial_shape(0).same_scheme(PartialShape{Dimension::dynamic(), 3}));
 }
+
+// ------------------------------ V4 ------------------------------
+
+TEST(type_prop, nms_v4_incorrect_boxes_rank)
+{
+    try
+    {
+        const auto boxes = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3, 4});
+        const auto scores = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3});
+
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(), "Expected a 3D tensor for the 'boxes' input");
+    }
+}
+
+TEST(type_prop, nms_v4_incorrect_scores_rank)
+{
+    try
+    {
+        const auto boxes = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3});
+        const auto scores = make_shared<op::Parameter>(element::f32, Shape{1, 2});
+
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(), "Expected a 3D tensor for the 'scores' input");
+    }
+}
+
+TEST(type_prop, nms_v4_incorrect_scheme_num_batches)
+{
+    try
+    {
+        const auto boxes = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3});
+        const auto scores = make_shared<op::Parameter>(element::f32, Shape{2, 2, 3});
+
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(),
+                             "The first dimension of both 'boxes' and 'scores' must match");
+    }
+}
+
+TEST(type_prop, nms_v4_incorrect_scheme_num_boxes)
+{
+    try
+    {
+        const auto boxes = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3});
+        const auto scores = make_shared<op::Parameter>(element::f32, Shape{1, 2, 3});
+
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(),
+                             "'boxes' and 'scores' input shapes must match at the second and third "
+                             "dimension respectively");
+    }
+}
+
+TEST(type_prop, nms_v4_scalar_inputs_check)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, Shape{1, 2, 4});
+    const auto scores = make_shared<op::Parameter>(element::f32, Shape{1, 2, 2});
+
+    const auto scalar = make_shared<op::Parameter>(element::f32, Shape{});
+    const auto non_scalar = make_shared<op::Parameter>(element::f32, Shape{1});
+
+    try
+    {
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores, non_scalar, scalar, scalar);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(),
+                             "Expected a scalar for the 'max_output_boxes_per_class' input");
+    }
+
+    try
+    {
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores, scalar, non_scalar, scalar);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(), "Expected a scalar for the 'iou_threshold' input");
+    }
+
+    try
+    {
+        make_shared<op::v4::NonMaxSuppression>(boxes, scores, scalar, scalar, non_scalar);
+    }
+    catch (const NodeValidationFailure& error)
+    {
+        EXPECT_HAS_SUBSTRING(error.what(), "Expected a scalar for the 'score_threshold' input");
+    }
+}
+
+TEST(type_prop, nms_v4_output_shape)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, Shape{5, 2, 4});
+    const auto scores = make_shared<op::Parameter>(element::f32, Shape{5, 3, 2});
+
+    const auto nms = make_shared<op::v4::NonMaxSuppression>(boxes, scores);
+    const auto nms_out_ps = nms->get_output_partial_shape(0);
+
+    EXPECT_TRUE(nms_out_ps.rank().is_static());
+    EXPECT_EQ(nms_out_ps.rank().get_length(), 2);
+    EXPECT_EQ(nms->get_shape(), (Shape{0, 3}));
+}
+
+TEST(type_prop, nms_v4_output_shape_2)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, Shape{2, 7, 4});
+    const auto scores = make_shared<op::Parameter>(element::f32, Shape{2, 5, 7});
+    const auto max_output_boxes_per_class = op::Constant::create(element::i32, Shape{}, {3});
+    const auto iou_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+    const auto score_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+
+    const auto nms = make_shared<op::v4::NonMaxSuppression>(
+        boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold);
+
+    ASSERT_EQ(nms->get_element_type(), element::i64);
+    ASSERT_EQ(nms->get_shape(), (Shape{2 * 5 * 3, 3}));
+}
+
+TEST(type_prop, nms_v4_output_shape_3)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, Shape{2, 7, 4});
+    const auto scores = make_shared<op::Parameter>(element::f32, Shape{2, 5, 7});
+    const auto max_output_boxes_per_class = op::Constant::create(element::i16, Shape{}, {1000});
+    const auto iou_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+    const auto score_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+
+    const auto nms = make_shared<op::v4::NonMaxSuppression>(
+        boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold);
+
+    ASSERT_EQ(nms->get_element_type(), element::i64);
+    ASSERT_EQ(nms->get_shape(), (Shape{2 * 5 * 7, 3}));
+}
+
+TEST(type_prop, nms_v4_output_shape_i32)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, Shape{2, 7, 4});
+    const auto scores = make_shared<op::Parameter>(element::f32, Shape{2, 5, 7});
+    const auto max_output_boxes_per_class = op::Constant::create(element::i16, Shape{}, {3});
+    const auto iou_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+    const auto score_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+
+    const auto nms =
+        make_shared<op::v4::NonMaxSuppression>(boxes,
+                                               scores,
+                                               max_output_boxes_per_class,
+                                               iou_threshold,
+                                               score_threshold,
+                                               op::v3::NonMaxSuppression::BoxEncodingType::CORNER,
+                                               true,
+                                               element::i32);
+
+    ASSERT_EQ(nms->get_element_type(), element::i32);
+    ASSERT_EQ(nms->get_shape(), (Shape{30, 3}));
+}
+
+TEST(type_prop, nms_v4_dynamic_boxes_and_scores)
+{
+    const auto boxes = make_shared<op::Parameter>(element::f32, PartialShape::dynamic());
+    const auto scores = make_shared<op::Parameter>(element::f32, PartialShape::dynamic());
+    const auto max_output_boxes_per_class = op::Constant::create(element::i16, Shape{}, {3});
+    const auto iou_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+    const auto score_threshold = make_shared<op::Parameter>(element::f32, Shape{});
+
+    const auto nms = make_shared<op::v4::NonMaxSuppression>(
+        boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold);
+
+    ASSERT_EQ(nms->get_element_type(), element::i64);
+    ASSERT_TRUE(
+        nms->get_output_partial_shape(0).same_scheme(PartialShape{Dimension::dynamic(), 3}));
+}