diff --git a/bob/pad/face/extractor/FFTFeatures.py b/bob/pad/face/extractor/FFTFeatures.py new file mode 100644 index 0000000000000000000000000000000000000000..889db74f6c32f5b115aff3089d2cdcb7b7574afe --- /dev/null +++ b/bob/pad/face/extractor/FFTFeatures.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import numpy + +from bob.bio.base.extractor import Extractor + +import logging +logger = logging.getLogger("bob.pad.face") + + +class FFTFeatures(Extractor, object): + """ + Compute the Frequency Spectrum of the given signal. + + The computation is made using numpy's rfft routine + + **Parameters:** + + framerate: int + The sampling frequency of the signal (i.e the framerate ...) + + nfft: int + Number of points to compute the FFT + + debug: boolean + Plot stuff + """ + def __init__(self, framerate=25, nfft=256, debug=False, **kwargs): + + super(FFTFeatures, self).__init__(**kwargs) + + self.framerate = framerate + self.nfft = nfft + self.debug = debug + + + def __call__(self, signal): + """ + Compute the frequency spectrum for the given signal. + + **Parameters:** + + signal: numpy.array + The signal + + **Returns:** + + freq: numpy.array + the frequency spectrum + """ + # sanity check + if signal.ndim == 1: + if numpy.isnan(numpy.sum(signal)): + return + if signal.ndim == 2 and (signal.shape[1] == 3): + if numpy.isnan(numpy.sum(signal[:, 1])): + return + + output_dim = int((self.nfft / 2) + 1) + + # get the frequencies + f = numpy.fft.fftfreq(self.nfft) * self.framerate + + # we have a single pulse signal + if signal.ndim == 1: + fft = abs(numpy.fft.rfft(signal, n=self.nfft)) + + # we have 3 pulse signal (Li's preprocessing) + # in this case, return the signal corresponding to the green channel + if signal.ndim == 2 and (signal.shape[1] == 3): + ffts = numpy.zeros((3, output_dim)) + for i in range(3): + ffts[i] = abs(numpy.fft.rfft(signal[:, i], n=self.nfft)) + fft = ffts[1] + + if self.debug: + from matplotlib import pyplot + pyplot.plot(f, fft, 'k') + pyplot.title('Power spectrum of the signal') + pyplot.show() + + return fft diff --git a/bob/pad/face/extractor/NormalizeLength.py b/bob/pad/face/extractor/NormalizeLength.py new file mode 100644 index 0000000000000000000000000000000000000000..21a42ff302b4b0334ff1f0850a2b16f94d490213 --- /dev/null +++ b/bob/pad/face/extractor/NormalizeLength.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import numpy + +from bob.bio.base.extractor import Extractor + +import logging +logger = logging.getLogger("bob.pad.face") + + +class NormalizeLength(Extractor, object): + """ + Normalize the length of feature vectors, such that + they all have the same dimensions + + **Parameters:** + + length: int + The final length of the final feature vector + + requires_training: boolean + This extractor actually may requires "training". + The goal here is to retrieve the length of the shortest sequence + + debug: boolean + Plot stuff + """ + def __init__(self, length=-1, debug=False, requires_training=True, **kwargs): + + super(NormalizeLength, self).__init__(requires_training=requires_training, **kwargs) + + self.length = length + self.debug = debug + + def __call__(self, signal): + """ + Normalize the length of the signal + + **Parameters:** + + signal: numpy.array + The signal + + **Returns:** + + signal: numpy.array + the signal with the provided length + """ + # we have a single pulse signal + if signal.ndim == 1: + signal = signal[:self.length] + + # we have 3 pulse signal (Li's preprocessing) + # in this case, return the signal corresponding to the green channel + if signal.ndim == 2 and (signal.shape[1] == 3): + signal = signal[:self.length, 1] + + if numpy.isnan(numpy.sum(signal)): + return + + if signal.shape[0] < self.length: + logger.debug("signal shorter than training shape: {} vs {}".format(signal.shape[0], self.length)) + import sys + sys.exit() + tmp = numpy.zeros((self.length), dtype=signal.dtype) + tmp[:, signal.shape[0]] + signal = tmp + + if self.debug: + from matplotlib import pyplot + pyplot.plot(signal, 'k') + pyplot.title('Signal truncated') + pyplot.show() + + return signal + + def train(self, training_data, extractor_file): + """ + This function determines the shortest length across the training set. + It will be used to normalize the length of all the sequences. + + **Parameters:** + + training_data : [object] or [[object]] + A list of *preprocessed* data that can be used for training the extractor. + Data will be provided in a single list, if ``split_training_features_by_client = False`` was specified in the constructor, + otherwise the data will be split into lists, each of which contains the data of a single (training-)client. + + extractor_file : str + The file to write. + This file should be readable with the :py:meth:`load` function. + """ + self.length = 100000 + for i in range(len(training_data)): + if training_data[i].shape[0] < self.length: + self.length = training_data[i].shape[0] + logger.info("Signals will be truncated to {} dimensions".format(self.length)) diff --git a/bob/pad/face/extractor/__init__.py b/bob/pad/face/extractor/__init__.py index 9ff0c47a7ae57c69b784a9511d19212f6c65d80f..1cbc4e4a45b17442bf173657a0e85c7224d76fd9 100644 --- a/bob/pad/face/extractor/__init__.py +++ b/bob/pad/face/extractor/__init__.py @@ -7,6 +7,8 @@ from .FrameDiffFeatures import FrameDiffFeatures from .FrequencySpectrum import FrequencySpectrum from .FreqFeatures import FreqFeatures +from .NormalizeLength import NormalizeLength +from .FFTFeatures import FFTFeatures def __appropriate__(*args): """Says object was actually declared here, and not in the import module.