Coverage for src/bob/bio/vein/extractor/RepeatedLineTracking.py: 83%

129 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-12 23:27 +0200

1#!/usr/bin/env python 

2# vim: set fileencoding=utf-8 : 

3 

4import math 

5 

6import numpy 

7import scipy.ndimage 

8 

9from PIL import Image 

10 

11from bob.bio.base.extractor import Extractor 

12 

13 

14class RepeatedLineTracking(Extractor): 

15 """Repeated Line Tracking feature extractor 

16 

17 Based on N. Miura, A. Nagasaka, and T. Miyatake. Feature extraction of finger 

18 vein patterns based on repeated line tracking and its application to personal 

19 identification. Machine Vision and Applications, Vol. 15, Num. 4, pp. 

20 194--203, 2004 

21 """ 

22 

23 def __init__( 

24 self, 

25 iterations=3000, # Maximum number of iterations 

26 r=1, # Distance between tracking point and cross section of the profile 

27 profile_w=21, # Width of profile (Error: profile_w must be odd) 

28 rescale=True, 

29 seed=0, # Seed for the algorithm's random walk 

30 ): 

31 # call base class constructor 

32 Extractor.__init__( 

33 self, 

34 iterations=iterations, 

35 r=r, 

36 profile_w=profile_w, 

37 rescale=rescale, 

38 seed=seed, 

39 ) 

40 

41 # block parameters 

42 self.iterations = iterations 

43 self.r = r 

44 self.profile_w = profile_w 

45 self.rescale = rescale 

46 self.seed = seed 

47 

48 def repeated_line_tracking(self, finger_image, mask): 

49 """Computes and returns the MiuraMax features for the given input 

50 fingervein image""" 

51 

52 # Sets the random seed before starting to process 

53 numpy.random.seed(self.seed) 

54 

55 finger_mask = numpy.zeros(mask.shape) 

56 finger_mask[mask == True] = 1 # noqa: E712 

57 

58 # Rescale image if required 

59 if self.rescale: 

60 scaling_factor = 0.6 

61 # finger_image = bob.ip.base.scale(finger_image, scaling_factor) 

62 # finger_mask = bob.ip.base.scale(finger_mask, scaling_factor) 

63 new_size = tuple( 

64 (numpy.array(finger_image.shape) * scaling_factor).astype(int) 

65 ) 

66 finger_image = numpy.array( 

67 Image.fromarray(finger_image).resize(size=new_size) 

68 ).T 

69 

70 new_size = tuple( 

71 (numpy.array(finger_mask.shape) * scaling_factor).astype(int) 

72 ) 

73 finger_mask = numpy.array( 

74 Image.fromarray(finger_mask).resize(size=new_size) 

75 ).T 

76 

77 # To eliminate residuals from the scalation of the binary mask 

78 finger_mask = scipy.ndimage.binary_dilation( 

79 finger_mask, structure=numpy.ones((1, 1)) 

80 ).astype(int) 

81 

82 p_lr = 0.5 # Probability of goin left or right 

83 p_ud = 0.25 # Probability of going up or down 

84 

85 Tr = numpy.zeros(finger_image.shape) # Locus space 

86 filtermask = numpy.array( 

87 ( 

88 [-1, -1], 

89 [-1, 0], 

90 [-1, 1], 

91 [0, -1], 

92 [0, 0], 

93 [0, 1], 

94 [1, -1], 

95 [1, 0], 

96 [1, 1], 

97 ) 

98 ) 

99 

100 # Check if progile w is even 

101 if self.profile_w.__mod__(2) == 0: 

102 print("Error: profile_w must be odd") 

103 

104 ro = numpy.round(self.r * math.sqrt(2) / 2) # r for oblique directions 

105 hW = ( 

106 self.profile_w - 1 

107 ) / 2 # half width for horz. and vert. directions 

108 hWo = numpy.round( 

109 hW * math.sqrt(2) / 2 

110 ) # half width for oblique directions 

111 

112 # Omit unreachable borders 

113 border = int(self.r + hW) 

114 finger_mask[0:border, :] = 0 

115 finger_mask[finger_mask.shape[0] - border :, :] = 0 

116 finger_mask[:, 0:border] = 0 

117 finger_mask[:, finger_mask.shape[1] - border :] = 0 

118 

119 # Uniformly distributed starting points 

120 aux = numpy.argwhere(finger_mask > 0) 

121 indices = numpy.random.permutation(aux) 

122 indices = indices[ 

123 0 : self.iterations, : 

124 ] # Limit to number of iterations 

125 

126 # Iterate through all starting points 

127 for it in range(0, self.iterations): 

128 yc = indices[it, 0] # Current tracking point, y 

129 xc = indices[it, 1] # Current tracking point, x 

130 

131 # Determine the moving-direction attributes 

132 # Going left or right ? 

133 if numpy.random.random_sample() >= 0.5: 

134 Dlr = -1 # Going left 

135 else: 

136 Dlr = 1 # Going right 

137 

138 # Going up or down ? 

139 if numpy.random.random_sample() >= 0.5: 

140 Dud = -1 # Going up 

141 else: 

142 Dud = 1 # Going down 

143 

144 # Initialize locus-positition table Tc 

145 Tc = numpy.zeros(finger_image.shape, bool) 

146 

147 # Dlr = -1; Dud=-1; LET OP 

148 Vl = 1 

149 while Vl > 0: 

150 # Determine the moving candidate point set Nc 

151 Nr = numpy.zeros([3, 3], bool) 

152 Rnd = numpy.random.random_sample() 

153 # Rnd = 0.8 LET OP 

154 if Rnd < p_lr: 

155 # Going left or right 

156 Nr[:, 1 + Dlr] = True 

157 elif (Rnd >= p_lr) and (Rnd < (p_lr + p_ud)): 

158 # Going up or down 

159 Nr[1 + Dud, :] = True 

160 else: 

161 # Going any direction 

162 Nr = numpy.ones([3, 3], bool) 

163 Nr[1, 1] = False 

164 # tmp = numpy.argwhere( (~Tc[yc-2:yc+1,xc-2:xc+1] & Nr & finger_mask[yc-2:yc+1,xc-2:xc+1].astype(bool)).T.reshape(-1) == True ) 

165 tmp = numpy.argwhere( 

166 ( 

167 ~Tc[yc - 1 : yc + 2, xc - 1 : xc + 2] 

168 & Nr 

169 & finger_mask[yc - 1 : yc + 2, xc - 1 : xc + 2].astype( 

170 bool 

171 ) 

172 ).T.reshape(-1) 

173 ) 

174 Nc = numpy.concatenate( 

175 (xc + filtermask[tmp, 0], yc + filtermask[tmp, 1]), axis=1 

176 ) 

177 if Nc.size == 0: 

178 Vl = -1 

179 continue 

180 

181 # Detect dark line direction near current tracking point 

182 Vdepths = numpy.zeros((Nc.shape[0], 1)) # Valley depths 

183 for i in range(0, Nc.shape[0]): 

184 # Horizontal or vertical 

185 if Nc[i, 1] == yc: 

186 # Horizontal plane 

187 yp = Nc[i, 1] 

188 if Nc[i, 0] > xc: 

189 # Right direction 

190 xp = Nc[i, 0] + self.r 

191 else: 

192 # Left direction 

193 xp = Nc[i, 0] - self.r 

194 Vdepths[i] = ( 

195 finger_image[int(yp + hW), int(xp)] 

196 - 2 * finger_image[int(yp), int(xp)] 

197 + finger_image[int(yp - hW), int(xp)] 

198 ) 

199 elif Nc[i, 0] == xc: 

200 # Vertical plane 

201 xp = Nc[i, 0] 

202 if Nc[i, 1] > yc: 

203 # Down direction 

204 yp = Nc[i, 1] + self.r 

205 else: 

206 # Up direction 

207 yp = Nc[i, 1] - self.r 

208 Vdepths[i] = ( 

209 finger_image[int(yp), int(xp + hW)] 

210 - 2 * finger_image[int(yp), int(xp)] 

211 + finger_image[int(yp), int(xp - hW)] 

212 ) 

213 

214 # Oblique directions 

215 if ((Nc[i, 0] > xc) and (Nc[i, 1] < yc)) or ( 

216 (Nc[i, 0] < xc) and (Nc[i, 1] > yc) 

217 ): 

218 # Diagonal, up / 

219 if Nc[i, 0] > xc and Nc[i, 1] < yc: 

220 # Top right 

221 xp = Nc[i, 0] + ro 

222 yp = Nc[i, 1] - ro 

223 else: 

224 # Bottom left 

225 xp = Nc[i, 0] - ro 

226 yp = Nc[i, 1] + ro 

227 Vdepths[i] = ( 

228 finger_image[int(yp - hWo), int(xp - hWo)] 

229 - 2 * finger_image[int(yp), int(xp)] 

230 + finger_image[int(yp + hWo), int(xp + hWo)] 

231 ) 

232 else: 

233 # Diagonal, down \ 

234 if Nc[i, 0] < xc and Nc[i, 1] < yc: 

235 # Top left 

236 xp = Nc[i, 0] - ro 

237 yp = Nc[i, 1] - ro 

238 else: 

239 # Bottom right 

240 xp = Nc[i, 0] + ro 

241 yp = Nc[i, 1] + ro 

242 Vdepths[i] = ( 

243 finger_image[int(yp + hWo), int(xp - hWo)] 

244 - 2 * finger_image[int(yp), int(xp)] 

245 + finger_image[int(yp - hWo), int(xp + hWo)] 

246 ) 

247 # End search of candidates 

248 index = numpy.argmax(Vdepths) # Determine best candidate 

249 # Register tracking information 

250 Tc[yc, xc] = True 

251 # Increase value of tracking space 

252 Tr[yc, xc] = Tr[yc, xc] + 1 

253 # Move tracking point 

254 xc = Nc[index, 0] 

255 yc = Nc[index, 1] 

256 

257 img_veins = Tr 

258 

259 # Binarise the vein image 

260 md = numpy.median(img_veins[img_veins > 0]) 

261 img_veins_bin = img_veins > md 

262 img_veins_bin = scipy.ndimage.binary_closing( 

263 img_veins_bin, structure=numpy.ones((2, 2)) 

264 ).astype(int) 

265 

266 return img_veins_bin.astype(numpy.float64) 

267 

268 def skeletonize(self, img): 

269 import scipy.ndimage.morphology as m 

270 

271 h1 = numpy.array([[0, 0, 0], [0, 1, 0], [1, 1, 1]]) 

272 m1 = numpy.array([[1, 1, 1], [0, 0, 0], [0, 0, 0]]) 

273 h2 = numpy.array([[0, 0, 0], [1, 1, 0], [0, 1, 0]]) 

274 m2 = numpy.array([[0, 1, 1], [0, 0, 1], [0, 0, 0]]) 

275 hit_list = [] 

276 miss_list = [] 

277 for k in range(4): 

278 hit_list.append(numpy.rot90(h1, k)) 

279 hit_list.append(numpy.rot90(h2, k)) 

280 miss_list.append(numpy.rot90(m1, k)) 

281 miss_list.append(numpy.rot90(m2, k)) 

282 img = img.copy() 

283 while True: 

284 last = img 

285 for hit, miss in zip(hit_list, miss_list): 

286 hm = m.binary_hit_or_miss(img, hit, miss) 

287 img = numpy.logical_and(img, numpy.logical_not(hm)) 

288 if numpy.all(img == last): 

289 break 

290 return img 

291 

292 def __call__(self, image): 

293 """Reads the input image, extract the features based on Maximum Curvature 

294 of the fingervein image, and writes the resulting template""" 

295 

296 finger_image = image[ 

297 0 

298 ] # Normalized image with or without histogram equalization 

299 finger_mask = image[1] 

300 

301 return self.repeated_line_tracking(finger_image, finger_mask)