Skip to content

Commit 7482bff

Browse files
committed
WIP: Support Resize
1 parent 438fd65 commit 7482bff

File tree

4 files changed

+911
-0
lines changed

4 files changed

+911
-0
lines changed

runtime/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ This runtime supports only below operators.
149149
- ReduceSumSquare
150150
- Relu
151151
- Reshape
152+
- Resize
152153
- Round
153154
- ScatterND
154155
- Shape

runtime/onnion_runtime/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
from .reducesumsquare import ReduceSumSquare # noqa: F401
9797
from .relu import Relu # noqa: F401
9898
from .reshape import Reshape # noqa: F401
99+
from .resize import Resize # noqa: F401
99100
from .round import Round # noqa: F401
100101
from .scatternd import ScatterND # noqa: F401
101102
from .shape import Shape # noqa: F401

runtime/onnion_runtime/resize.py

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
from typing import Any, Callable, List, Optional, Text
2+
3+
import numpy as np
4+
5+
from .error import RunError
6+
7+
8+
class Resize:
9+
def __init__(self, opset_version, **kwargs):
10+
self.version = opset_version
11+
self.coordinate_transformation_mode = kwargs.get("coordinate_transformation_mode", "half_pixel")
12+
self.cubic_coeff_a = kwargs.get("cubic_coeff_a", -0.75)
13+
self.exclude_outside = kwargs.get("exclude_outside", 0)
14+
self.extrapolation_value = kwargs.get("extrapolation_value", 0.0)
15+
self.mode = kwargs.get("mode", "nearest")
16+
self.nearest_mode = kwargs.get("nearest_mode", "round_prefer_floor")
17+
18+
def run(self, x, roi=None, scales=None, sizes=None):
19+
if self.version < 10:
20+
raise RunError("Resize", self.version)
21+
elif self.version == 10:
22+
if self.mode == "linear":
23+
# just guess
24+
self.coordinate_transformation_mode = "asymmetric"
25+
scales = roi
26+
roi = None
27+
28+
assert sizes is not None or scales is not None
29+
if self.mode == "nearest":
30+
return [
31+
interpolate_nd(
32+
x,
33+
lambda r: nearest_coeffs(r, mode=self.nearest_mode),
34+
output_size=sizes,
35+
scale_factors=scales,
36+
roi=roi,
37+
coordinate_transformation_mode=self.coordinate_transformation_mode,
38+
extrapolation_value=self.extrapolation_value,
39+
exclude_outside=self.exclude_outside,
40+
).astype(x.dtype)
41+
]
42+
elif self.mode == "linear":
43+
return [
44+
interpolate_nd(
45+
x,
46+
linear_coeffs,
47+
output_size=sizes,
48+
scale_factors=scales,
49+
roi=roi,
50+
coordinate_transformation_mode=self.coordinate_transformation_mode,
51+
extrapolation_value=self.extrapolation_value,
52+
exclude_outside=self.exclude_outside,
53+
).astype(x.dtype)
54+
]
55+
elif self.mode == "cubic":
56+
return [
57+
interpolate_nd(
58+
x,
59+
lambda r: cubic_coeffs(r, self.cubic_coeff_a),
60+
output_size=sizes,
61+
scale_factors=scales,
62+
roi=roi,
63+
coordinate_transformation_mode=self.coordinate_transformation_mode,
64+
extrapolation_value=self.extrapolation_value,
65+
exclude_outside=self.exclude_outside,
66+
).astype(x.dtype)
67+
]
68+
else:
69+
raise RunError("Resize", self.version)
70+
71+
72+
# The following code has been copied from
73+
# https://github.com/onnx/onnx/blob/6af1ed14b2f74eb6b5a52e12e2ebffa65a34001b/onnx/backend/test/case/node/resize.py#L16-L228
74+
# Copyrights (c) ONNX Project Contributers
75+
# License: Apache-2.0
76+
def cartesian(arrays, out=None):
77+
# type: (List[np.ndarray], np.ndarray) -> np.ndarray
78+
"""
79+
From https://stackoverflow.com/a/1235363
80+
Generate a cartesian product of input arrays.
81+
Parameters
82+
----------
83+
arrays : list of array-like
84+
1-D arrays to form the cartesian product of.
85+
out : ndarray
86+
Array to place the cartesian product in.
87+
Returns
88+
-------
89+
out : ndarray
90+
2-D array of shape (M, len(arrays)) containing cartesian products
91+
formed of input arrays.
92+
Examples
93+
--------
94+
>>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
95+
array([[1, 4, 6],
96+
[1, 4, 7],
97+
[1, 5, 6],
98+
[1, 5, 7],
99+
[2, 4, 6],
100+
[2, 4, 7],
101+
[2, 5, 6],
102+
[2, 5, 7],
103+
[3, 4, 6],
104+
[3, 4, 7],
105+
[3, 5, 6],
106+
[3, 5, 7]])
107+
"""
108+
109+
arrays = [np.asarray(x) for x in arrays]
110+
dtype = arrays[0].dtype
111+
112+
n = np.prod([x.size for x in arrays])
113+
if out is None:
114+
out = np.zeros([n, len(arrays)], dtype=dtype)
115+
116+
m = n // arrays[0].size
117+
out[:, 0] = np.repeat(arrays[0], m)
118+
if arrays[1:]:
119+
cartesian(arrays[1:], out=out[0:m, 1:])
120+
for j in range(1, arrays[0].size):
121+
out[j * m : (j + 1) * m, 1:] = out[0:m, 1:]
122+
return out
123+
124+
125+
def interpolate_1d_with_x(
126+
data, # type: np.ndarray
127+
scale_factor, # type: float
128+
x, # type: float
129+
get_coeffs, # type: Callable[[float], np.ndarray]
130+
roi=None, # type: np.ndarray
131+
extrapolation_value=0.0, # type: float
132+
coordinate_transformation_mode="half_pixel", # type: Text
133+
exclude_outside=False, # type: bool
134+
): # type: (...) -> np.ndarray
135+
def get_neighbor_idxes(x, n, limit): # type: (float, int, int) -> np.ndarray
136+
"""
137+
Return the n nearest indexes to x among [0, limit), prefer the indexes smaller than x.
138+
As a result, the ratio must be in (0, 1]
139+
Examples:
140+
get_neighbor_idxes(4, 2, 10) == [3, 4]
141+
get_neighbor_idxes(4, 3, 10) == [3, 4, 5]
142+
get_neighbor_idxes(4.4, 3, 10) == [3, 4, 5]
143+
get_neighbor_idxes(4.5, 3, 10) == [3, 4, 5]
144+
get_neighbor_idxes(4.6, 3, 10) == [4, 5, 6]
145+
get_neighbor_idxes(4.4, 1, 10) == [4]
146+
get_neighbor_idxes(4.6, 1, 10) == [5]
147+
:param x:
148+
:param n: the number of the wanted indexes
149+
:param limit: the maximum value of index
150+
:return: An np.array containing n nearest indexes in ascending order
151+
"""
152+
idxes = sorted(range(limit), key=lambda idx: (abs(x - idx), idx))[:n]
153+
idxes = sorted(idxes)
154+
return np.array(idxes)
155+
156+
def get_neighbor(x, n, data): # type: (float, int, np.ndarray) -> np.ndarray
157+
"""
158+
Pad `data` in 'edge' mode, and get n nearest elements in the padded array and their indexes in the original
159+
array
160+
:param x: center index (in the unpadded coordinate system) of the found nearest elements.
161+
:param n: the number of neighbors.
162+
:param data: the array
163+
:return: A tuple containing the indexes of neighbor elements (the index can be smaller than 0 or higher than
164+
len(data)) and the value of these elements
165+
"""
166+
pad_width = np.ceil(n / 2).astype(int)
167+
padded = np.pad(data, pad_width, mode="edge")
168+
x += pad_width
169+
170+
idxes = get_neighbor_idxes(x, n, len(padded))
171+
ret = padded[idxes]
172+
return idxes - pad_width, ret
173+
174+
input_width = len(data)
175+
output_width = scale_factor * input_width
176+
if coordinate_transformation_mode == "align_corners":
177+
if output_width == 1:
178+
x_ori = 0.0
179+
else:
180+
x_ori = x * (input_width - 1) / (output_width - 1)
181+
elif coordinate_transformation_mode == "asymmetric":
182+
x_ori = x / scale_factor
183+
elif coordinate_transformation_mode == "tf_crop_and_resize":
184+
if output_width == 1:
185+
x_ori = (roi[1] - roi[0]) * (input_width - 1) / 2
186+
else:
187+
x_ori = x * (roi[1] - roi[0]) * (input_width - 1) / (output_width - 1)
188+
x_ori += roi[0] * (input_width - 1)
189+
# Return extrapolation_value directly as what TF CropAndResize does
190+
if x_ori < 0 or x_ori > input_width - 1:
191+
return extrapolation_value
192+
elif coordinate_transformation_mode == "pytorch_half_pixel":
193+
if output_width == 1:
194+
x_ori = -0.5
195+
else:
196+
x_ori = (x + 0.5) / scale_factor - 0.5
197+
else: # coordinate_transformation_mode == 'half_pixel'
198+
x_ori = (x + 0.5) / scale_factor - 0.5
199+
x_ori_int = np.floor(x_ori).astype(int).item()
200+
201+
# ratio must be in (0, 1] since we prefer the pixel on the left of `x_ori`
202+
if x_ori.is_integer():
203+
ratio = 1
204+
else:
205+
ratio = x_ori - x_ori_int
206+
207+
coeffs = get_coeffs(ratio)
208+
n = len(coeffs)
209+
210+
idxes, points = get_neighbor(x_ori, n, data)
211+
212+
if exclude_outside:
213+
for i, idx in enumerate(idxes):
214+
if idx < 0 or idx >= input_width:
215+
coeffs[i] = 0
216+
coeffs /= sum(coeffs)
217+
218+
return np.dot(coeffs, points).item()
219+
220+
221+
def interpolate_nd_with_x(
222+
data, # type: np.ndarray
223+
n, # type: int
224+
scale_factors, # type: List[float]
225+
x, # type: List[float]
226+
get_coeffs, # type: Callable[[float], np.ndarray]
227+
roi=None, # type: np.ndarray
228+
**kwargs, # type: Any
229+
): # type: (...) -> np.ndarray
230+
if n == 1:
231+
return interpolate_1d_with_x(data, scale_factors[0], x[0], get_coeffs, roi=roi, **kwargs)
232+
return interpolate_1d_with_x(
233+
[
234+
interpolate_nd_with_x(
235+
data[i],
236+
n - 1,
237+
scale_factors[1:],
238+
x[1:],
239+
get_coeffs,
240+
roi=None if roi is None else np.concatenate([roi[1:n], roi[n + 1 :]]),
241+
**kwargs,
242+
)
243+
for i in range(data.shape[0])
244+
],
245+
scale_factors[0],
246+
x[0],
247+
get_coeffs,
248+
roi=None if roi is None else [roi[0], roi[n]],
249+
**kwargs,
250+
)
251+
252+
253+
def interpolate_nd(
254+
data, # type: np.ndarray
255+
get_coeffs, # type: Callable[[float], np.ndarray]
256+
output_size=None, # type: Optional[List[int]]
257+
scale_factors=None, # type: Optional[List[float]]
258+
roi=None, # type: np.ndarray
259+
**kwargs, # type: Any
260+
): # type: (...) -> np.ndarray
261+
def get_all_coords(data): # type: (np.ndarray) -> np.ndarray
262+
return cartesian([list(range(data.shape[i])) for i in range(len(data.shape))])
263+
264+
assert output_size is not None or scale_factors is not None
265+
if output_size is not None:
266+
scale_factors = np.array(output_size) / np.array(data.shape)
267+
else:
268+
output_size = (scale_factors * np.array(data.shape)).astype(int)
269+
assert scale_factors is not None
270+
271+
ret = np.zeros(output_size)
272+
for x in get_all_coords(ret):
273+
ret[tuple(x)] = interpolate_nd_with_x(data, len(data.shape), scale_factors, x, get_coeffs, roi=roi, **kwargs)
274+
return ret
275+
276+
277+
def cubic_coeffs(ratio, A=-0.75): # type: (float, float) -> np.ndarray
278+
coeffs = [
279+
((A * (ratio + 1) - 5 * A) * (ratio + 1) + 8 * A) * (ratio + 1) - 4 * A,
280+
((A + 2) * ratio - (A + 3)) * ratio * ratio + 1,
281+
((A + 2) * (1 - ratio) - (A + 3)) * (1 - ratio) * (1 - ratio) + 1,
282+
((A * ((1 - ratio) + 1) - 5 * A) * ((1 - ratio) + 1) + 8 * A) * ((1 - ratio) + 1) - 4 * A,
283+
]
284+
285+
return np.array(coeffs)
286+
287+
288+
def linear_coeffs(ratio): # type: (float) -> np.ndarray
289+
return np.array([1 - ratio, ratio])
290+
291+
292+
def nearest_coeffs(ratio, mode="round_prefer_floor"): # type: (float, Text) -> np.ndarray
293+
if type(ratio) == int or ratio.is_integer():
294+
return np.array([0, 1])
295+
elif mode == "round_prefer_floor":
296+
return np.array([ratio <= 0.5, ratio > 0.5])
297+
elif mode == "round_prefer_ceil":
298+
return np.array([ratio < 0.5, ratio >= 0.5])
299+
elif mode == "floor":
300+
return np.array([1, 0])
301+
elif mode == "ceil":
302+
return np.array([0, 1])

0 commit comments

Comments
 (0)