From 95c678d992e4cf29dcd174c200af85458c13d480 Mon Sep 17 00:00:00 2001
From: Yannick DAYER <yannick.dayer@idiap.ch>
Date: Fri, 22 Oct 2021 14:39:04 +0200
Subject: [PATCH] Using python GMM from bob.learn.em

---
 bob/bio/gmm/__init__.py                       |   1 +
 bob/bio/gmm/algorithm/__init__.py             |   8 +-
 bob/bio/gmm/bioalgorithm/GMM.py               | 385 ++++++++++++++++++
 bob/bio/gmm/bioalgorithm/__init__.py          |  25 ++
 bob/bio/gmm/config/bioalgorithm/__init__.py   |   0
 bob/bio/gmm/config/bioalgorithm/gmm.py        |   3 +
 .../gmm/config/bioalgorithm/gmm_regular.py    |   5 +
 bob/bio/gmm/test/data/gmm_projected.hdf5      | Bin 5280 -> 6232 bytes
 bob/bio/gmm/test/data/gmm_projector.hdf5      | Bin 11176 -> 10256 bytes
 bob/bio/gmm/test/test_algorithms.py           |  50 ++-
 setup.py                                      |   4 +
 11 files changed, 456 insertions(+), 25 deletions(-)
 create mode 100644 bob/bio/gmm/bioalgorithm/GMM.py
 create mode 100644 bob/bio/gmm/bioalgorithm/__init__.py
 create mode 100644 bob/bio/gmm/config/bioalgorithm/__init__.py
 create mode 100644 bob/bio/gmm/config/bioalgorithm/gmm.py
 create mode 100644 bob/bio/gmm/config/bioalgorithm/gmm_regular.py

diff --git a/bob/bio/gmm/__init__.py b/bob/bio/gmm/__init__.py
index c020e48..24f76d9 100644
--- a/bob/bio/gmm/__init__.py
+++ b/bob/bio/gmm/__init__.py
@@ -1,4 +1,5 @@
 from . import algorithm  # noqa: F401
+from . import bioalgorithm  # noqa: F401
 from . import test  # noqa: F401
 
 
diff --git a/bob/bio/gmm/algorithm/__init__.py b/bob/bio/gmm/algorithm/__init__.py
index 046970f..fc5f4fe 100644
--- a/bob/bio/gmm/algorithm/__init__.py
+++ b/bob/bio/gmm/algorithm/__init__.py
@@ -1,5 +1,5 @@
-from .GMM import GMM
-from .GMM import GMMRegular
+# from .GMM import GMM
+# from .GMM import GMMRegular
 from .ISV import ISV
 from .IVector import IVector
 from .JFA import JFA
@@ -22,8 +22,8 @@ def __appropriate__(*args):
 
 
 __appropriate__(
-    GMM,
-    GMMRegular,
+    # GMM,
+    # GMMRegular,
     JFA,
     ISV,
     IVector,
diff --git a/bob/bio/gmm/bioalgorithm/GMM.py b/bob/bio/gmm/bioalgorithm/GMM.py
new file mode 100644
index 0000000..23d9ebc
--- /dev/null
+++ b/bob/bio/gmm/bioalgorithm/GMM.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+# Manuel Guenther <Manuel.Guenther@idiap.ch>
+
+"""Interface between the lower level GMM classes and the Algorithm Transformer.
+
+Implements the enroll and score methods using the low level GMM implementation.
+
+This adds the notions of models, probes, enrollment, and scores to GMM.
+"""
+
+
+import logging
+
+from typing import Callable
+
+import numpy
+
+from sklearn.base import BaseEstimator
+
+import bob.core
+import bob.io.base
+
+from bob.bio.base.pipelines.vanilla_biometrics.abstract_classes import BioAlgorithm
+from bob.learn.em.mixture import GMMMachine
+from bob.learn.em.mixture import GMMStats
+from bob.learn.em.mixture import linear_scoring
+
+logger = logging.getLogger(__name__)
+
+
+class GMM(BioAlgorithm, BaseEstimator):
+    """Algorithm for computing UBM and Gaussian Mixture Models of the features.
+
+    Features must be normalized to zero mean and unit standard deviation.
+
+    Models are MAP GMM machines trained from a UBM on the enrollment feature set.
+
+    The UBM is a ML GMM machine trained on the training feature set.
+
+    Probes are GMM statistics of features projected on the UBM.
+    """
+
+    def __init__(
+        self,
+        # parameters for the GMM
+        number_of_gaussians: int,
+        # parameters of UBM training
+        kmeans_training_iterations: int = 25,  # Maximum number of iterations for K-Means
+        ubm_training_iterations: int = 25,  # Maximum number of iterations for GMM Training
+        training_threshold: float = 5e-4,  # Threshold to end the ML training
+        variance_threshold: float = 5e-4,  # Minimum value that a variance can reach
+        update_weights: bool = True,
+        update_means: bool = True,
+        update_variances: bool = True,
+        # parameters of the GMM enrollment
+        relevance_factor: float = 4,  # Relevance factor as described in Reynolds paper
+        gmm_enroll_iterations: int = 1,  # Number of iterations for the enrollment phase
+        responsibility_threshold: float = 0,  # If set, the weight of a particular Gaussian will at least be greater than this threshold. In the case the real weight is lower, the prior mean value will be used to estimate the current mean and variance.
+        init_seed: int = 5489,
+        # scoring
+        scoring_function: Callable = linear_scoring,
+        # n_threads=None,
+    ):
+        """Initializes the local UBM-GMM tool chain.
+
+        Parameters
+        ----------
+        number_of_gaussians
+            The number of Gaussians used in the UBM and the models.
+        kmeans_training_iterations
+            Number of e-m iterations to train k-means initializing the UBM.
+        ubm_training_iterations
+            Number of e-m iterations for training the UBM.
+        training_threshold
+            Convergence threshold to halt the GMM training early.
+        variance_threshold
+            Minimum value a variance of the Gaussians can reach.
+        update_weights
+            Decides wether the weights of the Gaussians are updated while training.
+        update_means
+            Decides wether the means of the Gaussians are updated while training.
+        update_variances
+            Decides wether the variancess of the Gaussians are updated while training.
+        relevance_factor
+            Relevance factor as described in Reynolds paper.
+        gmm_enroll_iterations
+            Number of iterations for the MAP GMM used for enrollment.
+        responsibility_threshold
+            If set, the weight of a particular Gaussian will at least be greater than
+            this threshold. In the case where the real weight is lower, the prior mean
+            value will be used to estimate the current mean and variance.
+        init_seed
+            Seed for the random number generation.
+        scoring_function
+            Function returning a score from a model, a UBM, and a probe.
+        """
+
+        # call base class constructor and register that this tool performs projection
+        # super().__init__(score_reduction_operation=??)
+
+        # copy parameters
+        self.number_of_gaussians = number_of_gaussians
+        self.kmeans_training_iterations = kmeans_training_iterations
+        self.ubm_training_iterations = ubm_training_iterations
+        self.training_threshold = training_threshold
+        self.variance_threshold = variance_threshold
+        self.update_weights = update_weights
+        self.update_means = update_means
+        self.update_variances = update_variances
+        self.relevance_factor = relevance_factor
+        self.gmm_enroll_iterations = gmm_enroll_iterations
+        self.init_seed = init_seed
+        self.rng = bob.core.random.mt19937(self.init_seed)  # TODO
+        self.responsibility_threshold = responsibility_threshold
+        self.scoring_function = scoring_function
+
+        self.ubm = None
+
+    def _check_feature(self, feature):
+        """Checks that the features are appropriate"""
+        if (
+            not isinstance(feature, numpy.ndarray)
+            or feature.ndim != 2
+            or feature.dtype != numpy.float64
+        ):
+            raise ValueError("The given feature is not appropriate")
+        if self.ubm is not None and feature.shape[1] != self.ubm.shape[1]:
+            raise ValueError(
+                "The given feature is expected to have %d elements, but it has %d"
+                % (self.ubm.shape[1], feature.shape[1])
+            )
+
+    #######################################################
+    #                UBM training                         #
+
+    def train_ubm(self, array):
+
+        logger.debug(" .... Training UBM with %d feature vectors", array.shape[0])
+
+        logger.debug(" .... Creating UBM machine")
+        self.ubm = GMMMachine(
+            n_gaussians=self.number_of_gaussians,
+            trainer="ml",
+            max_fitting_steps=self.ubm_training_iterations,
+            convergence_threshold=self.training_threshold,
+            update_means=self.update_means,
+            update_variances=self.update_variances,
+            update_weights=self.update_weights,
+            # TODO more params?
+        )
+
+        # Trains the GMM
+        logger.info("  -> Training UBM GMM")
+        # Resetting the pseudo random number generator so we can have the same initialization for serial and parallel execution.
+        # self.rng = bob.core.random.mt19937(self.init_seed)
+        self.ubm.fit(array)
+
+    def save_ubm(self, projector_file):
+        """Saves the projector to file"""
+        # Saves the UBM to file
+        logger.debug(" .... Saving model to file '%s'", projector_file)
+
+        hdf5 = (
+            projector_file
+            if isinstance(projector_file, bob.io.base.HDF5File)
+            else bob.io.base.HDF5File(projector_file, "w")
+        )
+        self.ubm.save(hdf5)
+
+    def train_projector(self, train_features, projector_file):
+        """Computes the Universal Background Model from the training ("world") data"""
+        [self._check_feature(feature) for feature in train_features]
+
+        logger.info(
+            "  -> Training UBM model with %d training files", len(train_features)
+        )
+
+        # Loads the data into an array
+        array = numpy.vstack(train_features)
+
+        self.train_ubm(array)
+
+        self.save_ubm(projector_file)
+
+    #######################################################
+    #              GMM training using UBM                 #
+
+    def load_ubm(self, ubm_file):
+        hdf5file = bob.io.base.HDF5File(ubm_file)
+        # read UBM
+        self.ubm = GMMMachine.from_hdf5(hdf5file)
+        self.ubm.variance_thresholds = self.variance_threshold
+
+    def load_projector(self, projector_file):
+        """Reads the UBM model from file"""
+        # read UBM
+        self.load_ubm(projector_file)
+        # prepare MAP_GMM_Trainer
+        # kwargs = (
+        #     dict(
+        #         mean_var_update_responsibilities_threshold=self.responsibility_threshold
+        #     )
+        #     if self.responsibility_threshold > 0.0
+        #     else dict()
+        # )
+        # self.enroll_trainer = bob.learn.em.MAP_GMMTrainer(
+        #     self.ubm,
+        #     relevance_factor=self.relevance_factor,
+        #     update_means=True,
+        #     update_variances=False,
+        #     **kwargs
+        # )
+        self.rng = bob.core.random.mt19937(self.init_seed)
+
+    def project_ubm(self, array):
+        logger.debug(" .... Projecting %d feature vectors", array.shape[0])
+        # Accumulates statistics
+        gmm_stats = GMMStats(self.ubm.shape[0], self.ubm.shape[1])
+        self.ubm.acc_statistics(array, gmm_stats)
+
+        # return the resulting statistics
+        return gmm_stats
+
+    def project(self, feature):
+        """Computes GMM statistics against a UBM, given an input 2D numpy.ndarray of feature vectors"""
+        self._check_feature(feature)
+        return self.project_ubm(feature)
+
+    def read_gmm_stats(self, gmm_stats_file):
+        """Reads GMM stats from file."""
+        return GMMStats.from_hdf5(bob.io.base.HDF5File(gmm_stats_file))
+
+    def read_feature(self, feature_file):
+        """Read the type of features that we require, namely GMM_Stats"""
+        return self.read_gmm_stats(feature_file)
+
+    def write_feature(self, feature, feature_file):
+        """Write the features (GMM_Stats)"""
+        return feature.save(feature_file)
+
+    def enroll_gmm(self, array):
+        logger.debug(" .... Enrolling with %d feature vectors", array.shape[0])
+        # TODO responsibility_threshold
+        gmm = GMMMachine(
+            n_gaussians=self.number_of_gaussians,
+            trainer="map",
+            ubm=self.ubm,
+            convergence_threshold=self.training_threshold,
+            max_fitting_steps=self.gmm_enroll_iterations,
+            random_state=self.rng,  # TODO
+            update_means=True,
+            update_variances=True,  # TODO default?
+            update_weights=True,  # TODO default?
+        )
+        gmm.variance_thresholds = self.variance_threshold
+        gmm = gmm.fit(array)
+        return gmm
+
+    def enroll(self, data):
+        """Enrolls a GMM using MAP adaptation, given a list of 2D numpy.ndarray's of feature vectors"""
+        [self._check_feature(feature) for feature in data]
+        array = numpy.vstack(data)
+        # Use the array to train a GMM and return it
+        return self.enroll_gmm(array)
+
+    ######################################################
+    #                Feature comparison                  #
+    def read_model(self, model_file):
+        """Reads the model, which is a GMM machine"""
+        return GMMMachine.from_hdf5(bob.io.base.HDF5File(model_file))
+
+    def score(self, biometric_reference: GMMMachine, data: GMMStats):
+        """Computes the score for the given model and the given probe.
+
+        Uses the scoring function passed during initialization.
+
+        Parameters
+        ----------
+        biometric_reference:
+            The model to score against.
+        data:
+            The probe data to compare to the model.
+        """
+
+        assert isinstance(biometric_reference, GMMMachine)  # TODO is it a list?
+        assert isinstance(data, GMMStats)
+        return self.scoring_function(
+            models_means=[biometric_reference],
+            ubm=self.ubm,
+            test_stats=data,
+            frame_length_normalisation=True,
+        )[0, 0]
+
+    def score_multiple_biometric_references(
+        self, biometric_references: "list[GMMMachine]", data: GMMStats
+    ):
+        """Computes the score between multiple models and one probe.
+
+        Uses the scoring function passed during initialization.
+
+        Parameters
+        ----------
+        biometric_references:
+            The models to score against.
+        data:
+            The probe data to compare to the models.
+        """
+
+        assert isinstance(biometric_references, GMMMachine)  # TODO is it a list?
+        assert isinstance(data, GMMStats)
+        return self.scoring_function(
+            models_means=biometric_references,
+            ubm=self.ubm,
+            test_stats=data,
+            frame_length_normalisation=True,
+        )
+
+    # def score_for_multiple_probes(self, model, probes):
+    #     """This function computes the score between the given model and several given probe files."""
+    #     assert isinstance(model, GMMMachine)
+    #     for probe in probes:
+    #         assert isinstance(probe, GMMStats)
+    #     #    logger.warn("Please verify that this function is correct")
+    #     return self.probe_fusion_function(
+    #         self.scoring_function(
+    #             model.means, self.ubm, probes, [], frame_length_normalisation=True
+    #         )
+    #     )
+
+    def fit(self, X, y=None, **kwargs):
+        """Trains the UBM."""
+        self.train_ubm(X)
+        return self
+
+    def transform(self, X, **kwargs):
+        """Passthrough. Enroll applies a different transform as score."""
+        return X
+
+
+class GMMRegular(GMM):
+    """Algorithm for computing Universal Background Models and Gaussian Mixture Models of the features"""
+
+    def __init__(self, **kwargs):
+        """Initializes the local UBM-GMM tool chain with the given file selector object"""
+        #    logger.warn("This class must be checked. Please verify that I didn't do any mistake here. I had to rename 'train_projector' into a 'train_enroller'!")
+        # initialize the UBMGMM base class
+        GMM.__init__(self, **kwargs)
+        # register a different set of functions in the Tool base class
+        BioAlgorithm.__init__(
+            self, requires_enroller_training=True, performs_projection=False
+        )
+
+    #######################################################
+    #                UBM training                         #
+
+    def train_enroller(self, train_features, enroller_file):
+        """Computes the Universal Background Model from the training ("world") data"""
+        train_features = [feature for client in train_features for feature in client]
+        return self.train_projector(train_features, enroller_file)
+
+    #######################################################
+    #              GMM training using UBM                 #
+
+    def load_enroller(self, enroller_file):
+        """Reads the UBM model from file"""
+        return self.load_projector(enroller_file)
+
+    ######################################################
+    #                Feature comparison                  #
+    def score(self, model, probe):
+        """Computes the score for the given model and the given probe.
+        The score are Log-Likelihood.
+        Therefore, the log of the likelihood ratio is obtained by computing the following difference."""
+
+        assert isinstance(model, GMMMachine)
+        self._check_feature(probe)
+        score = sum(
+            model.log_likelihood(probe[i, :]) - self.ubm.log_likelihood(probe[i, :])
+            for i in range(probe.shape[0])
+        )
+        return score / probe.shape[0]
+
+    def score_for_multiple_probes(self, model, probes):
+        raise NotImplementedError("Implement Me!")
diff --git a/bob/bio/gmm/bioalgorithm/__init__.py b/bob/bio/gmm/bioalgorithm/__init__.py
new file mode 100644
index 0000000..e1b44bc
--- /dev/null
+++ b/bob/bio/gmm/bioalgorithm/__init__.py
@@ -0,0 +1,25 @@
+from .GMM import GMM
+from .GMM import GMMRegular
+
+
+# gets sphinx autodoc done right - don't remove it
+def __appropriate__(*args):
+    """Says object was actually declared here, and not in the import module.
+    Fixing sphinx warnings of not being able to find classes, when path is shortened.
+    Parameters:
+
+      *args: An iterable of objects to modify
+
+    Resolves `Sphinx referencing issues
+    <https://github.com/sphinx-doc/sphinx/issues/3048>`
+    """
+
+    for obj in args:
+        obj.__module__ = __name__
+
+
+__appropriate__(
+    GMM,
+    GMMRegular,
+)
+__all__ = [_ for _ in dir() if not _.startswith("_")]
diff --git a/bob/bio/gmm/config/bioalgorithm/__init__.py b/bob/bio/gmm/config/bioalgorithm/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bob/bio/gmm/config/bioalgorithm/gmm.py b/bob/bio/gmm/config/bioalgorithm/gmm.py
new file mode 100644
index 0000000..58aeddd
--- /dev/null
+++ b/bob/bio/gmm/config/bioalgorithm/gmm.py
@@ -0,0 +1,3 @@
+import bob.bio.gmm
+
+bioalgorithm = bob.bio.gmm.bioalgorithm.GMM(number_of_gaussians=512)
diff --git a/bob/bio/gmm/config/bioalgorithm/gmm_regular.py b/bob/bio/gmm/config/bioalgorithm/gmm_regular.py
new file mode 100644
index 0000000..f7166b5
--- /dev/null
+++ b/bob/bio/gmm/config/bioalgorithm/gmm_regular.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+import bob.bio.gmm
+
+bioalgorithm = bob.bio.gmm.bioalgorithm.GMMRegular(number_of_gaussians=512)
diff --git a/bob/bio/gmm/test/data/gmm_projected.hdf5 b/bob/bio/gmm/test/data/gmm_projected.hdf5
index 31d930b955098e3ae990c1e2509d2c232d1a86be..ba17796e7d8d0a7374edc3a9ae067447043feedc 100644
GIT binary patch
delta 2086
zcmZ3WdBb3W22+H@M6DD?riuNEd<qN@ARrE+C$V*GJdngVc?FXIR|iOdfq_9mXyV4W
z$qLLy+#C#y3=9my3=9m+llQWU^Me#HGcs_1XpmSpvn0Fi+M+}ThK`9FdnX&Pu<@`k
zWI$vYE(lHBD90HA<q1rl$l}hZF!?5n`oseZ&`j4xGMyP}y7pu=(-oLw*g=LdGwMyg
z&8o-(vUn0($HW5}^@e%|Hpw}O#l;HhN%={7IjM<7d3ve2dbybuC8b5Fdg-~jdhWiy
z!6k_$#p-rUAX69^7<8d@i|{J3O8<oiM4<e{6LO5Zjwv{7Fn{>Z)l%KzQGQNa;sWsl
z|5W>oGAs=bRJ}BBR{O|wVE6U~YwqTLuurR>$?&S@nW{tJFA-(KnxFPFavn_g=TttR
zv9n5SV=CW)Y2R;3`kxR!aLU%`(*}8o1FJ5xysF$G=J50F?YQ`Fq7GLkv|Gl^6LE+%
zTznv4vc`dW$@(nL$#M>-)?Q?YJfQ5LR9Aa(Vu`TBVfU6bb;S}6|LPnk*s|(7#BA`J
z@R~>IK>ck$)$(cHzw8a<_sdt6a2;@3^KIGv=(qd-?3~=L7^3SC9T7KU?PWy=pJ|`I
zeokO_klfbMagK-ILEV3uhva!N2c6k#U8Wfc9SHdG<E^&Dn*EdfOeHVx7daq!(`e$i
zJu(NjnS5Eb)AYLis|(SNi>65(kdY34($VyH|JUH>*6*jQ);pMVZW5jOU-3W=Px{pl
z;V<^Dt^ASp{Ik*l$rVbIrNy-mY_Sq~z5B3$L*4pMJPF%G9dhKfjn^`NvtRW3(@oZe
z!Uz6s(mNHztLz{*O*CPLt@43~S?eWL8I=xPNnY^sJSUTbB2S^hzEc7Rc%F)EetlEe
zVNRmEV9i;90|ykREeVyX-@Jdx)`}fgB1#9QG5k|KU&VZ2mbd-E{$iyAJEUWJ6wk{!
zc>Lk;h+3@TplED1>%X+31M9Ec&KL;}hu<IXSWG{ybijY&GsSOb{_j7X5oD(HkLAD~
zBh}m(gCF~Yx2Ans)hgxSVe~^sai*q2`bwsUr}B&r)KAR*Gu>3+KnCaix?}ZAPV7%V
zz}5b3f}+FAMQ6ACjFWKi(C^}&oX+F$;%1osT>tm>cHeIuRW)Ti@a%<zv}uEe!<V8Q
zy=Sd_2c8M8nY;cXr^B|xecQeiN*vJ6Io|WGR{Vh1makcSA)E)qOEUGsAF4U55#-tU
z_>{E6Is-?gOONFbJnG;2bVZ%ifo<~guj(~7{kLa-9C&0Ym%#ys$<waO@XH?fo7}kW
ztG0r}mX^$9fs3E_tIs(%<D)y%fi?eH{v2^+J<z}7+y@_?V28?Ak6D@uLL43yeW*&?
z>f<2e$N1GQ#K(cB?$MlYcLN==JRijwaD+R^A9?og(0w<DB%4oDBHX<kZuRh`E!T2&
zh<FuJAFHJq=y2K1gln#ut3#5drilJ}SBLzNgFF3NgB@BUxf-$-1vxY+dg=CE2ypms
z-txmlDA3{D8=3Zu5>JON#$N)e?ja6NtNruWy^eC2lcs(xAT!88o4X;Qo5RP!L22c>
z7nA%Qwwm!hubLj?kf3aod}?Z#!@Mg@;RdsU99-wufBrH1U!a3aaPsM$+HMX<YgNl5
zts))%drx|F_+^m8l>kp&&9oqg-$yI=&aiTJ=#_U$F4_^`5VX)gFYl9wLt*&ozH1@@
z4uRL6Yz&bPc1SjI<9w;^?(ik={04?EfewvtBP>KRT^!2A&+fRh(c5AD7u|<T=7l)$
z8;MCwsSJ1Et$)@MbyX(Jfm_=(@WzA?2R}oGW4u%S99WjOGcGalagZ~8kb2@ru)|W$
zHyiTS209oS{?k;b4R=_m=@Og}5$<4+6xMfXQ?P^o%KhKBbtE{fNndDmzC6<5!Aady
zQwpOTzVJO^Ok{F#_-A+iTS;M*!|}&8R@=4*JFM$E_j%EZ5QqAB?sk)D%cC7uFDz5|
z${y}e+PN%y?p8ksyAAVh#~lrE@SR+>>e=ZKhtpfbxwNw)98}A<Yz~_c;lTZEVvond
zFo&D~iBiiakq#MsjN(hXA{_F!UjMV~L7apA!}aUm8iYA~7vJ5#aet&kSh$p;x?F@q
zT7bujSI2@K64vkXx@3}2?@;k0wjk$!s6%p0OpHrVn1f-q<}S1J0Efx5MAv%u1UNLz
ziGIyJ(Z}J#$N$OqIiejDY8d5aUkY<LyK<NHWRC!c=aWku_nZ!ONVs*-dBx8Fho{a@
zq6M6T9By4H<`1b&ba>dh&aR6s)}b}jPqZaI&LQEUwl&+gAcw0P6iPy$afLdZ^WUCW
z%NXXc*^rNkX)>a!Fhi;;-axAlJA{}}!^8s|ak;4_iScQfIjQkwsYS(^`FYTa04!c!
zlv$Em6c4UmpyGM)>4~Mq#hHnD#bA-V__WkSP(=gbGUVi^$LC~br{-j4<mabMKF6-b
zs4;P)Jd(5A5KdrQF?oWZ3)E?TlWz+`Yc`=TP_eklzl9)TlNE%YfNMu)Mm_Gye}ojk
zb>L(L;Q~<A%gxGA0Co-o!-L6&tn!msIT#rxJF>_#T1-x4QRk|FN`4TTxKWO)0m@?#
Io49cr0QyQXsQ>@~

delta 1834
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

diff --git a/bob/bio/gmm/test/data/gmm_projector.hdf5 b/bob/bio/gmm/test/data/gmm_projector.hdf5
index 4c47be97a009e963d25301904a7420eced1b55e9..d39d6920f1e7d335eddb7ce2c2be0ba892aa0541 100644
GIT binary patch
literal 10256
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

literal 11176
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
M>nTFqIoQ%O0P^5SCIA2c

diff --git a/bob/bio/gmm/test/test_algorithms.py b/bob/bio/gmm/test/test_algorithms.py
index 7cb0bb5..e0375ec 100644
--- a/bob/bio/gmm/test/test_algorithms.py
+++ b/bob/bio/gmm/test/test_algorithms.py
@@ -24,6 +24,7 @@ import sys
 
 import numpy
 import pkg_resources
+import pytest
 
 import bob.bio.gmm
 import bob.io.base
@@ -32,9 +33,9 @@ import bob.learn.linear
 
 from bob.bio.base.test import utils
 
-logger = logging.getLogger("bob.bio.gmm")
+logger = logging.getLogger(__name__)
 
-regenerate_refs = False
+regenerate_refs = True
 
 seed_value = 5489
 
@@ -72,25 +73,30 @@ def _compare_complex(
             assert numpy.allclose(d, r, atol=1e-5)
 
 
+@pytest.mark.isolated_gmm
 def test_gmm():
-    temp_file = bob.io.base.test_utils.temporary_filename()
+    temp_file = (
+        "./temptest/test_file"  # TODO bob.io.base.test_utils.temporary_filename()
+    )
     gmm1 = bob.bio.base.load_resource(
-        "gmm", "algorithm", preferred_package="bob.bio.gmm"
+        "gmm", "bioalgorithm", preferred_package="bob.bio.gmm"
     )
-    assert isinstance(gmm1, bob.bio.gmm.algorithm.GMM)
-    assert isinstance(gmm1, bob.bio.base.algorithm.Algorithm)
-    assert gmm1.performs_projection
-    assert gmm1.requires_projector_training
-    assert not gmm1.use_projected_features_for_enrollment
-    assert not gmm1.split_training_features_by_client
-    assert not gmm1.requires_enroller_training
+    assert isinstance(gmm1, bob.bio.gmm.bioalgorithm.GMM)
+    assert isinstance(
+        gmm1, bob.bio.base.pipelines.vanilla_biometrics.abstract_classes.BioAlgorithm
+    )
+    # assert gmm1.performs_projection
+    # assert gmm1.requires_projector_training
+    # assert not gmm1.use_projected_features_for_enrollment
+    # assert not gmm1.split_training_features_by_client
+    # assert not gmm1.requires_enroller_training
 
     # create smaller GMM object
-    gmm2 = bob.bio.gmm.algorithm.GMM(
+    gmm2 = bob.bio.gmm.bioalgorithm.GMM(
         number_of_gaussians=2,
         kmeans_training_iterations=1,
-        gmm_training_iterations=1,
-        INIT_SEED=seed_value,
+        ubm_training_iterations=1,
+        init_seed=seed_value,
     )
 
     train_data = utils.random_training_set(
@@ -120,7 +126,7 @@ def test_gmm():
     # generate and project random feature
     feature = utils.random_array((20, 45), -5.0, 5.0, seed=84)
     projected = gmm1.project(feature)
-    assert isinstance(projected, bob.learn.em.GMMStats)
+    assert isinstance(projected, bob.learn.em.mixture.GMMStats)
     _compare(
         projected,
         pkg_resources.resource_filename("bob.bio.gmm.test", "data/gmm_projected.hdf5"),
@@ -131,7 +137,7 @@ def test_gmm():
     # enroll model from random features
     enroll = utils.random_training_set((20, 45), 5, -5.0, 5.0, seed=21)
     model = gmm1.enroll(enroll)
-    assert isinstance(model, bob.learn.em.GMMMachine)
+    assert isinstance(model, bob.learn.em.mixture.GMMMachine)
     _compare(
         model,
         pkg_resources.resource_filename("bob.bio.gmm.test", "data/gmm_model.hdf5"),
@@ -159,16 +165,18 @@ def test_gmm_regular():
     gmm1 = bob.bio.base.load_resource(
         "gmm-regular", "algorithm", preferred_package="bob.bio.gmm"
     )
-    assert isinstance(gmm1, bob.bio.gmm.algorithm.GMMRegular)
-    assert isinstance(gmm1, bob.bio.gmm.algorithm.GMM)
-    assert isinstance(gmm1, bob.bio.base.algorithm.Algorithm)
+    assert isinstance(gmm1, bob.bio.gmm.bioalgorithm.GMMRegular)
+    assert isinstance(gmm1, bob.bio.gmm.bioalgorithm.GMM)
+    assert isinstance(
+        gmm1, bob.bio.base.pipelines.vanilla_biometrics.abstract_classes.BioAlgorithm
+    )
     assert not gmm1.performs_projection
     assert not gmm1.requires_projector_training
     assert not gmm1.use_projected_features_for_enrollment
     assert gmm1.requires_enroller_training
 
     # create smaller GMM object
-    gmm2 = bob.bio.gmm.algorithm.GMMRegular(
+    gmm2 = bob.bio.gmm.bioalgorithm.GMMRegular(
         number_of_gaussians=2,
         kmeans_training_iterations=1,
         gmm_training_iterations=1,
@@ -202,7 +210,7 @@ def test_gmm_regular():
     # enroll model from random features
     enroll = utils.random_training_set((20, 45), 5, -5.0, 5.0, seed=21)
     model = gmm1.enroll(enroll)
-    assert isinstance(model, bob.learn.em.GMMMachine)
+    assert isinstance(model, bob.learn.em.mixture.GMMMachine)
     _compare(
         model,
         pkg_resources.resource_filename("bob.bio.gmm.test", "data/gmm_model.hdf5"),
diff --git a/setup.py b/setup.py
index 3b512f4..a69ee95 100644
--- a/setup.py
+++ b/setup.py
@@ -110,6 +110,10 @@ setup(
             "ivector-plda                = bob.bio.gmm.config.algorithm.ivector_plda:algorithm",
             "ivector-lda-wccn-plda = bob.bio.gmm.config.algorithm.ivector_lda_wccn_plda:algorithm",
         ],
+        "bob.bio.bioalgorithm": [
+            "gmm                            = bob.bio.gmm.config.bioalgorithm.gmm:bioalgorithm",
+            "gmm-regular               = bob.bio.gmm.config.bioalgorithm.gmm_regular:bioalgorithm",
+        ],
     },
     # Classifiers are important if you plan to distribute this package through
     # PyPI. You can find the complete list of classifiers that are valid and
-- 
GitLab