FingerCrop.py 18.5 KB
Newer Older
Pedro TOME's avatar
Pedro TOME committed
1
2
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
3
4
5
6

import math
import numpy
from PIL import Image
Pedro TOME's avatar
Pedro TOME committed
7
8

import bob.io.base
9
import bob.io.image
Pedro TOME's avatar
Pedro TOME committed
10
11
12
13
import bob.ip.base
import bob.sp
import bob.core

14
15
from bob.bio.base.preprocessor import Preprocessor

Pedro TOME's avatar
Pedro TOME committed
16
from .. import utils
17

Pedro TOME's avatar
Pedro TOME committed
18
19

class FingerCrop (Preprocessor):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
20
21
  """
  Extracts the mask and pre-processes fingervein images.
22
23
24
25
26

  Based on the implementation: E.C. Lee, H.C. Lee and K.R. Park. Finger vein
  recognition using minutia-based alignment and local binary pattern-based
  feature extraction. International Journal of Imaging Systems and
  Technology. Vol. 19, No. 3, pp. 175-178, September 2009.
André Anjos's avatar
André Anjos committed
27

28
29
  In this implementation, the finger image is (in this order):

30
31
32
33
    1. Padded
    2. The mask is extracted
    3. The finger is normalized (made horizontal)
    4. (optionally) Post processed
34

André Anjos's avatar
André Anjos committed
35

Olegs NIKISINS's avatar
Olegs NIKISINS committed
36
  **Parameters:**
André Anjos's avatar
André Anjos committed
37

Olegs NIKISINS's avatar
Olegs NIKISINS committed
38
39
  mask_h : :py:class:`int`
      Optional,  Height of contour mask in pixels, must be an even
40
      number
André Anjos's avatar
André Anjos committed
41

Olegs NIKISINS's avatar
Olegs NIKISINS committed
42
43
  mask_w : :py:class:`int`
      Optional,  Width of the contour mask in pixels
André Anjos's avatar
André Anjos committed
44

Olegs NIKISINS's avatar
Olegs NIKISINS committed
45
46
  padding_width : :py:class:`int`
      Optional,  How much padding (in pixels) to add around
47
48
      the borders of the input image. We normally always keep this value on its
      default (5 pixels).
André Anjos's avatar
André Anjos committed
49

Olegs NIKISINS's avatar
Olegs NIKISINS committed
50
51
  padding_constant : :py:class:`int`
      Optional,  What is the value of the pixels added
52
53
54
55
      to the padding. This number should be a value between 0 and 255. (From
      Pedro Tome: for UTFVP (high-quality samples), use 0. For the VERA
      Fingervein database (low-quality samples), use 51 (that corresponds to
      0.2 in a float image with values between 0 and 1).
André Anjos's avatar
André Anjos committed
56

Olegs NIKISINS's avatar
Olegs NIKISINS committed
57
58
  fingercontour : :py:class:`str`
      Optional,  Select between three finger contour
59
60
61
62
63
      implementations: ``"leemaskMod"``, ``"leemaskMatlab"`` or ``"konomask"``.
      (From Pedro Tome: the option ``leemaskMatlab`` was just implemented for
      testing purposes so we could compare with MAT files generated from Matlab
      code of other authors. He only used it with the UTFVP database, using
      ``leemaskMod`` with that database yields slight worse results.)
André Anjos's avatar
André Anjos committed
64

Olegs NIKISINS's avatar
Olegs NIKISINS committed
65
66
  postprocessing : :py:class:`str`
      Optional,  Select between ``HE`` (histogram
André Anjos's avatar
André Anjos committed
67
68
69
      equalization, as with :py:func:`bob.ip.base.histogram_equalization`),
      ``HFE`` (high-frequency emphasis filter, with hard-coded parameters - see
      implementation) or ``CircGabor`` (circular Gabor filter with band-width
70
      1.12 octaves and standard deviation of 5 pixels (this is hard-coded)). By
André Anjos's avatar
André Anjos committed
71
      default, no postprocessing is applied on the image.
72
  """
Pedro TOME's avatar
Pedro TOME committed
73

André Anjos's avatar
André Anjos committed
74

75
76
77
  def __init__(self, mask_h = 4, mask_w = 40,
      padding_width = 5, padding_constant = 51,
      fingercontour = 'leemaskMod', postprocessing = None, **kwargs):
Pedro TOME's avatar
Pedro TOME committed
78

André Anjos's avatar
André Anjos committed
79
    Preprocessor.__init__(self,
Pedro TOME's avatar
Pedro TOME committed
80
81
        mask_h = mask_h,
        mask_w = mask_w,
82
83
        padding_width = padding_width,
        padding_constant = padding_constant,
Pedro TOME's avatar
Pedro TOME committed
84
85
86
        fingercontour = fingercontour,
        postprocessing = postprocessing,
        **kwargs
André Anjos's avatar
André Anjos committed
87
        )
Pedro TOME's avatar
Pedro TOME committed
88
89
90

    self.mask_h = mask_h
    self.mask_w = mask_w
91

Pedro TOME's avatar
Pedro TOME committed
92
93
94
    self.fingercontour = fingercontour
    self.postprocessing = postprocessing

95
96
    self.padding_width = padding_width
    self.padding_constant = padding_constant
97
98


Pedro TOME's avatar
Pedro TOME committed
99
  def __konomask__(self, image, sigma):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
100
101
    """
    Finger vein mask extractor.
102
103
104
105
106

    Based on the work of M. Kono, H. Ueki and S. Umemura. Near-infrared finger
    vein patterns for personal identification, Applied Optics, Vol. 41, Issue
    35, pp. 7429-7436 (2002).

107
108
    """

Pedro TOME's avatar
Pedro TOME committed
109
110
    sigma = 5
    img_h,img_w = image.shape
111

Pedro TOME's avatar
Pedro TOME committed
112
113
114
115
116
117
118
119
120
121
122
123
    # Determine lower half starting point
    if numpy.mod(img_h,2) == 0:
        half_img_h = img_h/2 + 1
    else:
        half_img_h = numpy.ceil(img_h/2)

    #Construct filter kernel
    winsize = numpy.ceil(4*sigma)

    x = numpy.arange(-winsize, winsize+1)
    y = numpy.arange(-winsize, winsize+1)
    X, Y = numpy.meshgrid(x, y)
124

Pedro TOME's avatar
Pedro TOME committed
125
    hy = (-Y/(2*math.pi*sigma**4))*numpy.exp(-(X**2 + Y**2)/(2*sigma**2))
126

Pedro TOME's avatar
Pedro TOME committed
127
    # Filter the image with the directional kernel
128
    fy = utils.imfilter(image, hy)
Pedro TOME's avatar
Pedro TOME committed
129
130
131
132
133
134
135
136

    # Upper part of filtred image
    img_filt_up = fy[0:half_img_h,:]
    y_up = img_filt_up.argmax(axis=0)

    # Lower part of filtred image
    img_filt_lo = fy[half_img_h-1:,:]
    y_lo = img_filt_lo.argmin(axis=0)
137
138
139

    # Fill region between upper and lower edges
    finger_mask = numpy.ndarray(image.shape, numpy.bool)
Pedro TOME's avatar
Pedro TOME committed
140
141
142
143
    finger_mask[:,:] = False

    for i in range(0,img_w):
      finger_mask[y_up[i]:y_lo[i]+image.shape[0]-half_img_h+2,i] = True
144

Pedro TOME's avatar
Pedro TOME committed
145
146
147
    # Extract y-position of finger edges
    edges = numpy.zeros((2,img_w))
    edges[0,:] = y_up
148
149
    edges[1,:] = y_lo + image.shape[0] - half_img_h + 1

Pedro TOME's avatar
Pedro TOME committed
150
151
    return (finger_mask, edges)

152

Pedro TOME's avatar
Pedro TOME committed
153
  def __leemaskMod__(self, image):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
154
155
    """
    A method to calculate the finger mask.
156

157
158
159
160
    Based on the work of Finger vein recognition using minutia-based alignment
    and local binary pattern-based feature extraction, E.C. Lee, H.C. Lee and
    K.R. Park, International Journal of Imaging Systems and Technology, Volume
    19, Issue 3, September 2009, Pages 175--178, doi: 10.1002/ima.20193
161

162
163
    This code is a variant of the Matlab implementation by Bram Ton, available
    at:
164

165
    https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
Pedro TOME's avatar
Pedro TOME committed
166

167
168
169
170
    In this variant from Pedro Tome, the technique of filtering the image with
    a horizontal filter is also applied on the vertical axis.


Olegs NIKISINS's avatar
Olegs NIKISINS committed
171
    **Parameters:**
172

Olegs NIKISINS's avatar
Olegs NIKISINS committed
173
    image (numpy.ndarray): raw image to use for finding the mask, as 2D array
174
175
176
        of unsigned 8-bit integers


Olegs NIKISINS's avatar
Olegs NIKISINS committed
177
    **Returns:**
178

Olegs NIKISINS's avatar
Olegs NIKISINS committed
179
    numpy.ndarray: A 2D boolean array with the same shape of the input image
180
181
        representing the cropping mask. ``True`` values indicate where the
        finger is.
Pedro TOME's avatar
Pedro TOME committed
182

Olegs NIKISINS's avatar
Olegs NIKISINS committed
183
    numpy.ndarray: A 2D array with 64-bit floats indicating the indexes where
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
       the mask, for each column, starts and ends on the original image. The
       same of this array is (2, number of columns on input image).

    """


    img_h,img_w = image.shape

    # Determine lower half starting point
    half_img_h = img_h/2
    half_img_w = img_w/2

    # Construct mask for filtering (up-bottom direction)
    mask = numpy.ones((self.mask_h, self.mask_w), dtype='float64')
    mask[(self.mask_h/2):,:] = -1.0

    img_filt = utils.imfilter(image, mask)
201

Pedro TOME's avatar
Pedro TOME committed
202
    # Upper part of filtred image
203
    img_filt_up = img_filt[:half_img_h,:]
Pedro TOME's avatar
Pedro TOME committed
204
    y_up = img_filt_up.argmax(axis=0)
205

206
207
    # Lower part of filtred image
    img_filt_lo = img_filt[half_img_h:,:]
Pedro TOME's avatar
Pedro TOME committed
208
    y_lo = img_filt_lo.argmin(axis=0)
209

210
    img_filt = utils.imfilter(image, mask.T)
211

212
213
    # Left part of filtered image
    img_filt_lf = img_filt[:,:half_img_w]
Pedro TOME's avatar
Pedro TOME committed
214
    y_lf = img_filt_lf.argmax(axis=1)
215

216
    # Right part of filtred image
Pedro TOME's avatar
Pedro TOME committed
217
218
    img_filt_rg = img_filt[:,half_img_w:]
    y_rg = img_filt_rg.argmin(axis=1)
219

220
    finger_mask = numpy.zeros(image.shape, dtype='bool')
221

Pedro TOME's avatar
Pedro TOME committed
222
223
    for i in range(0,y_up.size):
        finger_mask[y_up[i]:y_lo[i]+img_filt_lo.shape[0]+1,i] = True
224

Pedro TOME's avatar
Pedro TOME committed
225
226
227
    # Left region
    for i in range(0,y_lf.size):
        finger_mask[i,0:y_lf[i]+1] = False
228

229
230
    # Right region has always the finger ending, crop the padding with the
    # meadian
231
    finger_mask[:,int(numpy.median(y_rg)+img_filt_rg.shape[1]):] = False
232

Pedro TOME's avatar
Pedro TOME committed
233
234
235
    # Extract y-position of finger edges
    edges = numpy.zeros((2,img_w))
    edges[0,:] = y_up
236
    edges[0,0:int(round(numpy.mean(y_lf))+1)] = edges[0,int(round(numpy.mean(y_lf))+1)]
237
238

    edges[1,:] = numpy.round(y_lo + img_filt_lo.shape[0])
239
    edges[1,0:int(round(numpy.mean(y_lf))+1)] = edges[1,int(round(numpy.mean(y_lf))+1)]
240

241
    return finger_mask, edges
242
243


Pedro TOME's avatar
Pedro TOME committed
244
  def __leemaskMatlab__(self, image):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
245
246
    """
    A method to calculate the finger mask.
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266

    Based on the work of Finger vein recognition using minutia-based alignment
    and local binary pattern-based feature extraction, E.C. Lee, H.C. Lee and
    K.R. Park, International Journal of Imaging Systems and Technology, Volume
    19, Issue 3, September 2009, Pages 175--178, doi: 10.1002/ima.20193

    This code is based on the Matlab implementation by Bram Ton, available at:

    https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m

    In this method, we calculate the mask of the finger independently for each
    column of the input image. Firstly, the image is convolved with a [1,-1]
    filter of size ``(self.mask_h, self.mask_w)``. Then, the upper and lower
    parts of the resulting filtered image are separated. The location of the
    maxima in the upper part is located. The same goes for the location of the
    minima in the lower part. The mask is then calculated, per column, by
    considering it starts in the point where the maxima is in the upper part
    and goes up to the point where the minima is detected on the lower part.


Olegs NIKISINS's avatar
Olegs NIKISINS committed
267
    **Parameters:**
268

Olegs NIKISINS's avatar
Olegs NIKISINS committed
269
    image (numpy.ndarray): raw image to use for finding the mask, as 2D array
270
271
272
        of unsigned 8-bit integers


Olegs NIKISINS's avatar
Olegs NIKISINS committed
273
    **Returns:**
274

Olegs NIKISINS's avatar
Olegs NIKISINS committed
275
    numpy.ndarray: A 2D boolean array with the same shape of the input image
276
277
278
        representing the cropping mask. ``True`` values indicate where the
        finger is.

Olegs NIKISINS's avatar
Olegs NIKISINS committed
279
    numpy.ndarray: A 2D array with 64-bit floats indicating the indexes where
280
281
282
283
       the mask, for each column, starts and ends on the original image. The
       same of this array is (2, number of columns on input image).

    """
Pedro TOME's avatar
Pedro TOME committed
284
285

    img_h,img_w = image.shape
286

Pedro TOME's avatar
Pedro TOME committed
287
    # Determine lower half starting point
288
    half_img_h = img_h/2
289
290

    # Construct mask for filtering
291
292
    mask = numpy.ones((self.mask_h,self.mask_w), dtype='float64')
    mask[(self.mask_h/2):,:] = -1.0
Pedro TOME's avatar
Pedro TOME committed
293

294
    img_filt = utils.imfilter(image, mask)
295

296
297
    # Upper part of filtered image
    img_filt_up = img_filt[:half_img_h,:]
Pedro TOME's avatar
Pedro TOME committed
298
299
    y_up = img_filt_up.argmax(axis=0)

300
301
    # Lower part of filtered image
    img_filt_lo = img_filt[half_img_h:,:]
Pedro TOME's avatar
Pedro TOME committed
302
    y_lo = img_filt_lo.argmin(axis=0)
303

304
305
306
307
308
309
    # Translation: for all columns of the input image, set to True all pixels
    # of the mask from index where the maxima occurred in the upper part until
    # the index where the minima occurred in the lower part.
    finger_mask = numpy.zeros(image.shape, dtype='bool')
    for i in range(img_filt.shape[1]):
      finger_mask[y_up[i]:(y_lo[i]+img_filt_lo.shape[0]+1), i] = True
310

Pedro TOME's avatar
Pedro TOME committed
311
    # Extract y-position of finger edges
312
    edges = numpy.zeros((2,img_w), dtype='float64')
Pedro TOME's avatar
Pedro TOME committed
313
    edges[0,:] = y_up
314
315
    edges[1,:] = numpy.round(y_lo + img_filt_lo.shape[0])

316
    return finger_mask, edges
317

Pedro TOME's avatar
Pedro TOME committed
318
319

  def __huangnormalization__(self, image, mask, edges):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
320
321
    """
    Simple finger normalization.
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337

    Based on B. Huang, Y. Dai, R. Li, D. Tang and W. Li, Finger-vein
    authentication based on wide line detector and pattern normalization,
    Proceedings on 20th International Conference on Pattern Recognition (ICPR),
    2010.

    This implementation aligns the finger to the centre of the image using an
    affine transformation. Elliptic projection which is described in the
    referenced paper is not included.

    In order to defined the affine transformation to be performed, the
    algorithm first calculates the center for each edge (column wise) and
    calculates the best linear fit parameters for a straight line passing
    through those points.


Olegs NIKISINS's avatar
Olegs NIKISINS committed
338
    **Parameters:**
339

Olegs NIKISINS's avatar
Olegs NIKISINS committed
340
    image (numpy.ndarray): raw image to normalize as 2D array of unsigned
341
342
        8-bit integers

Olegs NIKISINS's avatar
Olegs NIKISINS committed
343
    mask (numpy.ndarray): mask to normalize as 2D array of booleans
344

Olegs NIKISINS's avatar
Olegs NIKISINS committed
345
    edges (numpy.ndarray): edges of the mask, 2D array with 2 rows and as
346
347
348
349
        many columns as the input image, containing the start of the mask and
        the end of the mask.


Olegs NIKISINS's avatar
Olegs NIKISINS committed
350
    **Returns:**
351

Olegs NIKISINS's avatar
Olegs NIKISINS committed
352
    numpy.ndarray: A 2D boolean array with the same shape and data type of
353
354
        the input image representing the newly aligned image.

Olegs NIKISINS's avatar
Olegs NIKISINS committed
355
    numpy.ndarray: A 2D boolean array with the same shape and data type of
356
357
        the input mask representing the newly aligned mask.
    """
Pedro TOME's avatar
Pedro TOME committed
358
359
360

    img_h, img_w = image.shape

361
    bl = edges.mean(axis=0) #baseline
Pedro TOME's avatar
Pedro TOME committed
362
363
    x = numpy.arange(0,img_w)
    A = numpy.vstack([x, numpy.ones(len(x))]).T
364

Pedro TOME's avatar
Pedro TOME committed
365
366
    # Fit a straight line through the base line points
    w = numpy.linalg.lstsq(A,bl)[0] # obtaining the parameters
367

Pedro TOME's avatar
Pedro TOME committed
368
369
370
    angle = -1*math.atan(w[0])  # Rotation
    tr = img_h/2 - w[1]         # Translation
    scale = 1.0                 # Scale
371
372

    #Affine transformation parameters
Pedro TOME's avatar
Pedro TOME committed
373
374
375
    sx=sy=scale
    cosine = math.cos(angle)
    sine = math.sin(angle)
376

Pedro TOME's avatar
Pedro TOME committed
377
378
379
380
    a = cosine/sx
    b = -sine/sy
    #b = sine/sx
    c = 0 #Translation in x
381

Pedro TOME's avatar
Pedro TOME committed
382
383
384
385
386
    d = sine/sx
    e = cosine/sy
    f = tr #Translation in y
    #d = -sine/sy
    #e = cosine/sy
387
388
389
390
391
392
393
    #f = 0

    g = 0
    h = 0
    #h=tr
    i = 1

Pedro TOME's avatar
Pedro TOME committed
394
395
396
    T = numpy.matrix([[a,b,c],[d,e,f],[g,h,i]])
    Tinv = numpy.linalg.inv(T)
    Tinvtuple = (Tinv[0,0],Tinv[0,1], Tinv[0,2], Tinv[1,0],Tinv[1,1],Tinv[1,2])
397

Pedro TOME's avatar
Pedro TOME committed
398
399
    img=Image.fromarray(image)
    image_norm = img.transform(img.size, Image.AFFINE, Tinvtuple, resample=Image.BICUBIC)
400
    image_norm = numpy.array(image_norm)
Pedro TOME's avatar
Pedro TOME committed
401
402

    finger_mask = numpy.zeros(mask.shape)
403
    finger_mask[mask] = 1
Pedro TOME's avatar
Pedro TOME committed
404
405
406

    img_mask=Image.fromarray(finger_mask)
    mask_norm = img_mask.transform(img_mask.size, Image.AFFINE, Tinvtuple, resample=Image.BICUBIC)
407
    mask_norm = numpy.array(mask_norm).astype('bool')
408

409
    return (image_norm, mask_norm)
410

Pedro TOME's avatar
Pedro TOME committed
411

412
  def __HE__(self, image, mask):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
413
414
    """
    Applies histogram equalization on the input image inside the mask.
415
416
417
418
419

    In this implementation, only the pixels that lie inside the mask will be
    used to calculate the histogram equalization parameters. Because of this
    particularity, we don't use Bob's implementation for histogram equalization
    and have one based exclusively on NumPy.
Pedro TOME's avatar
Pedro TOME committed
420

421

Olegs NIKISINS's avatar
Olegs NIKISINS committed
422
    **Parameters:**
André Anjos's avatar
André Anjos committed
423

Olegs NIKISINS's avatar
Olegs NIKISINS committed
424
    image (numpy.ndarray): raw image to be filtered, as 2D array of
André Anjos's avatar
André Anjos committed
425
426
          unsigned 8-bit integers

Olegs NIKISINS's avatar
Olegs NIKISINS committed
427
    mask (numpy.ndarray): mask of the same size of the image, but composed
428
429
430
          of boolean values indicating which values should be considered for
          the histogram equalization

André Anjos's avatar
André Anjos committed
431

Olegs NIKISINS's avatar
Olegs NIKISINS committed
432
    **Returns:**
433

Olegs NIKISINS's avatar
Olegs NIKISINS committed
434
    numpy.ndarray: normalized image as a 2D array of unsigned 8-bit integers
435

André Anjos's avatar
André Anjos committed
436
437
    """

438
439
440
441
442
443
444
445
446
447
448
449
450
    image_histogram, bins = numpy.histogram(image[mask], 256, normed=True)
    cdf = image_histogram.cumsum() # cumulative distribution function
    cdf = 255 * cdf / cdf[-1] # normalize

    # use linear interpolation of cdf to find new pixel values
    image_equalized = numpy.interp(image.flatten(), bins[:-1], cdf)
    image_equalized = image_equalized.reshape(image.shape)

    # normalized image to be returned is a composition of the original image
    # (background) and the equalized image (finger area)
    retval = image.copy()
    retval[mask] = image_equalized[mask]

André Anjos's avatar
André Anjos committed
451
    return retval
Pedro TOME's avatar
Pedro TOME committed
452

453

Pedro TOME's avatar
Pedro TOME committed
454
  def __circularGabor__(self, image, bw, sigma):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
455
456
    """
    Applies a circular gabor filter on the input image, with parameters.
André Anjos's avatar
André Anjos committed
457
458


Olegs NIKISINS's avatar
Olegs NIKISINS committed
459
    **Parameters:**
André Anjos's avatar
André Anjos committed
460

Olegs NIKISINS's avatar
Olegs NIKISINS committed
461
    image (numpy.ndarray): raw image to be filtered, as 2D array of
André Anjos's avatar
André Anjos committed
462
463
          unsigned 8-bit integers

Olegs NIKISINS's avatar
Olegs NIKISINS committed
464
    bw (float): bandwidth (1.12 octave)
André Anjos's avatar
André Anjos committed
465

Olegs NIKISINS's avatar
Olegs NIKISINS committed
466
    sigma (int): standard deviation (5  pixels)
André Anjos's avatar
André Anjos committed
467
468


Olegs NIKISINS's avatar
Olegs NIKISINS committed
469
    **Returns:**
André Anjos's avatar
André Anjos committed
470

Olegs NIKISINS's avatar
Olegs NIKISINS committed
471
    numpy.ndarray: normalized image as a 2D array of unsigned 8-bit integers
Pedro TOME's avatar
Pedro TOME committed
472
    """
André Anjos's avatar
André Anjos committed
473
474

    # Converts image to doubles
475
    image_new = bob.core.convert(image,numpy.float64,(0,1),(0,255))
Pedro TOME's avatar
Pedro TOME committed
476
    img_h, img_w = image_new.shape
477

Pedro TOME's avatar
Pedro TOME committed
478
479
480
481
    fc = (1/math.pi * math.sqrt(math.log(2)/2) * (2**bw+1)/(2**bw-1))/sigma

    sz = numpy.fix(8*numpy.max([sigma,sigma]))

André Anjos's avatar
André Anjos committed
482
    if numpy.mod(sz,2) == 0: sz = sz+1
483

André Anjos's avatar
André Anjos committed
484
    #Constructs filter kernel
Pedro TOME's avatar
Pedro TOME committed
485
    winsize = numpy.fix(sz/2)
486

Pedro TOME's avatar
Pedro TOME committed
487
488
489
490
491
492
493
494
495
496
    x = numpy.arange(-winsize, winsize+1)
    y = numpy.arange(winsize, numpy.fix(-sz/2)-1, -1)
    X, Y = numpy.meshgrid(x, y)
    # X (right +)
    # Y (up +)

    gaborfilter = numpy.exp(-0.5*(X**2/sigma**2+Y**2/sigma**2))*numpy.cos(2*math.pi*fc*numpy.sqrt(X**2+Y**2))*(1/(2*math.pi*sigma))

    # Without normalisation
    #gaborfilter = numpy.exp(-0.5*(X**2/sigma**2+Y**2/sigma**2))*numpy.cos(2*math.pi*fc*numpy.sqrt(X**2+Y**2))
497

498
    imageEnhance = utils.imfilter(image, gaborfilter)
Pedro TOME's avatar
Pedro TOME committed
499
    imageEnhance = numpy.abs(imageEnhance)
500

André Anjos's avatar
André Anjos committed
501
502
    return bob.core.convert(imageEnhance,numpy.uint8, (0,255),
        (imageEnhance.min(),imageEnhance.max()))
Pedro TOME's avatar
Pedro TOME committed
503

504

Pedro TOME's avatar
Pedro TOME committed
505
  def __HFE__(self,image):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
506
507
    """
    High Frequency Emphasis Filtering (HFE).
Pedro TOME's avatar
Pedro TOME committed
508
    """
André Anjos's avatar
André Anjos committed
509
510

    ### Hard-coded parameters for the HFE filtering
Pedro TOME's avatar
Pedro TOME committed
511
512
513
514
515
    D0 = 0.025
    a = 0.6
    b = 1.2
    n = 2.0

André Anjos's avatar
André Anjos committed
516
517
    # converts image to doubles
    image_new = bob.core.convert(image,numpy.float64, (0,1), (0,255))
Pedro TOME's avatar
Pedro TOME committed
518
    img_h, img_w = image_new.shape
519

Pedro TOME's avatar
Pedro TOME committed
520
    # DFT
521
522
    Ffreq = bob.sp.fftshift(bob.sp.fft(image_new.astype(numpy.complex128))/math.sqrt(img_h*img_w))

Pedro TOME's avatar
Pedro TOME committed
523
524
525
526
527
    row = numpy.arange(1,img_w+1)
    x = (numpy.tile(row,(img_h,1)) - (numpy.fix(img_w/2)+1)) /img_w
    col = numpy.arange(1,img_h+1)
    y =  (numpy.tile(col,(img_w,1)).T - (numpy.fix(img_h/2)+1))/img_h

André Anjos's avatar
André Anjos committed
528
529
    # D  is  the  distance  from  point  (u,v)  to  the  centre  of the
    # frequency rectangle.
Pedro TOME's avatar
Pedro TOME committed
530
    radius = numpy.sqrt(x**2 + y**2)
531

Pedro TOME's avatar
Pedro TOME committed
532
533
    f = a + b / (1.0 + (D0 / radius)**(2*n))
    Ffreq = Ffreq * f
534

André Anjos's avatar
André Anjos committed
535
536
    # implements the inverse DFT
    imageEnhance = bob.sp.ifft(bob.sp.ifftshift(Ffreq))
Pedro TOME's avatar
Pedro TOME committed
537

André Anjos's avatar
André Anjos committed
538
539
    # skips complex part
    imageEnhance = numpy.abs(imageEnhance)
540

André Anjos's avatar
André Anjos committed
541
542
543
    # renormalizes and returns
    return bob.core.convert(imageEnhance, numpy.uint8, (0, 255),
        (imageEnhance.min(), imageEnhance.max()))
Pedro TOME's avatar
Pedro TOME committed
544
545


André Anjos's avatar
André Anjos committed
546
  def __call__(self, image, annotations=None):
Olegs NIKISINS's avatar
Olegs NIKISINS committed
547
548
    """
    Reads the input image, extract the mask of the fingervein, postprocesses.
André Anjos's avatar
André Anjos committed
549
    """
550

551
552
553
    # 1. Pads the input image if any padding should be added
    image = numpy.pad(image, self.padding_width, 'constant',
        constant_values = self.padding_constant)
554
555

    ## Finger edges and contour extraction:
Pedro TOME's avatar
Pedro TOME committed
556
    if self.fingercontour == 'leemaskMatlab':
557
      mask, edges = self.__leemaskMatlab__(image) #for UTFVP
558
    elif self.fingercontour == 'leemaskMod':
559
      mask, edges = self.__leemaskMod__(image) #for VERA
560
    elif self.fingercontour == 'konomask':
561
      mask, edges = self.__konomask__(image, sigma=5)
562

Pedro TOME's avatar
Pedro TOME committed
563
    ## Finger region normalization:
564
    image_norm, mask_norm = self.__huangnormalization__(image, mask, edges)
Pedro TOME's avatar
Pedro TOME committed
565

566
    ## veins enhancement:
André Anjos's avatar
André Anjos committed
567
    if self.postprocessing == 'HE':
568
      image_norm = self.__HE__(image_norm, mask_norm)
André Anjos's avatar
André Anjos committed
569
570
571
572
    elif self.postprocessing == 'HFE':
      image_norm = self.__HFE__(image_norm)
    elif self.postprocessing == 'CircGabor':
      image_norm = self.__circularGabor__(image_norm, 1.12, 5)
573

André Anjos's avatar
André Anjos committed
574
    ## returns the normalized image and the finger mask
575
    return image_norm, mask_norm
Pedro TOME's avatar
Pedro TOME committed
576
577


André Anjos's avatar
André Anjos committed
578
579
  def write_data(self, data, filename):
    '''Overrides the default method implementation to handle our tuple'''
Pedro TOME's avatar
Pedro TOME committed
580

André Anjos's avatar
André Anjos committed
581
582
583
    f = bob.io.base.HDF5File(filename, 'w')
    f.set('image', data[0])
    f.set('finger_mask', data[1])
584

Pedro TOME's avatar
Pedro TOME committed
585

André Anjos's avatar
André Anjos committed
586
587
  def read_data(self, filename):
    '''Overrides the default method implementation to handle our tuple'''
588

André Anjos's avatar
André Anjos committed
589
    f = bob.io.base.HDF5File(filename, 'r')
Pedro TOME's avatar
Pedro TOME committed
590
    image = f.read('image')
591
592
    finger_mask = f.read('finger_mask')
    return (image, finger_mask)