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