Commit a51faf5f authored by Tiago de Freitas Pereira's avatar Tiago de Freitas Pereira
Browse files

Pickled extractors

parent 18f8f52e
Pipeline #39180 failed with stage
in 4 minutes and 20 seconds
......@@ -9,9 +9,10 @@ import numpy
from bob.bio.base.extractor import Extractor
class DCTBlocks (Extractor):
"""Extracts *Discrete Cosine Transform* (DCT) features from (overlapping) image blocks.
class DCTBlocks(Extractor):
"""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 [WMM11]_.
......@@ -39,43 +40,73 @@ class DCTBlocks (Extractor):
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
block_overlap = 11, # 1 or two parameters for block overlap
number_of_dct_coefficients = 45,
normalize_blocks = True,
normalize_dcts = True,
auto_reduce_coefficients = False
):
# call base class constructor
Extractor.__init__(
def __init__(
self,
block_size = block_size,
block_overlap = block_overlap,
number_of_dct_coefficients = number_of_dct_coefficients,
normalize_blocks = normalize_blocks,
normalize_dcts = normalize_dcts,
auto_reduce_coefficients = auto_reduce_coefficients
)
# block parameters
block_size = block_size if isinstance(block_size, (tuple, list)) else (block_size, block_size)
block_overlap = block_overlap if isinstance(block_overlap, (tuple, list)) else (block_overlap, block_overlap)
if block_size[0] < block_overlap[0] or block_size[1] < block_overlap[1]:
raise ValueError("The overlap '%s' is bigger than the block size '%s'. This won't work. Please check your setup!"%(block_overlap, block_size))
if block_size[0] * block_size[1] <= number_of_dct_coefficients:
if auto_reduce_coefficients:
number_of_dct_coefficients = block_size[0] * block_size[1] - 1
else:
raise ValueError("You selected more coefficients %d than your blocks have %d. This won't work. Please check your setup!"%(number_of_dct_coefficients, block_size[0] * block_size[1]))
self.dct_features = bob.ip.base.DCTFeatures(number_of_dct_coefficients, block_size, block_overlap, normalize_blocks, normalize_dcts)
def __call__(self, image):
"""__call__(image) -> feature
block_size=12, # 1 or two parameters for block size
block_overlap=11, # 1 or two parameters for block overlap
number_of_dct_coefficients=45,
normalize_blocks=True,
normalize_dcts=True,
auto_reduce_coefficients=False,
):
# call base class constructor
Extractor.__init__(
self,
block_size=block_size,
block_overlap=block_overlap,
number_of_dct_coefficients=number_of_dct_coefficients,
normalize_blocks=normalize_blocks,
normalize_dcts=normalize_dcts,
auto_reduce_coefficients=auto_reduce_coefficients,
)
# block parameters
block_size = (
block_size
if isinstance(block_size, (tuple, list))
else (block_size, block_size)
)
block_overlap = (
block_overlap
if isinstance(block_overlap, (tuple, list))
else (block_overlap, block_overlap)
)
if block_size[0] < block_overlap[0] or block_size[1] < block_overlap[1]:
raise ValueError(
"The overlap '%s' is bigger than the block size '%s'. This won't work. Please check your setup!"
% (block_overlap, block_size)
)
if block_size[0] * block_size[1] <= number_of_dct_coefficients:
if auto_reduce_coefficients:
number_of_dct_coefficients = block_size[0] * block_size[1] - 1
else:
raise ValueError(
"You selected more coefficients %d than your blocks have %d. This won't work. Please check your setup!"
% (number_of_dct_coefficients, block_size[0] * block_size[1])
)
self.number_of_dct_coefficients = number_of_dct_coefficients
self.block_size = block_size
self.block_overlap = block_overlap
self.normalize_blocks = normalize_blocks
self.normalize_dcts = normalize_dcts
self._init_non_pickables()
def _init_non_pickables(self):
self.dct_features = bob.ip.base.DCTFeatures(
self.number_of_dct_coefficients,
self.block_size,
self.block_overlap,
self.normalize_blocks,
self.normalize_dcts,
)
def __call__(self, image):
"""__call__(image) -> feature
Computes and returns DCT blocks for the given input image.
......@@ -90,13 +121,27 @@ class DCTBlocks (Extractor):
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
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
def __getstate__(self):
d = dict(self.__dict__)
d.pop("dct_features")
return d
def __setstate__(self, d):
self.__dict__ = d
self._init_non_pickables()
......@@ -9,8 +9,9 @@ import numpy
import math
from bob.bio.base.extractor import Extractor
class GridGraph (Extractor):
"""Extracts Gabor jets in a grid structure [GHW12]_ using functionalities from :ref:`bob.ip.gabor <bob.ip.gabor>`.
class GridGraph(Extractor):
"""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.
......@@ -47,129 +48,155 @@ class GridGraph (Extractor):
If ``None``, it is calculated automatically to equally cover the whole image.
"""
def __init__(
self,
# Gabor parameters
gabor_directions = 8,
gabor_scales = 5,
gabor_sigma = 2. * math.pi,
gabor_maximum_frequency = math.pi / 2.,
gabor_frequency_step = math.sqrt(.5),
gabor_power_of_k = 0,
gabor_dc_free = True,
# what kind of information to extract
normalize_gabor_jets = True,
# setup of the aligned grid
eyes = None, # if set, the grid setup will be aligned to the eye positions {'leye' : LEFT_EYE_POS, 'reye' : RIGHT_EYE_POS},
nodes_between_eyes = 4,
nodes_along_eyes = 2,
nodes_above_eyes = 3,
nodes_below_eyes = 7,
# setup of static grid
node_distance = None, # one or two integral values
first_node = None, # one or two integral values, or None -> automatically determined
):
# call base class constructor
Extractor.__init__(
def __init__(
self,
gabor_directions = gabor_directions,
gabor_scales = gabor_scales,
gabor_sigma = gabor_sigma,
gabor_maximum_frequency = gabor_maximum_frequency,
gabor_frequency_step = gabor_frequency_step,
gabor_power_of_k = gabor_power_of_k,
gabor_dc_free = gabor_dc_free,
normalize_gabor_jets = normalize_gabor_jets,
eyes = eyes,
nodes_between_eyes = nodes_between_eyes,
nodes_along_eyes = nodes_along_eyes,
nodes_above_eyes = nodes_above_eyes,
nodes_below_eyes = nodes_below_eyes,
node_distance = node_distance,
first_node = first_node
)
# create Gabor wavelet transform class
self.gwt = bob.ip.gabor.Transform(
number_of_scales = gabor_scales,
number_of_directions = gabor_directions,
sigma = gabor_sigma,
k_max = gabor_maximum_frequency,
k_fac = gabor_frequency_step,
power_of_k = gabor_power_of_k,
dc_free = gabor_dc_free
)
# create graph extractor
if eyes is not None:
self._aligned_graph = bob.ip.gabor.Graph(
righteye = [int(e) for e in eyes['reye']],
lefteye = [int(e) for e in eyes['leye']],
between = int(nodes_between_eyes),
along = int(nodes_along_eyes),
above = int(nodes_above_eyes),
below = int(nodes_below_eyes)
)
else:
if node_distance is None:
raise ValueError("Please specify either 'eyes' or the grid parameters 'node_distance' (and 'first_node')!")
self._aligned_graph = None
self._last_image_resolution = None
self.first_node = first_node
self.node_distance = node_distance
if isinstance(self.node_distance, (int, float)):
self.node_distance = (int(self.node_distance), int(self.node_distance))
self.normalize_jets = normalize_gabor_jets
self.trafo_image = None
def _extractor(self, image):
"""Creates an extractor based on the given image.
# Gabor parameters
gabor_directions=8,
gabor_scales=5,
gabor_sigma=2.0 * math.pi,
gabor_maximum_frequency=math.pi / 2.0,
gabor_frequency_step=math.sqrt(0.5),
gabor_power_of_k=0,
gabor_dc_free=True,
# what kind of information to extract
normalize_gabor_jets=True,
# setup of the aligned grid
eyes=None, # if set, the grid setup will be aligned to the eye positions {'leye' : LEFT_EYE_POS, 'reye' : RIGHT_EYE_POS},
nodes_between_eyes=4,
nodes_along_eyes=2,
nodes_above_eyes=3,
nodes_below_eyes=7,
# setup of static grid
node_distance=None, # one or two integral values
first_node=None, # one or two integral values, or None -> automatically determined
):
# call base class constructor
Extractor.__init__(
self,
gabor_directions=gabor_directions,
gabor_scales=gabor_scales,
gabor_sigma=gabor_sigma,
gabor_maximum_frequency=gabor_maximum_frequency,
gabor_frequency_step=gabor_frequency_step,
gabor_power_of_k=gabor_power_of_k,
gabor_dc_free=gabor_dc_free,
normalize_gabor_jets=normalize_gabor_jets,
eyes=eyes,
nodes_between_eyes=nodes_between_eyes,
nodes_along_eyes=nodes_along_eyes,
nodes_above_eyes=nodes_above_eyes,
nodes_below_eyes=nodes_below_eyes,
node_distance=node_distance,
first_node=first_node,
)
self.gabor_directions = gabor_directions
self.gabor_scales = gabor_scales
self.gabor_sigma = gabor_sigma
self.gabor_maximum_frequency = gabor_maximum_frequency
self.gabor_frequency_step = gabor_frequency_step
self.gabor_power_of_k = gabor_power_of_k
self.gabor_dc_free = gabor_dc_free
self.normalize_gabor_jets = normalize_gabor_jets
self.eyes = eyes
self.nodes_between_eyes = nodes_between_eyes
self.nodes_along_eyes = nodes_along_eyes
self.nodes_above_eyes = nodes_above_eyes
self.nodes_below_eyes = nodes_below_eyes
self.node_distance = node_distance
self.first_node = first_node
self.normalize_jets = normalize_gabor_jets
self.trafo_image = None
self._init_non_pickables()
def _init_non_pickables(self):
# create Gabor wavelet transform class
self.gwt = bob.ip.gabor.Transform(
number_of_scales=self.gabor_scales,
number_of_directions=self.gabor_directions,
sigma=self.gabor_sigma,
k_max=self.gabor_maximum_frequency,
k_fac=self.gabor_frequency_step,
power_of_k=self.gabor_power_of_k,
dc_free=self.gabor_dc_free,
)
# create graph extractor
if self.eyes is not None:
self._aligned_graph = bob.ip.gabor.Graph(
righteye=[int(e) for e in self.eyes["reye"]],
lefteye=[int(e) for e in self.eyes["leye"]],
between=int(self.nodes_between_eyes),
along=int(self.nodes_along_eyes),
above=int(self.nodes_above_eyes),
below=int(self.nodes_below_eyes),
)
else:
if self.node_distance is None:
raise ValueError(
"Please specify either 'eyes' or the grid parameters 'node_distance' (and 'first_node')!"
)
self._aligned_graph = None
self._last_image_resolution = None
if isinstance(self.node_distance, (int, float)):
self.node_distance = (int(self.node_distance), int(self.node_distance))
def _extractor(self, 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
self.trafo_image = numpy.ndarray((self.gwt.number_of_wavelets, image.shape[0], image.shape[1]), numpy.complex128)
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)
if offset < self.node_distance[i]//2: # This is not tested, but should ALWAYS be the case.
offset += self.node_distance[i]//2
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
self._graph = bob.ip.gabor.Graph(
first = first_node,
last = last_node,
step = self.node_distance
)
return self._graph
def __call__(self, image):
"""__call__(image) -> feature
if self.trafo_image is None or self.trafo_image.shape[1:3] != image.shape:
# create trafo image
self.trafo_image = numpy.ndarray(
(self.gwt.number_of_wavelets, image.shape[0], image.shape[1]),
numpy.complex128,
)
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
)
if (
offset < self.node_distance[i] // 2
): # This is not tested, but should ALWAYS be the case.
offset += self.node_distance[i] // 2
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
self._graph = bob.ip.gabor.Graph(
first=first_node, last=last_node, step=self.node_distance
)
return self._graph
def __call__(self, image):
"""__call__(image) -> feature
Returns a list of Gabor jets extracted from the given image.
......@@ -185,28 +212,27 @@ class GridGraph (Extractor):
The 2D location of the jet's nodes is not returned.
"""
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
image = image.astype(numpy.float64)
assert image.dtype == numpy.float64
extractor = self._extractor(image)
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
image = image.astype(numpy.float64)
assert image.dtype == numpy.float64
# perform Gabor wavelet transform
self.gwt.transform(image, self.trafo_image)
# extract face graph
jets = extractor.extract(self.trafo_image)
extractor = self._extractor(image)
# normalize the Gabor jets of the graph only
if self.normalize_jets:
[j.normalize() for j in jets]
# perform Gabor wavelet transform
self.gwt.transform(image, self.trafo_image)
# extract face graph
jets = extractor.extract(self.trafo_image)
# return the extracted face graph
return jets
# normalize the Gabor jets of the graph only
if self.normalize_jets:
[j.normalize() for j in jets]
# return the extracted face graph
return jets
def write_feature(self, feature, feature_file):
"""Writes the feature extracted by the `__call__` function to the given file.
def write_feature(self, feature, feature_file):
"""Writes the feature extracted by the `__call__` function to the given file.
**Parameters:**
......@@ -216,12 +242,15 @@ class GridGraph (Extractor):
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)
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
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.
......@@ -235,8 +264,23 @@ class GridGraph (Extractor):
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
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
def __getstate__(self):
d = dict(self.__dict__)
d.pop("gwt")
d.pop("_aligned_graph")
return d
def __setstate__(self, d):
self.__dict__ = d
self._init_non_pickables()
......@@ -10,8 +10,9 @@ import math
from bob.bio.base.extractor import Extractor
class LGBPHS (Extractor):
"""Extracts *Local Gabor Binary Pattern Histogram Sequences* (LGBPHS) [ZSG05]_ from the images, using functionality from :ref:`bob.ip.base <bob.ip.base>` and :ref:`bob.ip.gabor <bob.ip.gabor>`.
class LGBPHS(Extractor):
"""Extracts *Local Gabor Binary Pattern Histogram Sequences* (LGBPHS) [ZSG05]_ 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`).
......@@ -53,129 +54,168 @@ class LGBPHS (Extractor):
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,
# Block setup
block_size, # one or two parameters for block size
block_overlap = 0, # one or two parameters for block overlap
# Gabor parameters
gabor_directions = 8,
gabor_scales = 5,
gabor_sigma = 2. * math.pi,
gabor_maximum_frequency = math.pi / 2.,
gabor_frequency_step = math.sqrt(.5),
gabor_power_of_k = 0,
gabor_dc_free = True,
use_gabor_phases = False,
# LBP parameters
lbp_radius = 2,
lbp_neighbor_count = 8,
lbp_uniform = True,
lbp_circular = True,
lbp_rotation_invariant = False,
lbp_compare_to_average = False,
lbp_add_average = False,
# histogram options
sparse_histogram = False,
split_histogram = None
):
# call base class constructor
Extractor.__init__(
def __init__(
self,
block_size = block_size,
block_overlap = block_overlap,
gabor_directions = gabor_directions,
gabor_scales = gabor_scales,
gabor_sigma = gabor_sigma,
gabor_maximum_frequency = gabor_maximum_frequency,
gabor_frequency_step = gabor_frequency_step,
gabor_power_of_k = gabor_power_of_k,
gabor_dc_free = gabor_dc_free,
use_gabor_phases = use_gabor_phases,
lbp_radius = lbp_radius,
lbp_neighbor_count = lbp_neighbor_count,
lbp_uniform = lbp_uniform,
lbp_circular = lbp_circular,
lbp_rotation_invariant = lbp_rotation_invariant,
lbp_compare_to_average = lbp_compare_to_average,
lbp_add_average = lbp_add_average,
sparse_histogram = sparse_histogram,
split_histogram = split_histogram
)