Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
bob.bio.vein
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
bob
bob.bio.vein
Commits
b9af7a82
Commit
b9af7a82
authored
Jun 30, 2017
by
André Anjos
💬
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-estructured Preprocessor to simplify experimentation and configuration
parent
a0aed3f5
Changes
11
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
880 additions
and
64 deletions
+880
-64
bob/bio/vein/configurations/maximum_curvature.py
bob/bio/vein/configurations/maximum_curvature.py
+17
-2
bob/bio/vein/configurations/repeated_line_tracking.py
bob/bio/vein/configurations/repeated_line_tracking.py
+17
-2
bob/bio/vein/configurations/wide_line_detector.py
bob/bio/vein/configurations/wide_line_detector.py
+17
-2
bob/bio/vein/database/__init__.py
bob/bio/vein/database/__init__.py
+23
-0
bob/bio/vein/database/fv3d.py
bob/bio/vein/database/fv3d.py
+55
-42
bob/bio/vein/database/utfvp.py
bob/bio/vein/database/utfvp.py
+1
-1
bob/bio/vein/database/verafinger.py
bob/bio/vein/database/verafinger.py
+14
-14
bob/bio/vein/preprocessor/__init__.py
bob/bio/vein/preprocessor/__init__.py
+34
-1
bob/bio/vein/preprocessor/mask.py
bob/bio/vein/preprocessor/mask.py
+430
-0
bob/bio/vein/preprocessor/normalize.py
bob/bio/vein/preprocessor/normalize.py
+185
-0
bob/bio/vein/preprocessor/preprocessor.py
bob/bio/vein/preprocessor/preprocessor.py
+87
-0
No files found.
bob/bio/vein/configurations/maximum_curvature.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/configurations/repeated_line_tracking.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/configurations/wide_line_detector.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/database/__init__.py
View file @
b9af7a82
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
'''Database definitions for Vein Recognition'''
import
numpy
class
AnnotatedArray
(
numpy
.
ndarray
):
"""Defines a numpy array subclass that can carry its own metadata
Copied from: https://docs.scipy.org/doc/numpy-1.12.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array
"""
def
__new__
(
cls
,
input_array
,
metadata
=
None
):
obj
=
numpy
.
asarray
(
input_array
).
view
(
cls
)
obj
.
metadata
=
metadata
if
metadata
is
not
None
else
dict
()
return
obj
def
__array_finalize__
(
self
,
obj
):
if
obj
is
None
:
return
self
.
metadata
=
getattr
(
obj
,
'metadata'
,
dict
())
bob/bio/vein/database/fv3d.py
View file @
b9af7a82
...
...
@@ -4,8 +4,11 @@
import
numpy
from
bob.bio.base.database
import
BioFile
,
BioDatabase
from
.
import
AnnotatedArray
class
File
(
BioFile
):
"""
...
...
@@ -25,13 +28,23 @@ class File(BioFile):
self
.
__f
=
f
def
mask
(
self
,
shape
):
"""Returns the binary mask from the ROI annotations available"""
return
poly_to_mask
(
shape
,
self
.
__f
.
roi
())
def
load
(
self
,
*
args
,
**
kwargs
):
"""(Overrides base method) Loads both image and mask"""
image
=
super
(
File
,
self
).
load
(
*
args
,
**
kwargs
)
roi
=
self
.
__f
.
roi
()
# calculates the 90 degrees anti-clockwise rotated RoI points
h
,
w
=
image
.
shape
roi
=
[(
x
,
-
y
+
h
)
for
k
in
roi
]
# image is upside, whereas this package requires fingers to be horizontal
return
numpy
.
rot90
(
image
)
return
ImageWithAnnotation
(
numpy
.
rot90
(
image
),
metadata
=
dict
(
mask
=
roi
))
class
Database
(
BioDatabase
):
...
...
bob/bio/vein/database/utfvp.py
View file @
b9af7a82
bob/bio/vein/database/verafinger.py
View file @
b9af7a82
...
...
@@ -5,6 +5,8 @@
from
bob.bio.base.database
import
BioFile
,
BioDatabase
from
.
import
AnnotatedArray
class
File
(
BioFile
):
"""
...
...
@@ -23,20 +25,14 @@ class File(BioFile):
file_id
=
f
.
id
)
self
.
__f
=
f
def
mask
(
self
):
"""Returns the binary mask from the ROI annotations available"""
from
..preprocessor.utils
import
poly_to_mask
# The size of images in this database is (250, 665) pixels (h, w)
return
poly_to_mask
((
250
,
665
),
self
.
__f
.
roi
())
def
load
(
self
,
*
args
,
**
kwargs
):
"""(Overrides base method) Loads both image and mask"""
image
=
super
(
File
,
self
).
load
(
*
args
,
**
kwargs
)
return
image
,
self
.
mask
()
roi
=
self
.
__f
.
roi
()
mask
=
poly_to_mask
(
image
.
shape
,
roi
)
return
AnnotatedArray
(
image
,
metadata
=
dict
(
mask
=
mask
,
roi
=
roi
))
class
Database
(
BioDatabase
):
...
...
@@ -63,21 +59,25 @@ class Database(BioDatabase):
return
self
.
_db
.
finger_name_from_model_id
(
model_id
)
def
model_ids_with_protocol
(
self
,
groups
=
None
,
protocol
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
return
self
.
_db
.
model_ids
(
groups
=
groups
,
protocol
=
protocol
)
def
objects
(
self
,
groups
=
None
,
protocol
=
None
,
purposes
=
None
,
model_ids
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
retval
=
self
.
_db
.
objects
(
groups
=
groups
,
protocol
=
protocol
,
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
return
[
File
(
f
)
for
f
in
retval
]
def
annotations
(
self
,
file
):
return
None
bob/bio/vein/preprocessor/__init__.py
View file @
b9af7a82
from
.FingerCrop
import
FingerCrop
from
.mask
import
Padder
,
Masker
,
NoMask
,
AnnotatedRoIMask
from
.mask
import
KonoMask
,
LeeMask
,
TomesLeeMask
from
.normalize
import
Normalizer
,
NoNormalization
,
HuangNormalization
from
.filters
import
Filter
,
NoFilter
,
HistogramEqualization
from
.preprocessor
import
Preprocessor
# gets sphinx autodoc done right - don't remove it
def
__appropriate__
(
*
args
):
"""Says object was actually declared here, an not on the import module.
Parameters:
*args: An iterable of objects to modify
Resolves `Sphinx referencing issues
<https://github.com/sphinx-doc/sphinx/issues/3048>`
"""
for
obj
in
args
:
obj
.
__module__
=
__name__
__appropriate__
(
Padder
,
Masker
,
NoMask
,
AnnotatedRoIMask
,
KonoMask
,
LeeMask
,
TomesLeeMask
,
Normalizer
,
NoNormalization
,
HuangNormalization
,
Filter
,
NoFilter
,
HistogramEqualization
,
Preprocessor
,
)
__all__
=
[
_
for
_
in
dir
()
if
not
_
.
startswith
(
'_'
)]
bob/bio/vein/preprocessor/mask.py
0 → 100644
View file @
b9af7a82
This diff is collapsed.
Click to expand it.
bob/bio/vein/preprocessor/normalize.py
0 → 100644
View file @
b9af7a82
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
'''Base utilities for normalization'''
import
math
import
numpy
from
PIL
import
Image
class
Normalizer
(
object
):
'''Objects of this class normalize the input image orientation and scale'''
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
,
mask
):
'''Inputs image and mask and outputs a normalized version of those
Parameters:
image (numpy.ndarray): raw image to normalize as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the newly aligned image.
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input mask representing the newly aligned mask.
'''
raise
NotImplemented
(
'You must implement the __call__ slot'
)
class
NoNormalization
(
Normalizer
):
'''Trivial implementation with no normalization'''
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
,
mask
):
'''Returns the input parameters, without changing them
Parameters:
image (numpy.ndarray): raw image to normalize as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the newly aligned image.
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input mask representing the newly aligned mask.
'''
return
image
,
mask
class
HuangNormalization
(
Normalizer
):
'''Simple finger normalization from Huang et. al
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.
'''
def
__init__
(
self
,
padding_width
=
5
,
padding_constant
=
51
):
self
.
padding_width
=
padding_width
self
.
padding_constant
=
padding_constant
def
__call__
(
self
,
image
,
mask
):
'''Inputs image and mask and outputs a normalized version of those
Parameters:
image (numpy.ndarray): raw image to normalize as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the newly aligned image.
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input mask representing the newly aligned mask.
'''
img_h
,
img_w
=
image
.
shape
# Calculates the mask edges along the columns
edges
=
numpy
.
zeros
((
2
,
mask
.
shape
[
1
]),
dtype
=
int
)
edges
[
0
,:]
=
mask
.
argmax
(
axis
=
0
)
# get upper edges
edges
[
1
,:]
=
len
(
mask
)
-
numpy
.
flipud
(
mask
).
argmax
(
axis
=
0
)
-
1
bl
=
edges
.
mean
(
axis
=
0
)
#baseline
x
=
numpy
.
arange
(
0
,
edges
.
shape
[
1
])
A
=
numpy
.
vstack
([
x
,
numpy
.
ones
(
len
(
x
))]).
T
# Fit a straight line through the base line points
w
=
numpy
.
linalg
.
lstsq
(
A
,
bl
)[
0
]
# obtaining the parameters
angle
=
-
1
*
math
.
atan
(
w
[
0
])
# Rotation
tr
=
img_h
/
2
-
w
[
1
]
# Translation
scale
=
1.0
# Scale
#Affine transformation parameters
sx
=
sy
=
scale
cosine
=
math
.
cos
(
angle
)
sine
=
math
.
sin
(
angle
)
a
=
cosine
/
sx
b
=
-
sine
/
sy
#b = sine/sx
c
=
0
#Translation in x
d
=
sine
/
sx
e
=
cosine
/
sy
f
=
tr
#Translation in y
#d = -sine/sy
#e = cosine/sy
#f = 0
g
=
0
h
=
0
#h=tr
i
=
1
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
])
def
_afftrans
(
img
):
'''Applies the affine transform on the resulting image'''
t
=
Image
.
fromarray
(
img
.
astype
(
'uint8'
))
w
,
h
=
t
.
size
#pillow image is encoded w, h
w
+=
2
*
self
.
padding_width
h
+=
2
*
self
.
padding_width
t
=
t
.
transform
(
(
w
,
h
),
Image
.
AFFINE
,
Tinvtuple
,
resample
=
Image
.
BICUBIC
,
fill
=
self
.
padding_constant
)
return
numpy
.
array
(
t
).
astype
(
img
.
dtype
)
return
_afftrans
(
image
),
_afftrans
(
mask
)
bob/bio/vein/preprocessor/preprocessor.py
0 → 100644
View file @
b9af7a82
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
import
bob.io.base
from
bob.bio.base.preprocessor
import
Preprocessor
as
BasePreprocessor
class
Preprocessor
(
BasePreprocessor
):
"""
Extracts the mask and pre-processes fingervein images.
In this implementation, the finger image is (in this order):
#. The mask is expolated from the image using one of our
:py:class:`Masker`'s concrete implementations
#. The image is normalized with one of our :py:class:`Normalizer`'s
#. The image is filtered with one of our :py:class:`Filter`'s
Parameters:
mask (:py:class:`Masker`): An object representing a Masker instance which
will extrapolate the mask from the input image.
normalize (:py:class:`Normalizer`): An object representing a Normalizer
instance which will normalize the input image and its mask returning a
new image mask pair.
filter (:py:class:`Filter`): An object representing a Filter instance will
will filter the input image and return a new filtered image. The filter
instance also receives the extrapolated mask so it can, if desired, only
apply the filtering operation where the mask has a value of ``True``
"""
def
__init__
(
self
,
mask
,
normalize
,
filter
,
**
kwargs
):
BasePreprocessor
.
__init__
(
self
,
mask
=
mask
,
normalize
=
normalize
,
filter
=
filter
,
**
kwargs
)
self
.
mask
=
mask
self
.
normalize
=
normalize
self
.
filter
=
filter
def
__call__
(
self
,
data
,
annotations
=
None
):
"""Reads the input image or (image, mask) and prepares for fex.
Parameters:
data (numpy.ndarray): An 2D numpy array containing a gray-scaled image
with dtype ``uint8``. The image maybe annotated with an RoI.
Returns:
numpy.ndarray: The image, preprocessed and normalized
numpy.ndarray: A mask, of the same size of the image, indicating where
the valid data for the object is.
"""
mask
=
self
.
mask
(
data
)
data
,
mask
=
self
.
normalize
(
data
,
mask
)
data
=
self
.
filter
(
data
,
mask
)
return
data
,
mask
def
write_data
(
self
,
data
,
filename
):
'''Overrides the default method implementation to handle our tuple'''
f
=
bob
.
io
.
base
.
HDF5File
(
filename
,
'w'
)
f
.
set
(
'image'
,
data
[
0
])
f
.
set
(
'mask'
,
data
[
1
])
def
read_data
(
self
,
filename
):
'''Overrides the default method implementation to handle our tuple'''
f
=
bob
.
io
.
base
.
HDF5File
(
filename
,
'r'
)
return
f
.
read
(
'image'
),
f
.
read
(
'mask'
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment