diff --git a/src/frontends/onnx/docs/supported_ops.md b/src/frontends/onnx/docs/supported_ops.md index 279a6e722340fa..9160cf008e4af2 100644 --- a/src/frontends/onnx/docs/supported_ops.md +++ b/src/frontends/onnx/docs/supported_ops.md @@ -36,7 +36,7 @@ OpenVINO provides support for operations of Default Opset (empty in table below) | |Celu |12 |12 | | | |CenterCropPad |18 |18 | | | |Clip |11, 1 |13, 12, 11, 6, 1 | | -| |Col2Im | |18 | | +| |Col2Im |18 |18 | | | |Compress |9 |11, 9 | | | |Concat |1 |13, 11, 4, 1 | | | |ConcatFromSequence |11 |11 |Supported only in certain patterns| diff --git a/src/frontends/onnx/frontend/src/op/col2im.cpp b/src/frontends/onnx/frontend/src/op/col2im.cpp new file mode 100644 index 00000000000000..154226c5f21953 --- /dev/null +++ b/src/frontends/onnx/frontend/src/op/col2im.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/op/col2im.hpp" + +#include "core/operator_set.hpp" +#include "exceptions.hpp" +#include "utils/common.hpp" + +namespace ov { +namespace frontend { +namespace onnx { +namespace ai_onnx { +namespace opset_18 { +ov::OutputVector col2im(const ov::frontend::onnx::Node& node) { + // 1. get inputs + common::default_op_checks(node, 3); + const auto inputs = node.get_ov_inputs(); + const auto& data = inputs[0]; // input + const auto& output_size = inputs[1]; // image_shape + const auto& kernel_size = inputs[2]; // block_shape + + // 2. get attributes + const size_t spatial_rank = 2; + + std::vector default_attr_vals(spatial_rank, 1); + auto dilations = node.get_attribute_value>("dilations", default_attr_vals); + auto strides = node.get_attribute_value>("strides", default_attr_vals); + + std::vector default_pads(spatial_rank * 2, 0); + auto pads = node.get_attribute_value>("pads", default_pads); + std::vector pads_begin; + std::vector pads_end; + + // ov::op::v15::Col2Im only supports 2D spatial dimensions + CHECK_VALID_NODE(node, + dilations.size() == spatial_rank, + "Col2Im 'dilations' attribute must have size [2]. Got: ", + dilations.size()); + CHECK_VALID_NODE(node, + strides.size() == spatial_rank, + "Col2Im 'strides' attribute must have size [2]. Got: ", + strides.size()); + CHECK_VALID_NODE(node, + pads.size() == spatial_rank * 2, + "Col2Im 'pads' attribute must have size [4]. Got: ", + pads.size()); + + // spatial_rank == pads.size() / 2 + pads_begin.assign(pads.begin(), pads.begin() + spatial_rank); + pads_end.assign(pads.begin() + spatial_rank, pads.end()); + + // 3. return Col2Im + return {std::make_shared(data, + output_size, + kernel_size, + strides, + dilations, + pads_begin, + pads_end)}; +} + +ONNX_OP("Col2Im", OPSET_SINCE(1), ai_onnx::opset_18::col2im); +} // namespace opset_18 +} // namespace ai_onnx +} // namespace onnx +} // namespace frontend +} // namespace ov diff --git a/src/frontends/onnx/tests/__init__.py b/src/frontends/onnx/tests/__init__.py index 8309e2f0614dc5..6732e2c9114a21 100644 --- a/src/frontends/onnx/tests/__init__.py +++ b/src/frontends/onnx/tests/__init__.py @@ -52,8 +52,7 @@ def xfail_test(reason="Mark the test as expected to fail", strict=True): "Axes input must be constant") skip_bitwise_ui64 = pytest.mark.skip(reason="AssertionError: Not equal to tolerance rtol=0.001, atol=1e-07") xfail_issue_99950 = xfail_test(reason="CenterCropPad func is not supported") -xfail_issue_99952 = xfail_test(reason="Col2Im operator is not supported") -xfail_issue_99954 = xfail_test(reason="Constant Pad - RuntimeError: Shape inference of Reference node with name y failed") +xfail_issue_99952 = xfail_test(reason="Col2Im operator is not supported 5 dimensions") xfail_issue_99955 = xfail_test(reason="GroupNorm is not supported") xfail_issue_99957 = xfail_test(reason="LayerNorm - RuntimeError: While validating node ''") xfail_issue_99960 = xfail_test(reason="MVN - Results mismatch") diff --git a/src/frontends/onnx/tests/models/col2im_2D_batch_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_batch_opset_18.prototxt new file mode 100644 index 00000000000000..c049208acd4099 --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_batch_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_batch_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "strides" + ints: 1 + ints: 1 + type: INTS + } + } + name: "col2im_2D_batch_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 4 + } + dim { + dim_value: 4 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 3 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/models/col2im_2D_channel_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_channel_opset_18.prototxt new file mode 100644 index 00000000000000..56d7b2a7fe8c3a --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_channel_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_channel_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "strides" + ints: 1 + ints: 1 + type: INTS + } + } + name: "col2im_2D_channel_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 12 + } + dim { + dim_value: 4 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 3 + } + dim { + dim_value: 3 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/models/col2im_2D_default_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_default_opset_18.prototxt new file mode 100644 index 00000000000000..0a66c36ea853c1 --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_default_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_default_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "strides" + ints: 1 + ints: 1 + type: INTS + } + } + name: "col2im_2D_default_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 4 + } + dim { + dim_value: 4 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 3 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/models/col2im_2D_dilations_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_dilations_opset_18.prototxt new file mode 100644 index 00000000000000..f64779e9db31d7 --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_dilations_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_dilations_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "strides" + ints: 1 + ints: 1 + type: INTS + } + } + name: "col2im_2D_dilations_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 4 + } + dim { + dim_value: 9 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 5 + } + dim { + dim_value: 5 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/models/col2im_2D_pads_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_pads_opset_18.prototxt new file mode 100644 index 00000000000000..ae624ef4059c2c --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_pads_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_pads_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "pads" + ints: 1 + ints: 1 + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "strides" + ints: 1 + ints: 1 + type: INTS + } + } + name: "col2im_2D_pads_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 4 + } + dim { + dim_value: 9 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/models/col2im_2D_strides_opset_18.prototxt b/src/frontends/onnx/tests/models/col2im_2D_strides_opset_18.prototxt new file mode 100644 index 00000000000000..82e70a1b8fb2ad --- /dev/null +++ b/src/frontends/onnx/tests/models/col2im_2D_strides_opset_18.prototxt @@ -0,0 +1,103 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "input" + input: "image_shape" + input: "block_shape" + output: "output" + name: "col2im_2D_strides_opset_18" + op_type: "Col2Im" + attribute { + name: "dilations" + ints: 1 + ints: 1 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + } + name: "col2im_2D_strides_opset_18" + input { + name: "input" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 4 + } + dim { + dim_value: 4 + } + } + } + } + } + input { + name: "image_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "block_shape" + type { + tensor_type { + elem_type: 7 + shape { + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "output" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 4 + } + dim { + dim_value: 4 + } + } + } + } + } +} +opset_import { + version: 18 +} diff --git a/src/frontends/onnx/tests/onnx_import.in.cpp b/src/frontends/onnx/tests/onnx_import.in.cpp index f065f30c6f1e1f..4395623a563c7a 100644 --- a/src/frontends/onnx/tests/onnx_import.in.cpp +++ b/src/frontends/onnx/tests/onnx_import.in.cpp @@ -320,6 +320,131 @@ OPENVINO_TEST(${BACKEND_NAME}, onnx_expand_model_with_initializers) { test_case.run(); } +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_default) { + const auto model = convert_model("col2im_2D_default_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [1, 4, 4] + std::vector input_data = + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f}; + test_case.add_input(ov::Shape{1, 4, 4}, input_data); + + // image_shape: [3, 3] + test_case.add_input(ov::Shape{2}, {3, 3}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [1, 1, 3, 3] + std::vector expected_output = {1.0f, 7.0f, 6.0f, 12.0f, 34.0f, 22.0f, 11.0f, 27.0f, 16.0f}; + test_case.add_expected_output(ov::Shape{1, 1, 3, 3}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_dilations) { + const auto model = convert_model("col2im_2D_dilations_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [1, 4, 9] + std::vector input_data(36, 1.0f); + test_case.add_input(ov::Shape{1, 4, 9}, input_data); + + // image_shape: [5, 5] + test_case.add_input(ov::Shape{2}, {5, 5}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [1, 1, 5, 5] + std::vector expected_output = {1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 2.0f, 2.0f, 4.0f, + 2.0f, 2.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f}; + test_case.add_expected_output(ov::Shape{1, 1, 5, 5}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_pads) { + const auto model = convert_model("col2im_2D_pads_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [1, 4, 9] + std::vector input_data(36, 1.0f); + test_case.add_input(ov::Shape{1, 4, 9}, input_data); + + // image_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [1, 1, 2, 2] + std::vector expected_output = {4.0f, 4.0f, 4.0f, 4.0f}; + test_case.add_expected_output(ov::Shape{1, 1, 2, 2}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_strides) { + const auto model = convert_model("col2im_2D_strides_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [1, 4, 4] + std::vector input_data(16, 1.0f); + test_case.add_input(ov::Shape{1, 4, 4}, input_data); + + // image_shape: [4, 4] + test_case.add_input(ov::Shape{2}, {4, 4}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [1, 1, 4, 4] + std::vector expected_output(16, 1.0f); + test_case.add_expected_output(ov::Shape{1, 1, 4, 4}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_batch) { + const auto model = convert_model("col2im_2D_batch_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [2, 4, 4] + std::vector input_data(32, 1.0f); + test_case.add_input(ov::Shape{2, 4, 4}, input_data); + + // image_shape: [3, 3] + test_case.add_input(ov::Shape{2}, {3, 3}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [2, 1, 3, 3] + std::vector single_batch_output = {1.0f, 2.0f, 1.0f, 2.0f, 4.0f, 2.0f, 1.0f, 2.0f, 1.0f}; + std::vector expected_output; + expected_output.insert(expected_output.end(), single_batch_output.begin(), single_batch_output.end()); + expected_output.insert(expected_output.end(), single_batch_output.begin(), single_batch_output.end()); + + test_case.add_expected_output(ov::Shape{2, 1, 3, 3}, expected_output); + test_case.run(); +} + +OPENVINO_TEST(${BACKEND_NAME}, onnx_col2im_channel) { + const auto model = convert_model("col2im_2D_channel_opset_18.onnx"); + auto test_case = ov::test::TestCase(model, s_device); + + // Input: [1, 12, 4] + std::vector input_data(48, 1.0f); + test_case.add_input(ov::Shape{1, 12, 4}, input_data); + + // image_shape: [3, 3] + test_case.add_input(ov::Shape{2}, {3, 3}); + // block_shape: [2, 2] + test_case.add_input(ov::Shape{2}, {2, 2}); + + // Expected Output: [1, 3, 3, 3] + std::vector single_channel_output = {1.0f, 2.0f, 1.0f, 2.0f, 4.0f, 2.0f, 1.0f, 2.0f, 1.0f}; + std::vector expected_output; + for (int i = 0; i < 3; ++i) { + expected_output.insert(expected_output.end(), single_channel_output.begin(), single_channel_output.end()); + } + + test_case.add_expected_output(ov::Shape{1, 3, 3, 3}, expected_output); + test_case.run(); +} + // ############################################################################ OPERATOR TESTS OPENVINO_TEST(${BACKEND_NAME}, onnx_model_addmul_abc) { auto model = convert_model("addmul_abc.onnx"); diff --git a/src/frontends/onnx/tests/tests_python/test_backend.py b/src/frontends/onnx/tests/tests_python/test_backend.py index f942f4d1843e29..d0554ce1e0649e 100644 --- a/src/frontends/onnx/tests/tests_python/test_backend.py +++ b/src/frontends/onnx/tests/tests_python/test_backend.py @@ -381,10 +381,6 @@ def expect_fail(test_case_path, xfail): # type: (str) -> None ( xfail_issue_99952, "OnnxBackendNodeModelTest.test_col2im_5d_cpu", - "OnnxBackendNodeModelTest.test_col2im_cpu", - "OnnxBackendNodeModelTest.test_col2im_dilations_cpu", - "OnnxBackendNodeModelTest.test_col2im_pads_cpu", - "OnnxBackendNodeModelTest.test_col2im_strides_cpu", ), ( xfail_issue_99961,