Source code for bob.bio.base.algorithm.Algorithm

#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# @author: Manuel Guenther <Manuel.Guenther@idiap.ch>
# @date: Tue Oct  2 12:12:39 CEST 2012
#
# Copyright (C) 2011-2012 Idiap Research Institute, Martigny, Switzerland
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import numpy
import os
from .. import utils

class Algorithm:
  """This is the base class for all biometric recognition algorithms.
  It defines the minimum requirements for all derived algorithm classes.

  Call the constructor in derived class implementations.
  If your derived algorithm performs feature projection, please register this here.
  If it needs training for the projector or the enroller, please set this here, too.

  **Parameters:**

  performs_projection : bool
    Set to ``True`` if your derived algorithm performs a projection.
    Also implement the :py:meth:`project` function, and the :py:meth:`load_projector` if necessary.

  requires_projector_training : bool
    Only valid, when ``performs_projection = True``.
    Set this flag to ``False``, when the projection is applied, but the projector does not need to be trained.

  split_training_features_by_client : bool
    Only valid, when ``performs_projection = True`` and ``requires_projector_training = True``.
    If set to ``True``, the :py:meth:`train_projector` function will receive a double list (a list of lists) of data (sorted by identity).
    Otherwise, the :py:meth:`train_projector` function will receive data in a single list.

  use_projected_features_for_enrollment : bool
    Only valid, when ``performs_projection = True``.
    If set to false, the enrollment is performed using the original features, otherwise the features projected using the :py:meth:`project` function are used for model enrollment.

  requires_enroller_training : bool
    Set this flag to ``True``, when the enroller requires specialized training.
    Which kind of features are used for training depends on the ``use_projected_features_for_enrollment`` flag.

  multiple_model_scoring : str or ``None``
    The way, scores are fused when multiple features are stored in a one model.
    See :py:func:`bob.bio.base.score_fusion_strategy` for possible values.

  multiple_probe_scoring : str or ``None``
    The way, scores are fused when multiple probes are available.
    See :py:func:`bob.bio.base.score_fusion_strategy` for possible values.

  kwargs : ``key=value`` pairs
    A list of keyword arguments to be written in the :py:meth:`__str__` function.

  """

  def __init__(
      self,
      performs_projection = False, # enable if your tool will project the features
      requires_projector_training = True, # by default, the projector needs training, if projection is enabled
      split_training_features_by_client = False, # enable if your projector training needs the training files sorted by client
      use_projected_features_for_enrollment = True, # by default, the enroller used projected features for enrollment, if projection is enabled.
      requires_enroller_training = False, # enable if your enroller needs training

      multiple_model_scoring = 'average', # by default, compute the average between several models and the probe
      multiple_probe_scoring = 'average', # by default, compute the average between the model and several probes
      **kwargs                            # parameters from the derived class that should be reported in the __str__() function
  ):
    self.performs_projection = performs_projection
    self.requires_projector_training = performs_projection and requires_projector_training
    self.split_training_features_by_client = split_training_features_by_client
    self.use_projected_features_for_enrollment = performs_projection and use_projected_features_for_enrollment
    self.requires_enroller_training = requires_enroller_training
    self.model_fusion_function = utils.score_fusion_strategy(multiple_model_scoring)
    self.probe_fusion_function = utils.score_fusion_strategy(multiple_probe_scoring)
    self._kwargs = kwargs
    self._kwargs.update({'multiple_model_scoring':multiple_model_scoring, 'multiple_probe_scoring':multiple_probe_scoring})


  def __str__(self):
    """__str__() -> info

    This function returns all parameters of this class (and its derived class).

    **Returns:**

    info : str
      A string containing the full information of all parameters of this (and the derived) class.
    """
    return "%s(%s)" % (str(self.__class__), ", ".join(["%s=%s" % (key, value) for key,value in self._kwargs.items() if value is not None]))


  def project(self, feature):
    """project(feature) -> projected

    This function will project the given feature.
    It must be overwritten by derived classes, as soon as ``performs_projection = True`` was set in the constructor.
    It is assured that the :py:meth:`load_projector` was called once before the ``project`` function is executed.

    **Parameters:**

    feature : object
      The feature to be projected.

    **Returns:**

    projected : object
      The projected features.
      Must be writable with the :py:meth:`write_feature` function and readable with the :py:meth:`read_feature` function.

    """
    raise NotImplementedError("Please overwrite this function in your derived class")


  def enroll(self, enroll_features):
    """enroll(enroll_features) -> model

    This function will enroll and return the model from the given list of features.
    It must be overwritten by derived classes.

    **Parameters:**

    enroll_features : [object]
      A list of features used for the enrollment of one model.

    **Returns:**

    model : object
      The model enrolled from the ``enroll_features``.
      Must be writable with the :py:meth:`write_model` function and readable with the :py:meth:`read_model` function.

    """
    raise NotImplementedError("Please overwrite this function in your derived class")


  def score(self, model, probe):
    """score(model, probe) -> score

    This function will compute the score between the given model and probe.
    It must be overwritten by derived classes.

    **Parameters:**

    model : object
      The model to compare the probe with.
      The ``model`` was read using the :py:meth:`read_model` function.

    probe : object
      The probe object to compare the model with.
      The ``probe`` was read using the :py:meth:`read_probe` function.

    **Returns:**

    score : float
      A similarity between ``model`` and ``probe``.
      Higher values define higher similarities.
    """
    raise NotImplementedError("Please overwrite this function in your derived class")


  def score_for_multiple_models(self, models, probe):
    """score_for_multiple_models(models, probe) -> score

    This function computes the score between the given model list and the given probe.
    In this base class implementation, it computes the scores for each model using the :py:meth:`score` method,
    and fuses the scores using the fusion method specified in the constructor of this class.
    Usually this function is called from derived class :py:meth:`score` functions.

    **Parameters:**

    models : [object]
      A list of model objects.

    probe : object
      The probe object to compare the models with.

    **Returns:**

    score : float
      The fused similarity between the given ``models`` and the ``probe``.
    """
    if isinstance(models, list):
      return self.model_fusion_function([self.score(model, probe) for model in models])
    elif isinstance(models, numpy.ndarray):
      return self.model_fusion_function([self.score(models[i,:], probe) for i in range(models.shape[0])])
    else:
      raise ValueError("The model does not have the desired format (list, array, ...)")


  def score_for_multiple_probes(self, model, probes):
    """score_for_multiple_probes(model, probes) -> score

    This function computes the score between the given model and the given probe files.
    In this base class implementation, it computes the scores for each probe file using the :py:meth:`score` method,
    and fuses the scores using the fusion method specified in the constructor of this class.

    **Parameters:**

    model : object
      A model object to compare the probes with.

    probes : [object]
      The list of probe object to compare the models with.

    **Returns:**

    score : float
      The fused similarity between the given ``model`` and the ``probes``.
    """
    if isinstance(probes, list):
      return self.probe_fusion_function([self.score(model, probe) for probe in probes])
    else:
      # only one probe feature -> use the default scoring function
      return self.score(model, probes)


  ############################################################
  ### Special functions that might be overwritten on need
  ############################################################

  def write_feature(self, feature, feature_file):
    """Saves the given *projected* feature to a file with the given name.
    In this base class implementation:

    - If the given feature has a ``save`` attribute, it calls ``feature.save(bob.io.base.HDF5File(feature_file), 'w')``.
      In this case, the given feature_file might be either a file name or a bob.io.base.HDF5File.
    - Otherwise, it uses :py:func:`bob.io.base.save` to do that.

    If you have a different format, please overwrite this function.

    Please register 'performs_projection = True' in the constructor to enable this function.

    **Parameters:**

    feature : object
      A feature as returned by the :py:meth:`project` function, which should be written.

    feature_file : str or :py:class:`bob.io.base.HDF5File`
      The file open for writing, or the file name to write to.
    """
    utils.save(feature, feature_file)


  def read_feature(self, feature_file):
    """read_feature(feature_file) -> feature

    Reads the *projected* feature from file.
    In this base class implementation, it uses :py:func:`bob.io.base.load` to do that.
    If you have different format, please overwrite this function.

    Please register ``performs_projection = True`` in the constructor to enable this function.

    **Parameters:**

    feature_file : str or :py:class:`bob.io.base.HDF5File`
      The file open for reading, or the file name to read from.

    **Returns:**

    feature : object
      The feature that was read from file.
    """
    return utils.load(feature_file)


  def write_model(self, model, model_file):
    """Writes the enrolled model to the given file.
    In this base class implementation:

    - If the given model has a 'save' attribute, it calls ``model.save(bob.io.base.HDF5File(model_file), 'w')``.
      In this case, the given model_file might be either a file name or a :py:class:`bob.io.base.HDF5File`.
    - Otherwise, it uses :py:func:`bob.io.base.save` to do that.

    If you have a different format, please overwrite this function.

    **Parameters:**

    model : object
      A model as returned by the :py:meth:`enroll` function, which should be written.

    model_file : str or :py:class:`bob.io.base.HDF5File`
      The file open for writing, or the file name to write to.
    """
    utils.save(model, model_file)


  def read_model(self, model_file):
    """read_model(model_file) -> model

    Loads the enrolled model from file.
    In this base class implementation, it uses :py:func:`bob.io.base.load` to do that.

    If you have a different format, please overwrite this function.

    **Parameters:**

    model_file : str or :py:class:`bob.io.base.HDF5File`
      The file open for reading, or the file name to read from.

    **Returns:**

    model : object
      The model that was read from file.
    """
    return utils.load(model_file)


  def read_probe(self, probe_file):
    """read_probe(probe_file) -> probe

    Reads the probe feature from file.
    By default, the probe feature is identical to the projected feature.
    Hence, this base class implementation simply calls :py:meth:`read_feature`.

    If your algorithm requires different behavior, please overwrite this function.

    **Parameters:**

    probe_file : str or :py:class:`bob.io.base.HDF5File`
      The file open for reading, or the file name to read from.

    **Returns:**

    probe : object
      The probe that was read from file.
    """
    return self.read_feature(probe_file)



  def train_projector(self, training_features, projector_file):
    """This function can be overwritten to train the feature projector.
    If you do this, please also register the function by calling this base class constructor
    and enabling the training by ``requires_projector_training = True``.

    **Parameters:**

    training_features : [object] or [[object]]
      A list of *extracted* features that can be used for training the projector.
      Features will be provided in a single list, if ``split_training_features_by_client = False`` was specified in the constructor,
      otherwise the features will be split into lists, each of which contains the features of a single (training-)client.

    projector_file : str
      The file to write.
      This file should be readable with the :py:meth:`load_projector` function.
    """
    raise NotImplementedError("Please overwrite this function in your derived class, or unset the 'requires_projector_training' option in the constructor.")


  def load_projector(self, projector_file):
    """Loads the parameters required for feature projection from file.
    This function usually is useful in combination with the :py:meth:`train_projector` function.
    In this base class implementation, it does nothing.

    Please register `performs_projection = True` in the constructor to enable this function.

    **Parameters:**

    projector_file : str
      The file to read the projector from.
    """
    pass


  def train_enroller(self, training_features, enroller_file):
    """This function can be overwritten to train the model enroller.
    If you do this, please also register the function by calling this base class constructor
    and enabling the training by ``require_enroller_training = True``.

    **Parameters:**

    training_features : [object] or [[object]]
      A list of *extracted* features that can be used for training the projector.
      Features will be split into lists, each of which contains the features of a single (training-)client.

    enroller_file : str
      The file to write.
      This file should be readable with the :py:meth:`load_enroller` function.
    """


  def load_enroller(self, enroller_file):
    """Loads the parameters required for model enrollment from file.
    This function usually is only useful in combination with the :py:meth:`train_enroller` function.
    This function is always called **after** calling :py:meth:`load_projector`.
    In this base class implementation, it does nothing.

    **Parameters:**

    enroller_file : str
      The file to read the enroller from.
    """
    pass