Coverage for mlprodict/onnxrt/ops_cpu/op_resize.py: 95%
110 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-04 02:28 +0100
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-04 02:28 +0100
1# -*- encoding: utf-8 -*-
2# pylint: disable=E0203,E1101,C0111
3"""
4@file
5@brief Runtime operator.
6"""
7import numpy
8from ._op import OpRun
11def _cartesian(arrays, out=None):
12 """
13 From https://stackoverflow.com/a/1235363
14 Generate a cartesian product of input arrays.
15 Parameters
16 ----------
17 arrays : list of array-like
18 1-D arrays to form the cartesian product of.
19 out : ndarray
20 Array to place the cartesian product in.
21 Returns
22 -------
23 out : ndarray
24 2-D array of shape (M, len(arrays)) containing cartesian products
25 formed of input arrays.
26 Examples
27 --------
28 >>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
29 array([[1, 4, 6],
30 [1, 4, 7],
31 [1, 5, 6],
32 [1, 5, 7],
33 [2, 4, 6],
34 [2, 4, 7],
35 [2, 5, 6],
36 [2, 5, 7],
37 [3, 4, 6],
38 [3, 4, 7],
39 [3, 5, 6],
40 [3, 5, 7]])
41 """
43 arrays = [numpy.asarray(x) for x in arrays]
44 dtype = arrays[0].dtype
46 n = numpy.prod([x.size for x in arrays])
47 if out is None:
48 out = numpy.zeros([n, len(arrays)], dtype=dtype)
50 m = n // arrays[0].size
51 out[:, 0] = numpy.repeat(arrays[0], m)
52 if arrays[1:]:
53 _cartesian(arrays[1:], out=out[0:m, 1:])
54 for j in range(1, arrays[0].size):
55 out[j * m:(j + 1) * m, 1:] = out[0:m, 1:]
56 return out
59def _nearest_coeffs(ratio, mode=b'round_prefer_floor'):
60 if type(ratio) == int or ratio.is_integer():
61 return numpy.array([0, 1])
62 if mode == b'round_prefer_floor':
63 return numpy.array([ratio <= 0.5, ratio > 0.5])
64 if mode == b'round_prefer_ceil':
65 return numpy.array([ratio < 0.5, ratio >= 0.5])
66 if mode == b'floor':
67 return numpy.array([1, 0])
68 if mode == b'ceil':
69 return numpy.array([0, 1])
70 raise ValueError( # pragma: no cover
71 f"Unexpected value {mode!r}.")
74def _cubic_coeffs(ratio, A=-0.75):
75 coeffs = [
76 ((A * (ratio + 1) - 5 * A) * (ratio + 1) + 8 * A) * (ratio + 1) - 4 * A,
77 ((A + 2) * ratio - (A + 3)) * ratio * ratio + 1,
78 ((A + 2) * (1 - ratio) - (A + 3)) * (1 - ratio) * (1 - ratio) + 1,
79 ((A * ((1 - ratio) + 1) - 5 * A) * ((1 - ratio) + 1) + 8 * A) * ((1 - ratio) + 1) - 4 * A]
81 return numpy.array(coeffs)
84def _linear_coeffs(ratio):
85 return numpy.array([1 - ratio, ratio])
88def _get_neighbor_idxes(x, n, limit):
89 idxes = sorted(range(limit), key=lambda idx: (abs(x - idx), idx))[:n]
90 idxes = sorted(idxes)
91 return numpy.array(idxes)
94def _get_neighbor(x, n, data):
95 pad_width = numpy.ceil(n / 2).astype(int)
96 padded = numpy.pad(data, pad_width, mode='edge')
97 x += pad_width
99 idxes = _get_neighbor_idxes(x, n, len(padded))
100 ret = padded[idxes]
101 return idxes - pad_width, ret
104def _interpolate_1d_with_x(
105 data, scale_factor, x, get_coeffs, roi=None,
106 extrapolation_value=0.0, coordinate_transformation_mode=b'half_pixel',
107 exclude_outside=False):
109 input_width = len(data)
110 output_width = scale_factor * input_width
111 if coordinate_transformation_mode == b'align_corners':
112 if output_width == 1:
113 x_ori = 0.
114 else:
115 x_ori = x * (input_width - 1) / (output_width - 1)
116 elif coordinate_transformation_mode == b'asymmetric':
117 x_ori = x / scale_factor
118 elif coordinate_transformation_mode == b'tf_crop_and_resize':
119 if output_width == 1:
120 x_ori = (roi[1] - roi[0]) * (input_width - 1) / 2
121 else:
122 x_ori = x * (roi[1] - roi[0]) * \
123 (input_width - 1) / (output_width - 1)
124 x_ori += (roi[0] * (input_width - 1))
125 # Return extrapolation_value directly as what TF CropAndResize does
126 if x_ori < 0 or x_ori > input_width - 1:
127 return extrapolation_value
128 elif coordinate_transformation_mode == b'pytorch_half_pixel':
129 if output_width == 1:
130 x_ori = -0.5
131 else:
132 x_ori = (x + 0.5) / scale_factor - 0.5
133 elif coordinate_transformation_mode == b'half_pixel':
134 x_ori = (x + 0.5) / scale_factor - 0.5
135 else:
136 raise ValueError('invalid coordinate_transformation_mode: %r.' %
137 coordinate_transformation_mode)
138 x_ori_int = numpy.floor(x_ori).astype(int).item()
140 # ratio must be in (0, 1] since we prefer the pixel on the left of `x_ori`
141 if x_ori.is_integer():
142 ratio = 1
143 else:
144 ratio = x_ori - x_ori_int
146 coeffs = get_coeffs(ratio)
147 n = len(coeffs)
149 idxes, points = _get_neighbor(x_ori, n, data)
151 if exclude_outside:
152 for i, idx in enumerate(idxes):
153 if idx < 0 or idx >= input_width:
154 coeffs[i] = 0
155 coeffs /= sum(coeffs)
157 return numpy.dot(coeffs, points).item()
160def _interpolate_nd_with_x(data, n, scale_factors, x,
161 get_coeffs, roi=None, **kwargs):
162 if n == 1:
163 return _interpolate_1d_with_x(
164 data, scale_factors[0], x[0], get_coeffs, roi=roi, **kwargs)
165 return _interpolate_1d_with_x(
166 [_interpolate_nd_with_x(
167 data[i], n - 1, scale_factors[1:], x[1:], get_coeffs,
168 roi=None if roi is None else numpy.concatenate(
169 [roi[1:n], roi[n + 1:]]), **kwargs)
170 for i in range(data.shape[0])],
171 scale_factors[0], x[0], get_coeffs,
172 roi=None if roi is None else [roi[0], roi[n]], **kwargs)
175def _get_all_coords(data):
176 return _cartesian(
177 [list(range(data.shape[i])) for i in range(len(data.shape))])
180def _interpolate_nd(data, get_coeffs, output_size=None,
181 scale_factors=None, roi=None, **kwargs):
183 assert output_size is not None or scale_factors is not None
184 if output_size is not None:
185 scale_factors = numpy.array(output_size) / numpy.array(data.shape)
186 else:
187 output_size = (scale_factors * numpy.array(data.shape)).astype(int)
188 assert scale_factors is not None
190 ret = numpy.zeros(output_size)
191 for x in _get_all_coords(ret):
192 ret[tuple(x)] = _interpolate_nd_with_x(
193 data, len(data.shape), scale_factors, x, get_coeffs,
194 roi=roi, **kwargs)
195 return ret
198class Resize(OpRun):
200 atts = {
201 'coordinate_transformation_mode': b'half_pixel',
202 'cubic_coeff_a': -0.75,
203 'exclude_outside': 0,
204 'extrapolation_value': 0.0,
205 'mode': b'nearest',
206 'nearest_mode': b'round_prefer_floor',
207 }
209 def __init__(self, onnx_node, desc=None,
210 expected_attributes=None, **options):
211 OpRun.__init__(self, onnx_node, desc=desc,
212 expected_attributes=Resize.atts,
213 **options)
214 if self.mode == b'nearest':
215 if self.nearest_mode is not None:
216 self.fct = lambda x: _nearest_coeffs(x, mode=self.nearest_mode)
217 else:
218 self.fct = _nearest_coeffs
219 elif self.mode == b'cubic':
220 self.fct = _cubic_coeffs
221 elif self.mode == b'linear':
222 self.fct = _linear_coeffs
223 else:
224 raise ValueError( # pragma: no cover
225 f"Unexpected value {self.mode!r} for mode.")
227 def _run(self, X, roi, scales=None, sizes=None, attributes=None, verbose=0, fLOG=None): # pylint: disable=W0221
228 output = _interpolate_nd(
229 X, self.fct, scale_factors=scales,
230 output_size=sizes, roi=roi,
231 coordinate_transformation_mode=self.coordinate_transformation_mode,
232 extrapolation_value=self.extrapolation_value).astype(X.dtype)
233 return (output, )