Commit f934dbf1 authored by André Anjos's avatar André Anjos 💬
Browse files

Move package name, advance

parent 2c8971c0
......@@ -18,4 +18,4 @@ build
*.egg
src/
db.sql3
bob/db/3dfv/data/
bob/db/fv3d/data/
......@@ -2,17 +2,17 @@
.. Fri 02 Dec 2016 11:41:17 CET
.. image:: http://img.shields.io/badge/docs-stable-yellow.png
:target: http://pythonhosted.org/bob.db.3dfv/index.html
:target: http://pythonhosted.org/bob.db.fv3d/index.html
.. image:: http://img.shields.io/badge/docs-latest-orange.png
:target: https://www.idiap.ch/software/bob/docs/latest/bob/bob.db.3dfv/master/index.html
.. image:: https://gitlab.idiap.ch/bob/bob.db.3dfv/badges/master/build.svg
:target: https://gitlab.idiap.ch/bob/bob.db.3dfv/commits/master
:target: https://www.idiap.ch/software/bob/docs/latest/bob/bob.db.fv3d/master/index.html
.. image:: https://gitlab.idiap.ch/bob/bob.db.fv3d/badges/master/build.svg
:target: https://gitlab.idiap.ch/bob/bob.db.fv3d/commits/master
.. image:: https://img.shields.io/badge/gitlab-project-0000c0.svg
:target: https://gitlab.idiap.ch/bob/bob.db.3dfv
.. image:: http://img.shields.io/pypi/v/bob.db.3dfv.png
:target: https://pypi.python.org/pypi/bob.db.3dfv
.. image:: http://img.shields.io/pypi/dm/bob.db.3dfv.png
:target: https://pypi.python.org/pypi/bob.db.3dfv
:target: https://gitlab.idiap.ch/bob/bob.db.fv3d
.. image:: http://img.shields.io/pypi/v/bob.db.fv3d.png
:target: https://pypi.python.org/pypi/bob.db.fv3d
.. image:: http://img.shields.io/pypi/dm/bob.db.fv3d.png
:target: https://pypi.python.org/pypi/bob.db.fv3d
==========================================
......
......@@ -15,5 +15,30 @@ def get_config():
return bob.extension.get_config(__name__)
# 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__(
Database,
Client,
Finger,
File,
Protocol,
Model,
Probe,
)
# gets sphinx autodoc done right - don't remove it
__all__ = [_ for _ in dir() if not _.startswith('_')]
......@@ -70,7 +70,7 @@ class Interface(BaseInterface):
def name(self):
return '3dfv'
return 'fv3d'
def version(self):
......
......@@ -174,7 +174,7 @@ class File(Base, bob.db.base.File):
Filenames inside the 3D Fingervein are like these:
<session>/<attempt>/<client>-<age>-<gender><skin><occ><side><finger><session><attempt><snap><cam>>png
<client>/<session>/<attempt>/<client>-<age>-<gender><skin><occ><side><finger><session><attempt><snap><cam>.png
The fields can have these values:
......@@ -323,6 +323,8 @@ class Model(Base):
protocol_id = Column(Integer, ForeignKey('protocol.id'))
protocol = relationship("Protocol", backref=backref("models", order_by=id))
UniqueConstraint('name', 'protocol', name='name_protocol')
def __init__(self, name, group, finger, protocol):
self.name = name
......
......@@ -8,6 +8,9 @@ from .models import *
from .driver import Interface
from sqlalchemy import and_, not_
import bob.core
logger = bob.core.log.setup(__name__)
import bob.db.base
SQLITE_FILE = Interface().files()[0]
......@@ -39,7 +42,7 @@ class Database(bob.db.base.SQLiteDatabase):
def groups(self):
"""Returns a list of all supported groups"""
return Model.group_choices
return ('train', 'dev', 'eval')
def genders(self):
......@@ -54,6 +57,12 @@ class Database(bob.db.base.SQLiteDatabase):
return Finger.side_choices
def fingers(self):
"""Returns a list of all supported finger values"""
return Finger.name_choices
def sessions(self):
"""Returns a list of all supported session values"""
......@@ -63,11 +72,11 @@ class Database(bob.db.base.SQLiteDatabase):
def finger_name_from_model_id(self, model_id):
"""Returns the unique finger name in the database given a ``model_id``"""
return self.query(File).filter(File.model_id==model_id).one().unique_finger_name
return self.query(File).filter(File.model_id==model_id).one().unique_name
def model_ids(self, protocol=None, groups=None):
"""Returns a set of models for a given protocol/group
"""Returns a set of models identifiers for a given protocol/group
Parameters:
......@@ -76,9 +85,8 @@ class Database(bob.db.base.SQLiteDatabase):
groups (:py:class:`str`, :py:class:`list`, optional): One or more of the
supported groups. If not set, returns data from all groups. Notice this
parameter should either not set or set to ``dev``. Otherwise, this
method will return an empty list given we don't have a test set, only a
development set.
parameter should either not set or set to ``dev``, ``eval`` or an
iterator that yields both.
Returns:
......@@ -88,43 +96,41 @@ class Database(bob.db.base.SQLiteDatabase):
"""
protocols = None
if protocol:
valid_protocols = self.protocol_names()
protocols = self.check_parameters_for_validity(protocol, "protocol",
valid_protocols)
if 'train' in groups:
# there are no models in the training set
if len(groups) == 1: return [] #only group required, so return empty
groups = tuple(k for k in groups if k != 'train')
if groups:
valid_groups = self.groups()
groups = self.check_parameters_for_validity(groups, "group",
valid_groups)
valid_protocols = self.protocol_names()
protocols = self.check_parameters_for_validity(protocol, "protocol",
valid_protocols)
retval = self.query(File)
valid_groups = Model.group_choices
groups = self.check_parameters_for_validity(groups, "group",
valid_groups)
joins = []
filters = []
retval = self.query(Model).join(Protocol)
retval = retval.filter(Protocol.name.in_(protocols))
subquery = self.query(Subset)
subfilters = []
if groups:
filters.append(Model.group.in_(groups))
if protocols:
subquery = subquery.join(Protocol)
subfilters.append(Protocol.name.in_(protocols))
retval = retval.filter(*filters).distinct().order_by('id')
if groups: subfilters.append(Subset.group.in_(groups))
return [k.id for k in retval]
subfilters.append(Subset.purpose == 'enroll')
subsets = subquery.filter(*subfilters)
filters.append(File.subsets.any(Subset.id.in_([k.id for k in subsets])))
def _train_objects(self, protocols, genders, sides, fingers, sessions):
"""Returns a query that yields objects related to training
retval = retval.join(*joins).filter(*filters).distinct().order_by('id')
This is a private function, input parameters are assumed pre-checked
"""
return sorted(set([k.model_id for k in retval.distinct()]))
return query
def objects(self, protocol=None, groups=None, purposes=None,
model_ids=None, genders=None, sides=None, sessions=None):
model_ids=None, genders=None, sides=None, fingers=None, sessions=None):
"""Returns objects filtered by criteria
......@@ -148,6 +154,9 @@ class Database(bob.db.base.SQLiteDatabase):
sides (:py:class:`str`, :py:class:`list`, optional): If set, limit output
using the provided side identifier
fingers (:py:class:`str`, :py:class:`list`, optional): If set, limit
output using the provided finger identifiers
sessions (:py:class:`str`, :py:class:`list`, optional): If set, limit
output using the provided session identifiers
......@@ -159,20 +168,26 @@ class Database(bob.db.base.SQLiteDatabase):
"""
protocols = None
if protocol:
valid_protocols = self.protocol_names()
protocols = self.check_parameters_for_validity(protocol, "protocol",
valid_protocols)
valid_protocols = self.protocol_names()
protocols = self.check_parameters_for_validity(protocol, "protocol",
valid_protocols)
if groups:
valid_groups = self.groups()
groups = self.check_parameters_for_validity(groups, "group", valid_groups)
valid_groups = self.groups()
groups = self.check_parameters_for_validity(groups, "group", valid_groups)
if purposes:
valid_purposes = self.purposes()
purposes = self.check_parameters_for_validity(purposes, "purpose",
valid_purposes)
valid_purposes = self.purposes()
purposes = self.check_parameters_for_validity(purposes, "purpose",
valid_purposes)
# cleans up groups and purposes to solve for the minimum
if ('train' in purposes and not ('train' in groups)):
purposes = tuple(k for k in purposes if k != 'train')
if ('train' in groups and not ('train' in purposes)):
groups = tuple(k for k in groups if k != 'train')
if ('enroll' in purposes or 'probe' in purposes) and not 'dev' in groups:
purposes = tuple(k for k in purposes if k not in ['enroll', 'probe'])
if 'dev' in groups and not ('enroll' in purposes or 'probe' in purposes):
groups = tuple(k for k in groups if k != 'dev')
# if only asking for 'probes', then ignore model_ids as all of our
# protocols do a full probe-model scan
......@@ -184,57 +199,59 @@ class Database(bob.db.base.SQLiteDatabase):
model_ids = self.check_parameters_for_validity(model_ids, "model_ids",
valid_model_ids)
if genders:
valid_genders = self.genders()
genders = self.check_parameters_for_validity(genders, "genders",
valid_genders)
if sides:
valid_sides = self.sides()
sides = self.check_parameters_for_validity(sides, "sides", valid_sides)
if sessions:
valid_sessions = self.sessions()
sessions = self.check_parameters_for_validity(sessions, "sessions",
valid_sessions)
retval = self.query(File)
joins = []
filters = []
if protocols or groups or purposes:
subquery = self.query(Subset)
subfilters = []
if protocols:
subquery = subquery.join(Protocol)
subfilters.append(Protocol.name.in_(protocols))
if groups: subfilters.append(Subset.group.in_(groups))
if purposes: subfilters.append(Subset.purpose.in_(purposes))
subsets = subquery.filter(*subfilters)
filters.append(File.subsets.any(Subset.id.in_([k.id for k in subsets])))
if genders or sides:
joins.append(Finger)
if genders:
fingers = self.query(Finger).join(Client).filter(Client.gender.in_(genders))
filters.append(Finger.id.in_([k.id for k in fingers]))
if sides:
filters.append(Finger.side.in_(sides))
if sessions:
filters.append(File.session.in_(sessions))
if model_ids:
filters.append(File.model_id.in_(model_ids))
retval = retval.join(*joins).filter(*filters).distinct().order_by('id')
return list(retval)
valid_genders = self.genders()
genders = self.check_parameters_for_validity(genders, "genders",
valid_genders)
valid_fingers = self.fingers()
fingers = self.check_parameters_for_validity(fingers, "fingers",
valid_fingers)
valid_sides = self.sides()
sides = self.check_parameters_for_validity(sides, "sides", valid_sides)
valid_sessions = self.sessions()
sessions = self.check_parameters_for_validity(sessions, "sessions",
valid_sessions)
# this database contains 3 sets of "files" for each protocol: the ones
# related to the training_set, models and probes related to both dev and
# eval groups. These file lists don't overlap
retval = None
if 'train' in purposes:
q = self.query(File).join(Protocol.training_set)
q = q.join(Finger).join(Client)
q = q.filter(Protocol.name.in_(protocols))
q = q.filter(Client.gender.in_(genders))
q = q.filter(Finger.side.in_(sides))
q = q.filter(Finger.name.in_(fingers))
q = q.filter(File.session.in_(sessions))
retval = q
if 'enroll' in purposes:
q = self.query(File).join(Model.files)
q = q.join(Finger).join(Client)
q = q.filter(Model.group.in_(groups))
if model_ids: q = q.filter(Model.id.in_(model_ids))
#q = q.filter(Protocol.name.in_(protocols))
q = q.filter(Client.gender.in_(genders))
q = q.filter(Finger.side.in_(sides))
q = q.filter(Finger.name.in_(fingers))
q = q.filter(File.session.in_(sessions))
retval = q if retval is None else retval.union(q)
if 'probe' in purposes:
q = self.query(File).join(Probe.file).join(Model.protocol)
q = q.join(Finger).join(Client)
q = q.filter(Probe.group.in_(groups))
q = q.filter(File.protocol.name.in_(protocols))
q = q.filter(Client.gender.in_(genders))
q = q.filter(Finger.side.in_(sides))
q = q.filter(Finger.name.in_(fingers))
q = q.filter(File.session.in_(sessions))
retval = q if retval is None else retval.union(q)
# combine all queries, sort and uniq'fy
return list(retval.distinct().order_by('id'))
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
"""A few checks at the VERA Fingervein database.
"""A few checks at the 3D Fingervein database.
"""
import os
import numpy
from . import Database
from .query import Database
import nose.tools
from nose.plugins.skip import SkipTest
# base directories where the VERA files sit
DATABASE_PATH = "/idiap/project/vera/databases/VERA-fingervein"
def sql3_available(test):
"""Decorator for detecting if the sql3 file is available"""
def metadata_available(test):
"""Decorator for detecting if the metadata is available"""
from bob.io.base.test_utils import datafile
from nose.plugins.skip import SkipTest
......@@ -29,7 +26,7 @@ def sql3_available(test):
if os.path.exists(dbfile):
return test(*args, **kwargs)
else:
raise SkipTest("The interface SQL file (%s) is not available; did you forget to run 'bob_dbmanage.py %s create' ?" % (dbfile, 'vera'))
raise SkipTest("The interface SQL file (%s) is not available; did you forget to run 'bob_dbmanage.py %s create' ?" % (dbfile, 'fv3d'))
return wrapper
......@@ -51,24 +48,38 @@ def db_available(test):
return wrapper
@sql3_available
@metadata_available
def test_recreate():
from bob.db.base.script.dbmanage import main
nose.tools.eq_(main('fv3d create --recreate'.split()), None)
@metadata_available
def test_counts():
# test whether the correct number of clients is returned
db = Database()
nose.tools.eq_(db.groups(), ('train', 'dev'))
nose.tools.eq_(db.groups(), ('train', 'dev', 'eval'))
protocols = db.protocol_names()
nose.tools.eq_(len(protocols), 4)
assert 'Nom' in protocols
assert 'Full' in protocols
assert 'Fifty' in protocols
assert 'B' in protocols
nose.tools.eq_(len(protocols), 1)
assert 'central' in protocols
nose.tools.eq_(db.purposes(), ('train', 'enroll', 'probe'))
nose.tools.eq_(db.genders(), ('M', 'F'))
nose.tools.eq_(db.sides(), ('L', 'R'))
nose.tools.eq_(db.genders(), ('m', 'f'))
nose.tools.eq_(db.sides(), ('l', 'r'))
nose.tools.eq_(db.fingers(), ('t', 'i', 'm', 'r', 'l'))
# FDV: 89 subjects * 2 fingers * 5 snapshots * 1 attempt = 890
# IDI: 2 subjects * 6 fingers * 2 snapshots = 48
# Total: 938 images
nose.tools.eq_(len(db.objects(protocol='central', groups='train')), 938)
# IDI: 50 subjects * 6 fingers * 2 snapshots * 2 attempts = 1200 images
nose.tools.eq_(len(db.objects(protocol='central', groups='dev',
purposes='enroll')), 1200)
# test model ids
model_ids = db.model_ids()
......@@ -124,17 +135,19 @@ def test_counts():
purposes='probe', model_ids=model_ids[0])), 440)
@sql3_available
@nose.tools.nottest
@metadata_available
def test_driver_api():
from bob.db.base.script.dbmanage import main
nose.tools.eq_(main('verafinger dumplist --self-test'.split()), 0)
nose.tools.eq_(main('verafinger dumplist --protocol=Full --group=dev --purpose=enroll --model=101_L_1 --self-test'.split()), 0)
nose.tools.eq_(main('verafinger checkfiles --self-test'.split()), 0)
nose.tools.eq_(main('fv3d dumplist --self-test'.split()), 0)
nose.tools.eq_(main('fv3d dumplist --protocol=Full --group=dev --purpose=enroll --model=101_L_1 --self-test'.split()), 0)
nose.tools.eq_(main('fv3d checkfiles --self-test'.split()), 0)
@sql3_available
@nose.tools.nottest
@metadata_available
@db_available
def test_load():
......@@ -149,32 +162,8 @@ def test_load():
nose.tools.eq_(image.dtype, numpy.uint8)
@sql3_available
@db_available
def test_annotations():
db = Database()
for f in db.objects():
# loads an image from the database
image = f.load(DATABASE_PATH)
roi = f.roi()
assert isinstance(roi, numpy.ndarray)
nose.tools.eq_(len(roi.shape), 2) #it is a 2D array
nose.tools.eq_(roi.shape[1], 2) #two columns
nose.tools.eq_(roi.dtype, numpy.uint16)
assert len(roi) > 10 #at least 10 points
# ensures all annotation points are within image boundary
Y,X = image.shape
for y,x in roi:
assert y < Y, 'Annotation (%d, %d) for %s surpasses the image size (%d, %d)' % (y, x, f.path, Y, X)
assert x < X, 'Annotation (%d, %d) for %s surpasses the image size (%d, %d)' % (y, x, f.path, Y, X)
@sql3_available
@nose.tools.nottest
@metadata_available
def test_model_id_to_finger_name_conversion():
db = Database()
......
......@@ -4,7 +4,7 @@
[buildout]
parts = scripts
develop = .
eggs = bob.db.3dfv
eggs = bob.db.fv3d
extensions = bob.buildout
newest = false
verbose = true
......
......@@ -3,7 +3,7 @@
[buildout]
parts = scripts
eggs = bob.db.3dfv
eggs = bob.db.fv3d
extensions = bob.buildout
mr.developer
auto-checkout = *
......
......@@ -80,7 +80,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'bob.db.3dfv'
project = u'bob.db.fv3d'
import time
copyright = u'%s, Idiap Research Institute' % time.strftime('%Y')
......
.. vim: set fileencoding=utf-8 :
.. Fri 02 Dec 2016 11:45:48 CET
.. _bob.db.3dfv:
.. _bob.db.fv3d:
==========================
========================
3D Fingervein Database
==========================
========================
The `3D Fingervein Database <https://www.idiap.ch/dataset/3d-fingervein>`_
for finger vein recognition consists of 440 images from 110 clients.
This database was produced at the `Idiap Research Institute
<http://www.idiap.ch>`_, in Switzerland.
for finger vein recognition consists of 13614 images from 141 subjects
collected in various acquisition campaigns. This database was produced at the
`Idiap Research Institute <http://www.idiap.ch>`_, in Switzerland.
Notice this package does not contain the raw data files from this dataset,
which need to be obtained through the link provided above.
If you use this database in your publication, please cite the following paper
on your references:
......@@ -25,11 +28,74 @@ on your references:
.. title = {On the Vulnerability of Finger Vein Recognition to Spoofing},
.. booktitle = {IEEE International Conference of the Biometrics Special Interest Group (BIOSIG)},
.. year = {2014},
.. location = {Darmstadt, Germay},
.. location = {Darmstadt, Germany},
.. url = {http://publications.idiap.ch/index.php/publications/show/2910}
.. }
Data Acquisition Campaign
-------------------------
Data for the 3DFV database was collected through various campaings in
Switzerland, at different locations. Each of the campaigns and outcomes are
summarized next:
* Foire du Valais (FDV): 89 subjects provided data from both index fingers in a
single data-acquisition session with a single attempt. For each subject
finger, 5 snapshots were taken. The unique subject identifiers vary between 1
and 94, but numeration is not contiguous.
* Idiap (IDI): 50 subjects provided data from both left and right index, middle
and ring fingers in 3 data acquisition sessions, each with 2 attempts. Two
more subjects provided data for the same fingers, but for only 1 acquisition
session (subjects 131 and 147). The unique subject identifiers range from 101
to 153. Numeration is contiguous.
Filename Structure
------------------
Filenames inside the 3D Fingervein are structured like this:
.. code-block:: text
<database-root>/<client:%03d>/<session:%d>/<attempt:%d>/<client:%03d>-<age:%02d>-<gender:%s><skin:%s><occ:%s><side:%s><finger:%s><session:%d><attempt:%d><snap:%d><cam:%d>.png
Each field can have these values:
* client: integer > 0
* age = integer > 0