#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: Olegs Nikisins
"""
# =============================================================================
# Import what is needed here:
from bob.pad.base.algorithm import Algorithm
from bob.bio.video.utils import FrameContainer
import numpy as np
from bob.ip.pytorch_extractor.utils import transform_and_net_forward
from bob.ip.pytorch_extractor.utils import load_pretrained_model
from bob.pad.base.utils import mean_std_normalize
from bob.pad.base.utils import convert_list_of_frame_cont_to_array
import bob.io.base
import torch
# =============================================================================
# Main body :
class MLPAlgorithm(Algorithm):
"""
This class is an MLP-based PAD algorithm, allowing to pass input feature
vectors (1D numpy arrays) via a pretrained MLP.
Attributes
-----------
network : object
An instance of an MLP Network to be used for score computation.
Note: in current algorith the ``forward()`` method of the Network
is used for score computation. For example, if you don't want to use
a sigmoid in the output of an MLP, set the kwargs accodingly.
model_file : str
A paths to the model file to be used for ``network`` initialization.
url : str
URL to download the pretrained models from.
If models are not available in the locations specified in the
``model_file`` sting, the system will try to download them from
``url``. The downloaded models **will be placed to the locations**
specified in ``model_file`` argument.
Default: None
archive_extension : str
Extension of the archived file to download from above ``url``.
Default: '.tar.gz'
frame_level_scores_flag : bool
Return scores for each frame individually if True. Otherwise, return a
single score per video. Default: ``True``.
mean_std_norm_flag : bool
Perform mean-std normalization of data if set to ``True``.
Note: make sure an MLP was trained on mean-std normalizaed
features, if this flag is set to ``True``. The tutorial on MLP
training in ``bob.learn.pytorch`` provides an example on how to train
network on normalized features.
Default: ``True``.
"""
[docs] def __init__(self,
network,
model_file = None,
url = None,
archive_extension = '.tar.gz',
frame_level_scores_flag = True,
mean_std_norm_flag = True):
super(MLPAlgorithm, self).__init__(network = network,
model_file = model_file,
url = url,
archive_extension = archive_extension,
frame_level_scores_flag = frame_level_scores_flag,
mean_std_norm_flag = mean_std_norm_flag,
performs_projection=True,
requires_projector_training=True)
self.transform = lambda x : torch.Tensor(x).unsqueeze(0)
self.network = network
self.model_file = model_file
self.url = url
self.archive_extension = archive_extension
self.frame_level_scores_flag = frame_level_scores_flag
self.mean_std_norm_flag = mean_std_norm_flag
self.features_mean = None # this argument will be updated with features mean
self.features_std = None # this argument will be updated with features std
# =========================================================================
def _save_mean_std(self,
projector_file,
features_mean,
features_std):
"""
Saves mean and std normalization to the hdf5 file.
The absolute name of the file is specified in ``projector_file`` string.
Parameters
----------
projector_file : str
Absolute name of the file to save the data to, as returned by
``bob.pad.base`` framework.
features_mean : 1D :py:class:`numpy.ndarray`
Mean of the features.
features_std : 1D :py:class:`numpy.ndarray`
Standart deviation of the features.
"""
f = bob.io.base.HDF5File(projector_file,'w') # open hdf5 file to save
f.set("features_mean", features_mean)
f.set("features_std", features_std)
del f
# =========================================================================
[docs] def train_projector(self, training_features, projector_file):
"""
Compute mean-std normalizers using samples of the real class only.
Parameters
----------
training_features : [[object], [object]]
A list containing two elements: [0] - a list of Frame Containers
with feature vectors for the real class;
[1] - a list of Frame Containers with feature vectors for the
attack class.
projector_file : str
The file to save the trained projector to, as returned by the
``bob.pad.base`` framework.
"""
# training_features[0] - training features for the REAL class.
# training_features[1] - training features for the ATTACK class.
real = convert_list_of_frame_cont_to_array(training_features[0])
# compute normalization params:
_, features_mean, features_std = mean_std_normalize(real)
# Save the normalizers:
self._save_mean_std(projector_file, features_mean, features_std)
# =========================================================================
[docs] def load_projector(self, projector_file):
"""
Loads features mean and std from the hdf5 file.
The absolute name of the file is specified in ``projector_file`` string.
Parameters
----------
projector_file : str
Absolute name of the file to load the trained projector from, as
returned by ``bob.pad.base`` framework.
"""
f = bob.io.base.HDF5File(projector_file, 'r') # file to read
features_mean = f.read("features_mean")
features_std = f.read("features_std")
del f
self.features_mean = features_mean
self.features_std = features_std
# =========================================================================
[docs] def project(self, feature):
"""
This function computes a vector of scores, one score for each sample
in the input array of features.
Set ``performs_projection = True`` in the constructor to enable this
function.
Parameters
----------
feature : FrameContainer or :py:class:`numpy.ndarray`
Two types of inputs are accepted.
A Frame Container containing the features of an individual frmaes,
see ``bob.bio.video.utils.FrameContainer``.
Or a ND feature array of the size (n_samples x n_features).
Returns
-------
scores : 1D :py:class:`numpy.ndarray`
Vector of scores. Scores for the bona-fide class are expected to be
higher, than the scores of the negative / attack class.
"""
# try to load the model if not available, do nothing if available:
load_pretrained_model(model_path = self.model_file,
url = self.url,
archive_extension = self.archive_extension)
# 1. Convert input array to numpy array if necessary.
if isinstance(
feature,
FrameContainer): # if FrameContainer convert to 3D numpy array
feature = feature.as_array()
if self.mean_std_norm_flag:
feature, _, _ = mean_std_normalize(feature,
self.features_mean,
self.features_std)
scores = transform_and_net_forward(feature = feature,
transform = self.transform,
network = self.network,
model_file = self.model_file,
color_input_flag = False)
return scores
# =========================================================================
[docs] def score(self, toscore):
"""
Returns a probability of a sample being a real class.
Parameters
----------
toscore : 1D :py:class:`numpy.ndarray`
Vector with scores for each frame/sample defining the probability
of the frame being a sample of the real class.
Returns
-------
score : [:py:class:`float`]
If ``frame_level_scores_flag = False`` a single score is returned.
One score per video. This score is placed into a list, because
the ``score`` must be an iterable.
Score is a probability of a sample being a real class.
If ``frame_level_scores_flag = True`` a list of scores is returned.
One score per frame/sample.
"""
if self.frame_level_scores_flag:
score = list(toscore)
else:
score = [np.mean(toscore)] # compute a single score per video
return score
# =========================================================================
[docs] def score_for_multiple_projections(self, toscore):
"""
Returns a list of scores computed by the score method of this class.
Parameters
----------
toscore : 1D :py:class:`numpy.ndarray`
Vector with scores for each frame/sample defining the probability
of the frame being a sample of the real class.
Returns
-------
list_of_scores : [:py:class:`float`]
A list containing the scores.
"""
scores = self.score(
toscore) # returns float score or 1D array of scores
if isinstance(scores, np.float): # if a single score
list_of_scores = [scores]
else:
list_of_scores = list(scores)
return list_of_scores