Commit 2603ceb7 authored by Manuel Günther's avatar Manuel Günther
Browse files

Improved documentation of classes.

parent 093770d9
......@@ -11,7 +11,34 @@ import math
from bob.bio.base.algorithm import Algorithm
class GaborJet (Algorithm):
"""Algorithm chain for computing Gabor jets, Gabor graphs, and Gabor graph comparisons"""
"""Computes a comparison of lists of Gabor jets using a similarity function of :py:class:`bob.ip.gabor.Similarity`.
The model enrollment simply stores all extracted Gabor jets for all enrollment features.
By default (i.e., ``multiple_feature_scoring = 'max_jet'``), the scoring uses an advanced local strategy.
For each node, the similarity between the given probe jet and all model jets is computed, and only the *highest* value is kept.
These values are finally averaged over all node positions.
Other strategies can be obtained using a different ``multiple_feature_scoring``.
**Parameters:**
gabor_jet_similarity_type : str:
The type of Gabor jet similarity to compute.
Please refer to the documentation of :py:class:`bob.ip.gabor.Similarity` for a list of possible values.
multiple_feature_scoring : str
How to fuse the local similarities into a single similarity value.
Possible values are:
* ``'average_model'`` : During enrollment, an average model is computed using functionality of :ref:`bob.ip.gabor <bob.ip.gabor>`.
* ``'average'`` : For each node, the average similarity is computed. Finally, the average of those similarities is returned.
* ``'min_jet', 'max_jet', 'med_jet'`` : For each node, the minimum, maximum or median similarity is computed. Finally, the average of those similarities is returned.
* ``'min_graph', 'max_graph', 'med_graph'`` : For each node, the average similarity is computed. Finally, the minimum, maximum or median of those similarities is returned.
gabor_directions, gabor_scales, gabor_sigma, gabor_maximum_frequency, gabor_frequency_step, gabor_power_of_k, gabor_dc_free
These parameters are required by the disparity-based Gabor jet similarity functions, see :py:class:`bob.ip.gabor.Similarity`..
The default values are identical to the ones in the :py:class:`bob.bio.face.extractor.GridGraph`.
Please assure that this class and the :py:class:`bob.bio.face.extractor.GridGraph` class get the same configuration, otherwise unexpected things might happen.
"""
def __init__(
self,
......@@ -90,7 +117,27 @@ class GaborJet (Algorithm):
assert all(isinstance(f, bob.ip.gabor.Jet) for f in feature)
def enroll(self, enroll_features):
"""Enrolls the model by computing an average graph for each model"""
"""enroll(enroll_features) -> model
Enrolls the model using one of several strategies.
Commonly, the bunch graph strategy [WFK97]_ is applied, by storing several Gabor jets for each node.
When ``multiple_feature_scoring = 'average_model'``, for each node the average :py:class:`bob.ip.gabor.Jet` is computed.
Otherwise, all enrollment jets are stored, grouped by node.
**Parameters:**
enroll_features : [[:py:class:`bob.ip.gabor.Jet`]]
The list of enrollment features.
Each sub-list contains a full graph.
**Returns:**
model : [[:py:class:`bob.ip.gabor.Jet`]]
The enrolled model.
Each sub-list contains a list of jets, which correspond to the same node.
When ``multiple_feature_scoring = 'average_model'`` each sub-list contains a single :py:class:`bob.ip.gabor.Jet`.
"""
[self._check_feature(feature) for feature in enroll_features]
assert len(enroll_features)
assert all(len(feature) == len(enroll_features[0]) for feature in enroll_features)
......@@ -106,7 +153,16 @@ class GaborJet (Algorithm):
def write_model(self, model, model_file):
"""Saves the enrolled model of Gabor jets to file."""
"""Writes the model enrolled by the :py:meth:`enroll` function to the given file.
**Parameters:**
model : [[:py:class:`bob.ip.gabor.Jet`]]
The enrolled model.
model_file : str or :py:class:`bob.io.base.HDF5File`
The name of the file or the file opened for writing.
"""
f = bob.io.base.HDF5File(model_file, 'w')
# several model graphs
f.set("NumberOfNodes", len(model))
......@@ -118,7 +174,22 @@ class GaborJet (Algorithm):
f.cd("..")
f.close()
def read_model(self, model_file):
"""read_model(model_file) -> model
Reads the model written by the :py:meth:`write_model` function from the given file.
**Parameters:**
model_file : str or :py:class:`bob.io.base.HDF5File`
The name of the file or the file opened for reading.
**Returns:**
model : [[:py:class:`bob.ip.gabor.Jet`]]
The list of Gabor jets read from file.
"""
f = bob.io.base.HDF5File(model_file)
count = f.get("NumberOfNodes")
model = []
......@@ -131,11 +202,41 @@ class GaborJet (Algorithm):
def read_probe(self, probe_file):
"""read_probe(probe_file) -> probe
Reads the probe file, e.g., as written by the :py:meth:`bob.bio.face.extractor.GridGraph.write_feature` function from the given file.
**Parameters:**
probe_file : str or :py:class:`bob.io.base.HDF5File`
The name of the file or the file opened for reading.
**Returns:**
probe : [:py:class:`bob.ip.gabor.Jet`]
The list of Gabor jets read from file.
"""
return bob.ip.gabor.load_jets(bob.io.base.HDF5File(probe_file))
def score(self, model, probe):
"""Computes the score of the probe and the model"""
"""score(model, probe) -> score
Computes the score of the probe and the model using the desired Gabor jet similarity function and the desired score fusion strategy.
**Parameters:**
model : [[:py:class:`bob.ip.gabor.Jet`]]
The model enrolled by the :py:meth:`enroll` function.
probe : [:py:class:`bob.ip.gabor.Jet`]
The probe read by the :py:meth:`read_probe` function.
**Returns:**
score : float
The fused similarity score.
"""
self._check_feature(probe)
[self._check_feature(m) for m in model]
assert len(model) == len(probe)
......@@ -148,7 +249,42 @@ class GaborJet (Algorithm):
def score_for_multiple_probes(self, model, probes):
"""This function computes the score between the given model graph(s) and several given probe graphs."""
"""score(model, probes) -> score
This function computes the score between the given model graph(s) and several given probe graphs.
The same local scoring strategy as for several model jets is applied, but this time the local scoring strategy is applied between all graphs from the model and probes.
**Parameters:**
model : [[:py:class:`bob.ip.gabor.Jet`]]
The model enrolled by the :py:meth:`enroll` function.
The sub-lists are groups by nodes.
probes : [[:py:class:`bob.ip.gabor.Jet`]]
A list of probe graphs.
The sub-lists are groups by graph.
**Returns:**
score : float
The fused similarity score.
"""
[self._check_feature(probe) for probe in probes]
[self._check_feature(m) for m in model]
assert all(len(model) == len(probe) for probe in probes)
jet_scoring = numpy.average if self.jet_scoring is None else self.jet_scoring
graph_scoring = numpy.average if self.graph_scoring is None else self.graph_scoring
return graph_scoring([self.score(model, probe) for probe in probes])
local_scores = [jet_scoring([self.similarity_function(m, probe[n]) for m in model[n] for probe in probes]) for n in range(len(model))]
return graph_scoring(local_scores)
# overwrite functions to avoid them being documented.
def train_projector(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load_projector(*args, **kwargs) : pass
def project(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def write_feature(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def read_feature(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def train_enroller(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load_enroller(*args, **kwargs) : pass
def score_for_multiple_models(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
......@@ -9,7 +9,24 @@ import numpy
from bob.bio.base.algorithm import Algorithm
class Histogram (Algorithm):
"""Tool chain for computing local Gabor binary pattern histogram sequences"""
"""Computes the distance between histogram sequences.
Both sparse and non-sparse representations of histograms are supported.
For enrollment, to date only the averaging of histograms is implemented.
**Parameters:**
distance_function : function
The function to be used to compare two histograms.
This function should accept sparse histograms.
is_distance_function : bool
Is the given ``distance_function`` distance function (lower values are better) or a similarity function (higher values are better)?
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.
"""
def __init__(
self,
......@@ -17,7 +34,6 @@ class Histogram (Algorithm):
is_distance_function = True,
multiple_probe_scoring = 'average'
):
"""Initializes the local Gabor binary pattern histogram sequence tool"""
# call base class constructor
Algorithm.__init__(
......@@ -50,7 +66,19 @@ class Histogram (Algorithm):
def enroll(self, enroll_features):
"""Enrolling model by taking the average of all features"""
"""enroll(enroll_features) -> model
Enrolls a model by taking the average of all histograms.
enroll_features : [1D or 2D :py:class:`numpy.ndarray`]
The histograms that should be averaged.
Histograms can be specified sparse (2D) or non-sparse (1D)
**Returns:**
model : 1D or 2D :py:class:`numpy.ndarray`
The averaged histogram, sparse (2D) or non-sparse (1D).
"""
assert len(enroll_features)
sparse = self._is_sparse(enroll_features[0])
[self._check_feature(feature, sparse) for feature in enroll_features]
......@@ -87,8 +115,44 @@ class Histogram (Algorithm):
return model
def read_probe(self, probe_file):
"""read_probe(probe_file) -> probe
Reads the probe feature from the given file.
**Parameters:**
probe_file : str or :py:class:`bob.io.base.HDF5File`
The file (open for reading) or the name of an existing file to read from.
**Returns:**
probe : 1D or 2D :py:class:`numpy.ndarray`
The probe read by the :py:meth:`read_probe` function.
"""
return bob.bio.base.load(probe_file)
def score(self, model, probe):
"""Computes the score using the specified histogram measure; returns a similarity value (bigger -> better)"""
"""score(model, probe) -> score
Computes the score of the probe and the model using the desired histogram distance function.
The resulting score is the negative distance, if ``is_distance_function = True``.
Both sparse and non-sparse models and probes are accepted, but their sparseness must agree.
**Parameters:**
model : 1D or 2D :py:class:`numpy.ndarray`
The model enrolled by the :py:meth:`enroll` function.
probe : 1D or 2D :py:class:`numpy.ndarray`
The probe read by the :py:meth:`read_probe` function.
**Returns:**
score : float
The resulting similarity score.
"""
sparse = self._is_sparse(probe)
self._check_feature(model, sparse)
self._check_feature(probe, sparse)
......@@ -98,3 +162,14 @@ class Histogram (Algorithm):
return self.factor * self.distance_function(model[0,:], model[1,:], probe[0,:], probe[1,:])
else:
return self.factor * self.distance_function(model, probe)
# overwrite functions to avoid them being documented.
def train_projector(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load_projector(*args, **kwargs) : pass
def project(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def write_feature(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def read_feature(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def train_enroller(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load_enroller(*args, **kwargs) : pass
def score_for_multiple_models(*args, **kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
......@@ -11,7 +11,34 @@ from bob.bio.base.extractor import Extractor
class DCTBlocks (Extractor):
"""Extracts DCT blocks"""
"""Extracts *Discrete Cosine Transform* (DCT) features from (overlapping) image blocks.
These features are based on the :py:class:`bob.ip.base.DCTFeatures` class.
The default parametrization is the one that performed best on the BANCA database in [WMM+11]_.
Usually, these features are used in combination with the algorithms defined in :ref:`bob.bio.gmm <bob.bio.gmm>`.
However, you can try to use them with other algorithms.
**Parameters:**
block_size : int or (int, int)
The size of the blocks that will be extracted.
This parameter might be either a single integral value, or a pair ``(block_height, block_width)`` of integral values.
block_overlap : int or (int, int)
The overlap of the blocks in vertical and horizontal direction.
This parameter might be either a single integral value, or a pair ``(block_overlap_y, block_overlap_x)`` of integral values.
It needs to be smaller than the ``block_size``.
number_of_dct_coefficients : int
The number of DCT coefficients to use.
The actual number will be one less since the first DCT coefficient (which should be 0, if normalization is used) will be removed.
normalize_blocks : bool
Normalize the values of the blocks to zero mean and unit standard deviation before extracting DCT coefficients.
normalize_dcts : bool
Normalize the values of the DCT components to zero mean and unit standard deviation. Default is ``True``.
"""
def __init__(
self,
block_size = 12, # 1 or two parameters for block size
......@@ -48,10 +75,28 @@ class DCTBlocks (Extractor):
self.dct_features = bob.ip.base.DCTFeatures(number_of_dct_coefficients, block_size, block_overlap, normalize_blocks, normalize_dcts)
def __call__(self, image):
"""Computes and returns DCT blocks for the given input image"""
"""__call__(image) -> feature
Computes and returns DCT blocks for the given input image.
**Parameters:**
image : 2D :py:class:`numpy.ndarray` (floats)
The image to extract the features from.
**Returns:**
feature : 2D :py:class:`numpy.ndarray` (floats)
The extracted DCT features for all blocks inside the image.
The first index is the block index, while the second index is the DCT coefficient.
"""
assert isinstance(image, numpy.ndarray)
assert image.ndim == 2
assert image.dtype == numpy.float64
# Computes DCT features
return self.dct_features(image)
# re-define the train function to get it non-documented
def train(*args,**kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load(*args,**kwargs) : pass
......@@ -99,12 +99,12 @@ class Eigenface (Extractor):
**Parameters:**
image : 2D :py:class:`numpy.ndarray`
image : 2D :py:class:`numpy.ndarray` (floats)
The image to extract the eigenface feature from.
**Returns:**
feature : 1D :py:class:`numpy.ndarray`
feature : 1D :py:class:`numpy.ndarray` (floats)
The extracted eigenface feature.
"""
self._check_data(image)
......
......@@ -10,7 +10,42 @@ import math
from bob.bio.base.extractor import Extractor
class GridGraph (Extractor):
"""Extracts grid graphs from the images"""
"""Extracts Gabor jets in a grid structure [GHW12]_ using functionalities from :ref:`bob.ip.gabor <bob.ip.gabor>`.
The grid can be either aligned to the eye locations (in which case the grid might be rotated), or a fixed grid graph can be extracted.
In the first case, the eye locations in the aligned image need to be provided.
Additionally, the number of node between, along, above and below the eyes need to be specified.
In the second case, a regular grid graph is created, by specifying the distance between two nodes.
Additionally, the coordinate of the first node can be provided, which otherwise is calculated to evenly fill the whole image with nodes.
**Parameters:**
gabor_directions, gabor_scales, gabor_sigma, gabor_maximum_frequency, gabor_frequency_step, gabor_power_of_k, gabor_dc_free
The parameters of the Gabor wavelet family, with its default values set as given in [WFK97]_.
Please refer to :py:class:`bob.ip.gabor.Transform` for the documentation of these values.
normalize_gabor_jets : bool
Perform Gabor jet normalization during extraction?
eyes : dict or ``None``
If specified, the grid setup will be aligned to the eye positions {'reye' : (re_y, re_x), 'leye' : (le_y, le_x)}.
Otherwise a regular grid graph will be extracted.
nodes_between_eyes, nodes_along_eyes, nodes_above_eyes, nodes_below_eyes : int
Only used when ``eyes`` is not ``None``.
The number of nodes to be placed between, along, above or below the eyes.
The final number of nodes will be: :math:`(above + below + 1) \\times (between + 2*along + 2)`.
node_distance : (int, int)
Only used when ``eyes`` is ``None``.
The distance between two nodes in the regular grid graph.
first_node : (int, int) or ``None``
Only used when ``eyes`` is ``None``.
If ``None``, it is calculated automatically to equally cover the whole image.
"""
def __init__(
self,
......@@ -94,7 +129,11 @@ class GridGraph (Extractor):
self.trafo_image = None
def _extractor(self, image):
"""Creates an extractor based on the given image."""
"""Creates an extractor based on the given image.
If an aligned graph was specified in the constructor, it is simply returned.
Otherwise the resolution of the given image is used to create a graph extractor.
If the ``first_node`` was not specified, it is calculated automatically.
"""
if self.trafo_image is None or self.trafo_image.shape[1:3] != image.shape:
# create trafo image
......@@ -103,9 +142,11 @@ class GridGraph (Extractor):
if self._aligned_graph is not None:
return self._aligned_graph
# check if a new extractor needs to be created
if self._last_image_resolution != image.shape:
self._last_image_resolution = image.shape
if self.first_node is None:
# automatically compute the first node
first_node = [0,0]
for i in (0,1):
offset = int((image.shape[i] - int(image.shape[i]/self.node_distance[i])*self.node_distance[i]) / 2)
......@@ -114,6 +155,7 @@ class GridGraph (Extractor):
first_node[i] = offset
else:
first_node = self.first_node
# .. and the last node
last_node = tuple([int(image.shape[i] - max(first_node[i],1)) for i in (0,1)])
# take the specified nodes
......@@ -127,6 +169,21 @@ class GridGraph (Extractor):
def __call__(self, image):
"""__call__(image) -> feature
Returns a list of Gabor jets extracted from the given image.
**Parameters:**
image : 2D :py:class:`numpy.ndarray` (floats)
The image to extract the features from.
**Returns:**
feature : [:py:class:`bob.ip.gabor.Jet`]
The list of Gabor jets extracted from the image.
The 2D location of the jet's nodes is not returned.
"""
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
assert image.dtype == numpy.float64
......@@ -135,7 +192,6 @@ class GridGraph (Extractor):
# perform Gabor wavelet transform
self.gwt.transform(image, self.trafo_image)
# extract face graph
jets = extractor.extract(self.trafo_image)
......@@ -146,9 +202,39 @@ class GridGraph (Extractor):
# return the extracted face graph
return jets
def write_feature(self, feature, feature_file):
"""Writes the feature extracted by the :py:meth:`__call__` function to the given file.
**Parameters:**
feature : [:py:class:`bob.ip.gabor.Jet`]
The list of Gabor jets extracted from the image.
feature_file : str or :py:class:`bob.io.base.HDF5File`
The name of the file or the file opened for writing.
"""
feature_file = feature_file if isinstance(feature_file, bob.io.base.HDF5File) else bob.io.base.HDF5File(feature_file, 'w')
bob.ip.gabor.save_jets(feature, feature_file)
def read_feature(self, feature_file):
"""read_feature(feature_file) -> feature
Reads the feature written by the :py:meth:`write_feature` function from the given file.
**Parameters:**
feature_file : str or :py:class:`bob.io.base.HDF5File`
The name of the file or the file opened for reading.
**Returns:**
feature : [:py:class:`bob.ip.gabor.Jet`]
The list of Gabor jets read from file.
"""
return bob.ip.gabor.load_jets(bob.io.base.HDF5File(feature_file))
# re-define the train function to get it non-documented
def train(*args,**kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load(*args,**kwargs) : pass
......@@ -11,7 +11,47 @@ import math
from bob.bio.base.extractor import Extractor
class LGBPHS (Extractor):
"""Extractor for local Gabor binary pattern histogram sequences"""
"""Extracts *Local Gabor Binary Pattern Histogram Sequences* (LGBPHS) [ZSG+05]_ from the images, using functionality from :ref:`bob.ip.base <bob.ip.base>` and :ref:`bob.ip.gabor <bob.ip.gabor>`.
The block size and the overlap of the blocks can be varied, as well as the parameters of the Gabor wavelet (:py:class:`bob.ip.gabor.Transform`) and the LBP extractor (:py:class:`bob.ip.base.LBP`).
**Parameters:**
block_size : int or (int, int)
The size of the blocks that will be extracted.
This parameter might be either a single integral value, or a pair ``(block_height, block_width)`` of integral values.
block_overlap : int or (int, int)
The overlap of the blocks in vertical and horizontal direction.
This parameter might be either a single integral value, or a pair ``(block_overlap_y, block_overlap_x)`` of integral values.
It needs to be smaller than the ``block_size``.
gabor_directions, gabor_scales, gabor_sigma, gabor_maximum_frequency, gabor_frequency_step, gabor_power_of_k, gabor_dc_free
The parameters of the Gabor wavelet family, with its default values set as given in [WFK97]_.
Please refer to :py:class:`bob.ip.gabor.Transform` for the documentation of these values.
use_gabor_phases : bool
Extract also the Gabor phases (inline) and not only the absolute values.
In this case, Extended LGBPHS features [ZSQ+09]_ will be extracted.
lbp_radius, lbp_neighbor_count, lbp_uniform, lbp_circular, lbp_rotation_invariant, lbp_compare_to_average, lbp_add_average
The parameters of the LBP.
Please see :py:class:`bob.ip.base.LBP` for the documentation of these values.
.. note::
The default values are as given in [ZSG+05]_ (the values of [ZSQ+09]_ might differ).
sparse_histogram : bool
If specified, the histograms will be handled in a sparse way.
This reduces the size of the extracted features, but the computation will take longer.
.. note::
Sparse histograms are only supported, when ``split_histogram = None``.
split_histogram : one of ``('blocks', 'wavelets', 'both')`` or ``None``
Defines, how the histogram sequence is split.
This could be interesting, if the histograms should be used in another way as simply concatenating them into a single histogram sequence (the default).
"""
def __init__(
self,
......@@ -39,8 +79,6 @@ class LGBPHS (Extractor):
sparse_histogram = False,
split_histogram = None
):
"""Initializes the local Gabor binary pattern histogram sequence tool chain with the given file selector object"""
# call base class constructor
Extractor.__init__(
self,
......@@ -137,7 +175,22 @@ class LGBPHS (Extractor):
def __call__(self, image):
"""Extracts the local Gabor binary pattern histogram sequence from the given image"""
"""__call__(image) -> feature
Extracts the local Gabor binary pattern histogram sequence from the given image.
**Parameters:**
image : 2D :py:class:`numpy.ndarray` (floats)
The image to extract the features from.
**Returns:**
feature : 2D or 3D :py:class:`numpy.ndarray` (floats)
The list of Gabor jets extracted from the image.
The 2D location of the jet's nodes is not returned.
"""
""""""
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
assert image.dtype == numpy.float64
......@@ -193,3 +246,7 @@ class LGBPHS (Extractor):
# return the concatenated list of all histograms
return self._sparsify(lgbphs_array)
# re-define the train function to get it non-documented
def train(*args,**kwargs) : raise NotImplementedError("This function is not implemented and should not be called.")
def load(*args,**kwargs) : pass
......@@ -39,7 +39,7 @@ class INormLBP (Base):
"""Parameters of the constructor of this preprocessor: