Skip to content

Commit 99d9d9a

Browse files
authored
[ONNX] Create a native translator for AffineGrid (#31416)
### Details: Added native translator for function AffineGrid ### Tickets: N/A
1 parent c32c251 commit 99d9d9a

File tree

4 files changed

+278
-7
lines changed

4 files changed

+278
-7
lines changed

src/frontends/onnx/docs/supported_ops.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ OpenVINO provides support for operations of Default Opset (empty in table below)
1212
| |Acosh |9 |22, 9 | |
1313
| |Add |14, 13, 7, 6, 1 |14, 13, 7, 6, 1 | |
1414
| |Affine |1 | | |
15-
| |AffineGrid | |20 | |
15+
| |AffineGrid |20 |20 | |
1616
| |And |7, 1 |7, 1 | |
1717
| |ArgMax |12, 1 |13, 12, 11, 1 | |
1818
| |ArgMin |12, 1 |13, 12, 11, 1 | |

src/frontends/onnx/frontend/src/core/transform.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace transform {
1313

1414
using ::ONNX_NAMESPACE::ModelProto;
1515

16-
static const std::vector<std::string> onnx_functions_to_expand = {"AffineGrid",
17-
"Bernoulli",
16+
static const std::vector<std::string> onnx_functions_to_expand = {"Bernoulli",
1817
"CenterCropPad",
1918
"SoftmaxCrossEntropyLoss"};
2019

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright (C) 2018-2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
5+
#include "core/attribute.hpp"
6+
#include "core/operator_set.hpp"
7+
#include "exceptions.hpp"
8+
#include "openvino/op/add.hpp"
9+
#include "openvino/op/broadcast.hpp"
10+
#include "openvino/op/concat.hpp"
11+
#include "openvino/op/constant.hpp"
12+
#include "openvino/op/convert.hpp"
13+
#include "openvino/op/divide.hpp"
14+
#include "openvino/op/equal.hpp"
15+
#include "openvino/op/matmul.hpp"
16+
#include "openvino/op/multiply.hpp"
17+
#include "openvino/op/range.hpp"
18+
#include "openvino/op/reshape.hpp"
19+
#include "openvino/op/select.hpp"
20+
#include "openvino/op/shape_of.hpp"
21+
#include "openvino/op/slice.hpp"
22+
#include "openvino/op/squeeze.hpp"
23+
#include "openvino/op/subtract.hpp"
24+
#include "openvino/op/transpose.hpp"
25+
#include "openvino/op/unsqueeze.hpp"
26+
#include "utils/common.hpp"
27+
28+
using namespace ov::op;
29+
using ov::Shape;
30+
31+
namespace ov {
32+
namespace frontend {
33+
namespace onnx {
34+
namespace ai_onnx {
35+
namespace opset_1 {
36+
37+
namespace detail {
38+
39+
struct GridConstants {
40+
std::shared_ptr<ov::Node> zero, one, two, minus_one, half, epsilon, zero_range, one_plus_eps;
41+
42+
GridConstants() {
43+
zero = v0::Constant::create(ov::element::f32, Shape{}, {0.0f});
44+
one = v0::Constant::create(ov::element::f32, Shape{}, {1.0f});
45+
two = v0::Constant::create(ov::element::f32, Shape{}, {2.0f});
46+
minus_one = v0::Constant::create(ov::element::f32, Shape{}, {-1.0f});
47+
half = v0::Constant::create(ov::element::f32, Shape{}, {0.5f});
48+
epsilon = v0::Constant::create(ov::element::f32, Shape{}, {1e-6f});
49+
zero_range = v0::Constant::create(ov::element::f32, Shape{1}, {0.0f});
50+
one_plus_eps = std::make_shared<v1::Add>(one, epsilon);
51+
}
52+
};
53+
54+
std::shared_ptr<ov::Node> to_scalar(const ov::Output<ov::Node>& input) {
55+
const auto zero = v0::Constant::create(ov::element::i32, Shape{1}, {0});
56+
const auto one = v0::Constant::create(ov::element::i32, Shape{1}, {1});
57+
auto sliced = std::make_shared<v8::Slice>(input, zero, one, one, zero);
58+
auto squeeze_axes = v0::Constant::create(ov::element::i32, Shape{1}, {0});
59+
return std::make_shared<v0::Squeeze>(sliced, squeeze_axes);
60+
}
61+
62+
std::shared_ptr<ov::Node> create_coordinate_range(const std::shared_ptr<ov::Node>& dim_scalar,
63+
const std::shared_ptr<ov::Node>& dim_f,
64+
const GridConstants& consts,
65+
bool align_corners) {
66+
if (align_corners) {
67+
auto is_one = std::make_shared<v1::Equal>(dim_scalar, consts.one);
68+
auto minus_one = std::make_shared<v1::Subtract>(dim_f, consts.one);
69+
auto safe_denom = std::make_shared<v1::Select>(is_one, consts.one, to_scalar(minus_one));
70+
auto step = std::make_shared<v1::Divide>(consts.two, safe_denom);
71+
auto range_normal = std::make_shared<v4::Range>(consts.minus_one, consts.one_plus_eps, step, ov::element::f32);
72+
return std::make_shared<v1::Select>(is_one, consts.zero_range, range_normal);
73+
} else {
74+
auto step = std::make_shared<v1::Divide>(consts.two, dim_scalar);
75+
auto half_step = std::make_shared<v1::Multiply>(step, consts.half);
76+
auto start = std::make_shared<v1::Add>(consts.minus_one, half_step);
77+
return std::make_shared<v4::Range>(start, consts.one, step, ov::element::f32);
78+
}
79+
}
80+
81+
std::shared_ptr<ov::Node> construct_original_grid_2d(const ov::Output<ov::Node>& data_size, bool align_corners) {
82+
GridConstants consts;
83+
84+
std::vector<std::shared_ptr<ov::Node>> dims, dim_scalars;
85+
86+
for (int i = 0; i < 2; ++i) {
87+
auto idx_start = v0::Constant::create(ov::element::i32, Shape{1}, {i});
88+
auto idx_end = v0::Constant::create(ov::element::i32, Shape{1}, {i + 1});
89+
auto step = v0::Constant::create(ov::element::i32, Shape{1}, {1});
90+
auto zero_ax = v0::Constant::create(ov::element::i32, Shape{1}, {0});
91+
92+
auto dim = std::make_shared<v8::Slice>(data_size, idx_start, idx_end, step, zero_ax);
93+
auto dim_f = std::make_shared<v0::Convert>(dim, ov::element::f32);
94+
dims.push_back(dim_f);
95+
dim_scalars.push_back(to_scalar(dim_f));
96+
}
97+
98+
auto range_0 = create_coordinate_range(dim_scalars[0], dims[0], consts, align_corners);
99+
auto range_1 = create_coordinate_range(dim_scalars[1], dims[1], consts, align_corners);
100+
101+
auto range_shape_0 = std::make_shared<v3::ShapeOf>(range_0);
102+
auto range_shape_1 = std::make_shared<v3::ShapeOf>(range_1);
103+
auto one_i64 = v0::Constant::create(ov::element::i64, Shape{1}, {1});
104+
105+
auto shape_y = std::make_shared<v0::Concat>(OutputVector{range_shape_0, one_i64}, 0);
106+
auto shape_x = std::make_shared<v0::Concat>(OutputVector{one_i64, range_shape_1}, 0);
107+
auto shape_broadcast = std::make_shared<v0::Concat>(OutputVector{range_shape_0, range_shape_1}, 0);
108+
109+
auto y_reshaped = std::make_shared<v1::Reshape>(range_0, shape_y, false);
110+
auto x_reshaped = std::make_shared<v1::Reshape>(range_1, shape_x, false);
111+
auto y_broadcast = std::make_shared<v3::Broadcast>(y_reshaped, shape_broadcast);
112+
auto x_broadcast = std::make_shared<v3::Broadcast>(x_reshaped, shape_broadcast);
113+
114+
auto ones = v0::Constant::create(ov::element::f32, Shape{1}, {1.0f});
115+
auto ones_broadcast = std::make_shared<v3::Broadcast>(ones, shape_broadcast);
116+
auto axis = v0::Constant::create(ov::element::i32, Shape{1}, {2});
117+
118+
return std::make_shared<v0::Concat>(OutputVector{std::make_shared<v0::Unsqueeze>(x_broadcast, axis),
119+
std::make_shared<v0::Unsqueeze>(y_broadcast, axis),
120+
std::make_shared<v0::Unsqueeze>(ones_broadcast, axis)},
121+
2);
122+
}
123+
124+
std::shared_ptr<ov::Node> construct_original_grid_3d(const ov::Output<ov::Node>& data_size, bool align_corners) {
125+
const auto f32 = ov::element::f32;
126+
const auto i32 = ov::element::i32;
127+
const auto i64 = ov::element::i64;
128+
129+
GridConstants consts;
130+
131+
std::vector<std::shared_ptr<ov::Node>> dims, dim_scalars, ranges;
132+
133+
for (int i = 0; i < 3; ++i) {
134+
auto idx_start = v0::Constant::create(i32, Shape{1}, {i});
135+
auto idx_end = v0::Constant::create(i32, Shape{1}, {i + 1});
136+
auto step = v0::Constant::create(i32, Shape{1}, {1});
137+
auto zero_ax = v0::Constant::create(i32, Shape{1}, {0});
138+
139+
auto dim = std::make_shared<v8::Slice>(data_size, idx_start, idx_end, step, zero_ax);
140+
auto dim_f = std::make_shared<v0::Convert>(dim, f32);
141+
dims.push_back(dim_f);
142+
dim_scalars.push_back(to_scalar(dim_f));
143+
ranges.push_back(create_coordinate_range(dim_scalars[i], dims[i], consts, align_corners));
144+
}
145+
146+
std::vector<std::shared_ptr<ov::Node>> range_shapes, broadcast_shapes, grids;
147+
auto i64_one = v0::Constant::create(i64, Shape{1}, {1});
148+
149+
for (int i = 0; i < 3; ++i) {
150+
range_shapes.push_back(std::make_shared<v3::ShapeOf>(ranges[i]));
151+
}
152+
153+
for (int i = 0; i < 3; ++i) {
154+
OutputVector shape_vec(3, i64_one);
155+
shape_vec[i] = range_shapes[i];
156+
broadcast_shapes.push_back(std::make_shared<v0::Concat>(shape_vec, 0));
157+
}
158+
159+
auto final_shape = std::make_shared<v0::Concat>(OutputVector{range_shapes.begin(), range_shapes.end()}, 0);
160+
161+
for (int i = 0; i < 3; ++i) {
162+
auto reshaped = std::make_shared<v1::Reshape>(ranges[i], broadcast_shapes[i], false);
163+
grids.push_back(std::make_shared<v3::Broadcast>(reshaped, final_shape));
164+
}
165+
166+
auto ones = v0::Constant::create(f32, Shape{1}, {1.0f});
167+
auto ones_b = std::make_shared<v3::Broadcast>(ones, final_shape);
168+
auto axis = v0::Constant::create(i32, Shape{1}, {3});
169+
170+
return std::make_shared<v0::Concat>(OutputVector{std::make_shared<v0::Unsqueeze>(grids[2], axis),
171+
std::make_shared<v0::Unsqueeze>(grids[1], axis),
172+
std::make_shared<v0::Unsqueeze>(grids[0], axis),
173+
std::make_shared<v0::Unsqueeze>(ones_b, axis)},
174+
3);
175+
}
176+
177+
std::shared_ptr<ov::Node> apply_affine_transform(const ov::Output<ov::Node>& theta,
178+
const ov::Output<ov::Node>& grid_homo,
179+
int spatial_dims) {
180+
auto tshape = std::make_shared<v3::ShapeOf>(theta);
181+
auto gshape = std::make_shared<v3::ShapeOf>(grid_homo);
182+
183+
auto i0 = v0::Constant::create(ov::element::i32, Shape{1}, {0});
184+
auto i1 = v0::Constant::create(ov::element::i32, Shape{1}, {1});
185+
auto step = v0::Constant::create(ov::element::i32, Shape{1}, {1});
186+
187+
auto N = std::make_shared<v8::Slice>(tshape, i0, i1, step, i0);
188+
189+
std::vector<std::shared_ptr<ov::Node>> spatial_dims_vec;
190+
for (int i = 0; i < spatial_dims; ++i) {
191+
auto idx = v0::Constant::create(ov::element::i32, Shape{1}, {i});
192+
auto idx_next = v0::Constant::create(ov::element::i32, Shape{1}, {i + 1});
193+
spatial_dims_vec.push_back(std::make_shared<v8::Slice>(gshape, idx, idx_next, step, i0));
194+
}
195+
196+
auto spatial_size = spatial_dims_vec[0];
197+
for (int i = 1; i < spatial_dims; ++i) {
198+
spatial_size = std::make_shared<v1::Multiply>(spatial_size, spatial_dims_vec[i]);
199+
}
200+
201+
auto spatial_size_i64 = std::make_shared<v0::Convert>(spatial_size, ov::element::i64);
202+
auto coord_dim = v0::Constant::create(ov::element::i64, Shape{1}, {spatial_dims + 1});
203+
auto resh = std::make_shared<v0::Concat>(OutputVector{spatial_size_i64, coord_dim}, 0);
204+
auto flat = std::make_shared<v1::Reshape>(grid_homo, resh, false);
205+
206+
auto perm1 = v0::Constant::create(ov::element::i32, Shape{2}, {1, 0});
207+
auto trans = std::make_shared<v1::Transpose>(flat, perm1);
208+
209+
auto mm = std::make_shared<v0::MatMul>(theta, trans);
210+
211+
auto perm2 = v0::Constant::create(ov::element::i32, Shape{3}, {0, 2, 1});
212+
auto trans2 = std::make_shared<v1::Transpose>(mm, perm2);
213+
214+
auto N_i64 = std::make_shared<v0::Convert>(N, ov::element::i64);
215+
auto output_coord_dim = v0::Constant::create(ov::element::i64, Shape{1}, {spatial_dims});
216+
217+
OutputVector final_shape_vec = {N_i64};
218+
for (int i = 0; i < spatial_dims; ++i) {
219+
final_shape_vec.push_back(std::make_shared<v0::Convert>(spatial_dims_vec[i], ov::element::i64));
220+
}
221+
final_shape_vec.push_back(output_coord_dim);
222+
223+
auto final_shape = std::make_shared<v0::Concat>(final_shape_vec, 0);
224+
225+
return std::make_shared<v1::Reshape>(trans2, final_shape, false);
226+
}
227+
228+
ov::OutputVector affine_grid(const ov::OutputVector& inputs, const bool align_corners) {
229+
const auto& theta = inputs[0];
230+
const auto& size = inputs[1];
231+
232+
const auto& size_pshape = size.get_partial_shape();
233+
234+
if (size_pshape.rank().is_static()) {
235+
int64_t rank = size_pshape[0].get_length();
236+
237+
if (rank == 4) {
238+
const auto data_size = std::make_shared<v8::Slice>(size,
239+
v0::Constant::create(ov::element::i32, Shape{1}, {2}),
240+
v0::Constant::create(ov::element::i32, Shape{1}, {4}),
241+
v0::Constant::create(ov::element::i32, Shape{1}, {1}),
242+
v0::Constant::create(ov::element::i32, Shape{1}, {0}));
243+
auto grid = construct_original_grid_2d(data_size, align_corners);
244+
return {apply_affine_transform(theta, grid, 2)};
245+
} else if (rank == 5) {
246+
const auto data_size = std::make_shared<v8::Slice>(size,
247+
v0::Constant::create(ov::element::i32, Shape{1}, {2}),
248+
v0::Constant::create(ov::element::i32, Shape{1}, {5}),
249+
v0::Constant::create(ov::element::i32, Shape{1}, {1}),
250+
v0::Constant::create(ov::element::i32, Shape{1}, {0}));
251+
auto grid = construct_original_grid_3d(data_size, align_corners);
252+
return {apply_affine_transform(theta, grid, 3)};
253+
} else {
254+
FRONT_END_THROW("AffineGrid supports only 4D (2D) or 5D (3D) input sizes.");
255+
}
256+
} else {
257+
FRONT_END_THROW("AffineGrid input 'size' must have a static rank.");
258+
}
259+
}
260+
261+
} // namespace detail
262+
263+
ov::OutputVector affine_grid(const ov::frontend::onnx::Node& node) {
264+
common::default_op_checks(node, 2);
265+
const auto inputs = node.get_ov_inputs();
266+
const auto align_corners = node.get_attribute_value<int64_t>("align_corners", 0) != 0;
267+
return detail::affine_grid(inputs, align_corners);
268+
}
269+
270+
ONNX_OP("AffineGrid", OPSET_SINCE(1), ai_onnx::opset_1::affine_grid);
271+
272+
} // namespace opset_1
273+
} // namespace ai_onnx
274+
} // namespace onnx
275+
} // namespace frontend
276+
} // namespace ov

src/frontends/onnx/tests/tests_python/test_backend.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,7 @@ def expect_fail(test_case_path, xfail): # type: (str) -> None
564564
),
565565
(
566566
xfail_issue_125485,
567-
"OnnxBackendNodeModelTest.test_affine_grid_2d_align_corners_cpu",
568-
"OnnxBackendNodeModelTest.test_affine_grid_2d_cpu",
569-
"OnnxBackendNodeModelTest.test_affine_grid_3d_align_corners_cpu",
570567
"OnnxBackendNodeModelTest.test_affine_grid_3d_align_corners_expanded_cpu",
571-
"OnnxBackendNodeModelTest.test_affine_grid_3d_cpu",
572568
"OnnxBackendNodeModelTest.test_affine_grid_3d_expanded_cpu",
573569
),
574570
(

0 commit comments

Comments
 (0)