From a1d08d1b92639c6aef66c6187b510ba9e505799b Mon Sep 17 00:00:00 2001
From: Tiago Freitas Pereira <tiagofrepereira@gmail.com>
Date: Tue, 26 May 2020 09:30:52 +0200
Subject: [PATCH] Implemented S-Norm score normalization

---
 .../pipelines/vanilla_biometrics/zt_norm.py   | 91 ++++++++++++++++++-
 .../test_vanilla_biometrics_score_norm.py     | 32 ++++++-
 2 files changed, 119 insertions(+), 4 deletions(-)

diff --git a/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py b/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py
index 0b58fb88..7181674b 100644
--- a/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py
+++ b/bob/bio/base/pipelines/vanilla_biometrics/zt_norm.py
@@ -110,6 +110,7 @@ class ZTNormPipeline(object):
             return z_normed_scores
 
         # T NORM
+
         t_normed_scores, t_scores, t_biometric_references = self.compute_tnorm_scores(
             t_biometric_reference_samples,
             probe_features,
@@ -128,8 +129,14 @@ class ZTNormPipeline(object):
             t_scores,
             allow_scoring_with_all_biometric_references,
         )
+
+
+        # S-norm
+        s_normed_scores = self.compute_snorm_scores(z_normed_scores, t_normed_scores)
+
+
         
-        return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores
+        return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores, s_normed_scores
 
     def train_background_model(self, background_model_samples):
         return self.vanilla_biometrics_pipeline.train_background_model(
@@ -227,6 +234,19 @@ class ZTNormPipeline(object):
 
         return zt_normed_scores
 
+
+    def compute_snorm_scores(
+        self,
+        znormed_scores,
+        tnormed_scores
+    ):
+
+        s_normed_scores = self.ztnorm_solver.compute_snorm_scores(
+            znormed_scores, tnormed_scores
+        )
+
+        return s_normed_scores
+
     def write_scores(self, scores):
         return self.vanilla_biometrics_pipeline.write_scores(scores)
 
@@ -395,6 +415,33 @@ class ZTNorm(object):
         return self._tnorm_samplesets(probe_scores, stats)
 
 
+    def _snorm(self, z_score, t_score):
+        return 0.5*(z_score + t_score)
+
+    def _snorm_samplesets(self, znormed_scores, tnormed_scores):
+        
+        s_normed_samplesets = []        
+        for z, t in zip(znormed_scores, tnormed_scores):
+            s_normed_scores = SampleSet([], parent=z)
+            for b_z, b_t in zip(z, t):
+                score = self._snorm(b_z.data, b_t.data)
+
+                new_sample = Sample(score, parent=b_z)
+                s_normed_scores.samples.append(new_sample)
+            s_normed_samplesets.append(s_normed_scores)
+
+        return s_normed_samplesets
+
+
+    def compute_snorm_scores(
+        self,
+        znormed_scores,
+        tnormed_scores
+    ):
+
+        return self._snorm_samplesets(znormed_scores, tnormed_scores)
+
+
 class ZTNormDaskWrapper(object):
     """
     Wrap :any:`ZTNorm` to work with DASK
@@ -435,6 +482,13 @@ class ZTNormDaskWrapper(object):
 
         return probe_scores.map_partitions(self.ztnorm._tnorm_samplesets, stats)
 
+    def compute_snorm_scores(
+        self,
+        znormed_scores,
+        tnormed_scores
+    ):
+        return znormed_scores.map_partitions(self.ztnorm._snorm_samplesets, tnormed_scores)
+
 
 class ZTNormCheckpointWrapper(object):
     """
@@ -488,6 +542,29 @@ class ZTNormCheckpointWrapper(object):
 
         return z_normed_score
 
+
+    def _apply_tnorm(self, probe_score, stats):
+
+        path = os.path.join(self.tnorm_score_path, str(probe_score.key) + ".pkl")
+
+        if self.force or not os.path.exists(path):
+            t_normed_score = self.ztnorm._apply_tnorm(probe_score, path)
+
+            self.write_scores(t_normed_score.samples)
+
+            t_normed_score = SampleSet(
+                [
+                    DelayedSample(
+                        functools.partial(self._load, path), parent=probe_score
+                    )
+                ],
+                parent=probe_score,
+            )
+        else:
+            t_normed_score = SampleSet(self._load(path), parent=probe_score)
+
+        return t_normed_score
+
     def compute_znorm_scores(
         self, probe_scores, sampleset_for_znorm, biometric_references
     ):
@@ -499,11 +576,18 @@ class ZTNormCheckpointWrapper(object):
     def compute_tnorm_scores(
         self, probe_scores, sampleset_for_tnorm, t_biometric_references
     ):
-
         return self.ztnorm.compute_tnorm_scores(
             probe_scores, sampleset_for_tnorm, t_biometric_references
         )
 
+    def compute_snorm_scores(
+        self,
+        znormed_scores,
+        tnormed_scores
+    ):
+        return self.ztnorm.compute_snorm_scores(znormed_scores, tnormed_scores)
+
+
     def _compute_stats(self, sampleset_for_norm, biometric_references, axis=0):
         return self.ztnorm._compute_stats(
             sampleset_for_norm, biometric_references, axis=axis
@@ -514,3 +598,6 @@ class ZTNormCheckpointWrapper(object):
 
     def _tnorm_samplesets(self, probe_scores, stats):
         return self.ztnorm._tnorm_samplesets(probe_scores, stats)
+
+    def _snorm_samplesets(self, probe_scores, stats):
+        return self.ztnorm._snorm_samplesets(probe_scores, stats)
diff --git a/bob/bio/base/test/test_vanilla_biometrics_score_norm.py b/bob/bio/base/test/test_vanilla_biometrics_score_norm.py
index eb9f1593..4829f78c 100644
--- a/bob/bio/base/test/test_vanilla_biometrics_score_norm.py
+++ b/bob/bio/base/test/test_vanilla_biometrics_score_norm.py
@@ -127,7 +127,11 @@ def zt_norm_stubs(references, probes, t_references, z_probes):
     zt_normed_scores = _norm(z_normed_scores, z_t_scores, axis=0)
     assert zt_normed_scores.shape == (n_reference, n_probes)
 
-    return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores
+    s_normed_scores = (z_normed_scores+t_normed_scores)*0.5
+    assert s_normed_scores.shape == (n_reference, n_probes)
+
+
+    return raw_scores, z_normed_scores, t_normed_scores, zt_normed_scores, s_normed_scores
 
 
 def test_norm_mechanics():
@@ -191,6 +195,7 @@ def test_norm_mechanics():
                 z_normed_scores_ref,
                 t_normed_scores_ref,
                 zt_normed_scores_ref,
+                s_normed_scores_ref,
             ) = zt_norm_stubs(references, probes, t_references, z_probes)
 
             ############
@@ -338,6 +343,7 @@ def test_norm_mechanics():
                 z_normed_score_samples,
                 t_normed_score_samples,
                 zt_normed_score_samples,
+                s_normed_score_samples,
             ) = zt_vanilla_pipeline(
                 [],
                 biometric_reference_sample_sets,
@@ -360,6 +366,11 @@ def test_norm_mechanics():
                     scheduler="single-threaded"
                 )
 
+                s_normed_score_samples = s_normed_score_samples.compute(
+                    scheduler="single-threaded"
+                )
+
+
             raw_scores = _dump_scores_from_samples(
                 raw_score_samples, shape=(n_probes, n_references)
             )
@@ -380,6 +391,14 @@ def test_norm_mechanics():
             )
             assert np.allclose(zt_normed_scores, zt_normed_scores_ref)
 
+            s_normed_scores = _dump_scores_from_samples(
+                s_normed_score_samples, shape=(n_probes, n_references)
+            )
+            assert np.allclose(s_normed_scores, s_normed_scores_ref)
+
+
+
+
     # No dask
     run(False)  # On memory
 
@@ -418,7 +437,7 @@ def test_znorm_on_memory():
                     vanilla_biometrics_pipeline, npartitions=2
                 )
 
-            raw_scores, z_scores, t_scores, zt_scores = vanilla_biometrics_pipeline(
+            raw_scores, z_scores, t_scores, zt_scores, s_scores = vanilla_biometrics_pipeline(
                 database.background_model_samples(),
                 database.references(),
                 database.probes(),
@@ -448,15 +467,22 @@ def test_znorm_on_memory():
                 t_scores = _concatenate(
                     vanilla_biometrics_pipeline, t_scores, "scores-dev_tscores"
                 )
+
                 zt_scores = _concatenate(
                     vanilla_biometrics_pipeline, zt_scores, "scores-dev_ztscores"
                 )
 
+                s_scores = _concatenate(
+                    vanilla_biometrics_pipeline, s_scores, "scores-dev_sscores"
+                )
+
             if with_dask:
                 raw_scores = raw_scores.compute(scheduler="single-threaded")
                 z_scores = z_scores.compute(scheduler="single-threaded")
                 t_scores = t_scores.compute(scheduler="single-threaded")
                 zt_scores = zt_scores.compute(scheduler="single-threaded")
+                s_scores = s_scores.compute(scheduler="single-threaded")
+
 
             if isinstance(score_writer, CSVScoreWriter):
                 n_lines = 51 if with_dask else 101
@@ -465,12 +491,14 @@ def test_znorm_on_memory():
                 assert len(open(z_scores[0], "r").readlines()) == n_lines
                 assert len(open(t_scores[0], "r").readlines()) == n_lines
                 assert len(open(zt_scores[0], "r").readlines()) == n_lines
+                assert len(open(s_scores[0], "r").readlines()) == n_lines
 
             else:
                 assert len(raw_scores) == 10
                 assert len(z_scores) == 10
                 assert len(t_scores) == 10
                 assert len(zt_scores) == 10
+                assert len(s_scores) == 10
 
         run_pipeline(False)
         run_pipeline(False)  # Testing checkpoint
-- 
GitLab