Commit dbbb3a78 authored by André Anjos's avatar André Anjos 💬

Revamped Teo's code to simplify it; Add thorough tests

parent 413b80be
Pipeline #5184 failed with stages
in 3 minutes and 18 seconds
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
# Wed 20 July 14:43:22 CEST 2016
# Thu 03 Nov 2016 12:23:52 CET
"""Single sample API"""
"""
Verification API for bob.db.voxforge
"""
from bob.bio.base.database.file import BioFile
class VeinBioFile(BioFile):
def __init__(self, client_id, path, file_id):
"""
Initializes this File object with an File equivalent for
VoxForge database.
"""
super(VeinBioFile, self).__init__(client_id=client_id, path=path, file_id=file_id)
"""A "sample" object that is specific to vein recognition experiments
Parameters:
f (object): Low-level file (or sample) object that is kept inside
"""
def __init__(self, f):
super(VeinBioFile, self).__init__(
client_id=f.model_id,
path=f.path,
file_id=f.id,
)
# keep copy of original low-level database file object
self.f = f
......@@ -3,8 +3,36 @@
# Tue 27 Sep 2016 16:48:57 CEST
from .database import VeinBioFile
from bob.bio.base.database import BioDatabase
from bob.bio.base.database import BioFile, BioDatabase
from bob.bio.base.database.file import BioFile
class VerafingerBioFile(BioFile):
"""
Implements extra properties of vein files
Parameters:
f (object): Low-level file (or sample) object that is kept inside
"""
def __init__(self, f):
super(VerafingerBioFile, self).__init__(
client_id=f.model_id,
path=f.path,
file_id=f.id,
)
self.f = f
def roi(self):
"""Returns the binary mask from the ROI annotations available"""
points = self.f.roi()
class VerafingerBioDatabase(BioDatabase):
......@@ -45,4 +73,4 @@ class VerafingerBioDatabase(BioDatabase):
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)
return [VeinBioFile(client_id=f.model_id, path=f.path, file_id=f.id) for f in retval]
return [VeinBioFile(f) for f in retval]
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
"""Utilities for preprocessing vein imagery"""
import numpy
def assert_points(area, points):
"""Checks all points fall within the determined shape region, inclusively
This assertion function, test all points given in ``points`` fall within a
certain area provided in ``area``.
Parameters:
area (tuple): A tuple containing the size of the limiting area where the
points should all be in.
points (numpy.ndarray): A 2D numpy ndarray with any number of rows (points)
and 2 columns (representing ``y`` and ``x`` coordinates respectively), or
any type convertible to this format. This array contains the points that
will be checked for conformity. In case one of the points doesn't fall
into the determined area an assertion is raised.
Raises:
AssertionError: In case one of the input points does not fall within the
area defined.
"""
for k in points:
assert 0 <= k[0] < area[0] and 0 <= k[1] < area[1], \
"Point (%d, %d) is not inside the region determined by area " \
"(%d, %d)" % (k[0], k[1], area[0], area[1])
def fix_points(area, points):
"""Checks/fixes all points so they fall within the determined shape region
Points which are lying outside the determined area will be brought into the
area by moving the offending coordinate to the border of the said area.
Parameters:
area (tuple): A tuple containing the size of the limiting area where the
points should all be in.
points (numpy.ndarray): A 2D :py:class:`numpy.ndarray` with any number of
rows (points) and 2 columns (representing ``y`` and ``x`` coordinates
respectively), or any type convertible to this format. This array
contains the points that will be checked/fixed for conformity. In case
one of the points doesn't fall into the determined area, it is silently
corrected so it does.
Returns:
numpy.ndarray: A **new** array of points with corrected coordinates
"""
retval = numpy.array(points).copy()
retval[retval<0] = 0 #floor at 0 for both axes
y, x = retval[:,0], retval[:,1]
y[y>=area[0]] = area[0] - 1
x[x>=area[1]] = area[1] - 1
return retval
def poly_to_mask(shape, points):
"""Generates a binary mask from a set of 2D points
Parameters:
shape (tuple): A tuple containing the size of the output mask in height and
width, for Bob compatibility ``(y, x)``.
points (list): A list of tuples containing the polygon points that form a
region on the target mask. A line connecting these points will be drawn
and all the points in the mask that fall on or within the polygon line,
will be set to ``True``. All other points will have a value of ``False``.
Returns:
numpy.ndarray: A 2D numpy ndarray with ``dtype=bool`` with the mask
generated with the determined shape, using the points for the polygon.
"""
from PIL import Image, ImageDraw
# n.b.: PIL images are (x, y), while Bob shapes are represented in (y, x)!
mask = Image.new('L', (shape[1], shape[0]))
# coverts whatever comes in into a list of tuples for PIL
fixed = tuple(map(tuple, numpy.roll(fix_points(shape, points), 1, 1)))
# draws polygon
ImageDraw.Draw(mask).polygon(fixed, fill=255)
return numpy.array(mask, dtype=numpy.bool)
def mask_to_image(mask, dtype=numpy.uint8):
"""Converts a binary (boolean) mask into an integer or floating-point image
This function converts a boolean binary mask into an image of the desired
type by setting the points where ``False`` is set to 0 and points where
``True`` is set to the most adequate value taking into consideration the
destination data type ``dtype``. Here are support types and their ranges:
* numpy.uint8: ``[0, (2^8)-1]``
* numpy.uint16: ``[0, (2^16)-1]``
* numpy.uint32: ``[0, (2^32)-1]``
* numpy.uint64: ``[0, (2^64)-1]``
* numpy.float32: ``[0, 1.0]`` (fixed)
* numpy.float64: ``[0, 1.0]`` (fixed)
* numpy.float128: ``[0, 1.0]`` (fixed)
All other types are currently unsupported.
Parameters:
mask (numpy.ndarray): A 2D numpy ndarray with boolean data type, containing
the mask that will be converted into an image.
dtype (numpy.dtype): A valid numpy data-type from the list above for the
resulting image
Returns:
numpy.ndarray: With the designated data type, containing the binary image
formed from the mask.
Raises:
TypeError: If the type is not supported by this function
"""
dtype = numpy.dtype(dtype)
retval = mask.astype(dtype)
if dtype in (numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64):
retval[retval == 1] = numpy.iinfo(dtype).max
elif dtype in (numpy.float32, numpy.float64, numpy.float128):
pass
else:
raise TypeError("Data type %s is unsupported" % dtype)
return retval
from .utils import ManualRoiCut
from .utils import ConstructVeinImage
from .utils import NormalizeImageRotation
# gets sphinx autodoc done right - don't remove it
__all__ = [_ for _ in dir() if not _.startswith('_')]
This diff is collapsed.
11 91
8 322
76 320
114 307
140 300
176 292
225 292
269 288
330 287
405 288
436 290
456 288
468 276
473 242
472 208
470 184
466 146
455 116
440 93
424 77
397 69
358 64
298 60
247 52
201 38
160 25
130 7
106 7
81 16
46 46
22 71
......@@ -93,7 +93,7 @@ def test_finger_crop():
assert numpy.mean(numpy.abs(preproc - preproc_ref)) < 1.3e2
def test_miuramax():
def test_max_curvature():
#Maximum Curvature method against Matlab reference
......@@ -118,7 +118,7 @@ def test_miuramax():
assert numpy.mean(numpy.abs(output_img - output_img_ref)) < 8e-3
def test_miurarlt():
def test_repeated_line_tracking():
#Repeated Line Tracking method against Matlab reference
......@@ -143,7 +143,7 @@ def test_miurarlt():
assert numpy.mean(numpy.abs(output_img - output_img_ref)) < 0.5
def test_huangwl():
def test_wide_line_detector():
#Wide Line Detector method against Matlab reference
......@@ -187,67 +187,135 @@ def test_miura_match():
score_imp = MM.score(template_vein, probe_imp_vein)
assert numpy.isclose(score_imp, 0.172906739278421)
def test_manualRoiCut():
"""
Test ManualRoitCut
"""
from bob.bio.vein.preprocessor.utils import ManualRoiCut
image_path = F(('preprocessors', '0019_3_1_120509-160517.png'))
annotation_path = F(('preprocessors', '0019_3_1_120509-160517.txt'))
c = ManualRoiCut(annotation_path, image_path)
mask_1 = c.roi_mask()
image_1 = c.roi_image()
# create mask using size:
c = ManualRoiCut(annotation_path, sizes=(672,380))
mask_2 = c.roi_mask()
# loading image:
image = bob.io.base.load(image_path)
c = ManualRoiCut(annotation_path, image)
mask_3 = c.roi_mask()
image_3 = c.roi_image()
# load text file:
with open(annotation_path,'r') as f:
retval = numpy.loadtxt(f, ndmin=2)
# carefully -- this is BOB format --- (x,y)
annotation = list([tuple([k[0], k[1]]) for k in retval])
c = ManualRoiCut(annotation, image)
mask_4 = c.roi_mask()
image_4 = c.roi_image()
assert (mask_1 == mask_2).all()
assert (mask_1 == mask_3).all()
assert (mask_1 == mask_4).all()
assert (image_1 == image_3).all()
assert (image_1 == image_4).all()
def test_ConstructAnnotations():
"""
Test ConstructAnnotations preprocessor
"""
image_filename = F( ( 'preprocessors', 'ConstructAnnotations.png' ) )
roi_annotations_filename = F( ( 'preprocessors', 'ConstructAnnotations.txt' ) )
vein_annotations_filename = F( ( 'preprocessors', 'ConstructAnnotations.npy' ) )
image = bob.io.base.load( image_filename )
roi_annotations = np.loadtxt(roi_annotations_filename, dtype='uint16')
roi_annotations = [tuple([point[0], point[1]]) for point in roi_annotations]
fp = open(vein_annotations_filename, 'rb')
vein_annotations = np.load(fp)
vein_annotations = vein_annotations['arr_0'].tolist()
fp.close()
vein_annotations = [[tuple([point[0], point[1]]) for point in line] for line in vein_annotations]
annotation_dictionary = {"image" : image, "roi_annotations" : roi_annotations, "vein_annotations" : vein_annotations}
from bob.bio.vein.preprocessor.utils import ConstructVeinImage
from bob.bio.vein.preprocessor.utils import NormalizeImageRotation
output = ConstructVeinImage(annotation_dictionary, center = True)
output = NormalizeImageRotation(output, dark_lines = False)
assert np.array_equal(output, image)
def test_assert_points():
# Tests that point assertion works as expected
from ..preprocessor import utils
area = (10, 5)
inside = [(0,0), (3,2), (9, 4)]
utils.assert_points(area, inside) #should not raise
def _check_outside(point):
# should raise, otherwise it is an error
try:
utils.assert_points(area, [point])
except AssertionError as e:
assert str(point) in str(e)
else:
raise AssertionError("Did not assert %s is outside of %s" % (point, area))
outside = [(-1, 0), (10, 0), (0, 5), (10, 5), (15,12)]
for k in outside: _check_outside(k)
def test_fix_points():
# Tests that point clipping works as expected
from ..preprocessor import utils
area = (10, 5)
inside = [(0,0), (3,2), (9, 4)]
fixed = utils.fix_points(area, inside)
assert numpy.array_equal(inside, fixed), '%r != %r' % (inside, fixed)
fixed = utils.fix_points(area, [(-1, 0)])
assert numpy.array_equal(fixed, [(0, 0)])
fixed = utils.fix_points(area, [(10, 0)])
assert numpy.array_equal(fixed, [(9, 0)])
fixed = utils.fix_points(area, [(0, 5)])
assert numpy.array_equal(fixed, [(0, 4)])
fixed = utils.fix_points(area, [(10, 5)])
assert numpy.array_equal(fixed, [(9, 4)])
fixed = utils.fix_points(area, [(15, 12)])
assert numpy.array_equal(fixed, [(9, 4)])
def test_poly_to_mask():
# Tests we can generate a mask out of a polygon correctly
from ..preprocessor import utils
area = (10, 9) #10 rows, 9 columns
polygon = [(2, 2), (2, 7), (7, 7), (7, 2)] #square shape, (y, x) format
mask = utils.poly_to_mask(area, polygon)
nose.tools.eq_(mask.dtype, numpy.bool)
# This should be the output:
expected = numpy.array([
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, True, True, True, True, True, True, False],
[False, False, True, True, True, True, True, True, False],
[False, False, True, True, True, True, True, True, False],
[False, False, True, True, True, True, True, True, False],
[False, False, True, True, True, True, True, True, False],
[False, False, True, True, True, True, True, True, False],
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
])
assert numpy.array_equal(mask, expected)
polygon = [(3, 2), (5, 7), (8, 7), (7, 3)] #trapezoid, (y, x) format
mask = utils.poly_to_mask(area, polygon)
nose.tools.eq_(mask.dtype, numpy.bool)
# This should be the output:
expected = numpy.array([
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False],
[False, False, True, False, False, False, False, False, False],
[False, False, True, True, True, False, False, False, False],
[False, False, False, True, True, True, True, True, False],
[False, False, False, True, True, True, True, True, False],
[False, False, False, True, True, True, True, True, False],
[False, False, False, False, False, False, False, True, False],
[False, False, False, False, False, False, False, False, False],
])
assert numpy.array_equal(mask, expected)
def test_mask_to_image():
# Tests we can correctly convert a boolean array into an image
# that makes sense according to the data types
from ..preprocessor import utils
sample = numpy.array([False, True])
nose.tools.eq_(sample.dtype, numpy.bool)
def _check_uint(n):
conv = utils.mask_to_image(sample, 'uint%d' % n)
nose.tools.eq_(conv.dtype, getattr(numpy, 'uint%d' % n))
target = [0, (2**n)-1]
assert numpy.array_equal(conv, target), '%r != %r' % (conv, target)
_check_uint(8)
_check_uint(16)
_check_uint(32)
_check_uint(64)
def _check_float(n):
conv = utils.mask_to_image(sample, 'float%d' % n)
nose.tools.eq_(conv.dtype, getattr(numpy, 'float%d' % n))
assert numpy.array_equal(conv, [0, 1.0]), '%r != %r' % (conv, target)
_check_float(32)
_check_float(64)
_check_float(128)
# This should be unsupported
try:
conv = utils.mask_to_image(sample, 'int16')
except TypeError as e:
assert 'int16' in str(e)
else:
raise AssertionError('Conversion to int16 did not trigger a TypeError')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment