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
« 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 :
4import math
6import numpy
7import scipy.ndimage
9from PIL import Image
11from bob.bio.base.extractor import Extractor
14class RepeatedLineTracking(Extractor):
15 """Repeated Line Tracking feature extractor
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 """
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 )
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
48 def repeated_line_tracking(self, finger_image, mask):
49 """Computes and returns the MiuraMax features for the given input
50 fingervein image"""
52 # Sets the random seed before starting to process
53 numpy.random.seed(self.seed)
55 finger_mask = numpy.zeros(mask.shape)
56 finger_mask[mask == True] = 1 # noqa: E712
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
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
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)
82 p_lr = 0.5 # Probability of goin left or right
83 p_ud = 0.25 # Probability of going up or down
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 )
100 # Check if progile w is even
101 if self.profile_w.__mod__(2) == 0:
102 print("Error: profile_w must be odd")
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
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
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
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
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
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
144 # Initialize locus-positition table Tc
145 Tc = numpy.zeros(finger_image.shape, bool)
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
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 )
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]
257 img_veins = Tr
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)
266 return img_veins_bin.astype(numpy.float64)
268 def skeletonize(self, img):
269 import scipy.ndimage.morphology as m
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
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"""
296 finger_image = image[
297 0
298 ] # Normalized image with or without histogram equalization
299 finger_mask = image[1]
301 return self.repeated_line_tracking(finger_image, finger_mask)