Coverage for src/pythia/utils/maskrcnn.py: 51%

65 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-10-07 19:27 +0000

1"""Simple python MASKRCNN output parser. 

2 

3The function `extract_maskrcnn_mask` should be used. 

4 

5The core function, `resize_mask_vec`, is based on its cpp counterpart 

6`resizeMask`, from deepstream cpp sources. It was ported to python 

7and vectorized via numpy to improve speed. 

8 

9Author: <Pablo Woolvett pablowoolvett@gmail.com> 

10 

11""" 

12 

13from typing import List 

14from typing import Tuple 

15from typing import TypedDict 

16 

17import numpy as np 

18import pyds 

19 

20from pythia.utils.ext import grouped 

21 

22 

23def _gen_ranges( 

24 original_height: int, 

25 original_width: int, 

26 target_height: int, 

27 target_width: int, 

28) -> Tuple[np.ndarray, np.ndarray]: 

29 ratio_h = float(original_height / target_height) 

30 ratio_w = float(original_width / target_width) 

31 

32 height = np.arange(0, original_height, ratio_h) 

33 width = np.arange(0, original_width, ratio_w) 

34 return height, width 

35 

36 

37def _gen_clips( 

38 width: np.ndarray, 

39 original_width: int, 

40 height: np.ndarray, 

41 original_height: int, 

42) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: 

43 

44 left = np.clip(np.floor(width), 0.0, original_width - 1) 

45 right = np.clip(np.ceil(width), 0.0, original_width - 1) 

46 top = np.clip(np.floor(height), 0.0, original_height - 1) 

47 bottom = np.clip(np.ceil(height), 0.0, original_height - 1) 

48 return left, right, top, bottom 

49 

50 

51def _gen_idxs( 

52 original_width: int, 

53 left: np.ndarray, 

54 right: np.ndarray, 

55 top: np.ndarray, 

56 bottom: np.ndarray, 

57) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: 

58 

59 left_top_idx = np.add.outer(top * original_width, left).astype(int) 

60 right_top_idx = np.add.outer(top * original_width, right).astype(int) 

61 left_bottom_idx = np.add.outer(bottom * original_width, left).astype(int) 

62 right_bottom_idx = np.add.outer(bottom * original_width, right).astype(int) 

63 

64 return left_top_idx, right_top_idx, left_bottom_idx, right_bottom_idx 

65 

66 

67def _take_vals( 

68 src, 

69 *idxmats, 

70): 

71 return tuple(src.take(idxmat) for idxmat in idxmats) 

72 

73 

74def _interpolate( # noqa: R0913 

75 width: np.ndarray, 

76 left: np.ndarray, 

77 height: np.ndarray, 

78 top: np.ndarray, 

79 left_top_val: np.ndarray, 

80 right_top_val: np.ndarray, 

81 left_bottom_val: np.ndarray, 

82 right_bottom_val: np.ndarray, 

83) -> np.ndarray: 

84 delta_w = width - left 

85 top_lerp = left_top_val + (right_top_val - left_top_val) * delta_w 

86 bottom_lerp = ( 

87 left_bottom_val + (right_bottom_val - left_bottom_val) * delta_w 

88 ) 

89 return top_lerp + ((bottom_lerp - top_lerp).T * (height - top)).T 

90 

91 

92def resize_mask_vec( # noqa: R0914 

93 src: np.ndarray, 

94 src_shape: Tuple[int, int], 

95 target_shape: Tuple[int, int], 

96 threshold: float, 

97) -> np.ndarray: 

98 """Resize mask from original deepstream object into numpy array. 

99 

100 Args: 

101 src: Mask array from deepstream object. 

102 src_shape: Shape of the original mask in (height,width) format. 

103 target_shape: Shape of the target mask in (height,width) format. 

104 threshold: Threshold for the mask. 

105 

106 Returns: 

107 A 2d binary mask of np.uint8 valued 0 and 255. 

108 

109 See Also: 

110 * `extract_maskrcnn_mask` in this module for sample usage from 

111 deepstream. 

112 * `resizeMask` function at 

113 `sample_apps/deepstream-mrcnn-app/deepstream_mrcnn_test.cpp` 

114 

115 """ 

116 

117 original_height, original_width = src_shape 

118 target_height, target_width = target_shape 

119 

120 height, width = _gen_ranges( 

121 original_height, 

122 original_width, 

123 target_height, 

124 target_width, 

125 ) 

126 

127 left, right, top, bottom = _gen_clips( 

128 width, 

129 original_width, 

130 height, 

131 original_height, 

132 ) 

133 

134 left_top_idx, right_top_idx, left_bottom_idx, right_bottom_idx = _gen_idxs( 

135 original_width, 

136 left, 

137 right, 

138 top, 

139 bottom, 

140 ) 

141 

142 ( 

143 left_top_val, 

144 right_top_val, 

145 left_bottom_val, 

146 right_bottom_val, 

147 ) = _take_vals( 

148 src, 

149 left_top_idx, 

150 right_top_idx, 

151 left_bottom_idx, 

152 right_bottom_idx, 

153 ) 

154 

155 lerp = _interpolate( 

156 width, 

157 left, 

158 height, 

159 top, 

160 left_top_val, 

161 right_top_val, 

162 left_bottom_val, 

163 right_bottom_val, 

164 ) 

165 

166 ret = np.zeros_like(lerp, dtype=np.uint8) 

167 ret[lerp >= threshold] = 255 

168 return ret 

169 

170 

171def extract_maskrcnn_mask(obj_meta: pyds.NvDsObjectMeta) -> np.ndarray: 

172 """Extract maskrcnn mask from deepstream object. 

173 

174 Args: 

175 obj_meta: Deepstream object meta from detection. 

176 

177 Returns: 

178 A 2d binary mask of np.uint8 valued 0 and 255. 

179 

180 Example: 

181 >>> obj_meta = pyds.NvDsObjectMeta.cast(...) 

182 >>> mask = extract_maskrcnn_mask(obj_meta) 

183 >>> mask.shape, mask.dtype 

184 ((300,100), dtype('uint8')) 

185 

186 See Also: 

187 `resize_mask_vec` for the internal implementation. 

188 

189 """ 

190 rect_height = int(np.ceil(obj_meta.rect_params.height)) 

191 rect_width = int(np.ceil(obj_meta.rect_params.width)) 

192 return resize_mask_vec( 

193 obj_meta.mask_params.data, 

194 (obj_meta.mask_params.height, obj_meta.mask_params.width), 

195 (rect_height, rect_width), 

196 obj_meta.mask_params.threshold, 

197 ) 

198 

199 

200class DsBBox(TypedDict): 

201 """Deepstream-style Bounding box.""" 

202 

203 top: float 

204 height: float 

205 width: float 

206 left: float 

207 

208 

209def polygon_to_bbox( 

210 polygons: List[List[int]], 

211 top: float, 

212 left: float, 

213) -> DsBBox: 

214 """Generate bounding box from polygons. 

215 

216 The polygons are assumed to be relative to a mask. 

217 

218 Args: 

219 polygons: collection of polygons, where each polygon is a 

220 sequence of the form 'y0,x0,y1,x1,...,yn,xn'. 

221 top: top offset from the mask. 

222 left: left offset from the mask. 

223 

224 Returns: 

225 Bounding box which circumscribes the mask. 

226 

227 """ 

228 x_max = 0 

229 y_max = 0 

230 x_min = 0 

231 y_min = 0 

232 for polygon in polygons: 

233 coords = np.array(list(grouped(polygon, 2))) 

234 y_max_, x_max_ = coords.max(0) 

235 y_min_, x_min_ = coords.min(0) 

236 x_max = max(x_max, x_max_) 

237 y_max = max(y_max, y_max_) 

238 x_min = min(x_min, x_min_) 

239 y_min = min(y_min, y_min_) 

240 return { 

241 "top": top + y_min, 

242 "left": left + x_min, 

243 "height": x_max - x_min, 

244 "width": y_max - y_min, 

245 }