From c0cd0dca2d8dcc9b61945826f8c6c83f3ebdd458 Mon Sep 17 00:00:00 2001 From: Tiago Freitas Pereira <tiagofrepereira@gmail.com> Date: Sun, 29 Mar 2020 21:47:16 +0200 Subject: [PATCH] Implemented score_for_multiple_biometric_references for legacy models --- .../base/config/examples/gabor_mobio-male.py | 4 +- .../vanilla_biometrics/abstract_classes.py | 8 +-- .../vanilla_biometrics/implemented.py | 22 ++++++-- .../pipelines/vanilla_biometrics/legacy.py | 52 ++++++++++++++----- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/bob/bio/base/config/examples/gabor_mobio-male.py b/bob/bio/base/config/examples/gabor_mobio-male.py index 000c2b37..a20453ed 100644 --- a/bob/bio/base/config/examples/gabor_mobio-male.py +++ b/bob/bio/base/config/examples/gabor_mobio-male.py @@ -55,7 +55,6 @@ extractor = functools.partial( node_distance=(8, 8), ) - transformer = make_pipeline( Preprocessor(preprocessor, features_dir=os.path.join(base_dir, "face_cropper")), Extractor(extractor, features_dir=os.path.join(base_dir, "gabor_graph")), @@ -71,7 +70,8 @@ gabor_jet = functools.partial( gabor_sigma=math.sqrt(2.0) * math.pi, ) -algorithm = AlgorithmAsBioAlg(callable=gabor_jet, features_dir=base_dir) +algorithm = AlgorithmAsBioAlg(callable=gabor_jet, features_dir=base_dir, allow_score_multiple_references=True) +#algorithm = AlgorithmAsBioAlg(callable=gabor_jet, features_dir=base_dir) # comment out the code below to disable dask diff --git a/bob/bio/base/pipelines/vanilla_biometrics/abstract_classes.py b/bob/bio/base/pipelines/vanilla_biometrics/abstract_classes.py index e040c0f1..f1dda535 100644 --- a/bob/bio/base/pipelines/vanilla_biometrics/abstract_classes.py +++ b/bob/bio/base/pipelines/vanilla_biometrics/abstract_classes.py @@ -19,7 +19,7 @@ class BioAlgorithm(metaclass=ABCMeta): """ - def __init__(self, allow_score_multiple_references=False): + def __init__(self, allow_score_multiple_references=False, **kwargs): self.allow_score_multiple_references = allow_score_multiple_references self.stacked_biometric_references = None @@ -126,10 +126,10 @@ class BioAlgorithm(metaclass=ABCMeta): scores = self.score_multiple_biometric_references( self.stacked_biometric_references, s ) - + # Wrapping the scores in samples for ref, score in zip(biometric_references, scores): - subprobe_scores.append(_write_sample(ref, sampleset, score[0])) + subprobe_scores.append(_write_sample(ref, sampleset, score)) else: for ref in [ @@ -172,7 +172,7 @@ class BioAlgorithm(metaclass=ABCMeta): @abstractmethod def score_multiple_biometric_references(self, biometric_references, data): """ - It handles the score computation of one probe and multiple biometric references + It handles the score computation of one probe against multiple biometric references This method is called is called if `allow_scoring_multiple_references` is set to true Parameters diff --git a/bob/bio/base/pipelines/vanilla_biometrics/implemented.py b/bob/bio/base/pipelines/vanilla_biometrics/implemented.py index f9bcd6c9..f77e66d3 100644 --- a/bob/bio/base/pipelines/vanilla_biometrics/implemented.py +++ b/bob/bio/base/pipelines/vanilla_biometrics/implemented.py @@ -3,11 +3,14 @@ from sklearn.utils.validation import check_array import numpy from .abstract_classes import BioAlgorithm from .mixins import BioAlgCheckpointMixin +from scipy.spatial.distance import cdist class Distance(BioAlgorithm): - def __init__(self, distance_function=scipy.spatial.distance.euclidean, factor=-1): - + def __init__( + self, distance_function=scipy.spatial.distance.euclidean, factor=-1, **kwargs + ): + super().__init__(**kwargs) self.distance_function = distance_function self.factor = factor @@ -33,7 +36,7 @@ class Distance(BioAlgorithm): return numpy.mean(enroll_features, axis=0) - def score(self, model, probe): + def score(self, biometric_reference, data): """score(model, probe) -> float Computes the distance of the model to the probe using the distance function specified in the constructor. @@ -54,9 +57,18 @@ class Distance(BioAlgorithm): A similarity value between ``model`` and ``probe`` """ - probe = probe.flatten() + data = data.flatten() # return the negative distance (as a similarity measure) - return self.factor * self.distance_function(model, probe) + return self.factor * self.distance_function(biometric_reference, data) + + def score_multiple_biometric_references(self, biometric_references, data): + data = data.flatten() + references_stacked = numpy.vstack(biometric_references) + scores = self.factor * cdist( + references_stacked, data.reshape(1, -1), self.distance_function + ) + + return list(scores.flatten()) class CheckpointDistance(BioAlgCheckpointMixin, Distance): diff --git a/bob/bio/base/pipelines/vanilla_biometrics/legacy.py b/bob/bio/base/pipelines/vanilla_biometrics/legacy.py index 0681fbee..41f6a4d8 100644 --- a/bob/bio/base/pipelines/vanilla_biometrics/legacy.py +++ b/bob/bio/base/pipelines/vanilla_biometrics/legacy.py @@ -173,12 +173,12 @@ class _NonPickableWrapper: def __setstate__(self, d): # Handling unpicklable objects self._instance = None - #return super().__setstate__(d) + # return super().__setstate__(d) def __getstate__(self): # Handling unpicklable objects self._instance = None - #return super().__getstate__() + # return super().__getstate__() class _Preprocessor(_NonPickableWrapper, TransformerMixin, BaseEstimator): @@ -345,7 +345,7 @@ class AlgorithmAsTransformer(CheckpointMixin, SampleMixin, _AlgorithmTransformer return self -class AlgorithmAsBioAlg(BioAlgorithm, _NonPickableWrapper): +class AlgorithmAsBioAlg(_NonPickableWrapper, BioAlgorithm): """Biometric Algorithm that handles legacy :py:class:`bob.bio.base.algorithm.Algorithm` @@ -387,19 +387,35 @@ class AlgorithmAsBioAlg(BioAlgorithm, _NonPickableWrapper): # We should add an agregator function here so we can properlly agregate samples from # a sampleset either after or before scoring. # To be honest, this should be the default behaviour + + def _write_sample(ref, probe, score): + data = make_four_colums_score(ref.subject, probe.subject, probe.path, score) + return Sample(data, parent=ref) + retval = [] for subprobe_id, s in enumerate(sampleset.samples): # Creating one sample per comparison subprobe_scores = [] - for ref in [ - r for r in biometric_references if r.key in sampleset.references - ]: - score = self.score(ref.data, s.data) - data = make_four_colums_score( - ref.subject, sampleset.subject, sampleset.path, score + if self.allow_score_multiple_references: + if self.stacked_biometric_references is None: + self.stacked_biometric_references = [ + ref.data for ref in biometric_references + ] + scores = self.score_multiple_biometric_references( + self.stacked_biometric_references, s.data ) - subprobe_scores.append(Sample(data, parent=ref)) + + # Wrapping the scores in samples + for ref, score in zip(biometric_references, scores): + subprobe_scores.append(_write_sample(ref, sampleset, score)) + + else: + for ref in [ + r for r in biometric_references if r.key in sampleset.references + ]: + score = self.score(ref.data, s.data) + subprobe_scores.append(_write_sample(ref, sampleset, score)) # Creating one sampleset per probe subprobe = SampleSet(subprobe_scores, parent=sampleset) @@ -438,5 +454,17 @@ class AlgorithmAsBioAlg(BioAlgorithm, _NonPickableWrapper): reader = _get_pickable_method(self.instance.read_model) return DelayedSample(functools.partial(reader, path), parent=enroll_features) - def score(self, model, probe, **kwargs): - return self.instance.score(model, probe) + def score(self, biometric_reference, data, **kwargs): + return self.instance.score(biometric_reference, data) + + def score_multiple_biometric_references(self, biometric_references, data, **kwargs): + """ + It handles the score computation of one probe against multiple biometric references using legacy + `bob.bio.base` + + Basically it wraps :py:meth:`bob.bio.base.algorithm.Algorithm.score_for_multiple_models`. + + """ + + scores = self.instance.score_for_multiple_models(biometric_references, data) + return scores -- GitLab