Skip to content
Snippets Groups Projects
models.py 9.42 KiB
Newer Older
André Anjos's avatar
André Anjos committed
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Andre Anjos <andre.dos.anjos@gmail.com>
# Wed 11 May 18:52:38 2011

"""Table models and functionality for the Replay Attack DB.
"""

André Anjos's avatar
André Anjos committed
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from bob.db.sqlalchemy_migration import Enum, relationship
import bob.db.utils
André Anjos's avatar
André Anjos committed
from sqlalchemy.orm import backref
from sqlalchemy.ext.declarative import declarative_base
André Anjos's avatar
André Anjos committed
import bob
André Anjos's avatar
André Anjos committed

Base = declarative_base()

class Client(Base):
  """Database clients, marked by an integer identifier and the set they belong
  to"""

André Anjos's avatar
André Anjos committed
  __tablename__ = 'client'

  set_choices = ('train', 'devel', 'test')
  """Possible groups to which clients may belong to"""

André Anjos's avatar
André Anjos committed
  id = Column(Integer, primary_key=True)
  """Key identifier for clients"""

André Anjos's avatar
André Anjos committed
  set = Column(Enum(*set_choices))
  """Set to which this client belongs to"""
André Anjos's avatar
André Anjos committed

  def __init__(self, id, set):
    self.id = id
    self.set = set

  def __repr__(self):
    return "Client('%s', '%s')" % (self.id, self.set)
André Anjos's avatar
André Anjos committed

class File(Base):
  """Generic file container"""

André Anjos's avatar
André Anjos committed
  __tablename__ = 'file'

  light_choices = ('controlled', 'adverse')
  """List of illumination conditions for data taking"""
André Anjos's avatar
André Anjos committed

  id = Column(Integer, primary_key=True)
  """Key identifier for files"""

André Anjos's avatar
André Anjos committed
  client_id = Column(Integer, ForeignKey('client.id')) # for SQL
  """The client identifier to which this file is bound to"""

André Anjos's avatar
André Anjos committed
  path = Column(String(100), unique=True)
  """The (unique) path to this file inside the database"""

André Anjos's avatar
André Anjos committed
  light = Column(Enum(*light_choices))
  """The illumination condition in which the data for this file was taken"""
André Anjos's avatar
André Anjos committed

  # for Python
  client = relationship(Client, backref=backref('files', order_by=id))
  """A direct link to the client object that this file belongs to"""
André Anjos's avatar
André Anjos committed

  def __init__(self, client, path, light):
    self.client = client
    self.path = path
    self.light = light

  def __repr__(self):
    return "File('%s')" % self.path

  def make_path(self, directory=None, extension=None):
    """Wraps the current path so that a complete path is formed

    Keyword parameters:

    directory
      An optional directory name that will be prefixed to the returned result.

    extension
      An optional extension that will be suffixed to the returned filename. The
      extension normally includes the leading ``.`` character as in ``.jpg`` or
      ``.hdf5``.

    Returns a string containing the newly generated file path.
    """

    if not directory: directory = ''
    if not extension: extension = ''

    return str(os.path.join(directory, self.path + extension))
  def videofile(self, directory=None):
    """Returns the path to the database video file for this object

    Keyword parameters:

    directory
      An optional directory name that will be prefixed to the returned result.

    Returns a string containing the video file path.
    """

    return self.make_path(directory, '.mov')

  def facefile(self, directory=None):
    """Returns the path to the companion face bounding-box file

    Keyword parameters:

    directory
      An optional directory name that will be prefixed to the returned result.

    Returns a string containing the face file path.
    """

    if not directory: directory = ''
    directory = os.path.join(directory, 'face-locations')
    return self.make_path(directory, '.face')

  def bbx(self, directory=None):
    """Reads the file containing the face locations for the frames in the
    current video

    Keyword parameters:

    directory
      A directory name that will be prepended to the final filepaths where the
      face bounding boxes are located, if not on the current directory.

    Returns:
      A :py:class:`numpy.ndarray` containing information about the located
      faces in the videos. Each row of the :py:class:`numpy.ndarray`
      corresponds for one frame. The five columns of the
      :py:class:`numpy.ndarray` are (all integers):

      * Frame number (int)
      * Bounding box top-left X coordinate (int)
      * Bounding box top-left Y coordinate (int)
      * Bounding box width (int)
      * Bounding box height (int)

      Note that **not** all the frames may contain detected faces.
    """

    return numpy.loadtxt(self.facefile(directory), dtype=int)

  def is_real(self):
    """Returns True if this file belongs to a real access, False otherwise"""

    return bool(self.realaccess)

  def get_realaccess(self):
    """Returns the real-access object equivalent to this file or raise"""
    if len(self.realaccess) == 0:
      raise RuntimeError, "%s is not a real-access" % self
    return self.realaccess[0]

  def get_attack(self):
    """Returns the attack object equivalent to this file or raise"""
    if len(self.attack) == 0:
      raise RuntimeError, "%s is not an attack" % self
    return self.attack[0]

André Anjos's avatar
André Anjos committed
  def load(self, directory=None, extension='.hdf5'):
    """Loads the data at the specified location and using the given extension.

    Keyword parameters:

    data
      The data blob to be saved (normally a :py:class:`numpy.ndarray`).

    directory
      [optional] If not empty or None, this directory is prefixed to the final
      file destination

    extension
      [optional] The extension of the filename - this will control the type of
      output and the codec for saving the input blob.
    """
    return bob.io.load(self.make_path(directory, extension))

  def save(self, data, directory=None, extension='.hdf5'):
    """Saves the input data at the specified location and using the given
    extension.

    Keyword parameters:

    data
      The data blob to be saved (normally a :py:class:`numpy.ndarray`).

    directory
André Anjos's avatar
André Anjos committed
      [optional] If not empty or None, this directory is prefixed to the final
      file destination
André Anjos's avatar
André Anjos committed
      [optional] The extension of the filename - this will control the type of
      output and the codec for saving the input blob.
    """

    path = self.make_path(directory, extension)
    bob.db.utils.makedirs_safe(os.path.dirname(path))
    bob.io.save(data, path)
André Anjos's avatar
André Anjos committed

# Intermediate mapping from RealAccess's to Protocol's
realaccesses_protocols = Table('realaccesses_protocols', Base.metadata,
    Column('realaccess_id', Integer, ForeignKey('realaccess.id')),
    Column('protocol_id', Integer, ForeignKey('protocol.id')),
    )

# Intermediate mapping from Attack's to Protocol's
attacks_protocols = Table('attacks_protocols', Base.metadata,
    Column('attack_id', Integer, ForeignKey('attack.id')),
    Column('protocol_id', Integer, ForeignKey('protocol.id')),
    )

class Protocol(Base):
  """Replay attack protocol"""

André Anjos's avatar
André Anjos committed
  __tablename__ = 'protocol'

  id = Column(Integer, primary_key=True)
  """Unique identifier for the protocol (integer)"""

André Anjos's avatar
André Anjos committed
  name = Column(String(20), unique=True)
  """Protocol name"""
André Anjos's avatar
André Anjos committed

  def __init__(self, name):
    self.name = name

  def __repr__(self):
    return "Protocol('%s')" % (self.name,)
André Anjos's avatar
André Anjos committed

class RealAccess(Base):
  """Defines Real-Accesses (licit attempts to authenticate)"""

André Anjos's avatar
André Anjos committed
  __tablename__ = 'realaccess'

  purpose_choices = ('authenticate', 'enroll')
  """Types of purpose for this video"""
André Anjos's avatar
André Anjos committed

  id = Column(Integer, primary_key=True)
  """Unique identifier for this real-access object"""

André Anjos's avatar
André Anjos committed
  file_id = Column(Integer, ForeignKey('file.id')) # for SQL
  """The file identifier the current real-access is bound to"""

André Anjos's avatar
André Anjos committed
  purpose = Column(Enum(*purpose_choices))
  """The purpose of this video"""

André Anjos's avatar
André Anjos committed
  take = Column(Integer)
  """Take number"""
André Anjos's avatar
André Anjos committed

  # for Python
  file = relationship(File, backref=backref('realaccess', order_by=id))
  """A direct link to the :py:class:`.File` object this real-access belongs to"""

André Anjos's avatar
André Anjos committed
  protocols = relationship("Protocol", secondary=realaccesses_protocols,
      backref='realaccesses')
  """A direct link to the protocols this file is linked to"""
André Anjos's avatar
André Anjos committed

  def __init__(self, file, purpose, take):
    self.file = file
    self.purpose = purpose
    self.take = take

  def __repr__(self):
    return "RealAccess('%s')" % (self.file.path)
André Anjos's avatar
André Anjos committed

class Attack(Base):
  """Defines Spoofing Attacks (illicit attempts to authenticate)"""

André Anjos's avatar
André Anjos committed
  __tablename__ = 'attack'

  attack_support_choices = ('fixed', 'hand')
  """Types of attack support"""

André Anjos's avatar
André Anjos committed
  attack_device_choices = ('print', 'mobile', 'highdef', 'mask')
  """Types of devices used for spoofing"""

André Anjos's avatar
André Anjos committed
  sample_type_choices = ('video', 'photo')
  """Original sample type"""

André Anjos's avatar
André Anjos committed
  sample_device_choices = ('mobile', 'highdef')
  """Original sample device"""
André Anjos's avatar
André Anjos committed

  id = Column(Integer, primary_key=True)
  """Unique identifier for this attack"""

André Anjos's avatar
André Anjos committed
  file_id = Column(Integer, ForeignKey('file.id')) # for SQL
  """The file identifier this attack is linked to"""

André Anjos's avatar
André Anjos committed
  attack_support = Column(Enum(*attack_support_choices))
  """The attack support"""

André Anjos's avatar
André Anjos committed
  attack_device = Column(Enum(*attack_device_choices))
  """The attack device"""

André Anjos's avatar
André Anjos committed
  sample_type = Column(Enum(*sample_type_choices))
  """The attack sample type"""

André Anjos's avatar
André Anjos committed
  sample_device = Column(Enum(*sample_device_choices))
  """The attack sample device"""
André Anjos's avatar
André Anjos committed

  # for Python
  file = relationship(File, backref=backref('attack', order_by=id))
  """A direct link to the :py:class:`.File` object bound to this attack"""

André Anjos's avatar
André Anjos committed
  protocols = relationship("Protocol", secondary=attacks_protocols,
      backref='attacks')
  """A direct link to the protocols this file is linked to"""
André Anjos's avatar
André Anjos committed

  def __init__(self, file, attack_support, attack_device, sample_type, sample_device):
    self.file = file
    self.attack_support = attack_support
    self.attack_device = attack_device
    self.sample_type = sample_type
    self.sample_device = sample_device

  def __repr__(self):
    return "<Attack('%s')>" % (self.file.path)