Coverage for src/bob/bio/vein/preprocessor/mask.py: 50%
123 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 :
4"""Base utilities for mask processing"""
6import math
8import numpy
9import scipy.ndimage
11from .utils import poly_to_mask
14class Padder(object):
15 """A class that pads the input image returning a new object
18 Parameters:
20 padding_width (:py:obj:`int`, optional): How much padding (in pixels) to
21 add around the borders of the input image. We normally always keep this
22 value on its default (5 pixels). This parameter is always used before
23 normalizing the finger orientation.
25 padding_constant (:py:obj:`int`, optional): What is the value of the pixels
26 added to the padding. This number should be a value between 0 and 255.
27 (From Pedro Tome: for UTFVP (high-quality samples), use 0. For the VERA
28 Fingervein database (low-quality samples), use 51 (that corresponds to
29 0.2 in a float image with values between 0 and 1). This parameter is
30 always used before normalizing the finger orientation.
32 """
34 def __init__(self, padding_width=5, padding_constant=51):
35 self.padding_width = padding_width
36 self.padding_constant = padding_constant
38 def __call__(self, image):
39 """Inputs an image, returns a padded (larger) image
41 Parameters:
43 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
44 input image
47 Returns:
49 numpy.ndarray: A 2D numpy array of the same type as the input, but with
50 the extra padding
52 """
54 return numpy.pad(
55 image,
56 self.padding_width,
57 "constant",
58 constant_values=self.padding_constant,
59 )
62class Masker(object):
63 """This is the base class for all maskers
65 It defines the minimum requirements for all derived masker classes.
68 """
70 def __init__(self):
71 pass
73 def __call__(self, image):
74 """Overwrite this method to implement your masking method
77 Parameters:
79 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
80 input image
83 Returns:
85 numpy.ndarray: A 2D numpy array of type boolean with the caculated
86 mask. ``True`` values correspond to regions where the finger is
87 situated
89 """
91 raise NotImplementedError("You must implement the __call__ slot")
94class FixedMask(Masker):
95 """Implements masking using a fixed suppression of border pixels
97 The defaults mask no lines from the image and returns a mask of the same size
98 of the original image where all values are ``True``.
101 .. note::
103 Before choosing values, note you're responsible for knowing what is the
104 orientation of images fed into this masker.
107 Parameters:
109 top (:py:class:`int`, optional): Number of lines to suppress from the top
110 of the image. The top of the image corresponds to ``y = 0``.
112 bottom (:py:class:`int`, optional): Number of lines to suppress from the
113 bottom of the image. The bottom of the image corresponds to ``y =
114 height``.
116 left (:py:class:`int`, optional): Number of lines to suppress from the left
117 of the image. The left of the image corresponds to ``x = 0``.
119 right (:py:class:`int`, optional): Number of lines to suppress from the
120 right of the image. The right of the image corresponds to ``x = width``.
122 """
124 def __init__(self, top=0, bottom=0, left=0, right=0):
125 self.top = top
126 self.bottom = bottom
127 self.left = left
128 self.right = right
130 def __call__(self, image):
131 """Returns a big mask
134 Parameters:
136 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
137 input image
140 Returns:
142 numpy.ndarray: A 2D numpy array of type boolean with the caculated
143 mask. ``True`` values correspond to regions where the finger is
144 situated
147 """
149 retval = numpy.zeros(image.shape, dtype="bool")
150 h, w = image.shape
151 retval[self.top : h - self.bottom, self.left : w - self.right] = True
152 return retval
155class NoMask(FixedMask):
156 """Convenience: same as FixedMask()"""
158 def __init__(self):
159 super(NoMask, self).__init__(0, 0, 0, 0)
162class AnnotatedRoIMask(Masker):
163 """Devises the mask from the annotated RoI"""
165 def __init__(self):
166 pass
168 def __call__(self, image):
169 """Returns a mask extrapolated from RoI annotations
172 Parameters:
174 image (bob.bio.vein.database.AnnotatedArray): A 2D numpy array of type
175 ``uint8`` with the input image containing an attribute called
176 ``metadata`` (a python dictionary). The ``metadata`` object just
177 contain a key called ``roi`` containing the annotated points
180 Returns:
182 numpy.ndarray: A 2D numpy array of type boolean with the caculated
183 mask. ``True`` values correspond to regions where the finger is
184 situated
187 """
189 return poly_to_mask(image.shape, image.metadata["roi"])
192class KonoMask(Masker):
193 """Estimates the finger region given an input NIR image using Kono et al.
195 This method is based on the work of M. Kono, H. Ueki and S. Umemura.
196 Near-infrared finger vein patterns for personal identification, Applied
197 Optics, Vol. 41, Issue 35, pp. 7429-7436 (2002).
200 Parameters:
202 sigma (:py:obj:`float`, optional): The standard deviation of the gaussian
203 blur filter to apply for low-passing the input image (background
204 extraction). Defaults to ``5``.
206 padder (:py:class:`Padder`, optional): If passed, will pad the image before
207 evaluating the mask. The returned value will have the padding removed and
208 is, therefore, of the exact size of the input image.
210 """
212 def __init__(self, sigma=5, padder=Padder()):
213 self.sigma = sigma
214 self.padder = padder
216 def __call__(self, image):
217 """Inputs an image, returns a mask (numpy boolean array)
219 Parameters:
221 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
222 input image
225 Returns:
227 numpy.ndarray: A 2D numpy array of type boolean with the caculated
228 mask. ``True`` values correspond to regions where the finger is
229 situated
231 """
233 image = image if self.padder is None else self.padder(image)
234 if image.dtype == numpy.uint8:
235 image = image.astype("float64") / 255.0
237 img_h, img_w = image.shape
239 # Determine lower half starting point
240 if numpy.mod(img_h, 2) == 0:
241 half_img_h = img_h / 2 + 1
242 else:
243 half_img_h = numpy.ceil(img_h / 2)
245 # Construct filter kernel
246 winsize = numpy.ceil(4 * self.sigma)
248 x = numpy.arange(-winsize, winsize + 1)
249 y = numpy.arange(-winsize, winsize + 1)
250 X, Y = numpy.meshgrid(x, y)
252 hy = (-Y / (2 * math.pi * self.sigma**4)) * numpy.exp(
253 -(X**2 + Y**2) / (2 * self.sigma**2)
254 )
256 # Filter the image with the directional kernel
257 fy = scipy.ndimage.convolve(image, hy, mode="nearest")
259 # Upper part of filtred image
260 img_filt_up = fy[0:half_img_h, :]
261 y_up = img_filt_up.argmax(axis=0)
263 # Lower part of filtred image
264 img_filt_lo = fy[half_img_h - 1 :, :]
265 y_lo = img_filt_lo.argmin(axis=0)
267 # Fill region between upper and lower edges
268 finger_mask = numpy.ndarray(image.shape, bool)
269 finger_mask[:, :] = False
271 for i in range(0, img_w):
272 finger_mask[
273 y_up[i] : y_lo[i] + image.shape[0] - half_img_h + 2, i
274 ] = True
276 if not self.padder:
277 return finger_mask
278 else:
279 w = self.padder.padding_width
280 return finger_mask[w:-w, w:-w]
283class LeeMask(Masker):
284 """Estimates the finger region given an input NIR image using Lee et al.
286 This method is based on the work of Finger vein recognition using
287 minutia-based alignment and local binary pattern-based feature extraction,
288 E.C. Lee, H.C. Lee and K.R. Park, International Journal of Imaging Systems
289 and Technology, Volume 19, Issue 3, September 2009, Pages 175--178, doi:
290 10.1002/ima.20193
292 This code is based on the Matlab implementation by Bram Ton, available at:
294 https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
296 In this method, we calculate the mask of the finger independently for each
297 column of the input image. Firstly, the image is convolved with a [1,-1]
298 filter of size ``(self.filter_height, self.filter_width)``. Then, the upper and
299 lower parts of the resulting filtered image are separated. The location of
300 the maxima in the upper part is located. The same goes for the location of
301 the minima in the lower part. The mask is then calculated, per column, by
302 considering it starts in the point where the maxima is in the upper part and
303 goes up to the point where the minima is detected on the lower part.
306 Parameters:
308 filter_height (:py:obj:`int`, optional): Height of contour mask in pixels,
309 must be an even number
311 filter_width (:py:obj:`int`, optional): Width of the contour mask in pixels
313 """
315 def __init__(self, filter_height=4, filter_width=40, padder=Padder()):
316 self.filter_height = filter_height
317 self.filter_width = filter_width
318 self.padder = padder
320 def __call__(self, image):
321 """Inputs an image, returns a mask (numpy boolean array)
323 Parameters:
325 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
326 input image
329 Returns:
331 numpy.ndarray: A 2D numpy array of type boolean with the caculated
332 mask. ``True`` values correspond to regions where the finger is
333 situated
335 """
337 image = image if self.padder is None else self.padder(image)
338 if image.dtype == numpy.uint8:
339 image = image.astype("float64") / 255.0
341 img_h, img_w = image.shape
343 # Determine lower half starting point
344 half_img_h = int(img_h / 2)
346 # Construct mask for filtering
347 mask = numpy.ones(
348 (self.filter_height, self.filter_width), dtype="float64"
349 )
350 mask[int(self.filter_height / 2.0) :, :] = -1.0
352 img_filt = scipy.ndimage.convolve(image, mask, mode="nearest")
354 # Upper part of filtered image
355 img_filt_up = img_filt[:half_img_h, :]
356 y_up = img_filt_up.argmax(axis=0)
358 # Lower part of filtered image
359 img_filt_lo = img_filt[half_img_h:, :]
360 y_lo = img_filt_lo.argmin(axis=0)
362 # Translation: for all columns of the input image, set to True all pixels
363 # of the mask from index where the maxima occurred in the upper part until
364 # the index where the minima occurred in the lower part.
365 finger_mask = numpy.zeros(image.shape, dtype="bool")
366 for i in range(img_filt.shape[1]):
367 finger_mask[
368 y_up[i] : (y_lo[i] + img_filt_lo.shape[0] + 1), i
369 ] = True
371 if not self.padder:
372 return finger_mask
373 else:
374 w = self.padder.padding_width
375 return finger_mask[w:-w, w:-w]
378class TomesLeeMask(Masker):
379 """Estimates the finger region given an input NIR image using Lee et al.
381 This method is based on the work of Finger vein recognition using
382 minutia-based alignment and local binary pattern-based feature extraction,
383 E.C. Lee, H.C. Lee and K.R. Park, International Journal of Imaging Systems
384 and Technology, Volume 19, Issue 3, September 2009, Pages 175--178, doi:
385 10.1002/ima.20193
387 This code is a variant of the Matlab implementation by Bram Ton, available
388 at:
390 https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
392 In this variant from Pedro Tome, the technique of filtering the image with
393 a horizontal filter is also applied on the vertical axis. The objective is to
394 find better limits on the horizontal axis in case finger images show the
395 finger tip. If that is not your case, you may use the original variant
396 :py:class:`LeeMask` above.
399 Parameters:
401 filter_height (:py:obj:`int`, optional): Height of contour mask in pixels,
402 must be an even number
404 filter_width (:py:obj:`int`, optional): Width of the contour mask in pixels
406 """
408 def __init__(self, filter_height=4, filter_width=40, padder=Padder()):
409 self.filter_height = filter_height
410 self.filter_width = filter_width
411 self.padder = padder
413 def __call__(self, image):
414 """Inputs an image, returns a mask (numpy boolean array)
416 Parameters:
418 image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
419 input image
422 Returns:
424 numpy.ndarray: A 2D numpy array of type boolean with the caculated
425 mask. ``True`` values correspond to regions where the finger is
426 situated
428 """
430 image = image if self.padder is None else self.padder(image)
431 if image.dtype == numpy.uint8:
432 image = image.astype("float64") / 255.0
434 img_h, img_w = image.shape
436 # Determine lower half starting point
437 half_img_h = img_h / 2
438 half_img_w = img_w / 2
440 # Construct mask for filtering (up-bottom direction)
441 mask = numpy.ones(
442 (self.filter_height, self.filter_width), dtype="float64"
443 )
444 mask[int(self.filter_height / 2.0) :, :] = -1.0
446 img_filt = scipy.ndimage.convolve(image, mask, mode="nearest")
448 # Upper part of filtred image
449 img_filt_up = img_filt[: int(half_img_h), :]
450 y_up = img_filt_up.argmax(axis=0)
452 # Lower part of filtred image
453 img_filt_lo = img_filt[int(half_img_h) :, :]
454 y_lo = img_filt_lo.argmin(axis=0)
456 img_filt = scipy.ndimage.convolve(image, mask.T, mode="nearest")
458 # Left part of filtered image
459 img_filt_lf = img_filt[:, : int(half_img_w)]
460 y_lf = img_filt_lf.argmax(axis=1)
462 # Right part of filtred image
463 img_filt_rg = img_filt[:, int(half_img_w) :]
464 y_rg = img_filt_rg.argmin(axis=1)
466 finger_mask = numpy.zeros(image.shape, dtype="bool")
468 for i in range(0, y_up.size):
469 finger_mask[y_up[i] : y_lo[i] + img_filt_lo.shape[0] + 1, i] = True
471 # Left region
472 for i in range(0, y_lf.size):
473 finger_mask[i, 0 : y_lf[i] + 1] = False
475 # Right region has always the finger ending, crop the padding with the
476 # meadian
477 finger_mask[:, int(numpy.median(y_rg) + img_filt_rg.shape[1]) :] = False
479 if not self.padder:
480 return finger_mask
481 else:
482 w = self.padder.padding_width
483 return finger_mask[w:-w, w:-w]