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

1# -*- encoding: utf-8 -*- 

2# pylint: disable=E0203,E1101,C0111 

3""" 

4@file 

5@brief Runtime operator. 

6""" 

7import numpy 

8from ._op import OpRun 

9 

10 

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 """ 

42 

43 arrays = [numpy.asarray(x) for x in arrays] 

44 dtype = arrays[0].dtype 

45 

46 n = numpy.prod([x.size for x in arrays]) 

47 if out is None: 

48 out = numpy.zeros([n, len(arrays)], dtype=dtype) 

49 

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 

57 

58 

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}.") 

72 

73 

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] 

80 

81 return numpy.array(coeffs) 

82 

83 

84def _linear_coeffs(ratio): 

85 return numpy.array([1 - ratio, ratio]) 

86 

87 

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) 

92 

93 

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 

98 

99 idxes = _get_neighbor_idxes(x, n, len(padded)) 

100 ret = padded[idxes] 

101 return idxes - pad_width, ret 

102 

103 

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): 

108 

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() 

139 

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 

145 

146 coeffs = get_coeffs(ratio) 

147 n = len(coeffs) 

148 

149 idxes, points = _get_neighbor(x_ori, n, data) 

150 

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) 

156 

157 return numpy.dot(coeffs, points).item() 

158 

159 

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) 

173 

174 

175def _get_all_coords(data): 

176 return _cartesian( 

177 [list(range(data.shape[i])) for i in range(len(data.shape))]) 

178 

179 

180def _interpolate_nd(data, get_coeffs, output_size=None, 

181 scale_factors=None, roi=None, **kwargs): 

182 

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 

189 

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 

196 

197 

198class Resize(OpRun): 

199 

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 } 

208 

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.") 

226 

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, )