#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
"""Table models and functionality for the 3D Fingervein database.
"""
import os
import pkg_resources
import bob.io.base
import bob.io.image
import bob.db.base
import numpy
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy import or_, and_, not_
from sqlalchemy import UniqueConstraint
from bob.db.base.sqlalchemy_migration import Enum, relationship
from sqlalchemy.orm import backref
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Client(Base):
"""Unique clients in the database, referred by a single integer"""
__tablename__ = 'client'
id = Column(Integer, primary_key=True)
age = Column(Integer)
gender_choices = ('m', 'f')
gender = Column(Enum(*gender_choices))
skin_color_choices = tuple([str(k) for k in range(1,7)] + ['x'])
skin_color = Column(Enum(*skin_color_choices))
occupation_choices = tuple([str(k) for k in range(10)] + ['x'])
occupation = Column(Enum(*occupation_choices))
def __init__(self, id, gender, age, skin_color, occupation):
self.id = id
self.gender = gender
self.age = age
self.skin_color = skin_color
self.occupation = occupation
[docs] def gender_display(self):
"""Returns a representation of the client gender"""
return 'male' if self.gender == 'm' else 'female'
[docs] def skin_color_display(self):
"""Returns a representation of the client skin color"""
mapping = {
'x': 'unknown',
'1': 'I', #celtic
'2': 'II', #scandinavian
'3': 'III', #caucasian
'4': 'IV', #mediterranean, hispanic and some asian
'5': 'V', # pakistani and indian
'6': 'VI', # african
}
return mapping[self.skin_color]
[docs] def occupation_display(self):
"""Returns a representation of the client occupation"""
mapping = {
'x': 'unknown',
'0': '0-armed forces',
'1': '1-manager',
'2': '2-professional',
'3': '3-technician',
'4': '4-cleric',
'5': '5-sales',
'6': '6-agricultural',
'7': '7-craft',
'8': '8-machines',
'9': '9-elementary',
}
return mapping[self.occupation]
def __repr__(self):
return "Client(%03d) <%s>, %d years old, %s, %s" % \
(self.id, self.gender_display(), self.age, self.skin_color_display(),
self.occupation_display())
class Finger(Base):
"""Unique fingers in the database, referred by a string
Fingers have the format ``003_L`` (i.e. <client>_<finger>)
"""
__tablename__ = 'finger'
id = Column(Integer, primary_key=True)
client_id = Column(Integer, ForeignKey('client.id'))
client = relationship("Client", backref=backref("fingers", order_by=id))
side_choices = ('l', 'r')
side = Column(Enum(*side_choices))
name_choices = ('t', 'i', 'm', 'r', 'l')
name = Column(Enum(*name_choices))
UniqueConstraint('client_id', 'side', 'name')
def __init__(self, client, side, name):
self.client = client
self.side = side
self.name = name
[docs] def side_display(self):
"""Returns a representation of the finger side"""
mapping = {
'l': 'left',
'r': 'right',
}
return mapping[self.side]
[docs] def name_display(self):
"""Returns a representation of the finger name"""
mapping = {
't': 'thumb',
'i': 'index',
'm': 'middle',
'r': 'ring',
'l': 'little',
}
return mapping[self.name]
def __repr__(self):
return "Finger(%03d, %s, %s)" % (self.client.id, self.side_display(),
self.name_display())
@property
def unique_name(self):
"""Unique name for a given finger in the database"""
return '{id:03}-{side}{finger}'.format(id=self.client.id,
side=self.side, finger=self.name)
class File(Base, bob.db.base.File):
"""Unique files in the database, referred by a string
Filenames inside the 3D Fingervein are like these:
<client>/<session>/<attempt>/<client>-<age>-<gender><skin><occ><side><finger><session><attempt><snap><cam>.png
The fields can have these values:
* client: integer > 0
* age = integer > 0
* gender = str, 'm' or 'f'
* skin (color) = str, '1'..'6' or 'x'
* occ(upation) = str, '0'..'9' or 'x'
* side = str, 'l' or 'r'
* finger = str, 't', 'i', 'm', 'r', 'l'
* session = int > 0
* attempt = int > 0
* snap = int > 0
* cam = str, one of '1', '2', '3' or 'S' ('stitched')
"""
__tablename__ = 'file'
id = Column(Integer, primary_key=True)
finger_id = Column(Integer, ForeignKey('finger.id'))
finger = relationship("Finger", backref=backref("files", order_by=id))
session_choices = ('1', '2', '3')
session = Column(Enum(*session_choices))
attempt_choices = ('1', '2')
attempt = Column(Enum(*attempt_choices))
snapshot_choices = ('1', '2', '3', '4', '5')
snapshot = Column(Enum(*snapshot_choices))
camera_choices = ('1', '2', '3', 'S')
camera = Column(Enum(*camera_choices))
UniqueConstraint('finger_id', 'session', 'attempt', 'snapshot', 'camera')
def __init__(self, finger, session, attempt, snapshot, camera):
self.finger = finger
self.session = session
self.attempt = attempt
self.snapshot = snapshot
self.camera = camera
@property
def path(self):
fmt = '{id:03}/{session}/{attempt}/{id:03}-{age:03}-{gender}{skin}{occ}{side}{finger}{session}{attempt}{snap}{cam}'
info = {
'id': self.finger.client.id,
'age': self.finger.client.age,
'gender': self.finger.client.gender,
'skin': self.finger.client.skin_color,
'occ': self.finger.client.occupation,
'side': self.finger.side,
'finger': self.finger.name,
'session': self.session,
'attempt': self.attempt,
'snap': self.snapshot,
'cam': self.camera,
}
return fmt.format(**info)
[docs] def load(self, directory=None, extension='.png'):
"""Loads the image for this file entry
Parameters:
directory (str): The path to the root of the database installation. This
is the path leading to directories named ``DDD`` where ``D``'s
correspond to digits.
Returns:
numpy.ndarray: A 2D array of unsigned integers corresponding to the input
image for this file in (y,x) notation (Bob-style).
"""
return bob.io.base.load(self.make_path(directory, extension))
[docs] def has_roi(self):
"""Tells if the RoI for a sample is available
Returns:
bool: ``True`` if this sample has an RoI
"""
# calculate where the annotations for this file are
directory = pkg_resources.resource_filename(__name__,
os.path.join('data', 'annotations', 'roi'))
return os.path.exists(self.make_path(directory, '.txt'))
[docs] def roi(self):
"""Loads region-of-interest annotations for a particular image
The returned points (see return value below) correspond to a polygon in the
2D space delimiting the finger image. It is up to you to generate a mask
out of these annotations.
Returns:
numpy.ndarray: A 2D array of 8-bit unsigned integers corresponding to
annotations for the given fingervein image. Points are loaded in (y,x)
format so, the first column of the returned array correspond to the
y-values while the second column to the x-values of each coordinate.
"""
# calculate where the annotations for this file are
directory = pkg_resources.resource_filename(__name__,
os.path.join('data', 'annotations', 'roi'))
# loads it w/o mercy ;-)
return numpy.loadtxt(self.make_path(directory, '.txt'), dtype='uint16')
train_file_association = Table('train_file_association', Base.metadata,
Column('protocol_id', Integer, ForeignKey('protocol.id')),
Column('file_id', Integer, ForeignKey('file.id')))
class Protocol(Base):
"""3D Fingervein protocols"""
__tablename__ = 'protocol'
id = Column(Integer, primary_key=True)
# Name of the protocol
name = Column(String(10), unique=True)
training_set = relationship("File",
secondary=train_file_association,
backref=backref("train_subsets"))
def __init__(self, name):
self.name = name
def __repr__(self):
return "Protocol('%s')" % self.name
model_file_association = Table('model_file_association', Base.metadata,
Column('model_id', Integer, ForeignKey('model.id')),
Column('file_id', Integer, ForeignKey('file.id')))
class Model(Base):
"""Unique models in the database, referred by a string and associate to a
protocol
"""
__tablename__ = 'model'
id = Column(Integer, primary_key=True)
# Name of the model
name = Column(String(9))
# Group of this model
group_choices = ('dev', 'eval')
group = Column(Enum(*group_choices))
# The finger it models
finger_id = Column(Integer, ForeignKey('finger.id'))
finger = relationship("Finger", backref=backref("models", order_by=id))
# Which files to use for the said finger. All files should belong to the same
# finger
files = relationship("File",
secondary=model_file_association,
backref=backref("models"))
# To which protocol is this model associated to?
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
self.group = group
self.finger = finger
self.protocol = protocol
def __repr__(self):
return "Model(%s, %s, %s, %s)" % (self.name, self.group, self.finger,
self.protocol)
class Probe(Base):
"""Unique probes in the database associated to a protocol and a single file
"""
__tablename__ = 'probe'
id = Column(Integer, primary_key=True)
# Group of this probe
group_choices = Model.group_choices
group = Column(Enum(*group_choices))
# To which protocol is this probe associated to?
protocol_id = Column(Integer, ForeignKey('protocol.id'))
protocol = relationship("Protocol", backref=backref("probes", order_by=id))
# To which file is this probe associated to?
file_id = Column(Integer, ForeignKey('file.id'))
file = relationship("File", backref=backref("probes", order_by=id))
def __init__(self, group, protocol, file):
self.group = group
self.protocol = protocol
self.file = file
def __repr__(self):
return "Probe(%s, %s, %s)" % (self.group, self.protocol, self.file)