From 8a31c4b588681163f21b5d6dbcab099891cccb7b Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Fri, 23 May 2014 12:29:30 +0200
Subject: [PATCH] System is building, tests are running (still failing)

---
 buildout.cfg                              |   4 +
 setup.py                                  |  16 +-
 xbob/learn/misc/GaborWaveletTransform.cpp | 314 ++++++++
 xbob/learn/misc/kmeans.cpp                |   2 +-
 xbob/learn/misc/machine.cpp               |  35 +
 xbob/learn/misc/main.cpp                  |   3 +-
 xbob/learn/misc/test_bic.py               |  16 +-
 xbob/learn/misc/test_em.py                | 429 ++++++-----
 xbob/learn/misc/test_gaussian.py          | 133 ++--
 xbob/learn/misc/test_gmm.py               | 411 ++++++-----
 xbob/learn/misc/test_ivector.py           | 102 +--
 xbob/learn/misc/test_ivector_trainer.py   | 452 ++++++------
 xbob/learn/misc/test_jfa.py               | 669 +++++++++--------
 xbob/learn/misc/test_jfa_trainer.py       | 524 +++++++------
 xbob/learn/misc/test_kmeans.py            | 104 ++-
 xbob/learn/misc/test_kmeans_trainer.py    | 250 +++----
 xbob/learn/misc/test_linearscoring.py     | 231 +++---
 xbob/learn/misc/test_plda.py              | 826 ++++++++++-----------
 xbob/learn/misc/test_plda_trainer.py      | 847 +++++++++++-----------
 xbob/learn/misc/test_wiener.py            | 196 +++--
 xbob/learn/misc/test_wiener_trainer.py    |  87 ++-
 xbob/learn/misc/test_ztnorm.py            | 177 +++--
 22 files changed, 3069 insertions(+), 2759 deletions(-)
 create mode 100644 xbob/learn/misc/GaborWaveletTransform.cpp
 create mode 100644 xbob/learn/misc/machine.cpp

diff --git a/buildout.cfg b/buildout.cfg
index 69e4044..0579e05 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -10,7 +10,9 @@ extensions = xbob.buildout
 auto-checkout = *
 develop = src/xbob.extension
           src/xbob.blitz
+          src/xbob.core
           src/xbob.io.base
+          src/xbob.sp
           .
 
 ; options for xbob.buildout extension
@@ -22,7 +24,9 @@ prefixes = /idiap/group/torch5spro/releases/preview/install/linux-x86_64-release
 [sources]
 xbob.extension = git https://github.com/bioidiap/xbob.extension branch=prototype
 xbob.blitz = git https://github.com/bioidiap/xbob.blitz
+xbob.core = git https://github.com/bioidiap/xbob.core
 xbob.io.base = git https://github.com/bioidiap/xbob.io.base
+xbob.sp = git https://github.com/bioidiap/xbob.sp
 
 [scripts]
 recipe = xbob.buildout:scripts
diff --git a/setup.py b/setup.py
index 4dcce76..69d1d14 100644
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,9 @@ setup(
     install_requires=[
       'setuptools',
       'xbob.blitz',
+      'xbob.core',
       'xbob.io.base',
+      'xbob.sp',
       ],
 
     namespace_packages=[
@@ -53,7 +55,6 @@ setup(
         [
           "xbob/learn/misc/bic.cpp",
           "xbob/learn/misc/bic_trainer.cpp",
-          "xbob/learn/misc/blitz_numpy.cpp",
           "xbob/learn/misc/empca_trainer.cpp",
           "xbob/learn/misc/gabor.cpp",
           "xbob/learn/misc/gaussian.cpp",
@@ -65,16 +66,21 @@ setup(
           "xbob/learn/misc/jfa_trainer.cpp",
           "xbob/learn/misc/kmeans.cpp",
           "xbob/learn/misc/kmeans_trainer.cpp",
+          "xbob/learn/misc/machine.cpp",
           "xbob/learn/misc/linearscoring.cpp",
-          "xbob/learn/misc/main.cpp",
-          "xbob/learn/misc/ndarray.cpp",
-          "xbob/learn/misc/ndarray_numpy.cpp",
           "xbob/learn/misc/plda.cpp",
           "xbob/learn/misc/plda_trainer.cpp",
-          "xbob/learn/misc/tinyvector.cpp",
           "xbob/learn/misc/wiener.cpp",
           "xbob/learn/misc/wiener_trainer.cpp",
           "xbob/learn/misc/ztnorm.cpp",
+
+          # external requirements as boost::python bindings
+          "xbob/learn/misc/GaborWaveletTransform.cpp",
+          "xbob/learn/misc/blitz_numpy.cpp",
+          "xbob/learn/misc/ndarray.cpp",
+          "xbob/learn/misc/ndarray_numpy.cpp",
+          "xbob/learn/misc/tinyvector.cpp",
+
           "xbob/learn/misc/main.cpp",
         ],
         packages = packages,
diff --git a/xbob/learn/misc/GaborWaveletTransform.cpp b/xbob/learn/misc/GaborWaveletTransform.cpp
new file mode 100644
index 0000000..c1afb09
--- /dev/null
+++ b/xbob/learn/misc/GaborWaveletTransform.cpp
@@ -0,0 +1,314 @@
+/**
+ * @author Manuel Guenther <Manuel.Guenther@idiap.ch>
+ * @date 2012-02-27
+ *
+ * @brief Binds the Gabor wavelet transform
+ *
+ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
+ */
+
+#include "ndarray.h"
+
+#include <bob/core/array_type.h>
+
+#include <bob/ip/GaborWaveletTransform.h>
+#include <bob/sp/FFT2D.h>
+#include <blitz/array.h>
+
+#include "bob/core/cast.h"
+#include "bob/ip/color.h"
+
+template <class T>
+static inline const blitz::Array<std::complex<double>,2> complex_cast (bob::python::const_ndarray input){
+  blitz::Array<T,2> gray(input.type().shape[1],input.type().shape[2]);
+  bob::ip::rgb_to_gray(input.bz<T,3>(), gray);
+  return bob::core::array::cast<std::complex<double> >(gray);
+}
+
+static inline const blitz::Array<std::complex<double>, 2> convert_image(bob::python::const_ndarray input){
+  if (input.type().nd == 3){
+    // perform color type conversion
+    switch (input.type().dtype){
+      case bob::core::array::t_uint8: return complex_cast<uint8_t>(input);
+      case bob::core::array::t_uint16: return complex_cast<uint16_t>(input);
+      case bob::core::array::t_float64: return complex_cast<double>(input);
+      default: throw std::runtime_error("unsupported input data type");
+    }
+  } else {
+    switch (input.type().dtype){
+      case bob::core::array::t_uint8: return bob::core::array::cast<std::complex<double> >(input.bz<uint8_t,2>());
+      case bob::core::array::t_uint16: return bob::core::array::cast<std::complex<double> >(input.bz<uint16_t,2>());
+      case bob::core::array::t_float64: return bob::core::array::cast<std::complex<double> >(input.bz<double,2>());
+      case bob::core::array::t_complex128: return input.bz<std::complex<double>,2>();
+      default: throw std::runtime_error("unsupported input data type");
+    }
+  }
+}
+
+static inline void transform (bob::ip::GaborKernel& kernel, blitz::Array<std::complex<double>,2>& input, blitz::Array<std::complex<double>,2>& output){
+ // perform fft on input image
+  bob::sp::FFT2D fft(input.extent(0), input.extent(1));
+  blitz::Array<std::complex<double>,2> tmp(input.shape());
+  fft(input, output);
+
+  // apply the kernel in frequency domain
+  kernel.transform(output, tmp);
+
+  // perform ifft on the result
+  bob::sp::IFFT2D ifft(output.extent(0), output.extent(1));
+  ifft(tmp, output);
+}
+
+static void gabor_wavelet_transform_1 (bob::ip::GaborKernel& kernel, bob::python::const_ndarray input_image, bob::python::ndarray output_image){
+  // convert input image into complex type
+  blitz::Array<std::complex<double>,2> input = convert_image(input_image);
+  // cast output image to complex type
+  blitz::Array<std::complex<double>,2> output = output_image.bz<std::complex<double>,2>();
+  // transform input to output
+  transform(kernel, input, output);
+}
+
+static blitz::Array<std::complex<double>,2> gabor_wavelet_transform_2 (bob::ip::GaborKernel& kernel, bob::python::const_ndarray input_image){
+  // convert input ndarray to complex blitz array
+  blitz::Array<std::complex<double>,2> input = convert_image(input_image);
+  // allocate output array
+  blitz::Array<std::complex<double>,2> output(input.extent(0), input.extent(1));
+
+  // transform input to output
+  transform(kernel, input, output);
+
+  // return the nd array
+  return output;
+}
+
+
+static blitz::Array<std::complex<double>,3> empty_trafo_image (bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image){
+  int index = input_image.type().nd-2;
+  assert(index >= 0);
+  return blitz::Array<std::complex<double>,3>(gwt.numberOfKernels(), input_image.type().shape[index], input_image.type().shape[index+1]);
+}
+
+static void perform_gwt_1 (bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image, bob::python::ndarray output_trafo_image){
+  const blitz::Array<std::complex<double>,2>& image = convert_image(input_image);
+  blitz::Array<std::complex<double>,3> trafo_image = output_trafo_image.bz<std::complex<double>,3>();
+  gwt.performGWT(image, trafo_image);
+}
+
+static blitz::Array<std::complex<double>,3> perform_gwt_2 (bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image){
+  const blitz::Array<std::complex<double>,2>& image = convert_image(input_image);
+  blitz::Array<std::complex<double>,3> trafo_image(gwt.numberOfKernels(), image.shape()[0], image.shape()[1]);
+  gwt.performGWT(image, trafo_image);
+  return trafo_image;
+}
+
+static bob::python::ndarray empty_jet_image(bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image, bool include_phases){
+  const blitz::Array<std::complex<double>,2>& image = convert_image(input_image);
+  if (include_phases)
+    return bob::python::ndarray (bob::core::array::t_float64, image.extent(0), image.extent(1), 2, (int)gwt.numberOfKernels());
+  else
+    return bob::python::ndarray (bob::core::array::t_float64, image.extent(0), image.extent(1), (int)gwt.numberOfKernels());
+}
+
+static void compute_jets_1(bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image, bob::python::ndarray output_jet_image, bool normalized){
+  const blitz::Array<std::complex<double>,2>& image = convert_image(input_image);
+
+  if (output_jet_image.type().nd == 3){
+    // compute jet image with absolute values only
+    blitz::Array<double,3> jet_image = output_jet_image.bz<double,3>();
+    gwt.computeJetImage(image, jet_image, normalized);
+  } else if (output_jet_image.type().nd == 4){
+    blitz::Array<double,4> jet_image = output_jet_image.bz<double,4>();
+    gwt.computeJetImage(image, jet_image, normalized);
+  } else {
+    boost::format m("parameter `output_jet_image' has an unexpected shape: %s");
+    m % output_jet_image.type().str();
+    throw std::runtime_error(m.str());
+  }
+}
+
+static bob::python::ndarray compute_jets_2(bob::ip::GaborWaveletTransform& gwt, bob::python::const_ndarray input_image, bool include_phases, bool normalized){
+  bob::python::ndarray output_jet_image = empty_jet_image(gwt, input_image, include_phases);
+  compute_jets_1(gwt, input_image, output_jet_image, normalized);
+  return output_jet_image;
+}
+
+
+static void normalize_gabor_jet(bob::python::ndarray gabor_jet){
+  if (gabor_jet.type().nd == 1){
+    blitz::Array<double,1> jet(gabor_jet.bz<double,1>());
+    bob::ip::normalizeGaborJet(jet);
+  } else if (gabor_jet.type().nd == 2){
+    blitz::Array<double,2> jet(gabor_jet.bz<double,2>());
+    bob::ip::normalizeGaborJet(jet);
+  } else {
+    boost::format m("parameter `gabor_jet' has an unexpected shape: %s");
+    m % gabor_jet.type().str();
+    throw std::runtime_error(m.str());
+  }
+}
+
+void bind_ip_gabor_wavelet_transform() {
+  // bind Gabor Kernel class
+  boost::python::class_<bob::ip::GaborKernel, boost::shared_ptr<bob::ip::GaborKernel> >(
+    "GaborKernel",
+    "This class can be used to filter an image with a single Gabor wavelet.",
+    boost::python::no_init
+  )
+
+  .def(
+    boost::python::init< const blitz::TinyVector<int,2>&, const blitz::TinyVector<double,2>&, boost::python::optional <const double, const double, const bool, const double> >(
+      (
+        boost::python::arg("self"),
+        boost::python::arg("resolution"),
+        boost::python::arg("wavelet_frequency"),
+        boost::python::arg("sigma") = 2. * M_PI,
+        boost::python::arg("pow_of_k") = 0.,
+        boost::python::arg("dc_free") = true,
+        boost::python::arg("epsilon") = 1e-10
+      ),
+      "Initializes the Gabor wavelet of the given wavelet frequency to be used as a filter for the given image resolution. The optional parameters can be changed, but have useful default values."
+    )
+  )
+
+  .def(boost::python::init<bob::ip::GaborKernel&>((boost::python::arg("self"), boost::python::arg("other"))))
+  .def(boost::python::self == boost::python::self)
+  .def(boost::python::self != boost::python::self)
+
+  .def(
+    "__call__",
+    &gabor_wavelet_transform_1,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("output_image")),
+    "This function Gabor-filters the given input_image, which can be of any type, to the output image. The output image needs to have the same resolution as the input image and must be of complex type."
+  )
+
+  .def(
+    "__call__",
+    &gabor_wavelet_transform_2,
+    (boost::python::arg("self"), boost::python::arg("input_image")),
+    "This function Gabor-filters the given input_image, which can be of any type. The output image is of complex type. It will be automatically generated and returned."
+  );
+
+
+  // declare GWT class
+  boost::python::class_<bob::ip::GaborWaveletTransform, boost::shared_ptr<bob::ip::GaborWaveletTransform> >(
+    "GaborWaveletTransform",
+    "This class can be used to perform a Gabor wavelet transform from one image to an image of (normalized) Gabor jets or to a complex-valued multi-layer trafo image.",
+    boost::python::no_init
+  )
+
+  .def(
+    boost::python::init<boost::python::optional<int,int,double,double,double,double,bool> >(
+      (
+        boost::python::arg("self"),
+        boost::python::arg("number_of_scales") = 5,
+        boost::python::arg("number_of_angles") = 8,
+        boost::python::arg("sigma") = 2. * M_PI,
+        boost::python::arg("k_max") = M_PI / 2.,
+        boost::python::arg("k_fac") = 1./sqrt(2.),
+        boost::python::arg("pow_of_k") = 0.,
+        boost::python::arg("dc_free") = true
+      ),
+      "Initializes the Gabor wavelet transform by generating Gabor wavelets in number_of_scales different frequencies and number_of_angles different directions. The remaining parameters are parameters of the Gabor wavelets to be generated. "
+    )
+  )
+
+  .def(boost::python::init<bob::ip::GaborWaveletTransform&>((boost::python::arg("self"), boost::python::arg("other"))))
+  .def(boost::python::init<bob::io::HDF5File&>((boost::python::arg("self"), boost::python::arg("config"))))
+  .def(boost::python::self == boost::python::self)
+  .def(boost::python::self != boost::python::self)
+
+  .def(
+    "save",
+    &bob::ip::GaborWaveletTransform::save,
+    (boost::python::arg("self"), boost::python::arg("config")),
+    "Saves the parameterization of this Gabor wavelet transform to HDF5 file."
+  )
+
+  .def(
+    "load",
+    &bob::ip::GaborWaveletTransform::load,
+    (boost::python::arg("self"), boost::python::arg("config")),
+    "Loads the parameterization of this Gabor wavelet transform from HDF5 file."
+  )
+
+  .add_property(
+    "number_of_kernels",
+    &bob::ip::GaborWaveletTransform::numberOfKernels,
+    "The number of Gabor wavelets (i.e. number of directions times number of scales, i.e. the length of a Gabor jet, i.e. the number of layers of the trafo image) used in this Gabor wavelet transform."
+  )
+
+  .add_property(
+    "number_of_scales",
+    &bob::ip::GaborWaveletTransform::numberOfScales,
+    "The number of scales that this Gabor wavelet family holds."
+  )
+
+  .add_property(
+    "number_of_directions",
+    &bob::ip::GaborWaveletTransform::numberOfDirections,
+    "The number of directions that this Gabor wavelet family holds."
+  )
+
+  .def(
+    "empty_trafo_image",
+    &empty_trafo_image,
+    (boost::python::arg("self"), boost::python::arg("input_image")),
+    "This function creates an empty trafo image for the given input image. Use this function to generate the trafo image in the correct size and with the correct data type. In case you have to transform multiple images of the same size, this trafo image can be reused."
+  )
+
+  .def(
+    "perform_gwt",
+    &perform_gwt_1,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("output_trafo_image")),
+    "Performs a Gabor wavelet transform and fills the given Gabor wavelet transformed image (output_trafo_image)"
+  )
+
+  .def(
+    "perform_gwt",
+    &perform_gwt_2,
+    (boost::python::arg("self"), boost::python::arg("input_image")),
+    "Performs a Gabor wavelet transform and returns a Gabor wavelet transformed image"
+  )
+
+  .def(
+    "__call__",
+    &perform_gwt_1,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("output_trafo_image")),
+    "Performs a Gabor wavelet transform and fills the given Gabor wavelet transformed image (output_trafo_image)"
+   )
+
+  .def(
+    "__call__",
+    &perform_gwt_2,
+    (boost::python::arg("self"), boost::python::arg("input_image")),
+    "Performs a Gabor wavelet transform and returns a Gabor wavelet transformed image"
+  )
+
+  .def(
+    "empty_jet_image",
+    &empty_jet_image,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("include_phases")=true),
+    "This function creates an empty jet image (with or without Gabor phases) for the given input image. Use this function to generate the jet image in the correct size and with the correct data type. In case you have to transform multiple images of the same size, this jet image can be reused."
+  )
+
+  .def(
+    "compute_jets",
+    &compute_jets_1,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("output_jet_image"), boost::python::arg("normalized")=true),
+    "Performs a Gabor wavelet transform and fills given image of Gabor jets. If the normalized parameter is set to True (the default), the absolute parts of the Gabor jets are normalized to unit Euclidean length."
+  )
+
+  .def(
+    "compute_jets",
+    &compute_jets_2,
+    (boost::python::arg("self"), boost::python::arg("input_image"), boost::python::arg("include_phases")=true, boost::python::arg("normalized")=true),
+    "Performs a Gabor wavelet transform and returns the image of Gabor jets, with or without Gabor phases. If the normalized parameter is set to True (the default), the absolute parts of the Gabor jets are normalized to unit Euclidean length."
+  );
+
+  boost::python::def(
+    "normalize_gabor_jet",
+    &normalize_gabor_jet,
+    (boost::python::arg("gabor_jet")),
+    "Normalizes the Gabor jet (with or without phase) to unit Euclidean length."
+  );
+}
diff --git a/xbob/learn/misc/kmeans.cpp b/xbob/learn/misc/kmeans.cpp
index beaacb3..6ba49f7 100644
--- a/xbob/learn/misc/kmeans.cpp
+++ b/xbob/learn/misc/kmeans.cpp
@@ -5,7 +5,7 @@
  * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
  */
 
-#include "ndarray"
+#include "ndarray.h"
 
 #include <bob/machine/KMeansMachine.h>
 
diff --git a/xbob/learn/misc/machine.cpp b/xbob/learn/misc/machine.cpp
new file mode 100644
index 0000000..2f6f212
--- /dev/null
+++ b/xbob/learn/misc/machine.cpp
@@ -0,0 +1,35 @@
+/**
+ * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
+ * @date Tue Jul 26 15:11:33 2011 +0200
+ *
+ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
+ */
+
+#include "ndarray.h"
+#include <bob/machine/Machine.h>
+
+using namespace boost::python;
+
+static double forward(const bob::machine::Machine<blitz::Array<double,1>, double>& m,
+    bob::python::const_ndarray input) {
+  double output;
+  m.forward(input.bz<double,1>(), output);
+  return output;
+}
+
+static double forward_(const bob::machine::Machine<blitz::Array<double,1>, double>& m,
+    bob::python::const_ndarray input) {
+  double output;
+  m.forward_(input.bz<double,1>(), output);
+  return output;
+}
+
+void bind_machine_base()
+{
+  class_<bob::machine::Machine<blitz::Array<double,1>, double>, boost::noncopyable>("MachineDoubleBase",
+      "Root class for all Machine<blitz::Array<double,1>, double>", no_init)
+    .def("__call__", &forward_, (arg("self"), arg("input")), "Executes the machine on the given 1D numpy array of float64, and returns the output. NO CHECK is performed.")
+    .def("forward", &forward, (arg("self"), arg("input")), "Executes the machine on the given 1D numpy array of float64, and returns the output.")
+    .def("forward_", &forward_, (arg("self"), arg("input")), "Executes the machine on the given 1D numpy array of float64, and returns the output. NO CHECK is performed.")
+  ;
+}
diff --git a/xbob/learn/misc/main.cpp b/xbob/learn/misc/main.cpp
index 192b9cf..6058c65 100644
--- a/xbob/learn/misc/main.cpp
+++ b/xbob/learn/misc/main.cpp
@@ -13,6 +13,7 @@
 void bind_core_tinyvector();
 void bind_core_ndarray_numpy();
 void bind_core_bz_numpy();
+void bind_ip_gabor_wavelet_transform();
 
 /** machine bindings **/
 void bind_machine_base();
@@ -48,6 +49,7 @@ BOOST_PYTHON_MODULE(_library) {
   bind_core_tinyvector();
   bind_core_ndarray_numpy();
   bind_core_bz_numpy();
+  bind_ip_gabor_wavelet_transform();
 
   /** machine bindings **/
   bind_machine_base();
@@ -64,7 +66,6 @@ BOOST_PYTHON_MODULE(_library) {
   bind_machine_wiener();
 
   /** trainer bindings **/
-
   bind_trainer_gmm();
   bind_trainer_kmeans();
   bind_trainer_jfa();
diff --git a/xbob/learn/misc/test_bic.py b/xbob/learn/misc/test_bic.py
index beb924f..cad8fb7 100644
--- a/xbob/learn/misc/test_bic.py
+++ b/xbob/learn/misc/test_bic.py
@@ -3,15 +3,13 @@
 # Manuel Guenther <Manuel.Guenther@idiap.ch>
 # Thu Jun 14 14:45:06 CEST 2012
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Test BIC trainer and machine
 """
 
-import os, sys
-import unittest
-import bob
 import numpy
+from . import BICMachine, BICTrainer
 
 def equals(x, y, epsilon):
   return (abs(x - y) < epsilon).all()
@@ -43,8 +41,8 @@ class BICTrainerAndMachineTest(unittest.TestCase):
     intra_data, extra_data = self.training_data()
 
     # train BIC machine
-    machine = bob.machine.BICMachine()
-    trainer = bob.trainer.BICTrainer()
+    machine = BICMachine()
+    trainer = BICTrainer()
 
     # train machine with intrapersonal data only
     trainer.train(machine, intra_data, intra_data)
@@ -64,17 +62,17 @@ class BICTrainerAndMachineTest(unittest.TestCase):
     intra_data, extra_data = self.training_data()
 
     # train BIC machine
-    trainer = bob.trainer.BICTrainer(2,2)
+    trainer = BICTrainer(2,2)
 
     # The data are chosen such that the third eigenvalue is zero.
     # Hence, calculating rho (i.e., using the Distance From Feature Space) is impossible
-    machine = bob.machine.BICMachine(True)
+    machine = BICMachine(True)
     def should_raise():
       trainer.train(machine, intra_data, intra_data)
     self.assertRaises(RuntimeError, should_raise)
 
     # So, now without rho...
-    machine = bob.machine.BICMachine(False)
+    machine = BICMachine(False)
 
     # First, train the machine with intrapersonal data only
     trainer.train(machine, intra_data, intra_data)
diff --git a/xbob/learn/misc/test_em.py b/xbob/learn/misc/test_em.py
index 50bc8e0..eca6be6 100644
--- a/xbob/learn/misc/test_em.py
+++ b/xbob/learn/misc/test_em.py
@@ -7,26 +7,20 @@
 
 """Test trainer package
 """
-import os, sys
 import unittest
-import bob
-import random
 import numpy
-import pkg_resources
 
-def F(f, module=None):
-  """Returns the test file on the "data" subdirectory"""
-  if module is None:
-    return pkg_resources.resource_filename(__name__, os.path.join('data', f))
-  return pkg_resources.resource_filename('bob.%s.test' % module, 
-      os.path.join('data', f))
+import xbob.io.base
+from xbob.io.base.test_utils import datafile
+
+from . import KMeansMachine, GMMMachine
 
 def loadGMM():
   gmm = bob.machine.GMMMachine(2, 2)
 
-  gmm.weights = bob.io.load(F('gmm.init_weights.hdf5'))
-  gmm.means = bob.io.load(F('gmm.init_means.hdf5'))
-  gmm.variances = bob.io.load(F('gmm.init_variances.hdf5'))
+  gmm.weights = xbob.io.base.load(datafile('gmm.init_weights.hdf5', __name__))
+  gmm.means = xbob.io.base.load(datafile('gmm.init_means.hdf5', __name__))
+  gmm.variances = xbob.io.base.load(datafile('gmm.init_variances.hdf5', __name__))
   gmm.variance_threshold = numpy.array([0.001, 0.001], 'float64')
 
   return gmm
@@ -36,215 +30,212 @@ def equals(x, y, epsilon):
 
 class MyTrainer1(bob.trainer.KMeansTrainer):
   """Simple example of python trainer: """
-  def __init__(self):
-    bob.trainer.KMeansTrainer.__init__(self)
-  
+  def __init__():
+    bob.trainer.KMeansTrainer.__init__()
+
   def train(self, machine, data):
     a = numpy.ndarray((2, 2), 'float64')
     a[0, :] = data[1]
     a[1, :] = data[2]
     machine.means = a
 
-class GMMTest(unittest.TestCase):
-  """Performs various trainer tests."""
-      
-  def test01_gmm_ML(self):
-
-    # Trains a GMMMachine with ML_GMMTrainer
-    
-    ar = bob.io.load(F("faithful.torch3_f64.hdf5"))
-    
-    gmm = loadGMM()
-
-    ml_gmmtrainer = bob.trainer.ML_GMMTrainer(True, True, True)
-    ml_gmmtrainer.train(gmm, ar)
-
-    #config = bob.io.HDF5File(F('gmm_ML.hdf5", 'w'))
-    #gmm.save(config)
-
-    gmm_ref = bob.machine.GMMMachine(bob.io.HDF5File(F('gmm_ML.hdf5')))
-    gmm_ref_32bit_debug = bob.machine.GMMMachine(bob.io.HDF5File(F('gmm_ML_32bit_debug.hdf5')))
-    gmm_ref_32bit_release = bob.machine.GMMMachine(bob.io.HDF5File(F('gmm_ML_32bit_release.hdf5')))
-
-    self.assertTrue((gmm == gmm_ref) or (gmm == gmm_ref_32bit_release) or (gmm == gmm_ref_32bit_debug))
-
-  def test02_gmm_ML(self):
-
-    # Trains a GMMMachine with ML_GMMTrainer; compares to an old reference
-   
-    ar = bob.io.load(F('dataNormalized.hdf5')) 
-
-    # Initialize GMMMachine
-    gmm = bob.machine.GMMMachine(5, 45)
-    gmm.means = bob.io.load(F('meansAfterKMeans.hdf5')).astype('float64')
-    gmm.variances = bob.io.load(F('variancesAfterKMeans.hdf5')).astype('float64')
-    gmm.weights = numpy.exp(bob.io.load(F('weightsAfterKMeans.hdf5')).astype('float64'))
-   
-    threshold = 0.001
-    gmm.set_variance_thresholds(threshold)
-    
-    # Initialize ML Trainer
-    prior = 0.001
-    max_iter_gmm = 25
-    accuracy = 0.00001
-    ml_gmmtrainer = bob.trainer.ML_GMMTrainer(True, True, True, prior)
-    ml_gmmtrainer.max_iterations = max_iter_gmm
-    ml_gmmtrainer.convergence_threshold = accuracy
-
-    # Run ML
-    ml_gmmtrainer.train(gmm, ar)
-
-    # Test results
-    # Load torch3vision reference
-    meansML_ref = bob.io.load(F('meansAfterML.hdf5'))
-    variancesML_ref = bob.io.load(F('variancesAfterML.hdf5'))
-    weightsML_ref = bob.io.load(F('weightsAfterML.hdf5'))
-
-    # Compare to current results
-    self.assertTrue(equals(gmm.means, meansML_ref, 3e-3))
-    self.assertTrue(equals(gmm.variances, variancesML_ref, 3e-3))
-    self.assertTrue(equals(gmm.weights, weightsML_ref, 1e-4))
-    
-  def test03_gmm_MAP(self):
-
-    # Train a GMMMachine with MAP_GMMTrainer
-    
-    ar = bob.io.load(F('faithful.torch3_f64.hdf5'))
-    
-    gmm = bob.machine.GMMMachine(bob.io.HDF5File(F("gmm_ML.hdf5")))
-    gmmprior = bob.machine.GMMMachine(bob.io.HDF5File(F("gmm_ML.hdf5")))
-    
-    map_gmmtrainer = bob.trainer.MAP_GMMTrainer(16)
-    map_gmmtrainer.set_prior_gmm(gmmprior)
-    map_gmmtrainer.train(gmm, ar)
-
-    #config = bob.io.HDF5File(F('gmm_MAP.hdf5", 'w'))
-    #gmm.save(config)
-    
-    gmm_ref = bob.machine.GMMMachine(bob.io.HDF5File(F('gmm_MAP.hdf5')))
-    #gmm_ref_32bit_release = bob.machine.GMMMachine(bob.io.HDF5File(F('gmm_MAP_32bit_release.hdf5')))
-
-    self.assertTrue((equals(gmm.means,gmm_ref.means,1e-3) and equals(gmm.variances,gmm_ref.variances,1e-3) and equals(gmm.weights,gmm_ref.weights,1e-3)))
-    
-  def test04_gmm_MAP(self):
-
-    # Train a GMMMachine with MAP_GMMTrainer and compare with matlab reference
-
-    map_adapt = bob.trainer.MAP_GMMTrainer(4., True, False, False, 0.)
-    data = bob.io.load(F('data.hdf5', 'machine'))
-    data = data.reshape((1, data.shape[0])) # make a 2D array out of it
-    means = bob.io.load(F('means.hdf5', 'machine'))
-    variances = bob.io.load(F('variances.hdf5', 'machine'))
-    weights = bob.io.load(F('weights.hdf5', 'machine'))
-
-    gmm = bob.machine.GMMMachine(2,50)
-    gmm.means = means
-    gmm.variances = variances
-    gmm.weights = weights
-
-    map_adapt.set_prior_gmm(gmm)
-
-    gmm_adapted = bob.machine.GMMMachine(2,50)
-    gmm_adapted.means = means
-    gmm_adapted.variances = variances
-    gmm_adapted.weights = weights
-
-    map_adapt.max_iterations = 1
-    print(data.shape)
-    map_adapt.train(gmm_adapted, data)
-
-    new_means = bob.io.load(F('new_adapted_mean.hdf5'))
-
-    # Compare to matlab reference
-    self.assertTrue(equals(new_means[0,:], gmm_adapted.means[:,0], 1e-4))
-    self.assertTrue(equals(new_means[1,:], gmm_adapted.means[:,1], 1e-4))
-   
-  def test05_gmm_MAP(self):
-    
-    # Train a GMMMachine with MAP_GMMTrainer; compares to old reference
-   
-    ar = bob.io.load(F('dataforMAP.hdf5')) 
-
-    # Initialize GMMMachine
-    n_gaussians = 5
-    n_inputs = 45
-    prior_gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
-    prior_gmm.means = bob.io.load(F('meansAfterML.hdf5'))
-    prior_gmm.variances = bob.io.load(F('variancesAfterML.hdf5'))
-    prior_gmm.weights = bob.io.load(F('weightsAfterML.hdf5'))
-  
-    threshold = 0.001
-    prior_gmm.set_variance_thresholds(threshold)
-    
-    # Initialize MAP Trainer
-    relevance_factor = 0.1
-    prior = 0.001
-    max_iter_gmm = 1
-    accuracy = 0.00001
-    map_factor = 0.5
-    map_gmmtrainer = bob.trainer.MAP_GMMTrainer(relevance_factor, True, False, False, prior)
-    map_gmmtrainer.max_iterations = max_iter_gmm
-    map_gmmtrainer.convergence_threshold = accuracy
-    map_gmmtrainer.set_prior_gmm(prior_gmm) 
-    map_gmmtrainer.set_t3_map(map_factor); 
-
-    gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
-    gmm.set_variance_thresholds(threshold)
-
-    # Train
-    map_gmmtrainer.train(gmm, ar)
- 
-    # Test results
-    # Load torch3vision reference
-    meansMAP_ref = bob.io.load(F('meansAfterMAP.hdf5'))
-    variancesMAP_ref = bob.io.load(F('variancesAfterMAP.hdf5'))
-    weightsMAP_ref = bob.io.load(F('weightsAfterMAP.hdf5'))
-
-    # Compare to current results
-    # Gaps are quite large. This might be explained by the fact that there is no 
-    # adaptation of a given Gaussian in torch3 when the corresponding responsibilities
-    # are below the responsibilities threshold
-    self.assertTrue(equals(gmm.means, meansMAP_ref, 2e-1))
-    self.assertTrue(equals(gmm.variances, variancesMAP_ref, 1e-4))
-    self.assertTrue(equals(gmm.weights, weightsMAP_ref, 1e-4))
-
-  def test06_gmm_test(self):
-
-    # Tests a GMMMachine by computing scores against a model and compare to 
-    # an old reference
-   
-    ar = bob.io.load(F('dataforMAP.hdf5')) 
-
-    # Initialize GMMMachine
-    n_gaussians = 5
-    n_inputs = 45
-    gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
-    gmm.means = bob.io.load(F('meansAfterML.hdf5'))
-    gmm.variances = bob.io.load(F('variancesAfterML.hdf5'))
-    gmm.weights = bob.io.load(F('weightsAfterML.hdf5'))
-  
-    threshold = 0.001
-    gmm.set_variance_thresholds(threshold)
-    
-    # Test against the model
-    score_mean_ref = -1.50379e+06
-    score = 0.
-    for v in ar: score += gmm.forward(v)
-    score /= len(ar)
-  
-    # Compare current results to torch3vision
-    self.assertTrue(abs(score-score_mean_ref)/score_mean_ref<1e-4)
- 
-  def test07_custom_trainer(self):
-
-    # Custom python trainer
-    
-    ar = bob.io.load(F("faithful.torch3_f64.hdf5"))
-    
-    mytrainer = MyTrainer1()
-
-    machine = bob.machine.KMeansMachine(2, 2)
-    mytrainer.train(machine, ar)
-    
-    for i in range(0, 2):
-      self.assertTrue((ar[i+1] == machine.means[i, :]).all())
+def test_gmm_ML_1():
+
+  # Trains a GMMMachine with ML_GMMTrainer
+
+  ar = xbob.io.base.load(datafile("faithful.torch3_f64.hdf5", __name__))
+
+  gmm = loadGMM()
+
+  ml_gmmtrainer = bob.trainer.ML_GMMTrainer(True, True, True)
+  ml_gmmtrainer.train(gmm, ar)
+
+  #config = xbob.io.base.HDF5File(datafile('gmm_ML.hdf5", __name__), 'w')
+  #gmm.save(config)
+
+  gmm_ref = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile('gmm_ML.hdf5', __name__)))
+  gmm_ref_32bit_debug = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile('gmm_ML_32bit_debug.hdf5', __name__)))
+  gmm_ref_32bit_release = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile('gmm_ML_32bit_release.hdf5', __name__)))
+
+  assert (gmm == gmm_ref) or (gmm == gmm_ref_32bit_release) or (gmm == gmm_ref_32bit_debug)
+
+def test_gmm_ML_2():
+
+  # Trains a GMMMachine with ML_GMMTrainer; compares to an old reference
+
+  ar = xbob.io.base.load(datafile('dataNormalized.hdf5', __name__))
+
+  # Initialize GMMMachine
+  gmm = bob.machine.GMMMachine(5, 45)
+  gmm.means = xbob.io.base.load(datafile('meansAfterKMeans.hdf5', __name__)).astype('float64')
+  gmm.variances = xbob.io.base.load(datafile('variancesAfterKMeans.hdf5', __name__)).astype('float64')
+  gmm.weights = numpy.exp(xbob.io.base.load(datafile('weightsAfterKMeans.hdf5', __name__)).astype('float64'))
+
+  threshold = 0.001
+  gmm.set_variance_thresholds(threshold)
+
+  # Initialize ML Trainer
+  prior = 0.001
+  max_iter_gmm = 25
+  accuracy = 0.00001
+  ml_gmmtrainer = bob.trainer.ML_GMMTrainer(True, True, True, prior)
+  ml_gmmtrainer.max_iterations = max_iter_gmm
+  ml_gmmtrainer.convergence_threshold = accuracy
+
+  # Run ML
+  ml_gmmtrainer.train(gmm, ar)
+
+  # Test results
+  # Load torch3vision reference
+  meansML_ref = xbob.io.base.load(datafile('meansAfterML.hdf5', __name__))
+  variancesML_ref = xbob.io.base.load(datafile('variancesAfterML.hdf5', __name__))
+  weightsML_ref = xbob.io.base.load(datafile('weightsAfterML.hdf5', __name__))
+
+  # Compare to current results
+  assert equals(gmm.means, meansML_ref, 3e-3)
+  assert equals(gmm.variances, variancesML_ref, 3e-3)
+  assert equals(gmm.weights, weightsML_ref, 1e-4)
+
+def test_gmm_MAP_1():
+
+  # Train a GMMMachine with MAP_GMMTrainer
+
+  ar = xbob.io.base.load(datafile('faithful.torch3_f64.hdf5', __name__))
+
+  gmm = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile("gmm_ML.hdf5", __name__)))
+  gmmprior = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile("gmm_ML.hdf5", __name__)))
+
+  map_gmmtrainer = bob.trainer.MAP_GMMTrainer(16)
+  map_gmmtrainer.set_prior_gmm(gmmprior)
+  map_gmmtrainer.train(gmm, ar)
+
+  #config = xbob.io.base.HDF5File(datafile('gmm_MAP.hdf5", 'w', __name__))
+  #gmm.save(config)
+
+  gmm_ref = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile('gmm_MAP.hdf5', __name__)))
+  #gmm_ref_32bit_release = bob.machine.GMMMachine(xbob.io.base.HDF5File(datafile('gmm_MAP_32bit_release.hdf5', __name__)))
+
+  assert (equals(gmm.means,gmm_ref.means,1e-3) and equals(gmm.variances,gmm_ref.variances,1e-3) and equals(gmm.weights,gmm_ref.weights,1e-3))
+
+def test_gmm_MAP_2():
+
+  # Train a GMMMachine with MAP_GMMTrainer and compare with matlab reference
+
+  map_adapt = bob.trainer.MAP_GMMTrainer(4., True, False, False, 0.)
+  data = xbob.io.base.load(datafile('data.hdf5', 'machine', __name__))
+  data = data.reshape((1, data.shape[0])) # make a 2D array out of it
+  means = xbob.io.base.load(datafile('means.hdf5', 'machine', __name__))
+  variances = xbob.io.base.load(datafile('variances.hdf5', 'machine', __name__))
+  weights = xbob.io.base.load(datafile('weights.hdf5', 'machine', __name__))
+
+  gmm = bob.machine.GMMMachine(2,50)
+  gmm.means = means
+  gmm.variances = variances
+  gmm.weights = weights
+
+  map_adapt.set_prior_gmm(gmm)
+
+  gmm_adapted = bob.machine.GMMMachine(2,50)
+  gmm_adapted.means = means
+  gmm_adapted.variances = variances
+  gmm_adapted.weights = weights
+
+  map_adapt.max_iterations = 1
+  print(data.shape)
+  map_adapt.train(gmm_adapted, data)
+
+  new_means = xbob.io.base.load(datafile('new_adapted_mean.hdf5', __name__))
+
+  # Compare to matlab reference
+  assert equals(new_means[0,:], gmm_adapted.means[:,0], 1e-4)
+  assert equals(new_means[1,:], gmm_adapted.means[:,1], 1e-4)
+
+def test_gmm_MAP_3():
+
+  # Train a GMMMachine with MAP_GMMTrainer; compares to old reference
+
+  ar = xbob.io.base.load(datafile('dataforMAP.hdf5', __name__))
+
+  # Initialize GMMMachine
+  n_gaussians = 5
+  n_inputs = 45
+  prior_gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
+  prior_gmm.means = xbob.io.base.load(datafile('meansAfterML.hdf5', __name__))
+  prior_gmm.variances = xbob.io.base.load(datafile('variancesAfterML.hdf5', __name__))
+  prior_gmm.weights = xbob.io.base.load(datafile('weightsAfterML.hdf5', __name__))
+
+  threshold = 0.001
+  prior_gmm.set_variance_thresholds(threshold)
+
+  # Initialize MAP Trainer
+  relevance_factor = 0.1
+  prior = 0.001
+  max_iter_gmm = 1
+  accuracy = 0.00001
+  map_factor = 0.5
+  map_gmmtrainer = bob.trainer.MAP_GMMTrainer(relevance_factor, True, False, False, prior)
+  map_gmmtrainer.max_iterations = max_iter_gmm
+  map_gmmtrainer.convergence_threshold = accuracy
+  map_gmmtrainer.set_prior_gmm(prior_gmm)
+  map_gmmtrainer.set_t3_map(map_factor);
+
+  gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
+  gmm.set_variance_thresholds(threshold)
+
+  # Train
+  map_gmmtrainer.train(gmm, ar)
+
+  # Test results
+  # Load torch3vision reference
+  meansMAP_ref = xbob.io.base.load(datafile('meansAfterMAP.hdf5', __name__))
+  variancesMAP_ref = xbob.io.base.load(datafile('variancesAfterMAP.hdf5', __name__))
+  weightsMAP_ref = xbob.io.base.load(datafile('weightsAfterMAP.hdf5', __name__))
+
+  # Compare to current results
+  # Gaps are quite large. This might be explained by the fact that there is no
+  # adaptation of a given Gaussian in torch3 when the corresponding responsibilities
+  # are below the responsibilities threshold
+  assert equals(gmm.means, meansMAP_ref, 2e-1)
+  assert equals(gmm.variances, variancesMAP_ref, 1e-4)
+  assert equals(gmm.weights, weightsMAP_ref, 1e-4)
+
+def test_gmm_test():
+
+  # Tests a GMMMachine by computing scores against a model and compare to
+  # an old reference
+
+  ar = xbob.io.base.load(datafile('dataforMAP.hdf5', __name__))
+
+  # Initialize GMMMachine
+  n_gaussians = 5
+  n_inputs = 45
+  gmm = bob.machine.GMMMachine(n_gaussians, n_inputs)
+  gmm.means = xbob.io.base.load(datafile('meansAfterML.hdf5', __name__))
+  gmm.variances = xbob.io.base.load(datafile('variancesAfterML.hdf5', __name__))
+  gmm.weights = xbob.io.base.load(datafile('weightsAfterML.hdf5', __name__))
+
+  threshold = 0.001
+  gmm.set_variance_thresholds(threshold)
+
+  # Test against the model
+  score_mean_ref = -1.50379e+06
+  score = 0.
+  for v in ar: score += gmm.forward(v)
+  score /= len(ar)
+
+  # Compare current results to torch3vision
+  assert abs(score-score_mean_ref)/score_mean_ref<1e-4
+
+def test_custom_trainer():
+
+  # Custom python trainer
+
+  ar = xbob.io.base.load(datafile("faithful.torch3_f64.hdf5", __name__))
+
+  mytrainer = MyTrainer1()
+
+  machine = bob.machine.KMeansMachine(2, 2)
+  mytrainer.train(machine, ar)
+
+  for i in range(0, 2):
+    assert (ar[i+1] == machine.means[i, :]).all()
diff --git a/xbob/learn/misc/test_gaussian.py b/xbob/learn/misc/test_gaussian.py
index c4b8cc7..95e18b0 100644
--- a/xbob/learn/misc/test_gaussian.py
+++ b/xbob/learn/misc/test_gaussian.py
@@ -8,83 +8,82 @@
 """Tests the Gaussian machine
 """
 
-import os, sys
-import unittest
-import bob
+import os
 import numpy
 import tempfile
 
+import xbob.io.base
+
+from . import Gaussian
+
 def equals(x, y, epsilon):
   return (abs(x - y) < epsilon)
 
-class GaussianMachineTest(unittest.TestCase):
-  """Performs various Gaussian machine tests."""
-
-  def test01_GaussianNormal(self):
-    # Test the likelihood computation of a simple normal Gaussian
-    gaussian = bob.machine.Gaussian(2)
-    # By default, initialized with zero mean and unit variance
-    logLH = gaussian.log_likelihood(numpy.array([0.4, 0.2], 'float64'))
-    self.assertTrue( equals(logLH, -1.93787706641, 1e-10))
+def test_GaussianNormal():
+  # Test the likelihood computation of a simple normal Gaussian
+  gaussian = Gaussian(2)
+  # By default, initialized with zero mean and unit variance
+  logLH = gaussian.log_likelihood(numpy.array([0.4, 0.2], 'float64'))
+  assert equals(logLH, -1.93787706641, 1e-10)
 
-  def test02_GaussianMachine(self):
-    # Test a GaussianMachine more thoroughly
+def test_GaussianMachine():
+  # Test a GaussianMachine more thoroughly
 
-    # Initializes a Gaussian with zero mean and unit variance
-    g = bob.machine.Gaussian(3)
-    self.assertTrue( (g.mean == 0.0).all() )
-    self.assertTrue( (g.variance == 1.0).all() )
-    self.assertTrue( g.dim_d == 3 )
+  # Initializes a Gaussian with zero mean and unit variance
+  g = Gaussian(3)
+  assert (g.mean == 0.0).all()
+  assert (g.variance == 1.0).all()
+  assert g.dim_d == 3
 
-    # Set and check mean, variance, variance thresholds
-    mean     = numpy.array([0, 1, 2], 'float64')
-    variance = numpy.array([3, 2, 1], 'float64')
-    g.mean     = mean
-    g.variance = variance
-    g.set_variance_thresholds(0.0005)
-    self.assertTrue( (g.mean == mean).all() )
-    self.assertTrue( (g.variance == variance).all() )
-    self.assertTrue( (g.variance_thresholds == 0.0005).all() )
+  # Set and check mean, variance, variance thresholds
+  mean     = numpy.array([0, 1, 2], 'float64')
+  variance = numpy.array([3, 2, 1], 'float64')
+  g.mean     = mean
+  g.variance = variance
+  g.set_variance_thresholds(0.0005)
+  assert (g.mean == mean).all()
+  assert (g.variance == variance).all()
+  assert (g.variance_thresholds == 0.0005).all()
 
-    # Save and read from file
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    g.save(bob.io.HDF5File(filename, 'w'))
-    g_loaded = bob.machine.Gaussian(bob.io.HDF5File(filename))
-    self.assertTrue( g == g_loaded )
-    self.assertFalse( g != g_loaded )
-    self.assertTrue(g.is_similar_to(g_loaded))
-    # Make them different
-    g_loaded.set_variance_thresholds(0.001)
-    self.assertFalse( g == g_loaded )
-    self.assertTrue( g != g_loaded )
+  # Save and read from file
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  g.save(xbob.io.base.HDF5File(filename, 'w'))
+  g_loaded = Gaussian(xbob.io.base.HDF5File(filename))
+  assert g == g_loaded
+  assert (g != g_loaded ) is False
+  assert g.is_similar_to(g_loaded)
+  # Make them different
+  g_loaded.set_variance_thresholds(0.001)
+  assert (g == g_loaded ) is False
+  assert g != g_loaded
 
-    # Check likelihood computation
-    sample1 = numpy.array([0, 1, 2], 'float64')
-    sample2 = numpy.array([1, 2, 3], 'float64')
-    sample3 = numpy.array([2, 3, 4], 'float64')
-    ref1 = -3.652695334228046
-    ref2 = -4.569362000894712
-    ref3 = -7.319362000894712
-    eps = 1e-10
-    self.assertTrue( equals(g.log_likelihood(sample1), ref1, eps) )
-    self.assertTrue( equals(g.forward(sample1), ref1, eps) )
-    self.assertTrue( equals(g.log_likelihood(sample2), ref2, eps) )
-    self.assertTrue( equals(g.forward(sample2), ref2, eps) )
-    self.assertTrue( equals(g.log_likelihood(sample3), ref3, eps) )
-    self.assertTrue( equals(g.forward(sample3), ref3, eps) )
+  # Check likelihood computation
+  sample1 = numpy.array([0, 1, 2], 'float64')
+  sample2 = numpy.array([1, 2, 3], 'float64')
+  sample3 = numpy.array([2, 3, 4], 'float64')
+  ref1 = -3.652695334228046
+  ref2 = -4.569362000894712
+  ref3 = -7.319362000894712
+  eps = 1e-10
+  assert equals(g.log_likelihood(sample1), ref1, eps)
+  assert equals(g.forward(sample1), ref1, eps)
+  assert equals(g.log_likelihood(sample2), ref2, eps)
+  assert equals(g.forward(sample2), ref2, eps)
+  assert equals(g.log_likelihood(sample3), ref3, eps)
+  assert equals(g.forward(sample3), ref3, eps)
 
-    # Check resize and assignment
-    g.shape = (6,)
-    self.assertTrue( g.shape == (6,) )
-    g.resize(5)
-    self.assertTrue( g.dim_d == 5 )
-    g2 = bob.machine.Gaussian()
-    g2 = g
-    self.assertTrue( g == g2 )
-    self.assertFalse( g != g2 )
-    g3 = bob.machine.Gaussian(g)
-    self.assertTrue( g == g3 )
-    self.assertFalse( g != g3 )
+  # Check resize and assignment
+  g.shape = (6,)
+  assert g.shape == (6,)
+  g.resize(5)
+  assert g.dim_d == 5
+  g2 = Gaussian()
+  g2 = g
+  assert g == g2
+  assert (g != g2 ) is False
+  g3 = Gaussian(g)
+  assert g == g3
+  assert (g != g3 ) is False
 
-    # Clean-up
-    os.unlink(filename)
+  # Clean-up
+  os.unlink(filename)
diff --git a/xbob/learn/misc/test_gmm.py b/xbob/learn/misc/test_gmm.py
index a951723..2c95483 100644
--- a/xbob/learn/misc/test_gmm.py
+++ b/xbob/learn/misc/test_gmm.py
@@ -3,217 +3,212 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Thu Feb 16 17:57:10 2012 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests the GMM machine and the GMMStats container
 """
 
-import os, sys
-import unittest
-import bob
+import os
 import numpy
 import tempfile
-import pkg_resources
-
-def F(f):
-  """Returns the test file on the "data" subdirectory"""
-  return pkg_resources.resource_filename(__name__, os.path.join('data', f))
-
-class GMMMachineTest(unittest.TestCase):
-  """Performs various GMM machine-related tests."""
-
-  def test01_GMMStats(self):
-    # Test a GMMStats
-
-    # Initializes a GMMStats
-    gs = bob.machine.GMMStats(2,3)
-    log_likelihood = -3.
-    T = 57
-    n = numpy.array([4.37, 5.31], 'float64')
-    sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
-    sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
-    gs.log_likelihood = log_likelihood
-    gs.t = T
-    gs.n = n
-    gs.sum_px = sumpx
-    gs.sum_pxx = sumpxx
-    self.assertTrue( gs.log_likelihood == log_likelihood )
-    self.assertTrue( gs.t == T )
-    self.assertTrue( (gs.n == n).all() )
-    self.assertTrue( (gs.sum_px == sumpx).all() )
-    self.assertTrue( (gs.sum_pxx == sumpxx).all() )
-
-    # Saves and reads from file
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    gs.save(bob.io.HDF5File(filename, 'w'))
-    gs_loaded = bob.machine.GMMStats(bob.io.HDF5File(filename))
-    self.assertTrue( gs == gs_loaded )
-    self.assertFalse( gs != gs_loaded )
-    self.assertTrue( gs.is_similar_to(gs_loaded) )
-    # Makes them different
-    gs_loaded.t = 58
-    self.assertFalse( gs == gs_loaded )
-    self.assertTrue( gs != gs_loaded )
-    self.assertFalse( gs.is_similar_to(gs_loaded) )
-    # Accumulates from another GMMStats
-    gs2 = bob.machine.GMMStats(2,3)
-    gs2.log_likelihood = log_likelihood
-    gs2.t = T
-    gs2.n = n
-    gs2.sum_px = sumpx
-    gs2.sum_pxx = sumpxx
-    gs2 += gs
-    eps = 1e-8
-    self.assertTrue( gs2.log_likelihood == 2*log_likelihood )
-    self.assertTrue( gs2.t == 2*T )
-    self.assertTrue( numpy.allclose(gs2.n, 2*n, eps) )
-    self.assertTrue( numpy.allclose(gs2.sum_px, 2*sumpx, eps) )
-    self.assertTrue( numpy.allclose(gs2.sum_pxx, 2*sumpxx, eps) )
-
-    # Reinit and checks for zeros
-    gs_loaded.init()
-    self.assertTrue( gs_loaded.log_likelihood == 0 )
-    self.assertTrue( gs_loaded.t == 0 )
-    self.assertTrue( (gs_loaded.n == 0).all() )
-    self.assertTrue( (gs_loaded.sum_px == 0).all() )
-    self.assertTrue( (gs_loaded.sum_pxx == 0).all() )
-    # Resize and checks size
-    gs_loaded.resize(4,5)
-    self.assertTrue( gs_loaded.sum_px.shape[0] == 4)
-    self.assertTrue( gs_loaded.sum_px.shape[1] == 5)
-
-    # Clean-up
-    os.unlink(filename)
-
-  def test02_GMMMachine(self):
-    # Test a GMMMachine basic features
-
-    weights   = numpy.array([0.5, 0.5], 'float64')
-    weights2   = numpy.array([0.6, 0.4], 'float64')
-    means     = numpy.array([[3, 70, 0], [4, 72, 0]], 'float64')
-    means2     = numpy.array([[3, 7, 0], [4, 72, 0]], 'float64')
-    variances = numpy.array([[1, 10, 1], [2, 5, 2]], 'float64')
-    variances2 = numpy.array([[10, 10, 1], [2, 5, 2]], 'float64')
-    varianceThresholds = numpy.array([[0, 0, 0], [0, 0, 0]], 'float64')
-    varianceThresholds2 = numpy.array([[0.0005, 0.0005, 0.0005], [0, 0, 0]], 'float64')
-
-    # Initializes a GMMMachine
-    gmm = bob.machine.GMMMachine(2,3)
-    # Sets the weights, means, variances and varianceThresholds and
-    # Checks correctness
-    gmm.weights = weights
-    gmm.means = means
-    gmm.variances = variances
-    gmm.varianceThresholds = varianceThresholds
-    self.assertTrue( gmm.dim_c == 2 )
-    self.assertTrue( gmm.dim_d == 3 )
-    self.assertTrue( (gmm.weights == weights).all() )
-    self.assertTrue( (gmm.means == means).all() )
-    self.assertTrue( (gmm.variances == variances).all() )
-    self.assertTrue( (gmm.variance_thresholds == varianceThresholds).all() )
-
-    # Checks supervector-like accesses
-    self.assertTrue( (gmm.mean_supervector == means.reshape(means.size)).all() )
-    self.assertTrue( (gmm.variance_supervector == variances.reshape(variances.size)).all() )
-    newMeans = numpy.array([[3, 70, 2], [4, 72, 2]], 'float64')
-    newVariances = numpy.array([[1, 1, 1], [2, 2, 2]], 'float64')
-    gmm.mean_supervector = newMeans.reshape(newMeans.size)
-    gmm.variance_supervector = newVariances.reshape(newVariances.size)
-    self.assertTrue( (gmm.mean_supervector == newMeans.reshape(newMeans.size)).all() )
-    self.assertTrue( (gmm.variance_supervector == newVariances.reshape(newVariances.size)).all() )
-
-    # Checks particular varianceThresholds-related methods
-    varianceThresholds1D = numpy.array([0.3, 1, 0.5], 'float64')
-    gmm.set_variance_thresholds(varianceThresholds1D)
-    self.assertTrue( (gmm.variance_thresholds[0,:] == varianceThresholds1D).all() )
-    self.assertTrue( (gmm.variance_thresholds[1,:] == varianceThresholds1D).all() )
-    gmm.set_variance_thresholds(0.005)
-    self.assertTrue( (gmm.variance_thresholds == 0.005).all() )
-
-    # Checks Gaussians access
-    self.assertTrue( (gmm.update_gaussian(0).mean == newMeans[0,:]).all() )
-    self.assertTrue( (gmm.update_gaussian(1).mean == newMeans[1,:]).all() )
-    self.assertTrue( (gmm.update_gaussian(0).variance == newVariances[0,:]).all() )
-    self.assertTrue( (gmm.update_gaussian(1).variance == newVariances[1,:]).all() )
-
-    # Checks resize
-    gmm.shape = (5,6)
-    self.assertTrue( gmm.shape == (5,6) )
-    gmm.resize(4,5)
-    self.assertTrue( gmm.dim_c == 4 )
-    self.assertTrue( gmm.dim_d == 5 )
-
-    # Checks comparison
-    gmm2 = bob.machine.GMMMachine(gmm)
-    gmm3 = bob.machine.GMMMachine(2,3)
-    gmm3.weights = weights2
-    gmm3.means = means
-    gmm3.variances = variances
-    gmm3.varianceThresholds = varianceThresholds
-    gmm4 = bob.machine.GMMMachine(2,3)
-    gmm4.weights = weights
-    gmm4.means = means2
-    gmm4.variances = variances
-    gmm4.varianceThresholds = varianceThresholds
-    gmm5 = bob.machine.GMMMachine(2,3)
-    gmm5.weights = weights
-    gmm5.means = means
-    gmm5.variances = variances2
-    gmm5.varianceThresholds = varianceThresholds
-    gmm6 = bob.machine.GMMMachine(2,3)
-    gmm6.weights = weights
-    gmm6.means = means
-    gmm6.variances = variances
-    gmm6.varianceThresholds = varianceThresholds2
-
-    self.assertTrue( gmm == gmm2)
-    self.assertFalse( gmm != gmm2)
-    self.assertTrue( gmm.is_similar_to(gmm2) )
-    self.assertTrue( gmm != gmm3)
-    self.assertFalse( gmm == gmm3)
-    self.assertFalse( gmm.is_similar_to(gmm3) )
-    self.assertTrue( gmm != gmm4)
-    self.assertFalse( gmm == gmm4)
-    self.assertFalse( gmm.is_similar_to(gmm4) )
-    self.assertTrue( gmm != gmm5)
-    self.assertFalse( gmm == gmm5)
-    self.assertFalse( gmm.is_similar_to(gmm5) )
-    self.assertTrue( gmm != gmm6)
-    self.assertFalse( gmm == gmm6)
-    self.assertFalse( gmm.is_similar_to(gmm6) )
-
-  def test03_GMMMachine(self):
-    # Test a GMMMachine (statistics)
-
-    arrayset = bob.io.load(F("faithful.torch3_f64.hdf5"))
-    gmm = bob.machine.GMMMachine(2, 2)
-    gmm.weights   = numpy.array([0.5, 0.5], 'float64')
-    gmm.means     = numpy.array([[3, 70], [4, 72]], 'float64')
-    gmm.variances = numpy.array([[1, 10], [2, 5]], 'float64')
-    gmm.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
-
-    stats = bob.machine.GMMStats(2, 2)
-    gmm.acc_statistics(arrayset, stats)
-
-    stats_ref = bob.machine.GMMStats(bob.io.HDF5File(F("stats.hdf5")))
-
-    self.assertTrue(stats.t == stats_ref.t)
-    self.assertTrue( numpy.allclose(stats.n, stats_ref.n, atol=1e-10) )
-    #self.assertTrue( numpy.array_equal(stats.sumPx, stats_ref.sumPx) )
-    #Note AA: precision error above
-    self.assertTrue ( numpy.allclose(stats.sum_px, stats_ref.sum_px, atol=1e-10) )
-    self.assertTrue( numpy.allclose(stats.sum_pxx, stats_ref.sum_pxx, atol=1e-10) )
-
-  def test04_GMMMachine(self):
-    # Test a GMMMachine (log-likelihood computation)
-
-    data = bob.io.load(F('data.hdf5'))
-    gmm = bob.machine.GMMMachine(2, 50)
-    gmm.weights   = bob.io.load(F('weights.hdf5'))
-    gmm.means     = bob.io.load(F('means.hdf5'))
-    gmm.variances = bob.io.load(F('variances.hdf5'))
-
-    # Compare the log-likelihood with the one obtained using Chris Matlab
-    # implementation
-    matlab_ll_ref = -2.361583051672024e+02
-    self.assertTrue( abs(gmm(data) - matlab_ll_ref) < 1e-10)
+
+import xbob.io.base
+from xbob.io.base.test_utils import datafile
+
+from . import GMMStats, GMMMachine
+
+def test_GMMStats():
+  # Test a GMMStats
+
+  # Initializes a GMMStats
+  gs = GMMStats(2,3)
+  log_likelihood = -3.
+  T = 57
+  n = numpy.array([4.37, 5.31], 'float64')
+  sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
+  sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
+  gs.log_likelihood = log_likelihood
+  gs.t = T
+  gs.n = n
+  gs.sum_px = sumpx
+  gs.sum_pxx = sumpxx
+  assert gs.log_likelihood == log_likelihood
+  assert gs.t == T
+  assert (gs.n == n).all()
+  assert (gs.sum_px == sumpx).all()
+  assert (gs.sum_pxx == sumpxx).all()
+
+  # Saves and reads from file
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  gs.save(xbob.io.base.HDF5File(filename, 'w'))
+  gs_loaded = GMMStats(xbob.io.base.HDF5File(filename))
+  assert gs == gs_loaded
+  assert (gs != gs_loaded ) is False
+  assert gs.is_similar_to(gs_loaded)
+  # Makes them different
+  gs_loaded.t = 58
+  assert (gs == gs_loaded ) is False
+  assert gs != gs_loaded
+  assert (gs.is_similar_to(gs_loaded)) is False
+  # Accumulates from another GMMStats
+  gs2 = GMMStats(2,3)
+  gs2.log_likelihood = log_likelihood
+  gs2.t = T
+  gs2.n = n
+  gs2.sum_px = sumpx
+  gs2.sum_pxx = sumpxx
+  gs2 += gs
+  eps = 1e-8
+  assert gs2.log_likelihood == 2*log_likelihood
+  assert gs2.t == 2*T
+  assert numpy.allclose(gs2.n, 2*n, eps)
+  assert numpy.allclose(gs2.sum_px, 2*sumpx, eps)
+  assert numpy.allclose(gs2.sum_pxx, 2*sumpxx, eps)
+
+  # Reinit and checks for zeros
+  gs_loaded.init()
+  assert gs_loaded.log_likelihood == 0
+  assert gs_loaded.t == 0
+  assert (gs_loaded.n == 0).all()
+  assert (gs_loaded.sum_px == 0).all()
+  assert (gs_loaded.sum_pxx == 0).all()
+  # Resize and checks size
+  gs_loaded.resize(4,5)
+  assert gs_loaded.sum_px.shape[0] == 4
+  assert gs_loaded.sum_px.shape[1] == 5
+
+  # Clean-up
+  os.unlink(filename)
+
+def test_GMMMachine_1():
+  # Test a GMMMachine basic features
+
+  weights   = numpy.array([0.5, 0.5], 'float64')
+  weights2   = numpy.array([0.6, 0.4], 'float64')
+  means     = numpy.array([[3, 70, 0], [4, 72, 0]], 'float64')
+  means2     = numpy.array([[3, 7, 0], [4, 72, 0]], 'float64')
+  variances = numpy.array([[1, 10, 1], [2, 5, 2]], 'float64')
+  variances2 = numpy.array([[10, 10, 1], [2, 5, 2]], 'float64')
+  varianceThresholds = numpy.array([[0, 0, 0], [0, 0, 0]], 'float64')
+  varianceThresholds2 = numpy.array([[0.0005, 0.0005, 0.0005], [0, 0, 0]], 'float64')
+
+  # Initializes a GMMMachine
+  gmm = GMMMachine(2,3)
+  # Sets the weights, means, variances and varianceThresholds and
+  # Checks correctness
+  gmm.weights = weights
+  gmm.means = means
+  gmm.variances = variances
+  gmm.varianceThresholds = varianceThresholds
+  assert gmm.dim_c == 2
+  assert gmm.dim_d == 3
+  assert (gmm.weights == weights).all()
+  assert (gmm.means == means).all()
+  assert (gmm.variances == variances).all()
+  assert (gmm.variance_thresholds == varianceThresholds).all()
+
+  # Checks supervector-like accesses
+  assert (gmm.mean_supervector == means.reshape(means.size)).all()
+  assert (gmm.variance_supervector == variances.reshape(variances.size)).all()
+  newMeans = numpy.array([[3, 70, 2], [4, 72, 2]], 'float64')
+  newVariances = numpy.array([[1, 1, 1], [2, 2, 2]], 'float64')
+  gmm.mean_supervector = newMeans.reshape(newMeans.size)
+  gmm.variance_supervector = newVariances.reshape(newVariances.size)
+  assert (gmm.mean_supervector == newMeans.reshape(newMeans.size)).all()
+  assert (gmm.variance_supervector == newVariances.reshape(newVariances.size)).all()
+
+  # Checks particular varianceThresholds-related methods
+  varianceThresholds1D = numpy.array([0.3, 1, 0.5], 'float64')
+  gmm.set_variance_thresholds(varianceThresholds1D)
+  assert (gmm.variance_thresholds[0,:] == varianceThresholds1D).all()
+  assert (gmm.variance_thresholds[1,:] == varianceThresholds1D).all()
+  gmm.set_variance_thresholds(0.005)
+  assert (gmm.variance_thresholds == 0.005).all()
+
+  # Checks Gaussians access
+  assert (gmm.update_gaussian(0).mean == newMeans[0,:]).all()
+  assert (gmm.update_gaussian(1).mean == newMeans[1,:]).all()
+  assert (gmm.update_gaussian(0).variance == newVariances[0,:]).all()
+  assert (gmm.update_gaussian(1).variance == newVariances[1,:]).all()
+
+  # Checks resize
+  gmm.shape = (5,6)
+  assert gmm.shape == (5,6)
+  gmm.resize(4,5)
+  assert gmm.dim_c == 4
+  assert gmm.dim_d == 5
+
+  # Checks comparison
+  gmm2 = GMMMachine(gmm)
+  gmm3 = GMMMachine(2,3)
+  gmm3.weights = weights2
+  gmm3.means = means
+  gmm3.variances = variances
+  gmm3.varianceThresholds = varianceThresholds
+  gmm4 = GMMMachine(2,3)
+  gmm4.weights = weights
+  gmm4.means = means2
+  gmm4.variances = variances
+  gmm4.varianceThresholds = varianceThresholds
+  gmm5 = GMMMachine(2,3)
+  gmm5.weights = weights
+  gmm5.means = means
+  gmm5.variances = variances2
+  gmm5.varianceThresholds = varianceThresholds
+  gmm6 = GMMMachine(2,3)
+  gmm6.weights = weights
+  gmm6.means = means
+  gmm6.variances = variances
+  gmm6.varianceThresholds = varianceThresholds2
+
+  assert gmm == gmm2
+  assert (gmm != gmm2) is False
+  assert gmm.is_similar_to(gmm2)
+  assert gmm != gmm3
+  assert (gmm == gmm3) is False
+  assert gmm.is_similar_to(gmm3) is False
+  assert gmm != gmm4
+  assert (gmm == gmm4) is False
+  assert gmm.is_similar_to(gmm4) is False
+  assert gmm != gmm5
+  assert (gmm == gmm5) is False
+  assert gmm.is_similar_to(gmm5) is False
+  assert gmm != gmm6
+  assert (gmm == gmm6) is False
+  assert gmm.is_similar_to(gmm6) is False
+
+def test_GMMMachine_2():
+  # Test a GMMMachine (statistics)
+
+  arrayset = xbob.io.base.load(datafile("faithful.torch3_f64.hdf5", __name__))
+  gmm = GMMMachine(2, 2)
+  gmm.weights   = numpy.array([0.5, 0.5], 'float64')
+  gmm.means     = numpy.array([[3, 70], [4, 72]], 'float64')
+  gmm.variances = numpy.array([[1, 10], [2, 5]], 'float64')
+  gmm.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
+
+  stats = GMMStats(2, 2)
+  gmm.acc_statistics(arrayset, stats)
+
+  stats_ref = GMMStats(xbob.io.base.HDF5File(datafile("stats.hdf5", __name__)))
+
+  assert stats.t == stats_ref.t
+  assert numpy.allclose(stats.n, stats_ref.n, atol=1e-10)
+  #assert numpy.array_equal(stats.sumPx, stats_ref.sumPx)
+  #Note AA: precision error above
+  assert numpy.allclose(stats.sum_px, stats_ref.sum_px, atol=1e-10)
+  assert numpy.allclose(stats.sum_pxx, stats_ref.sum_pxx, atol=1e-10)
+
+def test_GMMMachine_3():
+  # Test a GMMMachine (log-likelihood computation)
+
+  data = xbob.io.base.load(datafile('data.hdf5', __name__))
+  gmm = GMMMachine(2, 50)
+  gmm.weights   = xbob.io.base.load(datafile('weights.hdf5', __name__))
+  gmm.means     = xbob.io.base.load(datafile('means.hdf5', __name__))
+  gmm.variances = xbob.io.base.load(datafile('variances.hdf5', __name__))
+
+  # Compare the log-likelihood with the one obtained using Chris Matlab
+  # implementation
+  matlab_ll_ref = -2.361583051672024e+02
+  assert abs(gmm(data) - matlab_ll_ref) < 1e-10
diff --git a/xbob/learn/misc/test_ivector.py b/xbob/learn/misc/test_ivector.py
index eaa6506..f111042 100644
--- a/xbob/learn/misc/test_ivector.py
+++ b/xbob/learn/misc/test_ivector.py
@@ -3,14 +3,17 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Mon Apr 2 11:19:00 2013 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 
 """Tests the I-Vector machine
 """
 
-import unittest
-import bob, numpy, numpy.linalg, numpy.random
+import numpy
+import numpy.linalg
+import numpy.random
+
+from . import GMMMachine, GMMStats, IVectorMachine
 
 
 ### Test class inspired by an implementation of Chris McCool
@@ -53,7 +56,7 @@ class IVectorMachinePy():
 
   def get_ubm(self):
     return self.m_ubm
- 
+
   def set_t(self, t):
     # @warning: no dimensions check
     self.m_t = t
@@ -62,7 +65,7 @@ class IVectorMachinePy():
 
   def get_t(self):
     return self.m_t
-    
+
   def set_sigma(self, sigma):
     # @warning: no dimensions check
     self.m_sigma = sigma
@@ -71,7 +74,7 @@ class IVectorMachinePy():
 
   def get_sigma(self):
     return self.m_sigma
-  
+
 
   def _get_TtSigmaInv_Fnorm(self, N, F):
     # Initialization
@@ -94,12 +97,12 @@ class IVectorMachinePy():
     dim_c = self.m_ubm.dim_c
     dim_d = self.m_ubm.dim_d
 
-    TtSigmaInvNT = numpy.eye(self.m_dim_t, dtype=numpy.float64) 
+    TtSigmaInvNT = numpy.eye(self.m_dim_t, dtype=numpy.float64)
     for c in range(dim_c):
       TtSigmaInvNT = TtSigmaInvNT + self.m_cache_TtSigmaInvT[c] * N[c]
 
     return TtSigmaInvNT
- 
+
   def forward(self, gmmstats):
     if self.m_ubm and not (self.m_t == None) and not (self.m_sigma == None):
       N = gmmstats.n
@@ -111,46 +114,43 @@ class IVectorMachinePy():
       return numpy.linalg.solve(TtSigmaInvNT, TtSigmaInv_Fnorm)
 
 
-
-class IVectorTests(unittest.TestCase):
-
-  def test01_machine(self):
-    # Ubm
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = numpy.array([0.4,0.6])
-    ubm.means = numpy.array([[1.,7,4],[4,5,3]])
-    ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
-
-    # Defines GMMStats
-    gs = bob.machine.GMMStats(2,3)
-    log_likelihood = -3. 
-    T = 1 
-    n = numpy.array([0.4, 0.6], numpy.float64)
-    sumpx = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
-    sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
-    gs.log_likelihood = log_likelihood
-    gs.t = T 
-    gs.n = n 
-    gs.sum_px = sumpx
-    gs.sum_pxx = sumpxx
-
-    # IVector (Python)
-    m = IVectorMachinePy(ubm, 2)
-    t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
-    m.set_t(t)
-    sigma = numpy.array([1.,2.,1.,3.,2.,4.])
-    m.set_sigma(sigma)
-
-    wij_ref = numpy.array([-0.04213415, 0.21463343]) # Reference from original Chris implementation
-    wij = m.forward(gs)
-    self.assertTrue(numpy.allclose(wij_ref, wij, 1e-5))
-
-    # IVector (C++)
-    mc = bob.machine.IVectorMachine(ubm, 2)
-    mc.t = t
-    mc.sigma = sigma
-
-    wij_ref = numpy.array([-0.04213415, 0.21463343]) # Reference from original Chris implementation
-    wij = mc.forward(gs)
-    self.assertTrue(numpy.allclose(wij_ref, wij, 1e-5))
-
+def test_machine():
+
+  # Ubm
+  ubm = GMMMachine(2,3)
+  ubm.weights = numpy.array([0.4,0.6])
+  ubm.means = numpy.array([[1.,7,4],[4,5,3]])
+  ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
+
+  # Defines GMMStats
+  gs = GMMStats(2,3)
+  log_likelihood = -3.
+  T = 1
+  n = numpy.array([0.4, 0.6], numpy.float64)
+  sumpx = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
+  sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
+  gs.log_likelihood = log_likelihood
+  gs.t = T
+  gs.n = n
+  gs.sum_px = sumpx
+  gs.sum_pxx = sumpxx
+
+  # IVector (Python)
+  m = IVectorMachinePy(ubm, 2)
+  t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
+  m.set_t(t)
+  sigma = numpy.array([1.,2.,1.,3.,2.,4.])
+  m.set_sigma(sigma)
+
+  wij_ref = numpy.array([-0.04213415, 0.21463343]) # Reference from original Chris implementation
+  wij = m.forward(gs)
+  assert numpy.allclose(wij_ref, wij, 1e-5)
+
+  # IVector (C++)
+  mc = IVectorMachine(ubm, 2)
+  mc.t = t
+  mc.sigma = sigma
+
+  wij_ref = numpy.array([-0.04213415, 0.21463343]) # Reference from original Chris implementation
+  wij = mc.forward(gs)
+  assert numpy.allclose(wij_ref, wij, 1e-5)
diff --git a/xbob/learn/misc/test_ivector_trainer.py b/xbob/learn/misc/test_ivector_trainer.py
index 60d72aa..2e047e3 100644
--- a/xbob/learn/misc/test_ivector_trainer.py
+++ b/xbob/learn/misc/test_ivector_trainer.py
@@ -2,13 +2,16 @@
 # vim: set fileencoding=utf-8 :
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests the I-Vector trainer
 """
 
-import unittest
-import bob, numpy, numpy.linalg, numpy.random
+import numpy
+import numpy.linalg
+import numpy.random
+
+from . import GMMMachine, GMMStats, IVectorMachine, IVectorTrainer
 
 ### Test class inspired by an implementation of Chris McCool
 ### Chris McCool (chris.mccool@nicta.com.au)
@@ -137,227 +140,224 @@ class IVectorTrainerPy():
       i += 1
 
 
-class IVectorTests(unittest.TestCase):
-
-  def test01_trainer_nosigma(self):
-    # Ubm
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = numpy.array([0.4,0.6])
-    ubm.means = numpy.array([[1.,7,4],[4,5,3]])
-    ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
-
-    # Defines GMMStats
-    gs1 = bob.machine.GMMStats(2,3)
-    log_likelihood1 = -3.
-    T1 = 1
-    n1 = numpy.array([0.4, 0.6], numpy.float64)
-    sumpx1 = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
-    sumpxx1 = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
-    gs1.log_likelihood = log_likelihood1
-    gs1.t = T1
-    gs1.n = n1
-    gs1.sum_px = sumpx1
-    gs1.sum_pxx = sumpxx1
-
-    gs2 = bob.machine.GMMStats(2,3)
-    log_likelihood2 = -4.
-    T2 = 1
-    n2 = numpy.array([0.2, 0.8], numpy.float64)
-    sumpx2 = numpy.array([[2., 1., 3.], [3., 4.1, 3.2]], numpy.float64)
-    sumpxx2 = numpy.array([[12., 15., 25.], [39., 51., 62.]], numpy.float64)
-    gs2.log_likelihood = log_likelihood2
-    gs2.t = T2
-    gs2.n = n2
-    gs2.sum_px = sumpx2
-    gs2.sum_pxx = sumpxx2
-
-    data = [gs1, gs2]
-
-
-    acc_Nij_Sigma_wij2_ref1  = {0: numpy.array([[ 0.03202305, -0.02947769], [-0.02947769,  0.0561132 ]]),
-                               1: numpy.array([[ 0.07953279, -0.07829414], [-0.07829414,  0.13814242]])}
-    acc_Fnorm_Sigma_wij_ref1 = {0: numpy.array([[-0.29622691,  0.61411796], [ 0.09391764, -0.27955961], [-0.39014455,  0.89367757]]),
-                               1: numpy.array([[ 0.04695882, -0.13977981], [-0.05718673,  0.24159665], [-0.17098161,  0.47326585]])}
-    acc_Snorm_ref1           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
-    N_ref1                   = numpy.array([0.6, 1.4])
-    t_ref1                   = numpy.array([[  1.59543739, 11.78239235], [ -3.20130371, -6.66379081], [  4.79674111, 18.44618316],
-                                            [ -0.91765407, -1.5319461 ], [  2.26805901,  3.03434944], [  2.76600031,  4.9935962 ]])
-
-    acc_Nij_Sigma_wij2_ref2  = {0: numpy.array([[ 0.37558389, -0.15405228], [-0.15405228,  0.1421269 ]]),
-                               1: numpy.array([[ 1.02076081, -0.57683953], [-0.57683953,  0.53912239]])}
-    acc_Fnorm_Sigma_wij_ref2 = {0: numpy.array([[-1.1261668 ,  1.46496753], [-0.03579289, -0.37875811], [-1.09037391,  1.84372565]]),
-                               1: numpy.array([[-0.01789645, -0.18937906], [ 0.35221084,  0.15854126], [-0.10004552,  0.72559036]])}
-    acc_Snorm_ref2           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
-    N_ref2                   = numpy.array([0.6, 1.4])
-    t_ref2                   = numpy.array([[  2.2133685,  12.70654597], [ -2.13959381, -4.98404887], [  4.35296231, 17.69059484],
-                                            [ -0.54644055, -0.93594252], [  1.29308324,  1.67762053], [  1.67583072,  3.13894546]])
-    acc_Nij_Sigma_wij2_ref = [acc_Nij_Sigma_wij2_ref1, acc_Nij_Sigma_wij2_ref2]
-    acc_Fnorm_Sigma_wij_ref = [acc_Fnorm_Sigma_wij_ref1, acc_Fnorm_Sigma_wij_ref2]
-    acc_Snorm_ref = [acc_Snorm_ref1, acc_Snorm_ref2]
-    N_ref = [N_ref1, N_ref2]
-    t_ref = [t_ref1, t_ref2]
-
-    # Python implementation
-    # Machine
-    m = bob.machine.IVectorMachine(ubm, 2)
-    t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
-    sigma = numpy.array([1.,2.,1.,3.,2.,4.])
-
-    # Initialization
-    trainer = IVectorTrainerPy()
-    trainer.initialize(m, data)
-    m.t = t
-    m.sigma = sigma
-    for it in range(2):
-      # E-Step
-      trainer.e_step(m, data)
-      for k in acc_Nij_Sigma_wij2_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.m_acc_Nij_Sigma_wij2[k], 1e-5))
-      for k in acc_Fnorm_Sigma_wij_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.m_acc_Fnorm_Sigma_wij[k], 1e-5))
-      self.assertTrue(numpy.allclose(acc_Snorm_ref[it], trainer.m_acc_Snorm, 1e-5))
-      self.assertTrue(numpy.allclose(N_ref[it], trainer.m_N, 1e-5))
-
-      # M-Step
-      trainer.m_step(m, data)
-      self.assertTrue(numpy.allclose(t_ref[it], m.t, 1e-5))
-
-    # C++ implementation
-    # Machine
-    m = bob.machine.IVectorMachine(ubm, 2)
-
-    # Initialization
-    trainer = bob.trainer.IVectorTrainer()
-    trainer.initialize(m, data)
-    m.t = t
-    m.sigma = sigma
-    for it in range(2):
-      # E-Step
-      trainer.e_step(m, data)
-      for k in acc_Nij_Sigma_wij2_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.acc_nij_wij2[k], 1e-5))
-      for k in acc_Fnorm_Sigma_wij_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.acc_fnormij_wij[k], 1e-5))
-
-      # M-Step
-      trainer.m_step(m, data)
-      self.assertTrue(numpy.allclose(t_ref[it], m.t, 1e-5))
-
-
-  def test02_trainer_update_sigma(self):
-    # Ubm
-    dim_c = 2
-    dim_d = 3
-    ubm = bob.machine.GMMMachine(dim_c,dim_d)
-    ubm.weights = numpy.array([0.4,0.6])
-    ubm.means = numpy.array([[1.,7,4],[4,5,3]])
-    ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
-
-    # Defines GMMStats
-    gs1 = bob.machine.GMMStats(dim_c,dim_d)
-    log_likelihood1 = -3.
-    T1 = 1
-    n1 = numpy.array([0.4, 0.6], numpy.float64)
-    sumpx1 = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
-    sumpxx1 = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
-    gs1.log_likelihood = log_likelihood1
-    gs1.t = T1
-    gs1.n = n1
-    gs1.sum_px = sumpx1
-    gs1.sum_pxx = sumpxx1
-
-    gs2 = bob.machine.GMMStats(dim_c,dim_d)
-    log_likelihood2 = -4.
-    T2 = 1
-    n2 = numpy.array([0.2, 0.8], numpy.float64)
-    sumpx2 = numpy.array([[2., 1., 3.], [3., 4.1, 3.2]], numpy.float64)
-    sumpxx2 = numpy.array([[12., 15., 25.], [39., 51., 62.]], numpy.float64)
-    gs2.log_likelihood = log_likelihood2
-    gs2.t = T2
-    gs2.n = n2
-    gs2.sum_px = sumpx2
-    gs2.sum_pxx = sumpxx2
-
-    data = [gs1, gs2]
-
-    # Reference values
-    acc_Nij_Sigma_wij2_ref1  = {0: numpy.array([[ 0.03202305, -0.02947769], [-0.02947769,  0.0561132 ]]),
-                                1: numpy.array([[ 0.07953279, -0.07829414], [-0.07829414,  0.13814242]])}
-    acc_Fnorm_Sigma_wij_ref1 = {0: numpy.array([[-0.29622691,  0.61411796], [ 0.09391764, -0.27955961], [-0.39014455,  0.89367757]]),
-                                1: numpy.array([[ 0.04695882, -0.13977981], [-0.05718673,  0.24159665], [-0.17098161,  0.47326585]])}
-    acc_Snorm_ref1           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
-    N_ref1                   = numpy.array([0.6, 1.4])
-    t_ref1                   = numpy.array([[  1.59543739, 11.78239235], [ -3.20130371, -6.66379081], [  4.79674111, 18.44618316],
-                                            [ -0.91765407, -1.5319461 ], [  2.26805901,  3.03434944], [  2.76600031,  4.9935962 ]])
-    sigma_ref1               = numpy.array([ 16.39472121, 34.72955353,  3.3108037, 43.73496916, 38.85472445, 68.22116903])
-
-    acc_Nij_Sigma_wij2_ref2  = {0: numpy.array([[ 0.50807426, -0.11907756], [-0.11907756,  0.12336544]]),
-                                1: numpy.array([[ 1.18602399, -0.2835859 ], [-0.2835859 ,  0.39440498]])}
-    acc_Fnorm_Sigma_wij_ref2 = {0: numpy.array([[ 0.07221453,  1.1189786 ], [-0.08681275, -0.35396112], [ 0.15902728,  1.47293972]]),
-                                1: numpy.array([[-0.04340637, -0.17698056], [ 0.10662127,  0.21484933],[ 0.13116645,  0.64474271]])}
-    acc_Snorm_ref2           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
-    N_ref2                   = numpy.array([0.6, 1.4])
-    t_ref2                   = numpy.array([[  2.93105054, 11.89961223], [ -1.08988119, -3.92120757], [  4.02093173, 15.82081981],
-                                            [ -0.17376634, -0.57366984], [  0.26585634,  0.73589952], [  0.60557877,   2.07014704]])
-    sigma_ref2               = numpy.array([5.12154025e+00, 3.48623823e+01, 1.00000000e-05, 4.37792350e+01, 3.91525332e+01, 6.85613258e+01])
-
-    acc_Nij_Sigma_wij2_ref = [acc_Nij_Sigma_wij2_ref1, acc_Nij_Sigma_wij2_ref2]
-    acc_Fnorm_Sigma_wij_ref = [acc_Fnorm_Sigma_wij_ref1, acc_Fnorm_Sigma_wij_ref2]
-    acc_Snorm_ref = [acc_Snorm_ref1, acc_Snorm_ref2]
-    N_ref = [N_ref1, N_ref2]
-    t_ref = [t_ref1, t_ref2]
-    sigma_ref = [sigma_ref1, sigma_ref2]
-
-
-    # Python implementation
-    # Machine
-    m = bob.machine.IVectorMachine(ubm, 2)
-    t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
-    sigma = numpy.array([1.,2.,1.,3.,2.,4.])
-
-    # Initialization
-    trainer = IVectorTrainerPy(sigma_update=True)
-    trainer.initialize(m, data)
-    m.t = t
-    m.sigma = sigma
-    for it in range(2):
-      # E-Step
-      trainer.e_step(m, data)
-      for k in acc_Nij_Sigma_wij2_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.m_acc_Nij_Sigma_wij2[k], 1e-5))
-      for k in acc_Fnorm_Sigma_wij_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.m_acc_Fnorm_Sigma_wij[k], 1e-5))
-      self.assertTrue(numpy.allclose(acc_Snorm_ref[it], trainer.m_acc_Snorm, 1e-5))
-      self.assertTrue(numpy.allclose(N_ref[it], trainer.m_N, 1e-5))
-
-      # M-Step
-      trainer.m_step(m, data)
-      self.assertTrue(numpy.allclose(t_ref[it], m.t, 1e-5))
-      self.assertTrue(numpy.allclose(sigma_ref[it], m.sigma, 1e-5))
-
-
-    # C++ implementation
-    # Machine
-    m = bob.machine.IVectorMachine(ubm, 2)
-    m.variance_threshold = 1e-5
-
-    # Initialization
-    trainer = bob.trainer.IVectorTrainer(update_sigma=True)
-    trainer.initialize(m, data)
-    m.t = t
-    m.sigma = sigma
-    for it in range(2):
-      # E-Step
-      trainer.e_step(m, data)
-      for k in acc_Nij_Sigma_wij2_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.acc_nij_wij2[k], 1e-5))
-      for k in acc_Fnorm_Sigma_wij_ref[it]:
-        self.assertTrue(numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.acc_fnormij_wij[k], 1e-5))
-      self.assertTrue(numpy.allclose(acc_Snorm_ref[it].reshape(dim_c,dim_d), trainer.acc_snormij, 1e-5))
-      self.assertTrue(numpy.allclose(N_ref[it], trainer.acc_nij, 1e-5))
-
-      # M-Step
-      trainer.m_step(m, data)
-      self.assertTrue(numpy.allclose(t_ref[it], m.t, 1e-5))
-      self.assertTrue(numpy.allclose(sigma_ref[it], m.sigma, 1e-5))
+def test_trainer_nosigma():
+  # Ubm
+  ubm = GMMMachine(2,3)
+  ubm.weights = numpy.array([0.4,0.6])
+  ubm.means = numpy.array([[1.,7,4],[4,5,3]])
+  ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
+
+  # Defines GMMStats
+  gs1 = GMMStats(2,3)
+  log_likelihood1 = -3.
+  T1 = 1
+  n1 = numpy.array([0.4, 0.6], numpy.float64)
+  sumpx1 = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
+  sumpxx1 = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
+  gs1.log_likelihood = log_likelihood1
+  gs1.t = T1
+  gs1.n = n1
+  gs1.sum_px = sumpx1
+  gs1.sum_pxx = sumpxx1
+
+  gs2 = GMMStats(2,3)
+  log_likelihood2 = -4.
+  T2 = 1
+  n2 = numpy.array([0.2, 0.8], numpy.float64)
+  sumpx2 = numpy.array([[2., 1., 3.], [3., 4.1, 3.2]], numpy.float64)
+  sumpxx2 = numpy.array([[12., 15., 25.], [39., 51., 62.]], numpy.float64)
+  gs2.log_likelihood = log_likelihood2
+  gs2.t = T2
+  gs2.n = n2
+  gs2.sum_px = sumpx2
+  gs2.sum_pxx = sumpxx2
+
+  data = [gs1, gs2]
+
+
+  acc_Nij_Sigma_wij2_ref1  = {0: numpy.array([[ 0.03202305, -0.02947769], [-0.02947769,  0.0561132 ]]),
+                             1: numpy.array([[ 0.07953279, -0.07829414], [-0.07829414,  0.13814242]])}
+  acc_Fnorm_Sigma_wij_ref1 = {0: numpy.array([[-0.29622691,  0.61411796], [ 0.09391764, -0.27955961], [-0.39014455,  0.89367757]]),
+                             1: numpy.array([[ 0.04695882, -0.13977981], [-0.05718673,  0.24159665], [-0.17098161,  0.47326585]])}
+  acc_Snorm_ref1           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
+  N_ref1                   = numpy.array([0.6, 1.4])
+  t_ref1                   = numpy.array([[  1.59543739, 11.78239235], [ -3.20130371, -6.66379081], [  4.79674111, 18.44618316],
+                                          [ -0.91765407, -1.5319461 ], [  2.26805901,  3.03434944], [  2.76600031,  4.9935962 ]])
+
+  acc_Nij_Sigma_wij2_ref2  = {0: numpy.array([[ 0.37558389, -0.15405228], [-0.15405228,  0.1421269 ]]),
+                             1: numpy.array([[ 1.02076081, -0.57683953], [-0.57683953,  0.53912239]])}
+  acc_Fnorm_Sigma_wij_ref2 = {0: numpy.array([[-1.1261668 ,  1.46496753], [-0.03579289, -0.37875811], [-1.09037391,  1.84372565]]),
+                             1: numpy.array([[-0.01789645, -0.18937906], [ 0.35221084,  0.15854126], [-0.10004552,  0.72559036]])}
+  acc_Snorm_ref2           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
+  N_ref2                   = numpy.array([0.6, 1.4])
+  t_ref2                   = numpy.array([[  2.2133685,  12.70654597], [ -2.13959381, -4.98404887], [  4.35296231, 17.69059484],
+                                          [ -0.54644055, -0.93594252], [  1.29308324,  1.67762053], [  1.67583072,  3.13894546]])
+  acc_Nij_Sigma_wij2_ref = [acc_Nij_Sigma_wij2_ref1, acc_Nij_Sigma_wij2_ref2]
+  acc_Fnorm_Sigma_wij_ref = [acc_Fnorm_Sigma_wij_ref1, acc_Fnorm_Sigma_wij_ref2]
+  acc_Snorm_ref = [acc_Snorm_ref1, acc_Snorm_ref2]
+  N_ref = [N_ref1, N_ref2]
+  t_ref = [t_ref1, t_ref2]
+
+  # Python implementation
+  # Machine
+  m = IVectorMachine(ubm, 2)
+  t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
+  sigma = numpy.array([1.,2.,1.,3.,2.,4.])
+
+  # Initialization
+  trainer = IVectorTrainerPy()
+  trainer.initialize(m, data)
+  m.t = t
+  m.sigma = sigma
+  for it in range(2):
+    # E-Step
+    trainer.e_step(m, data)
+    for k in acc_Nij_Sigma_wij2_ref[it]:
+      assert numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.m_acc_Nij_Sigma_wij2[k], 1e-5)
+    for k in acc_Fnorm_Sigma_wij_ref[it]:
+      assert numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.m_acc_Fnorm_Sigma_wij[k], 1e-5)
+    assert numpy.allclose(acc_Snorm_ref[it], trainer.m_acc_Snorm, 1e-5)
+    assert numpy.allclose(N_ref[it], trainer.m_N, 1e-5)
+
+    # M-Step
+    trainer.m_step(m, data)
+    assert numpy.allclose(t_ref[it], m.t, 1e-5)
+
+  # C++ implementation
+  # Machine
+  m = IVectorMachine(ubm, 2)
+
+  # Initialization
+  trainer = IVectorTrainer()
+  trainer.initialize(m, data)
+  m.t = t
+  m.sigma = sigma
+  for it in range(2):
+    # E-Step
+    trainer.e_step(m, data)
+    for k in acc_Nij_Sigma_wij2_ref[it]:
+      assert numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.acc_nij_wij2[k], 1e-5)
+    for k in acc_Fnorm_Sigma_wij_ref[it]:
+      assert numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.acc_fnormij_wij[k], 1e-5)
+
+    # M-Step
+    trainer.m_step(m, data)
+    assert numpy.allclose(t_ref[it], m.t, 1e-5)
+
+def test_trainer_update_sigma():
+  # Ubm
+  dim_c = 2
+  dim_d = 3
+  ubm = GMMMachine(dim_c,dim_d)
+  ubm.weights = numpy.array([0.4,0.6])
+  ubm.means = numpy.array([[1.,7,4],[4,5,3]])
+  ubm.variances = numpy.array([[0.5,1.,1.5],[1.,1.5,2.]])
+
+  # Defines GMMStats
+  gs1 = GMMStats(dim_c,dim_d)
+  log_likelihood1 = -3.
+  T1 = 1
+  n1 = numpy.array([0.4, 0.6], numpy.float64)
+  sumpx1 = numpy.array([[1., 2., 3.], [2., 4., 3.]], numpy.float64)
+  sumpxx1 = numpy.array([[10., 20., 30.], [40., 50., 60.]], numpy.float64)
+  gs1.log_likelihood = log_likelihood1
+  gs1.t = T1
+  gs1.n = n1
+  gs1.sum_px = sumpx1
+  gs1.sum_pxx = sumpxx1
+
+  gs2 = GMMStats(dim_c,dim_d)
+  log_likelihood2 = -4.
+  T2 = 1
+  n2 = numpy.array([0.2, 0.8], numpy.float64)
+  sumpx2 = numpy.array([[2., 1., 3.], [3., 4.1, 3.2]], numpy.float64)
+  sumpxx2 = numpy.array([[12., 15., 25.], [39., 51., 62.]], numpy.float64)
+  gs2.log_likelihood = log_likelihood2
+  gs2.t = T2
+  gs2.n = n2
+  gs2.sum_px = sumpx2
+  gs2.sum_pxx = sumpxx2
+
+  data = [gs1, gs2]
+
+  # Reference values
+  acc_Nij_Sigma_wij2_ref1  = {0: numpy.array([[ 0.03202305, -0.02947769], [-0.02947769,  0.0561132 ]]),
+                              1: numpy.array([[ 0.07953279, -0.07829414], [-0.07829414,  0.13814242]])}
+  acc_Fnorm_Sigma_wij_ref1 = {0: numpy.array([[-0.29622691,  0.61411796], [ 0.09391764, -0.27955961], [-0.39014455,  0.89367757]]),
+                              1: numpy.array([[ 0.04695882, -0.13977981], [-0.05718673,  0.24159665], [-0.17098161,  0.47326585]])}
+  acc_Snorm_ref1           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
+  N_ref1                   = numpy.array([0.6, 1.4])
+  t_ref1                   = numpy.array([[  1.59543739, 11.78239235], [ -3.20130371, -6.66379081], [  4.79674111, 18.44618316],
+                                          [ -0.91765407, -1.5319461 ], [  2.26805901,  3.03434944], [  2.76600031,  4.9935962 ]])
+  sigma_ref1               = numpy.array([ 16.39472121, 34.72955353,  3.3108037, 43.73496916, 38.85472445, 68.22116903])
+
+  acc_Nij_Sigma_wij2_ref2  = {0: numpy.array([[ 0.50807426, -0.11907756], [-0.11907756,  0.12336544]]),
+                              1: numpy.array([[ 1.18602399, -0.2835859 ], [-0.2835859 ,  0.39440498]])}
+  acc_Fnorm_Sigma_wij_ref2 = {0: numpy.array([[ 0.07221453,  1.1189786 ], [-0.08681275, -0.35396112], [ 0.15902728,  1.47293972]]),
+                              1: numpy.array([[-0.04340637, -0.17698056], [ 0.10662127,  0.21484933],[ 0.13116645,  0.64474271]])}
+  acc_Snorm_ref2           = numpy.array([16.6, 22.4, 16.6, 61.4, 55., 97.4])
+  N_ref2                   = numpy.array([0.6, 1.4])
+  t_ref2                   = numpy.array([[  2.93105054, 11.89961223], [ -1.08988119, -3.92120757], [  4.02093173, 15.82081981],
+                                          [ -0.17376634, -0.57366984], [  0.26585634,  0.73589952], [  0.60557877,   2.07014704]])
+  sigma_ref2               = numpy.array([5.12154025e+00, 3.48623823e+01, 1.00000000e-05, 4.37792350e+01, 3.91525332e+01, 6.85613258e+01])
+
+  acc_Nij_Sigma_wij2_ref = [acc_Nij_Sigma_wij2_ref1, acc_Nij_Sigma_wij2_ref2]
+  acc_Fnorm_Sigma_wij_ref = [acc_Fnorm_Sigma_wij_ref1, acc_Fnorm_Sigma_wij_ref2]
+  acc_Snorm_ref = [acc_Snorm_ref1, acc_Snorm_ref2]
+  N_ref = [N_ref1, N_ref2]
+  t_ref = [t_ref1, t_ref2]
+  sigma_ref = [sigma_ref1, sigma_ref2]
+
+
+  # Python implementation
+  # Machine
+  m = IVectorMachine(ubm, 2)
+  t = numpy.array([[1.,2],[4,1],[0,3],[5,8],[7,10],[11,1]])
+  sigma = numpy.array([1.,2.,1.,3.,2.,4.])
+
+  # Initialization
+  trainer = IVectorTrainerPy(sigma_update=True)
+  trainer.initialize(m, data)
+  m.t = t
+  m.sigma = sigma
+  for it in range(2):
+    # E-Step
+    trainer.e_step(m, data)
+    for k in acc_Nij_Sigma_wij2_ref[it]:
+      assert numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.m_acc_Nij_Sigma_wij2[k], 1e-5)
+    for k in acc_Fnorm_Sigma_wij_ref[it]:
+      assert numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.m_acc_Fnorm_Sigma_wij[k], 1e-5)
+    assert numpy.allclose(acc_Snorm_ref[it], trainer.m_acc_Snorm, 1e-5)
+    assert numpy.allclose(N_ref[it], trainer.m_N, 1e-5)
+
+    # M-Step
+    trainer.m_step(m, data)
+    assert numpy.allclose(t_ref[it], m.t, 1e-5)
+    assert numpy.allclose(sigma_ref[it], m.sigma, 1e-5)
+
+
+  # C++ implementation
+  # Machine
+  m = IVectorMachine(ubm, 2)
+  m.variance_threshold = 1e-5
+
+  # Initialization
+  trainer = IVectorTrainer(update_sigma=True)
+  trainer.initialize(m, data)
+  m.t = t
+  m.sigma = sigma
+  for it in range(2):
+    # E-Step
+    trainer.e_step(m, data)
+    for k in acc_Nij_Sigma_wij2_ref[it]:
+      assert numpy.allclose(acc_Nij_Sigma_wij2_ref[it][k], trainer.acc_nij_wij2[k], 1e-5)
+    for k in acc_Fnorm_Sigma_wij_ref[it]:
+      assert numpy.allclose(acc_Fnorm_Sigma_wij_ref[it][k], trainer.acc_fnormij_wij[k], 1e-5)
+    assert numpy.allclose(acc_Snorm_ref[it].reshape(dim_c,dim_d), trainer.acc_snormij, 1e-5)
+    assert numpy.allclose(N_ref[it], trainer.acc_nij, 1e-5)
+
+    # M-Step
+    trainer.m_step(m, data)
+    assert numpy.allclose(t_ref[it], m.t, 1e-5)
+    assert numpy.allclose(sigma_ref[it], m.sigma, 1e-5)
 
diff --git a/xbob/learn/misc/test_jfa.py b/xbob/learn/misc/test_jfa.py
index a71579c..06f219e 100644
--- a/xbob/learn/misc/test_jfa.py
+++ b/xbob/learn/misc/test_jfa.py
@@ -3,18 +3,20 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Wed Feb 15 23:24:35 2012 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests on the JFA-based machines
 """
 
-import os, sys
-import unittest
-import math
-import bob
-import numpy, numpy.linalg
+import os
+import numpy
+import numpy.linalg
 import tempfile
 
+import xbob.io.base
+
+from . import GMMMachine, GMMStats, JFABase, ISVBase
+
 def estimate_x(dim_c, dim_d, mean, sigma, U, N, F):
   # Compute helper values
   UtSigmaInv = {}
@@ -28,10 +30,10 @@ def estimate_x(dim_c, dim_d, mean, sigma, U, N, F):
     UtSigmaInvU[c] = numpy.dot(UtSigmaInv[c], Uc);
 
   # I + (U^{T} \Sigma^-1 N U)
-  I_UtSigmaInvNU = numpy.eye(dim_ru, dtype=numpy.float64) 
+  I_UtSigmaInvNU = numpy.eye(dim_ru, dtype=numpy.float64)
   for c in range(dim_c):
     I_UtSigmaInvNU = I_UtSigmaInvNU + UtSigmaInvU[c] * N[c]
-  
+
   # U^{T} \Sigma^-1 F
   UtSigmaInv_Fnorm = numpy.zeros((dim_ru,), numpy.float64)
   for c in range(dim_c):
@@ -39,336 +41,333 @@ def estimate_x(dim_c, dim_d, mean, sigma, U, N, F):
     end               = (c+1)*dim_d
     Fnorm             = F[c,:] - N[c] * mean[start:end]
     UtSigmaInv_Fnorm  = UtSigmaInv_Fnorm + numpy.dot(UtSigmaInv[c], Fnorm)
- 
+
   return numpy.linalg.solve(I_UtSigmaInvNU, UtSigmaInv_Fnorm)
 
 def estimate_ux(dim_c, dim_d, mean, sigma, U, N, F):
   return numpy.dot(U, estimate_x(dim_c, dim_d, mean, sigma, U, N, F))
 
 
-class FATest(unittest.TestCase):
-  """Performs various FA tests."""
-
-  def test01_JFABase(self):
-
-    # Creates a UBM
-    weights = numpy.array([0.4, 0.6], 'float64')
-    means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
-    variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = weights
-    ubm.means = means
-    ubm.variances = variances
-
-    # Creates a JFABase
-    U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
-    V = numpy.array([[6, 5], [4, 3], [2, 1], [1, 2], [3, 4], [5, 6]], 'float64')
-    d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
-    m = bob.machine.JFABase(ubm)
-    self.assertTrue( m.dim_ru == 1)
-    self.assertTrue( m.dim_rv == 1)
-
-    # Checks for correctness
-    m.resize(2,2)
-    m.u = U
-    m.v = V
-    m.d = d
-    self.assertTrue( (m.u == U).all() )
-    self.assertTrue( (m.v == V).all() )
-    self.assertTrue( (m.d == d).all() )
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( m.dim_rv == 2)
-
-    # Saves and loads
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.JFABase(bob.io.HDF5File(filename))
-    m_loaded.ubm = ubm
-    self.assertTrue( m == m_loaded )
-    self.assertFalse( m != m_loaded )
-    self.assertTrue( m.is_similar_to(m_loaded) )
-
-    # Copy constructor
-    mc = bob.machine.JFABase(m)
-    self.assertTrue( m == mc )
-
-    # Variant
-    mv = bob.machine.JFABase()
-    # Checks for correctness
-    mv.ubm = ubm
-    mv.resize(2,2)
-    mv.u = U
-    mv.v = V
-    mv.d = d
-    self.assertTrue( (m.u == U).all() )
-    self.assertTrue( (m.v == V).all() )
-    self.assertTrue( (m.d == d).all() )
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( m.dim_rv == 2)
-
-    # Clean-up
-    os.unlink(filename)
-
-  def test02_ISVBase(self):
-
-    # Creates a UBM
-    weights = numpy.array([0.4, 0.6], 'float64')
-    means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
-    variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = weights
-    ubm.means = means
-    ubm.variances = variances
-
-    # Creates a ISVBase
-    U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
-    d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
-    m = bob.machine.ISVBase(ubm)
-    self.assertTrue( m.dim_ru == 1)
-
-    # Checks for correctness
-    m.resize(2)
-    m.u = U
-    m.d = d
-    self.assertTrue( (m.u == U).all() )
-    self.assertTrue( (m.d == d).all() )
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-
-    # Saves and loads
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.ISVBase(bob.io.HDF5File(filename))
-    m_loaded.ubm = ubm
-    self.assertTrue( m == m_loaded )
-    self.assertFalse( m != m_loaded )
-    self.assertTrue( m.is_similar_to(m_loaded) )
-
-    # Copy constructor
-    mc = bob.machine.ISVBase(m)
-    self.assertTrue( m == mc )
-
-    # Variant
-    mv = bob.machine.ISVBase()
-    # Checks for correctness
-    mv.ubm = ubm
-    mv.resize(2)
-    mv.u = U
-    mv.d = d
-    self.assertTrue( (m.u == U).all() )
-    self.assertTrue( (m.d == d).all() )
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-
-    # Clean-up
-    os.unlink(filename)
-  
-  def test03_JFAMachine(self):
-
-    # Creates a UBM
-    weights = numpy.array([0.4, 0.6], 'float64')
-    means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
-    variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = weights
-    ubm.means = means
-    ubm.variances = variances
-
-    # Creates a JFABase
-    U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
-    V = numpy.array([[6, 5], [4, 3], [2, 1], [1, 2], [3, 4], [5, 6]], 'float64')
-    d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
-    base = bob.machine.JFABase(ubm,2,2)
-    base.u = U
-    base.v = V
-    base.d = d
-
-    # Creates a JFAMachine
-    y = numpy.array([1,2], 'float64')
-    z = numpy.array([3,4,1,2,0,1], 'float64')
-    m = bob.machine.JFAMachine(base)
-    m.y = y
-    m.z = z
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( m.dim_rv == 2)
-    self.assertTrue( (m.y == y).all() )
-    self.assertTrue( (m.z == z).all() )
-    
-    # Saves and loads
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.JFAMachine(bob.io.HDF5File(filename))
-    m_loaded.jfa_base = base
-    self.assertTrue( m == m_loaded )
-    self.assertFalse( m != m_loaded )
-    self.assertTrue(m.is_similar_to(m_loaded))
-
-    # Copy constructor
-    mc = bob.machine.JFAMachine(m)
-    self.assertTrue( m == mc )
-
-    # Variant
-    mv = bob.machine.JFAMachine()
-    # Checks for correctness
-    mv.jfa_base = base
-    m.y = y
-    m.z = z
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( m.dim_rv == 2)
-    self.assertTrue( (m.y == y).all() )
-    self.assertTrue( (m.z == z).all() )
-
-    # Defines GMMStats
-    gs = bob.machine.GMMStats(2,3)
-    log_likelihood = -3.
-    T = 1
-    n = numpy.array([0.4, 0.6], 'float64')
-    sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
-    sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
-    gs.log_likelihood = log_likelihood
-    gs.t = T
-    gs.n = n
-    gs.sum_px = sumpx
-    gs.sum_pxx = sumpxx
-
-    # Forward GMMStats and check estimated value of the x speaker factor
-    eps = 1e-10
-    x_ref = numpy.array([0.291042849767692, 0.310273618998444], 'float64')
-    score_ref = -2.111577181208289
-    score = m.forward(gs)
-    self.assertTrue( numpy.allclose(m.__x__, x_ref, eps) )
-    self.assertTrue( abs(score_ref-score) < eps )
-
-    # x and Ux
-    x = numpy.ndarray((2,), numpy.float64)
-    m.estimate_x(gs, x)
-    x_py = estimate_x(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
-    self.assertTrue( numpy.allclose(x, x_py, eps))
-
-    ux = numpy.ndarray((6,), numpy.float64)
-    m.estimate_ux(gs, ux)
-    ux_py = estimate_ux(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
-    self.assertTrue( numpy.allclose(ux, ux_py, eps))
-    self.assertTrue( numpy.allclose(m.__x__, x, eps) )
-
-    score = m.forward_ux(gs, ux)
-    self.assertTrue( abs(score_ref-score) < eps )
-
-    # Clean-up
-    os.unlink(filename)
-  
-  def test04_ISVMachine(self):
-
-    # Creates a UBM
-    weights = numpy.array([0.4, 0.6], 'float64')
-    means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
-    variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.weights = weights
-    ubm.means = means
-    ubm.variances = variances
-
-    # Creates a JFABaseMachine
-    U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
-    V = numpy.array([[0], [0], [0], [0], [0], [0]], 'float64')
-    d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
-    base = bob.machine.ISVBase(ubm,2)
-    base.u = U
-    base.v = V
-    base.d = d
-
-    # Creates a JFAMachine
-    z = numpy.array([3,4,1,2,0,1], 'float64')
-    m = bob.machine.ISVMachine(base)
-    m.z = z
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( (m.z == z).all() )
-
-    # Saves and loads
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.ISVMachine(bob.io.HDF5File(filename))
-    m_loaded.isv_base = base
-    self.assertTrue( m == m_loaded )
-    self.assertFalse( m != m_loaded )
-    self.assertTrue(m.is_similar_to(m_loaded))
-
-    # Copy constructor
-    mc = bob.machine.ISVMachine(m)
-    self.assertTrue( m == mc )
-
-    # Variant
-    mv = bob.machine.ISVMachine()
-    # Checks for correctness
-    mv.isv_base = base
-    m.z = z
-    self.assertTrue( m.dim_c == 2)
-    self.assertTrue( m.dim_d == 3)
-    self.assertTrue( m.dim_cd == 6)
-    self.assertTrue( m.dim_ru == 2)
-    self.assertTrue( (m.z == z).all() )
-
-    # Defines GMMStats
-    gs = bob.machine.GMMStats(2,3)
-    log_likelihood = -3.
-    T = 1
-    n = numpy.array([0.4, 0.6], 'float64')
-    sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
-    sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
-    gs.log_likelihood = log_likelihood
-    gs.t = T
-    gs.n = n
-    gs.sum_px = sumpx
-    gs.sum_pxx = sumpxx
-
-    # Forward GMMStats and check estimated value of the x speaker factor
-    eps = 1e-10
-    x_ref = numpy.array([0.291042849767692, 0.310273618998444], 'float64')
-    score_ref = -3.280498193082100
-
-    score = m.forward(gs)
-    self.assertTrue( numpy.allclose(m.__x__, x_ref, eps) )
-    self.assertTrue( abs(score_ref-score) < eps )
-
-    # Check using alternate forward() method
-    Ux = numpy.ndarray(shape=(m.dim_cd,), dtype=numpy.float64)
-    m.estimate_ux(gs, Ux)
-    score = m.forward_ux(gs, Ux)
-    self.assertTrue( abs(score_ref-score) < eps )
-
-    # x and Ux
-    x = numpy.ndarray((2,), numpy.float64)
-    m.estimate_x(gs, x)
-    x_py = estimate_x(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
-    self.assertTrue( numpy.allclose(x, x_py, eps))
-
-    ux = numpy.ndarray((6,), numpy.float64)
-    m.estimate_ux(gs, ux)
-    ux_py = estimate_ux(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
-    self.assertTrue( numpy.allclose(ux, ux_py, eps))
-    self.assertTrue( numpy.allclose(m.__x__, x, eps) )
-
-    score = m.forward_ux(gs, ux)
-    self.assertTrue( abs(score_ref-score) < eps )
-
-    # Clean-up
-    os.unlink(filename)
+def test_JFABase():
+
+  # Creates a UBM
+  weights = numpy.array([0.4, 0.6], 'float64')
+  means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
+  variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
+  ubm = GMMMachine(2,3)
+  ubm.weights = weights
+  ubm.means = means
+  ubm.variances = variances
+
+  # Creates a JFABase
+  U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
+  V = numpy.array([[6, 5], [4, 3], [2, 1], [1, 2], [3, 4], [5, 6]], 'float64')
+  d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
+  m = JFABase(ubm)
+  assert m.dim_ru == 1
+  assert m.dim_rv == 1
+
+  # Checks for correctness
+  m.resize(2,2)
+  m.u = U
+  m.v = V
+  m.d = d
+  assert (m.u == U).all()
+  assert (m.v == V).all()
+  assert (m.d == d).all()
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert m.dim_rv == 2
+
+  # Saves and loads
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = JFABase(xbob.io.base.HDF5File(filename))
+  m_loaded.ubm = ubm
+  assert m == m_loaded
+  assert (m != m_loaded) is False
+  assert m.is_similar_to(m_loaded)
+
+  # Copy constructor
+  mc = JFABase(m)
+  assert m == mc
+
+  # Variant
+  mv = JFABase()
+  # Checks for correctness
+  mv.ubm = ubm
+  mv.resize(2,2)
+  mv.u = U
+  mv.v = V
+  mv.d = d
+  assert (m.u == U).all()
+  assert (m.v == V).all()
+  assert (m.d == d).all()
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert m.dim_rv == 2
+
+  # Clean-up
+  os.unlink(filename)
+
+def test_ISVBase():
+
+  # Creates a UBM
+  weights = numpy.array([0.4, 0.6], 'float64')
+  means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
+  variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
+  ubm = GMMMachine(2,3)
+  ubm.weights = weights
+  ubm.means = means
+  ubm.variances = variances
+
+  # Creates a ISVBase
+  U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
+  d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
+  m = ISVBase(ubm)
+  assert m.dim_ru == 1
+
+  # Checks for correctness
+  m.resize(2)
+  m.u = U
+  m.d = d
+  assert (m.u == U).all()
+  assert (m.d == d).all()
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+
+  # Saves and loads
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = ISVBase(xbob.io.base.HDF5File(filename))
+  m_loaded.ubm = ubm
+  assert m == m_loaded
+  assert (m != m_loaded) is False
+  assert m.is_similar_to(m_loaded)
+
+  # Copy constructor
+  mc = ISVBase(m)
+  assert m == mc
+
+  # Variant
+  mv = ISVBase()
+  # Checks for correctness
+  mv.ubm = ubm
+  mv.resize(2)
+  mv.u = U
+  mv.d = d
+  assert (m.u == U).all()
+  assert (m.d == d).all()
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+
+  # Clean-up
+  os.unlink(filename)
+
+def test_JFAMachine():
+
+  # Creates a UBM
+  weights = numpy.array([0.4, 0.6], 'float64')
+  means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
+  variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
+  ubm = GMMMachine(2,3)
+  ubm.weights = weights
+  ubm.means = means
+  ubm.variances = variances
+
+  # Creates a JFABase
+  U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
+  V = numpy.array([[6, 5], [4, 3], [2, 1], [1, 2], [3, 4], [5, 6]], 'float64')
+  d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
+  base = JFABase(ubm,2,2)
+  base.u = U
+  base.v = V
+  base.d = d
+
+  # Creates a JFAMachine
+  y = numpy.array([1,2], 'float64')
+  z = numpy.array([3,4,1,2,0,1], 'float64')
+  m = JFAMachine(base)
+  m.y = y
+  m.z = z
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert m.dim_rv == 2
+  assert (m.y == y).all()
+  assert (m.z == z).all()
+
+  # Saves and loads
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = JFAMachine(xbob.io.base.HDF5File(filename))
+  m_loaded.jfa_base = base
+  assert m == m_loaded
+  assert (m != m_loaded) is False
+  assert m.is_similar_to(m_loaded)
+
+  # Copy constructor
+  mc = JFAMachine(m)
+  assert m == mc
+
+  # Variant
+  mv = JFAMachine()
+  # Checks for correctness
+  mv.jfa_base = base
+  m.y = y
+  m.z = z
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert m.dim_rv == 2
+  assert (m.y == y).all()
+  assert (m.z == z).all()
+
+  # Defines GMMStats
+  gs = GMMStats(2,3)
+  log_likelihood = -3.
+  T = 1
+  n = numpy.array([0.4, 0.6], 'float64')
+  sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
+  sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
+  gs.log_likelihood = log_likelihood
+  gs.t = T
+  gs.n = n
+  gs.sum_px = sumpx
+  gs.sum_pxx = sumpxx
+
+  # Forward GMMStats and check estimated value of the x speaker factor
+  eps = 1e-10
+  x_ref = numpy.array([0.291042849767692, 0.310273618998444], 'float64')
+  score_ref = -2.111577181208289
+  score = m.forward(gs)
+  assert numpy.allclose(m.__x__, x_ref, eps)
+  assert abs(score_ref-score) < eps
+
+  # x and Ux
+  x = numpy.ndarray((2,), numpy.float64)
+  m.estimate_x(gs, x)
+  x_py = estimate_x(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
+  assert numpy.allclose(x, x_py, eps)
+
+  ux = numpy.ndarray((6,), numpy.float64)
+  m.estimate_ux(gs, ux)
+  ux_py = estimate_ux(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
+  assert numpy.allclose(ux, ux_py, eps)
+  assert numpy.allclose(m.__x__, x, eps)
+
+  score = m.forward_ux(gs, ux)
+  assert abs(score_ref-score) < eps
+
+  # Clean-up
+  os.unlink(filename)
+
+def test_ISVMachine():
+
+  # Creates a UBM
+  weights = numpy.array([0.4, 0.6], 'float64')
+  means = numpy.array([[1, 6, 2], [4, 3, 2]], 'float64')
+  variances = numpy.array([[1, 2, 1], [2, 1, 2]], 'float64')
+  ubm = GMMMachine(2,3)
+  ubm.weights = weights
+  ubm.means = means
+  ubm.variances = variances
+
+  # Creates a JFABaseMachine
+  U = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], 'float64')
+  V = numpy.array([[0], [0], [0], [0], [0], [0]], 'float64')
+  d = numpy.array([0, 1, 0, 1, 0, 1], 'float64')
+  base = ISVBase(ubm,2)
+  base.u = U
+  base.v = V
+  base.d = d
+
+  # Creates a JFAMachine
+  z = numpy.array([3,4,1,2,0,1], 'float64')
+  m = ISVMachine(base)
+  m.z = z
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert (m.z == z).all()
+
+  # Saves and loads
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = ISVMachine(xbob.io.base.HDF5File(filename))
+  m_loaded.isv_base = base
+  assert m == m_loaded
+  assert (m != m_loaded) is False
+  assert m.is_similar_to(m_loaded)
+
+  # Copy constructor
+  mc = ISVMachine(m)
+  assert m == mc
+
+  # Variant
+  mv = ISVMachine()
+  # Checks for correctness
+  mv.isv_base = base
+  m.z = z
+  assert m.dim_c == 2
+  assert m.dim_d == 3
+  assert m.dim_cd == 6
+  assert m.dim_ru == 2
+  assert (m.z == z).all()
+
+  # Defines GMMStats
+  gs = GMMStats(2,3)
+  log_likelihood = -3.
+  T = 1
+  n = numpy.array([0.4, 0.6], 'float64')
+  sumpx = numpy.array([[1., 2., 3.], [4., 5., 6.]], 'float64')
+  sumpxx = numpy.array([[10., 20., 30.], [40., 50., 60.]], 'float64')
+  gs.log_likelihood = log_likelihood
+  gs.t = T
+  gs.n = n
+  gs.sum_px = sumpx
+  gs.sum_pxx = sumpxx
+
+  # Forward GMMStats and check estimated value of the x speaker factor
+  eps = 1e-10
+  x_ref = numpy.array([0.291042849767692, 0.310273618998444], 'float64')
+  score_ref = -3.280498193082100
+
+  score = m.forward(gs)
+  assert numpy.allclose(m.__x__, x_ref, eps)
+  assert abs(score_ref-score) < eps
+
+  # Check using alternate forward() method
+  Ux = numpy.ndarray(shape=(m.dim_cd,), dtype=numpy.float64)
+  m.estimate_ux(gs, Ux)
+  score = m.forward_ux(gs, Ux)
+  assert abs(score_ref-score) < eps
+
+  # x and Ux
+  x = numpy.ndarray((2,), numpy.float64)
+  m.estimate_x(gs, x)
+  x_py = estimate_x(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
+  assert numpy.allclose(x, x_py, eps)
+
+  ux = numpy.ndarray((6,), numpy.float64)
+  m.estimate_ux(gs, ux)
+  ux_py = estimate_ux(m.dim_c, m.dim_d, ubm.mean_supervector, ubm.variance_supervector, U, n, sumpx)
+  assert numpy.allclose(ux, ux_py, eps)
+  assert numpy.allclose(m.__x__, x, eps)
+
+  score = m.forward_ux(gs, ux)
+  assert abs(score_ref-score) < eps
+
+  # Clean-up
+  os.unlink(filename)
diff --git a/xbob/learn/misc/test_jfa_trainer.py b/xbob/learn/misc/test_jfa_trainer.py
index e8bb2cc..8754de8 100644
--- a/xbob/learn/misc/test_jfa_trainer.py
+++ b/xbob/learn/misc/test_jfa_trainer.py
@@ -3,15 +3,15 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Tue Jul 19 12:16:17 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Test JFA trainer package
 """
 
-import unittest
-import bob
-import numpy, numpy.linalg
+import numpy
+import numpy.linalg
 
+from . import GMMStats, GMMMachine, JFABase, JFAMachine, ISVBase, ISVMachine
 
 def equals(x, y, epsilon):
   return (abs(x - y) < epsilon).all()
@@ -27,17 +27,17 @@ N1 = numpy.array([0.1379, 0.1821, 0.2178, 0.0418]).reshape((2,2))
 N2 = numpy.array([0.1069, 0.9397, 0.6164, 0.3545]).reshape((2,2))
 N=[N1, N2]
 
-gs11 = bob.machine.GMMStats(2,3)
+gs11 = GMMStats(2,3)
 gs11.n = N1[:,0]
 gs11.sum_px = F1[:,0].reshape(2,3)
-gs12 = bob.machine.GMMStats(2,3)
+gs12 = GMMStats(2,3)
 gs12.n = N1[:,1]
 gs12.sum_px = F1[:,1].reshape(2,3)
 
-gs21 = bob.machine.GMMStats(2,3)
+gs21 = GMMStats(2,3)
 gs21.n = N2[:,0]
 gs21.sum_px = F2[:,0].reshape(2,3)
-gs22 = bob.machine.GMMStats(2,3)
+gs22 = GMMStats(2,3)
 gs22.n = N2[:,1]
 gs22.sum_px = F2[:,1].reshape(2,3)
 
@@ -61,260 +61,254 @@ M_y=[y1, y2]
 M_x=[x1, x2]
 
 
- 
-
-class FATrainerTest(unittest.TestCase):
-  """Performs various FA trainer tests."""
-
-
-  def test01_JFATrainer_updateYandV(self):
-    # test the JFATrainer for updating Y and V
-
-    v_ref = numpy.array( [0.7228, 0.7892, 0.6475, 0.6080, 0.8631, 0.8416,
-      1.6512, 1.6068, 0.0500, 0.0101, 0.4325, 0.6719]).reshape((6,2))
-
-    y1 = numpy.array([0., 0.])
-    y2 = numpy.array([0., 0.])
-    y3 = numpy.array([0.9630, 1.3868])
-    y4 = numpy.array([0.0426, -0.3721])
-    y=[y1, y2]
-
-    # call the updateY function
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-    m = bob.machine.JFABase(ubm,2,2)
-    t = bob.trainer.JFATrainer(10)
-    t.initialize(m, TRAINING_STATS)
-    m.u = M_u
-    m.v = M_v
-    m.d = M_d
-    t.__X__ = M_x
-    t.__Y__ = y
-    t.__Z__ = M_z
-    t.e_step1(m, TRAINING_STATS)
-    t.m_step1(m, TRAINING_STATS)
-
-    # Expected results(JFA cookbook, matlab)
-    self.assertTrue(equals(t.__Y__[0], y3, 2e-4))
-    self.assertTrue(equals(t.__Y__[1], y4, 2e-4))
-    self.assertTrue(equals(m.v, v_ref, 2e-4))
-
-
-  def test02_JFATrainer_updateXandU(self):
-    # test the JFATrainer for updating X and U
-
-    u_ref = numpy.array( [0.6729, 0.3408, 0.0544, 1.0653, 0.5399, 1.3035,
-      2.4995, 0.4385, 0.1292, -0.0576, 1.1962, 0.0117]).reshape((6,2))
-
-    x1 = numpy.array([0., 0., 0., 0.]).reshape((2,2))
-    x2 = numpy.array([0., 0., 0., 0.]).reshape((2,2))
-    x3 = numpy.array([0.2143, 1.8275, 3.1979, 0.1227]).reshape((2,2))
-    x4 = numpy.array([-1.3861, 0.2359, 5.3326, -0.7914]).reshape((2,2))
-    x  = [x1, x2]
-
-    # call the updateX function
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-    m = bob.machine.JFABase(ubm,2,2)
-    t = bob.trainer.JFATrainer(10)
-    t.initialize(m, TRAINING_STATS)
-    m.u = M_u
-    m.v = M_v
-    m.d = M_d
-    t.__X__ = x
-    t.__Y__ = M_y
-    t.__Z__ = M_z
-    t.e_step2(m, TRAINING_STATS)
-    t.m_step2(m, TRAINING_STATS)
-
-    # Expected results(JFA cookbook, matlab)
-    self.assertTrue(equals(t.__X__[0], x3, 2e-4))
-    self.assertTrue(equals(t.__X__[1], x4, 2e-4))
-    self.assertTrue(equals(m.u, u_ref, 2e-4))
-
-
-  def test03_JFATrainer_updateZandD(self):
-    # test the JFATrainer for updating Z and D
-
-    d_ref = numpy.array([0.3110, 1.0138, 0.8297, 1.0382, 0.0095, 0.6320])
-
-    z1 = numpy.array([0., 0., 0., 0., 0., 0.])
-    z2 = numpy.array([0., 0., 0., 0., 0., 0.])
-    z3_ref = numpy.array([0.3256, 1.8633, 0.6480, 0.8085, -0.0432, 0.2885])
-    z4_ref = numpy.array([-0.3324, -0.1474, -0.4404, -0.4529, 0.0484, -0.5848])
-    z=[z1, z2]
-
-    # call the updateZ function
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-    m = bob.machine.JFABase(ubm,2,2)
-    t = bob.trainer.JFATrainer(10)
-    t.initialize(m, TRAINING_STATS)
-    m.u = M_u
-    m.v = M_v
-    m.d = M_d
-    t.__X__ = M_x
-    t.__Y__ = M_y
-    t.__Z__ = z
-    t.e_step3(m, TRAINING_STATS)
-    t.m_step3(m, TRAINING_STATS)
-
-    # Expected results(JFA cookbook, matlab)
-    self.assertTrue(equals(t.__Z__[0], z3_ref, 2e-4))
-    self.assertTrue(equals(t.__Z__[1], z4_ref, 2e-4))
-    self.assertTrue(equals(m.d, d_ref, 2e-4))
-
-
-  def test04_JFATrainAndEnrol(self):
-    # Train and enrol a JFAMachine
-
-    # Calls the train function
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-    mb = bob.machine.JFABase(ubm, 2, 2)
-    t = bob.trainer.JFATrainer(10)
-    t.initialize(mb, TRAINING_STATS)
-    mb.u = M_u
-    mb.v = M_v
-    mb.d = M_d
-    t.train_loop(mb, TRAINING_STATS)
-
-    v_ref = numpy.array([[0.245364911936476, 0.978133261775424], [0.769646805052223, 0.940070736856596], [0.310779202800089, 1.456332053893072],
-          [0.184760934399551, 2.265139705602147], [0.701987784039800, 0.081632150899400], [0.074344030229297, 1.090248340917255]], 'float64')
-    u_ref = numpy.array([[0.049424652628448, 0.060480486336896], [0.178104127464007, 1.884873813495153], [1.204011484266777, 2.281351307871720],
-          [7.278512126426286, -0.390966087173334], [-0.084424326581145, -0.081725474934414], [4.042143689831097, -0.262576386580701]], 'float64')
-    d_ref = numpy.array([9.648467e-18, 2.63720683155e-12, 2.11822157653706e-10, 9.1047243e-17, 1.41163442535567e-10, 3.30581e-19], 'float64')
-
-    eps = 1e-10
-    self.assertTrue( numpy.allclose(mb.v, v_ref, eps) )
-    self.assertTrue( numpy.allclose(mb.u, u_ref, eps) )
-    self.assertTrue( numpy.allclose(mb.d, d_ref, eps) )
-
-    # Calls the enrol function
-    m = bob.machine.JFAMachine(mb)
-  
-    Ne = numpy.array([0.1579, 0.9245, 0.1323, 0.2458]).reshape((2,2))
-    Fe = numpy.array([0.1579, 0.1925, 0.3242, 0.1234, 0.2354, 0.2734, 0.2514, 0.5874, 0.3345, 0.2463, 0.4789, 0.5236]).reshape((6,2))
-    gse1 = bob.machine.GMMStats(2,3)
-    gse1.n = Ne[:,0]
-    gse1.sum_px = Fe[:,0].reshape(2,3)
-    gse2 = bob.machine.GMMStats(2,3)
-    gse2.n = Ne[:,1]
-    gse2.sum_px = Fe[:,1].reshape(2,3)
-
-    gse = [gse1, gse2]
-    t.enrol(m, gse, 5)
-
-    y_ref = numpy.array([0.555991469319657, 0.002773650670010], 'float64')
-    z_ref = numpy.array([8.2228e-20, 3.15216909492e-13, -1.48616735364395e-10, 1.0625905e-17, 3.7150503117895e-11, 1.71104e-19], 'float64')
-    self.assertTrue( numpy.allclose(m.y, y_ref, eps) )
-    self.assertTrue( numpy.allclose(m.z, z_ref, eps) )
-
-
-  def test05_ISVTrainAndEnrol(self):
-    # Train and enrol an 'ISVMachine'
-
-    eps = 1e-10
-    d_ref = numpy.array([0.39601136, 0.07348469, 0.47712682, 0.44738127, 0.43179856, 0.45086029], 'float64')
-    u_ref = numpy.array([[0.855125642430777, 0.563104284748032], [-0.325497865404680, 1.923598985291687], [0.511575659503837, 1.964288663083095], [9.330165761678115, 1.073623827995043], [0.511099245664012, 0.278551249248978], [5.065578541930268, 0.509565618051587]], 'float64')
-    z_ref = numpy.array([-0.079315777443826, 0.092702428248543, -0.342488761656616, -0.059922635809136 , 0.133539981073604, 0.213118695516570], 'float64')
-
-    # Calls the train function
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-    mb = bob.machine.ISVBase(ubm,2)
-    t = bob.trainer.ISVTrainer(10, 4.)
-    #t.train(mb, TRAINING_STATS)
-    t.initialize(mb, TRAINING_STATS)
-    mb.u = M_u
-    for i in range(10):
-      t.e_step(mb, TRAINING_STATS)
-      t.m_step(mb, TRAINING_STATS)
-    t.finalize(mb, TRAINING_STATS)
-
-    self.assertTrue( numpy.allclose(mb.d, d_ref, eps) )
-    self.assertTrue( numpy.allclose(mb.u, u_ref, eps) )
-
-    # Calls the enrol function
-    m = bob.machine.ISVMachine(mb)
-  
-    Ne = numpy.array([0.1579, 0.9245, 0.1323, 0.2458]).reshape((2,2))
-    Fe = numpy.array([0.1579, 0.1925, 0.3242, 0.1234, 0.2354, 0.2734, 0.2514, 0.5874, 0.3345, 0.2463, 0.4789, 0.5236]).reshape((6,2))
-    gse1 = bob.machine.GMMStats(2,3)
-    gse1.n = Ne[:,0]
-    gse1.sum_px = Fe[:,0].reshape(2,3)
-    gse2 = bob.machine.GMMStats(2,3)
-    gse2.n = Ne[:,1]
-    gse2.sum_px = Fe[:,1].reshape(2,3)
-
-    gse = [gse1, gse2]
-    t.enrol(m, gse, 5)
-    self.assertTrue( numpy.allclose(m.z, z_ref, eps) )
-
-  def test06_JFATrainInitialize(self):
-    # Check that the initialization is consistent and using the rng (cf. issue #118)
-
-    eps = 1e-10
-
-    # UBM GMM
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-
-    ## JFA
-    jb = bob.machine.JFABase(ubm, 2, 2)
-    # first round
-    rng = bob.core.random.mt19937(0)
-    jt = bob.trainer.JFATrainer(10)
-    jt.rng = rng
-    jt.initialize(jb, TRAINING_STATS)
-    u1 = jb.u
-    v1 = jb.v
-    d1 = jb.d
-
-    # second round
-    rng = bob.core.random.mt19937(0)
-    jt.rng = rng
-    jt.initialize(jb, TRAINING_STATS)
-    u2 = jb.u
-    v2 = jb.v
-    d2 = jb.d
-    
-    self.assertTrue( numpy.allclose(u1, u2, eps) )
-    self.assertTrue( numpy.allclose(v1, v2, eps) )
-    self.assertTrue( numpy.allclose(d1, d2, eps) )
-
-  def test07_ISVTrainInitialize(self):
-    # Check that the initialization is consistent and using the rng (cf. issue #118)
-
-    eps = 1e-10
-
-    # UBM GMM
-    ubm = bob.machine.GMMMachine(2,3)
-    ubm.mean_supervector = UBM_MEAN
-    ubm.variance_supervector = UBM_VAR
-
-    ## ISV
-    ib = bob.machine.ISVBase(ubm, 2)
-    # first round
-    rng = bob.core.random.mt19937(0)
-    it = bob.trainer.ISVTrainer(10)
-    it.rng = rng
-    it.initialize(ib, TRAINING_STATS)
-    u1 = ib.u
-    d1 = ib.d
-
-    # second round
-    rng = bob.core.random.mt19937(0)
-    it.rng = rng
-    it.initialize(ib, TRAINING_STATS)
-    u2 = ib.u
-    d2 = ib.d
-    
-    self.assertTrue( numpy.allclose(u1, u2, eps) )
-    self.assertTrue( numpy.allclose(d1, d2, eps) )
+def test_JFATrainer_updateYandV():
+  # test the JFATrainer for updating Y and V
+
+  v_ref = numpy.array( [0.7228, 0.7892, 0.6475, 0.6080, 0.8631, 0.8416,
+    1.6512, 1.6068, 0.0500, 0.0101, 0.4325, 0.6719]).reshape((6,2))
+
+  y1 = numpy.array([0., 0.])
+  y2 = numpy.array([0., 0.])
+  y3 = numpy.array([0.9630, 1.3868])
+  y4 = numpy.array([0.0426, -0.3721])
+  y=[y1, y2]
+
+  # call the updateY function
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+  m = JFABase(ubm,2,2)
+  t = bob.trainer.JFATrainer(10)
+  t.initialize(m, TRAINING_STATS)
+  m.u = M_u
+  m.v = M_v
+  m.d = M_d
+  t.__X__ = M_x
+  t.__Y__ = y
+  t.__Z__ = M_z
+  t.e_step1(m, TRAINING_STATS)
+  t.m_step1(m, TRAINING_STATS)
+
+  # Expected results(JFA cookbook, matlab)
+  assert equals(t.__Y__[0], y3, 2e-4)
+  assert equals(t.__Y__[1], y4, 2e-4)
+  assert equals(m.v, v_ref, 2e-4)
+
+
+def test_JFATrainer_updateXandU():
+  # test the JFATrainer for updating X and U
+
+  u_ref = numpy.array( [0.6729, 0.3408, 0.0544, 1.0653, 0.5399, 1.3035,
+    2.4995, 0.4385, 0.1292, -0.0576, 1.1962, 0.0117]).reshape((6,2))
+
+  x1 = numpy.array([0., 0., 0., 0.]).reshape((2,2))
+  x2 = numpy.array([0., 0., 0., 0.]).reshape((2,2))
+  x3 = numpy.array([0.2143, 1.8275, 3.1979, 0.1227]).reshape((2,2))
+  x4 = numpy.array([-1.3861, 0.2359, 5.3326, -0.7914]).reshape((2,2))
+  x  = [x1, x2]
+
+  # call the updateX function
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+  m = JFABase(ubm,2,2)
+  t = bob.trainer.JFATrainer(10)
+  t.initialize(m, TRAINING_STATS)
+  m.u = M_u
+  m.v = M_v
+  m.d = M_d
+  t.__X__ = x
+  t.__Y__ = M_y
+  t.__Z__ = M_z
+  t.e_step2(m, TRAINING_STATS)
+  t.m_step2(m, TRAINING_STATS)
+
+  # Expected results(JFA cookbook, matlab)
+  assert equals(t.__X__[0], x3, 2e-4)
+  assert equals(t.__X__[1], x4, 2e-4)
+  assert equals(m.u, u_ref, 2e-4)
+
+
+def test_JFATrainer_updateZandD():
+  # test the JFATrainer for updating Z and D
+
+  d_ref = numpy.array([0.3110, 1.0138, 0.8297, 1.0382, 0.0095, 0.6320])
+
+  z1 = numpy.array([0., 0., 0., 0., 0., 0.])
+  z2 = numpy.array([0., 0., 0., 0., 0., 0.])
+  z3_ref = numpy.array([0.3256, 1.8633, 0.6480, 0.8085, -0.0432, 0.2885])
+  z4_ref = numpy.array([-0.3324, -0.1474, -0.4404, -0.4529, 0.0484, -0.5848])
+  z=[z1, z2]
+
+  # call the updateZ function
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+  m = JFABase(ubm,2,2)
+  t = bob.trainer.JFATrainer(10)
+  t.initialize(m, TRAINING_STATS)
+  m.u = M_u
+  m.v = M_v
+  m.d = M_d
+  t.__X__ = M_x
+  t.__Y__ = M_y
+  t.__Z__ = z
+  t.e_step3(m, TRAINING_STATS)
+  t.m_step3(m, TRAINING_STATS)
+
+  # Expected results(JFA cookbook, matlab)
+  assert equals(t.__Z__[0], z3_ref, 2e-4)
+  assert equals(t.__Z__[1], z4_ref, 2e-4)
+  assert equals(m.d, d_ref, 2e-4)
+
+
+def test_JFATrainAndEnrol():
+  # Train and enrol a JFAMachine
+
+  # Calls the train function
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+  mb = JFABase(ubm, 2, 2)
+  t = bob.trainer.JFATrainer(10)
+  t.initialize(mb, TRAINING_STATS)
+  mb.u = M_u
+  mb.v = M_v
+  mb.d = M_d
+  t.train_loop(mb, TRAINING_STATS)
+
+  v_ref = numpy.array([[0.245364911936476, 0.978133261775424], [0.769646805052223, 0.940070736856596], [0.310779202800089, 1.456332053893072],
+        [0.184760934399551, 2.265139705602147], [0.701987784039800, 0.081632150899400], [0.074344030229297, 1.090248340917255]], 'float64')
+  u_ref = numpy.array([[0.049424652628448, 0.060480486336896], [0.178104127464007, 1.884873813495153], [1.204011484266777, 2.281351307871720],
+        [7.278512126426286, -0.390966087173334], [-0.084424326581145, -0.081725474934414], [4.042143689831097, -0.262576386580701]], 'float64')
+  d_ref = numpy.array([9.648467e-18, 2.63720683155e-12, 2.11822157653706e-10, 9.1047243e-17, 1.41163442535567e-10, 3.30581e-19], 'float64')
+
+  eps = 1e-10
+  assert numpy.allclose(mb.v, v_ref, eps)
+  assert numpy.allclose(mb.u, u_ref, eps)
+  assert numpy.allclose(mb.d, d_ref, eps)
+
+  # Calls the enrol function
+  m = JFAMachine(mb)
+
+  Ne = numpy.array([0.1579, 0.9245, 0.1323, 0.2458]).reshape((2,2))
+  Fe = numpy.array([0.1579, 0.1925, 0.3242, 0.1234, 0.2354, 0.2734, 0.2514, 0.5874, 0.3345, 0.2463, 0.4789, 0.5236]).reshape((6,2))
+  gse1 = GMMStats(2,3)
+  gse1.n = Ne[:,0]
+  gse1.sum_px = Fe[:,0].reshape(2,3)
+  gse2 = GMMStats(2,3)
+  gse2.n = Ne[:,1]
+  gse2.sum_px = Fe[:,1].reshape(2,3)
+
+  gse = [gse1, gse2]
+  t.enrol(m, gse, 5)
+
+  y_ref = numpy.array([0.555991469319657, 0.002773650670010], 'float64')
+  z_ref = numpy.array([8.2228e-20, 3.15216909492e-13, -1.48616735364395e-10, 1.0625905e-17, 3.7150503117895e-11, 1.71104e-19], 'float64')
+  assert numpy.allclose(m.y, y_ref, eps)
+  assert numpy.allclose(m.z, z_ref, eps)
+
+
+def test_ISVTrainAndEnrol():
+  # Train and enrol an 'ISVMachine'
+
+  eps = 1e-10
+  d_ref = numpy.array([0.39601136, 0.07348469, 0.47712682, 0.44738127, 0.43179856, 0.45086029], 'float64')
+  u_ref = numpy.array([[0.855125642430777, 0.563104284748032], [-0.325497865404680, 1.923598985291687], [0.511575659503837, 1.964288663083095], [9.330165761678115, 1.073623827995043], [0.511099245664012, 0.278551249248978], [5.065578541930268, 0.509565618051587]], 'float64')
+  z_ref = numpy.array([-0.079315777443826, 0.092702428248543, -0.342488761656616, -0.059922635809136 , 0.133539981073604, 0.213118695516570], 'float64')
+
+  # Calls the train function
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+  mb = ISVBase(ubm,2)
+  t = bob.trainer.ISVTrainer(10, 4.)
+  #t.train(mb, TRAINING_STATS)
+  t.initialize(mb, TRAINING_STATS)
+  mb.u = M_u
+  for i in range(10):
+    t.e_step(mb, TRAINING_STATS)
+    t.m_step(mb, TRAINING_STATS)
+  t.finalize(mb, TRAINING_STATS)
+
+  assert numpy.allclose(mb.d, d_ref, eps)
+  assert numpy.allclose(mb.u, u_ref, eps)
+
+  # Calls the enrol function
+  m = ISVMachine(mb)
+
+  Ne = numpy.array([0.1579, 0.9245, 0.1323, 0.2458]).reshape((2,2))
+  Fe = numpy.array([0.1579, 0.1925, 0.3242, 0.1234, 0.2354, 0.2734, 0.2514, 0.5874, 0.3345, 0.2463, 0.4789, 0.5236]).reshape((6,2))
+  gse1 = GMMStats(2,3)
+  gse1.n = Ne[:,0]
+  gse1.sum_px = Fe[:,0].reshape(2,3)
+  gse2 = GMMStats(2,3)
+  gse2.n = Ne[:,1]
+  gse2.sum_px = Fe[:,1].reshape(2,3)
+
+  gse = [gse1, gse2]
+  t.enrol(m, gse, 5)
+  assert numpy.allclose(m.z, z_ref, eps)
+
+def test_JFATrainInitialize():
+  # Check that the initialization is consistent and using the rng (cf. issue #118)
+
+  eps = 1e-10
+
+  # UBM GMM
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+
+  ## JFA
+  jb = JFABase(ubm, 2, 2)
+  # first round
+  rng = bob.core.random.mt19937(0)
+  jt = bob.trainer.JFATrainer(10)
+  jt.rng = rng
+  jt.initialize(jb, TRAINING_STATS)
+  u1 = jb.u
+  v1 = jb.v
+  d1 = jb.d
+
+  # second round
+  rng = bob.core.random.mt19937(0)
+  jt.rng = rng
+  jt.initialize(jb, TRAINING_STATS)
+  u2 = jb.u
+  v2 = jb.v
+  d2 = jb.d
+
+  assert numpy.allclose(u1, u2, eps)
+  assert numpy.allclose(v1, v2, eps)
+  assert numpy.allclose(d1, d2, eps)
+
+def test_ISVTrainInitialize():
+
+  # Check that the initialization is consistent and using the rng (cf. issue #118)
+  eps = 1e-10
+
+  # UBM GMM
+  ubm = GMMMachine(2,3)
+  ubm.mean_supervector = UBM_MEAN
+  ubm.variance_supervector = UBM_VAR
+
+  ## ISV
+  ib = ISVBase(ubm, 2)
+  # first round
+  rng = bob.core.random.mt19937(0)
+  it = bob.trainer.ISVTrainer(10)
+  it.rng = rng
+  it.initialize(ib, TRAINING_STATS)
+  u1 = ib.u
+  d1 = ib.d
+
+  # second round
+  rng = bob.core.random.mt19937(0)
+  it.rng = rng
+  it.initialize(ib, TRAINING_STATS)
+  u2 = ib.u
+  d2 = ib.d
+
+  assert numpy.allclose(u1, u2, eps)
+  assert numpy.allclose(d1, d2, eps)
diff --git a/xbob/learn/misc/test_kmeans.py b/xbob/learn/misc/test_kmeans.py
index f65f208..16f01a3 100644
--- a/xbob/learn/misc/test_kmeans.py
+++ b/xbob/learn/misc/test_kmeans.py
@@ -3,73 +3,71 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Thu Feb 16 17:57:10 2012 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests the KMeans machine
 """
 
-import os, sys
-import unittest
-import bob
-import numpy, math
+import os
+import numpy
 import tempfile
 
+import xbob.io.base
+from . import KMeansMachine
+
 def equals(x, y, epsilon):
   return (abs(x - y) < epsilon)
 
-class KMeansMachineTest(unittest.TestCase):
-  """Performs various KMeans machine-related tests."""
-
-  def test01_KMeansMachine(self):
-    # Test a KMeansMachine
+def test_KMeansMachine():
+  # Test a KMeansMachine
 
-    means = numpy.array([[3, 70, 0], [4, 72, 0]], 'float64')
-    mean  = numpy.array([3,70,1], 'float64')
+  means = numpy.array([[3, 70, 0], [4, 72, 0]], 'float64')
+  mean  = numpy.array([3,70,1], 'float64')
 
-    # Initializes a KMeansMachine
-    km = bob.machine.KMeansMachine(2,3)
-    km.means = means
-    self.assertTrue( km.dim_c == 2 )
-    self.assertTrue( km.dim_d == 3 )
+  # Initializes a KMeansMachine
+  km = KMeansMachine(2,3)
+  km.means = means
+  assert km.dim_c == 2
+  assert km.dim_d == 3
 
-    # Sets and gets
-    self.assertTrue( (km.means == means).all() )
-    self.assertTrue( (km.get_mean(0) == means[0,:]).all() )
-    self.assertTrue( (km.get_mean(1) == means[1,:]).all() )
-    km.set_mean(0, mean)
-    self.assertTrue( (km.get_mean(0) == mean).all() )
+  # Sets and gets
+  assert (km.means == means).all()
+  assert (km.get_mean(0) == means[0,:]).all()
+  assert (km.get_mean(1) == means[1,:]).all()
+  km.set_mean(0, mean)
+  assert (km.get_mean(0) == mean).all()
 
-    # Distance and closest mean
-    eps = 1e-10
-    self.assertTrue( equals( km.get_distance_from_mean(mean, 0), 0, eps) )
-    self.assertTrue( equals( km.get_distance_from_mean(mean, 1), 6, eps) )
-    (index, dist) = km.get_closest_mean(mean)
-    self.assertTrue( index == 0)
-    self.assertTrue( equals( dist, 0, eps) )
-    self.assertTrue( equals( km.get_min_distance(mean), 0, eps) )
+  # Distance and closest mean
+  eps = 1e-10
+  assert equals( km.get_distance_from_mean(mean, 0), 0, eps)
+  assert equals( km.get_distance_from_mean(mean, 1), 6, eps)
+  (index, dist) = km.get_closest_mean(mean)
+  assert index == 0
+  assert equals( dist, 0, eps)
+  assert equals( km.get_min_distance(mean), 0, eps)
 
-    # Loads and saves
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    km.save(bob.io.HDF5File(filename, 'w'))
-    km_loaded = bob.machine.KMeansMachine(bob.io.HDF5File(filename))
-    self.assertTrue( km == km_loaded )
+  # Loads and saves
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  km.save(xbob.io.base.HDF5File(filename, 'w'))
+  km_loaded = KMeansMachine(xbob.io.base.HDF5File(filename))
+  assert km == km_loaded
 
-    # Resize
-    km.resize(4,5)
-    self.assertTrue( km.dim_c == 4 )
-    self.assertTrue( km.dim_d == 5 )
+  # Resize
+  km.resize(4,5)
+  assert km.dim_c == 4
+  assert km.dim_d == 5
 
-    # Copy constructor and comparison operators
-    km.resize(2,3)
-    km2 = bob.machine.KMeansMachine(km)
-    self.assertTrue( km2 == km)
-    self.assertFalse( km2 != km)
-    self.assertTrue( km2.is_similar_to(km) )
-    means2 = numpy.array([[3, 70, 0], [4, 72, 2]], 'float64')
-    km2.means = means2
-    self.assertFalse( km2 == km)
-    self.assertTrue( km2 != km)
-    self.assertFalse( km2.is_similar_to(km) )
+  # Copy constructor and comparison operators
+  km.resize(2,3)
+  km2 = KMeansMachine(km)
+  assert km2 == km
+  assert (km2 != km) is False
+  assert km2.is_similar_to(km)
+  means2 = numpy.array([[3, 70, 0], [4, 72, 2]], 'float64')
+  km2.means = means2
+  assert (km2 == km) is False
+  assert km2 != km
+  assert (km2.is_similar_to(km)) is False
 
-    # Clean-up
-    os.unlink(filename)
+  # Clean-up
+  os.unlink(filename)
diff --git a/xbob/learn/misc/test_kmeans_trainer.py b/xbob/learn/misc/test_kmeans_trainer.py
index e7a5aa9..2964ce2 100644
--- a/xbob/learn/misc/test_kmeans_trainer.py
+++ b/xbob/learn/misc/test_kmeans_trainer.py
@@ -3,23 +3,15 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Fri Jan 18 12:46:00 2013 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Test K-Means algorithm
 """
-import os, sys
-import unittest
-import bob
-import random
 import numpy
-import pkg_resources
 
-def F(f, module=None):
-  """Returns the test file on the "data" subdirectory"""
-  if module is None:
-    return pkg_resources.resource_filename(__name__, os.path.join('data', f))
-  return pkg_resources.resource_filename('bob.%s.test' % module, 
-      os.path.join('data', f))
+import xbob.core
+import xbob.io
+from xbob.io.base.test_utils import datafile
 
 def equals(x, y, epsilon):
   return (abs(x - y) < epsilon).all()
@@ -27,8 +19,8 @@ def equals(x, y, epsilon):
 def kmeans_plus_plus(machine, data, seed):
   """Python implementation of K-Means++ (initialization)"""
   n_data = data.shape[0]
-  mt = bob.core.random.mt19937(seed)
-  rng = bob.core.random.uniform_int32(0, n_data-1)
+  mt = xbob.core.random.mt19937(seed)
+  rng = xbob.core.random.uniform_int32(0, n_data-1)
   index = rng(mt)
   machine.set_mean(0, data[index,:])
   weights = numpy.zeros(shape=(n_data,), dtype=numpy.float64)
@@ -42,13 +34,13 @@ def kmeans_plus_plus(machine, data, seed):
       weights[s] = w_cur
     weights *= weights
     weights /= numpy.sum(weights)
-    rng_d = bob.core.random.discrete_int32(weights)
+    rng_d = xbob.core.random.discrete_int32(weights)
     index = rng_d(mt)
     machine.set_mean(m, data[index,:])
 
 
 def NormalizeStdArray(path):
-  array = bob.io.load(path).astype('float64')
+  array = xbob.io.base.load(path).astype('float64')
   std = array.std(axis=0)
   return (array/std, std)
 
@@ -65,123 +57,119 @@ def flipRows(array):
   else:
     raise Exception('Input type not supportd by flipRows')
 
-class KMeansTest(unittest.TestCase):
-  """Performs various trainer tests."""
-  
-  if hasattr(bob.trainer.KMeansTrainer, 'KMEANS_PLUS_PLUS'):
-    def test00_kmeans_plus_plus(self):
-
-      # Tests the K-Means++ initialization
-      dim_c = 5
-      dim_d = 7
-      n_samples = 150
-      data = numpy.random.randn(n_samples,dim_d)
-      seed = 0
-      
-      # C++ implementation
-      machine = bob.machine.KMeansMachine(dim_c, dim_d)
-      trainer = bob.trainer.KMeansTrainer()
-      trainer.rng = bob.core.random.mt19937(seed)
-      trainer.initialization_method = bob.trainer.KMeansTrainer.KMEANS_PLUS_PLUS
-      trainer.initialize(machine, data)
-
-      # Python implementation
-      py_machine = bob.machine.KMeansMachine(dim_c, dim_d)
-      kmeans_plus_plus(py_machine, data, seed)
-      self.assertTrue(equals(machine.means, py_machine.means, 1e-8))
-
-  def test01_kmeans_noduplicate(self):
-    # Data/dimensions
-    dim_c = 2
-    dim_d = 3
+if hasattr(bob.trainer.KMeansTrainer, 'KMEANS_PLUS_PLUS'):
+  def test_kmeans_plus_plus():
+
+    # Tests the K-Means++ initialization
+    dim_c = 5
+    dim_d = 7
+    n_samples = 150
+    data = numpy.random.randn(n_samples,dim_d)
     seed = 0
-    data = numpy.array([[1,2,3],[1,2,3],[1,2,3],[4,5,6.]])
-    # Defines machine and trainer
+
+    # C++ implementation
     machine = bob.machine.KMeansMachine(dim_c, dim_d)
     trainer = bob.trainer.KMeansTrainer()
-    trainer.rng = bob.core.random.mt19937(seed)
-    trainer.initialization_method = bob.trainer.KMeansTrainer.RANDOM_NO_DUPLICATE
+    trainer.rng = xbob.core.random.mt19937(seed)
+    trainer.initialization_method = bob.trainer.KMeansTrainer.KMEANS_PLUS_PLUS
     trainer.initialize(machine, data)
-    # Makes sure that the two initial mean vectors selected are different
-    self.assertFalse(equals(machine.get_mean(0), machine.get_mean(1), 1e-8))
- 
-  def test02_kmeans_a(self):
-
-    # Trains a KMeansMachine
-    # This files contains draws from two 1D Gaussian distributions:
-    #   * 100 samples from N(-10,1)
-    #   * 100 samples from N(10,1)
-    data = bob.io.load(F("samplesFrom2G_f64.hdf5"))
-
-    machine = bob.machine.KMeansMachine(2, 1)
-
-    trainer = bob.trainer.KMeansTrainer()
-    trainer.train(machine, data)
-
-    [variances, weights] = machine.get_variances_and_weights_for_each_cluster(data)
-    variances_b = numpy.ndarray(shape=(2,1), dtype=numpy.float64)
-    weights_b = numpy.ndarray(shape=(2,), dtype=numpy.float64)
-    machine.__get_variances_and_weights_for_each_cluster_init__(variances_b, weights_b)
-    machine.__get_variances_and_weights_for_each_cluster_acc__(data, variances_b, weights_b)
-    machine.__get_variances_and_weights_for_each_cluster_fin__(variances_b, weights_b)
-    m1 = machine.get_mean(0)
-    m2 = machine.get_mean(1)
-
-    # Check means [-10,10] / variances [1,1] / weights [0.5,0.5]
-    if(m1<m2): means=numpy.array(([m1[0],m2[0]]), 'float64')
-    else: means=numpy.array(([m2[0],m1[0]]), 'float64')
-    self.assertTrue(equals(means, numpy.array([-10.,10.]), 2e-1))
-    self.assertTrue(equals(variances, numpy.array([1.,1.]), 2e-1))
-    self.assertTrue(equals(weights, numpy.array([0.5,0.5]), 1e-3))
-
-    self.assertTrue(equals(variances, variances_b, 1e-8))
-    self.assertTrue(equals(weights, weights_b, 1e-8))
-
-  def test03_kmeans_b(self):
-
-    # Trains a KMeansMachine
-    (arStd,std) = NormalizeStdArray(F("faithful.torch3.hdf5"))
-
-    machine = bob.machine.KMeansMachine(2, 2)
-
-    trainer = bob.trainer.KMeansTrainer()
-    #trainer.seed = 1337
-    trainer.train(machine, arStd)
-
-    [variances, weights] = machine.get_variances_and_weights_for_each_cluster(arStd)
-    means = machine.means
-
-    multiplyVectorsByFactors(means, std)
-    multiplyVectorsByFactors(variances, std ** 2)
-
-    gmmWeights = bob.io.load(F('gmm.init_weights.hdf5'))
-    gmmMeans = bob.io.load(F('gmm.init_means.hdf5'))
-    gmmVariances = bob.io.load(F('gmm.init_variances.hdf5'))
-
-    if (means[0, 0] < means[1, 0]):
-      means = flipRows(means)
-      variances = flipRows(variances)
-      weights = flipRows(weights)
-   
-    self.assertTrue(equals(means, gmmMeans, 1e-3))
-    self.assertTrue(equals(weights, gmmWeights, 1e-3))
-    self.assertTrue(equals(variances, gmmVariances, 1e-3))
-
-    # Check comparison operators
-    trainer1 = bob.trainer.KMeansTrainer()
-    trainer2 = bob.trainer.KMeansTrainer()
-    trainer1.rng = trainer2.rng
-    self.assertTrue( trainer1 == trainer2)
-    self.assertFalse( trainer1 != trainer2)
-    trainer1.max_iterations = 1337
-    self.assertFalse( trainer1 == trainer2)
-    self.assertTrue( trainer1 != trainer2)
-
-    # Check that there is no duplicate means during initialization
-    machine = bob.machine.KMeansMachine(2, 1)
-    trainer = bob.trainer.KMeansTrainer()
-    trainer.initialization_method = bob.trainer.KMeansTrainer.RANDOM_NO_DUPLICATE
-    data = numpy.array([[1.], [1.], [1.], [1.], [1.], [1.], [2.], [3.]])
-    trainer.train(machine, data)
-    self.assertFalse( numpy.isnan(machine.means).any())
 
+    # Python implementation
+    py_machine = bob.machine.KMeansMachine(dim_c, dim_d)
+    kmeans_plus_plus(py_machine, data, seed)
+    assert equals(machine.means, py_machine.means, 1e-8)
+
+def test_kmeans_noduplicate():
+  # Data/dimensions
+  dim_c = 2
+  dim_d = 3
+  seed = 0
+  data = numpy.array([[1,2,3],[1,2,3],[1,2,3],[4,5,6.]])
+  # Defines machine and trainer
+  machine = bob.machine.KMeansMachine(dim_c, dim_d)
+  trainer = bob.trainer.KMeansTrainer()
+  trainer.rng = xbob.core.random.mt19937(seed)
+  trainer.initialization_method = bob.trainer.KMeansTrainer.RANDOM_NO_DUPLICATE
+  trainer.initialize(machine, data)
+  # Makes sure that the two initial mean vectors selected are different
+  assert (equals(machine.get_mean(0), machine.get_mean(1), 1e-8)) is False
+
+def test_kmeans_a():
+
+  # Trains a KMeansMachine
+  # This files contains draws from two 1D Gaussian distributions:
+  #   * 100 samples from N(-10,1)
+  #   * 100 samples from N(10,1)
+  data = xbob.io.base.load(datafile("samplesFrom2G_f64.hdf5", __name__))
+
+  machine = bob.machine.KMeansMachine(2, 1)
+
+  trainer = bob.trainer.KMeansTrainer()
+  trainer.train(machine, data)
+
+  [variances, weights] = machine.get_variances_and_weights_for_each_cluster(data)
+  variances_b = numpy.ndarray(shape=(2,1), dtype=numpy.float64)
+  weights_b = numpy.ndarray(shape=(2,), dtype=numpy.float64)
+  machine.__get_variances_and_weights_for_each_cluster_init__(variances_b, weights_b)
+  machine.__get_variances_and_weights_for_each_cluster_acc__(data, variances_b, weights_b)
+  machine.__get_variances_and_weights_for_each_cluster_fin__(variances_b, weights_b)
+  m1 = machine.get_mean(0)
+  m2 = machine.get_mean(1)
+
+  # Check means [-10,10] / variances [1,1] / weights [0.5,0.5]
+  if(m1<m2): means=numpy.array(([m1[0],m2[0]]), 'float64')
+  else: means=numpy.array(([m2[0],m1[0]]), 'float64')
+  assert equals(means, numpy.array([-10.,10.]), 2e-1)
+  assert equals(variances, numpy.array([1.,1.]), 2e-1)
+  assert equals(weights, numpy.array([0.5,0.5]), 1e-3)
+
+  assert equals(variances, variances_b, 1e-8)
+  assert equals(weights, weights_b, 1e-8)
+
+def test_kmeans_b():
+
+  # Trains a KMeansMachine
+  (arStd,std) = NormalizeStdArray(datafile("faithful.torch3.hdf5", __name__))
+
+  machine = bob.machine.KMeansMachine(2, 2)
+
+  trainer = bob.trainer.KMeansTrainer()
+  #trainer.seed = 1337
+  trainer.train(machine, arStd)
+
+  [variances, weights] = machine.get_variances_and_weights_for_each_cluster(arStd)
+  means = machine.means
+
+  multiplyVectorsByFactors(means, std)
+  multiplyVectorsByFactors(variances, std ** 2)
+
+  gmmWeights = xbob.io.base.load(datafile('gmm.init_weights.hdf5', __name__))
+  gmmMeans = xbob.io.base.load(datafile('gmm.init_means.hdf5', __name__))
+  gmmVariances = xbob.io.base.load(datafile('gmm.init_variances.hdf5', __name__))
+
+  if (means[0, 0] < means[1, 0]):
+    means = flipRows(means)
+    variances = flipRows(variances)
+    weights = flipRows(weights)
+
+  assert equals(means, gmmMeans, 1e-3)
+  assert equals(weights, gmmWeights, 1e-3)
+  assert equals(variances, gmmVariances, 1e-3)
+
+  # Check comparison operators
+  trainer1 = bob.trainer.KMeansTrainer()
+  trainer2 = bob.trainer.KMeansTrainer()
+  trainer1.rng = trainer2.rng
+  assert trainer1 == trainer2
+  assert (trainer1 != trainer2) is False
+  trainer1.max_iterations = 1337
+  assert (trainer1 == trainer2) is False
+  assert trainer1 != trainer2
+
+  # Check that there is no duplicate means during initialization
+  machine = bob.machine.KMeansMachine(2, 1)
+  trainer = bob.trainer.KMeansTrainer()
+  trainer.initialization_method = bob.trainer.KMeansTrainer.RANDOM_NO_DUPLICATE
+  data = numpy.array([[1.], [1.], [1.], [1.], [1.], [1.], [2.], [3.]])
+  trainer.train(machine, data)
+  assert (numpy.isnan(machine.means).any()) is False
diff --git a/xbob/learn/misc/test_linearscoring.py b/xbob/learn/misc/test_linearscoring.py
index e72a5ef..a86f66e 100644
--- a/xbob/learn/misc/test_linearscoring.py
+++ b/xbob/learn/misc/test_linearscoring.py
@@ -3,126 +3,123 @@
 # Francois Moulin <Francois.Moulin@idiap.ch>
 # Wed Jul 13 16:00:04 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests on the LinearScoring function
 """
 
-import os, sys
-import unittest
-import bob
 import numpy
 
-class LinearScoringTest(unittest.TestCase):
-  """Performs various LinearScoring tests."""
-
-  def test01_LinearScoring(self):
-    ubm = bob.machine.GMMMachine(2, 2)
-    ubm.weights   = numpy.array([0.5, 0.5], 'float64')
-    ubm.means     = numpy.array([[3, 70], [4, 72]], 'float64')
-    ubm.variances = numpy.array([[1, 10], [2, 5]], 'float64')
-    ubm.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
-
-    model1 = bob.machine.GMMMachine(2, 2)
-    model1.weights   = numpy.array([0.5, 0.5], 'float64')
-    model1.means     = numpy.array([[1, 2], [3, 4]], 'float64')
-    model1.variances = numpy.array([[9, 10], [11, 12]], 'float64')
-    model1.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
-    
-    model2 = bob.machine.GMMMachine(2, 2)
-    model2.weights   = numpy.array([0.5, 0.5], 'float64')
-    model2.means     = numpy.array([[5, 6], [7, 8]], 'float64')
-    model2.variances = numpy.array([[13, 14], [15, 16]], 'float64')
-    model2.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
-    
-    stats1 = bob.machine.GMMStats(2, 2)
-    stats1.sum_px = numpy.array([[1, 2], [3, 4]], 'float64')
-    stats1.n = numpy.array([1, 2], 'float64')
-    stats1.t = 1+2
-    
-    stats2 = bob.machine.GMMStats(2, 2)
-    stats2.sum_px = numpy.array([[5, 6], [7, 8]], 'float64')
-    stats2.n = numpy.array([3, 4], 'float64')
-    stats2.t = 3+4
-    
-    stats3 = bob.machine.GMMStats(2, 2)
-    stats3.sum_px = numpy.array([[5, 6], [7, 3]], 'float64')
-    stats3.n = numpy.array([3, 4], 'float64')
-    stats3.t = 3+4
-
-    test_channeloffset = [numpy.array([9, 8, 7, 6], 'float64'), numpy.array([5, 4, 3, 2], 'float64'), numpy.array([1, 0, 1, 2], 'float64')]
-
-    # Reference scores (from Idiap internal matlab implementation)
-    ref_scores_00 = numpy.array([[2372.9, 5207.7, 5275.7], [2215.7, 4868.1, 4932.1]], 'float64')
-    ref_scores_01 = numpy.array( [[790.9666666666667, 743.9571428571428, 753.6714285714285], [738.5666666666667, 695.4428571428572, 704.5857142857144]], 'float64')
-    ref_scores_10 = numpy.array([[2615.5, 5434.1, 5392.5], [2381.5, 4999.3, 5022.5]], 'float64')
-    ref_scores_11 = numpy.array([[871.8333333333332, 776.3000000000001, 770.3571428571427], [793.8333333333333, 714.1857142857143, 717.5000000000000]], 'float64')
-
-
-    # 1/ Use GMMMachines
-    # 1/a/ Without test_channelOffset, without frame-length normalisation
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3])
-    self.assertTrue((abs(scores - ref_scores_00) < 1e-7).all())
-    
-    # 1/b/ Without test_channelOffset, with frame-length normalisation
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], [], True)
-    self.assertTrue((abs(scores - ref_scores_01) < 1e-7).all())
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], (), True)
-    self.assertTrue((abs(scores - ref_scores_01) < 1e-7).all())
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], None, True)
-    self.assertTrue((abs(scores - ref_scores_01) < 1e-7).all())
-
-    # 1/c/ With test_channelOffset, without frame-length normalisation
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], test_channeloffset)
-    self.assertTrue((abs(scores - ref_scores_10) < 1e-7).all())
-
-    # 1/d/ With test_channelOffset, with frame-length normalisation
-    scores = bob.machine.linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], test_channeloffset, True)
-    self.assertTrue((abs(scores - ref_scores_11) < 1e-7).all())
-
-    
-    # 2/ Use mean/variance supervectors
-    # 2/a/ Without test_channelOffset, without frame-length normalisation
-    scores = bob.machine.linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3])
-    self.assertTrue((abs(scores - ref_scores_00) < 1e-7).all())
-
-    # 2/b/ Without test_channelOffset, with frame-length normalisation
-    scores = bob.machine.linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], [], True)
-    self.assertTrue((abs(scores - ref_scores_01) < 1e-7).all())
-
-    # 2/c/ With test_channelOffset, without frame-length normalisation
-    scores = bob.machine.linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], test_channeloffset)
-    self.assertTrue((abs(scores - ref_scores_10) < 1e-7).all())
-
-    # 2/d/ With test_channelOffset, with frame-length normalisation
-    scores = bob.machine.linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], test_channeloffset, True)
-    self.assertTrue((abs(scores - ref_scores_11) < 1e-7).all())
-
-    # 3/ Using single model/sample
-    # 3/a/ without frame-length normalisation
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0])
-    self.assertTrue(abs(score - ref_scores_10[0,0]) < 1e-7)
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1])
-    self.assertTrue(abs(score - ref_scores_10[0,1]) < 1e-7)
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2])
-    self.assertTrue(abs(score - ref_scores_10[0,2]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0])
-    self.assertTrue(abs(score - ref_scores_10[1,0]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1])
-    self.assertTrue(abs(score - ref_scores_10[1,1]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2])
-    self.assertTrue(abs(score - ref_scores_10[1,2]) < 1e-7)
-
-    # 3/b/ without frame-length normalisation
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0], True)
-    self.assertTrue(abs(score - ref_scores_11[0,0]) < 1e-7)
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1], True)
-    self.assertTrue(abs(score - ref_scores_11[0,1]) < 1e-7)
-    score = bob.machine.linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2], True)
-    self.assertTrue(abs(score - ref_scores_11[0,2]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0], True)
-    self.assertTrue(abs(score - ref_scores_11[1,0]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1], True)
-    self.assertTrue(abs(score - ref_scores_11[1,1]) < 1e-7)
-    score = bob.machine.linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2], True)
-    self.assertTrue(abs(score - ref_scores_11[1,2]) < 1e-7)
+from . import GMMMachine, GMMStats, linear_scoring
+
+def test_LinearScoring():
+
+  ubm = GMMMachine(2, 2)
+  ubm.weights   = numpy.array([0.5, 0.5], 'float64')
+  ubm.means     = numpy.array([[3, 70], [4, 72]], 'float64')
+  ubm.variances = numpy.array([[1, 10], [2, 5]], 'float64')
+  ubm.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
+
+  model1 = GMMMachine(2, 2)
+  model1.weights   = numpy.array([0.5, 0.5], 'float64')
+  model1.means     = numpy.array([[1, 2], [3, 4]], 'float64')
+  model1.variances = numpy.array([[9, 10], [11, 12]], 'float64')
+  model1.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
+
+  model2 = GMMMachine(2, 2)
+  model2.weights   = numpy.array([0.5, 0.5], 'float64')
+  model2.means     = numpy.array([[5, 6], [7, 8]], 'float64')
+  model2.variances = numpy.array([[13, 14], [15, 16]], 'float64')
+  model2.variance_thresholds = numpy.array([[0, 0], [0, 0]], 'float64')
+
+  stats1 = GMMStats(2, 2)
+  stats1.sum_px = numpy.array([[1, 2], [3, 4]], 'float64')
+  stats1.n = numpy.array([1, 2], 'float64')
+  stats1.t = 1+2
+
+  stats2 = GMMStats(2, 2)
+  stats2.sum_px = numpy.array([[5, 6], [7, 8]], 'float64')
+  stats2.n = numpy.array([3, 4], 'float64')
+  stats2.t = 3+4
+
+  stats3 = GMMStats(2, 2)
+  stats3.sum_px = numpy.array([[5, 6], [7, 3]], 'float64')
+  stats3.n = numpy.array([3, 4], 'float64')
+  stats3.t = 3+4
+
+  test_channeloffset = [numpy.array([9, 8, 7, 6], 'float64'), numpy.array([5, 4, 3, 2], 'float64'), numpy.array([1, 0, 1, 2], 'float64')]
+
+  # Reference scores (from Idiap internal matlab implementation)
+  ref_scores_00 = numpy.array([[2372.9, 5207.7, 5275.7], [2215.7, 4868.1, 4932.1]], 'float64')
+  ref_scores_01 = numpy.array( [[790.9666666666667, 743.9571428571428, 753.6714285714285], [738.5666666666667, 695.4428571428572, 704.5857142857144]], 'float64')
+  ref_scores_10 = numpy.array([[2615.5, 5434.1, 5392.5], [2381.5, 4999.3, 5022.5]], 'float64')
+  ref_scores_11 = numpy.array([[871.8333333333332, 776.3000000000001, 770.3571428571427], [793.8333333333333, 714.1857142857143, 717.5000000000000]], 'float64')
+
+
+  # 1/ Use GMMMachines
+  # 1/a/ Without test_channelOffset, without frame-length normalisation
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3])
+  assert (abs(scores - ref_scores_00) < 1e-7).all()
+
+  # 1/b/ Without test_channelOffset, with frame-length normalisation
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], [], True)
+  assert (abs(scores - ref_scores_01) < 1e-7).all()
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], (), True)
+  assert (abs(scores - ref_scores_01) < 1e-7).all()
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], None, True)
+  assert (abs(scores - ref_scores_01) < 1e-7).all()
+
+  # 1/c/ With test_channelOffset, without frame-length normalisation
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], test_channeloffset)
+  assert (abs(scores - ref_scores_10) < 1e-7).all()
+
+  # 1/d/ With test_channelOffset, with frame-length normalisation
+  scores = linear_scoring([model1, model2], ubm, [stats1, stats2, stats3], test_channeloffset, True)
+  assert (abs(scores - ref_scores_11) < 1e-7).all()
+
+
+  # 2/ Use mean/variance supervectors
+  # 2/a/ Without test_channelOffset, without frame-length normalisation
+  scores = linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3])
+  assert (abs(scores - ref_scores_00) < 1e-7).all()
+
+  # 2/b/ Without test_channelOffset, with frame-length normalisation
+  scores = linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], [], True)
+  assert (abs(scores - ref_scores_01) < 1e-7).all()
+
+  # 2/c/ With test_channelOffset, without frame-length normalisation
+  scores = linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], test_channeloffset)
+  assert (abs(scores - ref_scores_10) < 1e-7).all()
+
+  # 2/d/ With test_channelOffset, with frame-length normalisation
+  scores = linear_scoring([model1.mean_supervector, model2.mean_supervector], ubm.mean_supervector, ubm.variance_supervector, [stats1, stats2, stats3], test_channeloffset, True)
+  assert (abs(scores - ref_scores_11) < 1e-7).all()
+
+  # 3/ Using single model/sample
+  # 3/a/ without frame-length normalisation
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0])
+  assert abs(score - ref_scores_10[0,0]) < 1e-7
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1])
+  assert abs(score - ref_scores_10[0,1]) < 1e-7
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2])
+  assert abs(score - ref_scores_10[0,2]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0])
+  assert abs(score - ref_scores_10[1,0]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1])
+  assert abs(score - ref_scores_10[1,1]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2])
+  assert abs(score - ref_scores_10[1,2]) < 1e-7
+
+  # 3/b/ without frame-length normalisation
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0], True)
+  assert abs(score - ref_scores_11[0,0]) < 1e-7
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1], True)
+  assert abs(score - ref_scores_11[0,1]) < 1e-7
+  score = linear_scoring(model1.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2], True)
+  assert abs(score - ref_scores_11[0,2]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats1, test_channeloffset[0], True)
+  assert abs(score - ref_scores_11[1,0]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats2, test_channeloffset[1], True)
+  assert abs(score - ref_scores_11[1,1]) < 1e-7
+  score = linear_scoring(model2.mean_supervector, ubm.mean_supervector, ubm.variance_supervector, stats3, test_channeloffset[2], True)
+  assert abs(score - ref_scores_11[1,2]) < 1e-7
diff --git a/xbob/learn/misc/test_plda.py b/xbob/learn/misc/test_plda.py
index c8953ec..07aec4f 100644
--- a/xbob/learn/misc/test_plda.py
+++ b/xbob/learn/misc/test_plda.py
@@ -3,18 +3,21 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Sat Oct 22 23:01:09 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests PLDA machine
 """
 
-import os, sys
-import unittest
-import bob
-import random, tempfile, math
+import os
+import tempfile
+import math
 import numpy
 import numpy.linalg
 
+import xbob.io.base
+
+from . import PLDABase, PLDAMachine
+
 # Defines common variables globally
 # Dimensionalities
 C_dim_d = 7
@@ -29,12 +32,12 @@ C_G=numpy.array([-1.1424, -0.5044, -0.1917,
       -0.5936, -0.8571, -0.2046,
       0.4364, -0.1699, -2.2015], 'float64').reshape(C_dim_d, C_dim_g)
 # F <-> PCA on G
-C_F=numpy.array([-0.054222647972093, -0.000000000783146, 
-      0.596449127693018,  0.000000006265167, 
-      0.298224563846509,  0.000000003132583, 
-      0.447336845769764,  0.000000009397750, 
-      -0.108445295944185, -0.000000001566292, 
-      -0.501559493741856, -0.000000006265167, 
+C_F=numpy.array([-0.054222647972093, -0.000000000783146,
+      0.596449127693018,  0.000000006265167,
+      0.298224563846509,  0.000000003132583,
+      0.447336845769764,  0.000000009397750,
+      -0.108445295944185, -0.000000001566292,
+      -0.501559493741856, -0.000000006265167,
       -0.298224563846509, -0.000000003132583], 'float64').reshape(C_dim_d, C_dim_f)
 
 def equals(x, y, epsilon):
@@ -62,7 +65,7 @@ def compute_gamma(F, G, sigma, a):
   dim_f = F.shape[1]
   beta = compute_beta(G, sigma)
   return numpy.linalg.inv(numpy.eye(dim_f) + a * numpy.dot(numpy.dot(F.transpose(), beta), F))
- 
+
 def compute_ft_beta(F, G, sigma):
   # F^T.beta = F^T.\mathcal{S}
   beta = compute_beta(G, sigma)
@@ -77,7 +80,7 @@ def compute_logdet_alpha(G, sigma):
   # \log(\det(\alpha)) = \log(\det(\mathcal{G}))
   alpha = compute_alpha(G, sigma)
   return math.log(numpy.linalg.det(alpha))
-  
+
 def compute_logdet_sigma(sigma):
   # \log(\det(\sigma)) = \log(\det(\sigma)) = \log(\prod(\sigma_{i}))
   return math.log(numpy.prod(sigma))
@@ -94,37 +97,37 @@ def compute_loglike_constterm(F, G, sigma, a):
   return res;
 
 def compute_log_likelihood_point_estimate(observation, mu, F, G, sigma, hi, wij):
-  """ 
+  """
   This function computes p(x_{ij} | h_{i}, w_{ij}, \Theta), which is given by
-    N_{x}[\mu + Fh_{i} + Gw_{ij} + epsilon_{ij}, \Sigma], N_{x} being a 
-    Gaussian distribution. As it returns the corresponding log likelihood, 
+    N_{x}[\mu + Fh_{i} + Gw_{ij} + epsilon_{ij}, \Sigma], N_{x} being a
+    Gaussian distribution. As it returns the corresponding log likelihood,
     this is given by the sum of the following three terms:
-      C1 = -dim_d/2 log(2pi)               
+      C1 = -dim_d/2 log(2pi)
       C2 = -1/2 log(det(\Sigma))
       C3 = -1/2 (x_{ij}-\mu-Fh_{i}-Gw_{ij})^{T}\Sigma^{-1}(x_{ij}-\mu-Fh_{i}-Gw_{ij})
   """
-  
+
   ### Pre-computes some of the constants
   dim_d          = observation.shape[0]             # A scalar
   log_2pi        = numpy.log(2. * numpy.pi);        # A scalar
   C1             = -(dim_d / 2.) * log_2pi;         # A scalar
   C2             = -(1. / 2.) * numpy.sum( numpy.log(sigma) ); # (dim_d, 1)
-  
-  ### Subtract the identity and session components from the observed vector.    
+
+  ### Subtract the identity and session components from the observed vector.
   session_plus_identity  = numpy.dot(F, hi) + numpy.dot(G, wij);
-  normalised_observation = numpy.reshape(observation - mu - session_plus_identity, (dim_d,1)); 
+  normalised_observation = numpy.reshape(observation - mu - session_plus_identity, (dim_d,1));
   ### Now calculate C3
   sigma_inverse  = numpy.reshape(1. / sigma, (dim_d,1));                      # (dim_d, 1)
   C3             = -(1. / 2.) * numpy.sum(normalised_observation * sigma_inverse * normalised_observation);
-  
+
   ### Returns the log likelihood
-  log_likelihood     = C1 + C2 + C3; 
+  log_likelihood     = C1 + C2 + C3;
   return (log_likelihood);
 
 
 def compute_log_likelihood(observations, mu, F, G, sigma):
   """
-  This function computes the log-likelihood of the observations given the parameters 
+  This function computes the log-likelihood of the observations given the parameters
   of the PLDA model. This is done by fulling integrating out the latent variables.
   """
   # Work out the number of samples that we have and normalise the data.
@@ -149,7 +152,7 @@ def compute_log_likelihood(observations, mu, F, G, sigma):
   C2 = - J_i/2.*(ld_sigma - ld_alpha)  + ld_gamma/2.
 
   # 3. Computes C3
-  # This is a quadratic part and consists of 
+  # This is a quadratic part and consists of
   # C3   = -0.5 * sum x^T beta x + 0.5 * Quadratic term in x
   # C3   = -0.5 * (C3a - C3b)
   C3a                  = 0.0;
@@ -173,389 +176,390 @@ def compute_log_likelihood(observations, mu, F, G, sigma):
   return C1 + C2 + C3
 
 
-class PLDAMachineTest(unittest.TestCase):
-  """Performs various PLDA machine tests."""
-
-  def test01_plda_basemachine(self):
-    # Data used for performing the tests
-    sigma = numpy.ndarray(C_dim_d, 'float64')
-    sigma.fill(0.01)
-    mu = numpy.ndarray(C_dim_d, 'float64')
-    mu.fill(0)
-
-    # Defines reference results based on matlab
-    alpha_ref = numpy.array([ 0.002189051545735,  0.001127099941432,
-      -0.000145483208153, 0.001127099941432,  0.003549267943741,
-      -0.000552001405453, -0.000145483208153, -0.000552001405453,
-      0.001440505362615], 'float64').reshape(C_dim_g, C_dim_g)
-    beta_ref  = numpy.array([ 50.587191765140361, -14.512478352504877,
-      -0.294799164567830,  13.382002504394316,  9.202063877660278,
-      -43.182264846086497,  11.932345916716455, -14.512478352504878,
-      82.320149045633045, -12.605578822979698,  19.618675892079366,
-      13.033691341150439,  -8.004874490989799, -21.547363307109187,
-      -0.294799164567832, -12.605578822979696,  52.123885798398241,
-      4.363739008635009, 44.847177605628545,  16.438137537463710,
-      5.137421840557050, 13.382002504394316,  19.618675892079366,
-      4.363739008635011,  75.070401560513488, -4.515472972526140,
-      9.752862741017488,  34.196127678931106, 9.202063877660285,
-      13.033691341150439,  44.847177605628552,  -4.515472972526142,
-      56.189416227691098,  -7.536676357632515, -10.555735414707383,
-      -43.182264846086497,  -8.004874490989799,  16.438137537463703,
-      9.752862741017490, -7.536676357632518,  56.430571485722126,
-      9.471758169835317, 11.932345916716461, -21.547363307109187,
-      5.137421840557051,  34.196127678931099, -10.555735414707385,
-      9.471758169835320,  27.996266602110637], 'float64').reshape(C_dim_d, C_dim_d)
-    gamma3_ref = numpy.array([ 0.005318799462241, -0.000000012993151,
-      -0.000000012993151,  0.999999999999996], 'float64').reshape(C_dim_f, C_dim_f)
-
-    # Constructor tests
-    m = bob.machine.PLDABase()
-    self.assertTrue(m.dim_d == 0)
-    self.assertTrue(m.dim_f == 0)
-    self.assertTrue(m.dim_g == 0)
-    del m
-    m = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g)
-    self.assertTrue(m.dim_d == C_dim_d)
-    self.assertTrue(m.dim_f == C_dim_f)
-    self.assertTrue(m.dim_g == C_dim_g)
-    self.assertTrue( abs(m.variance_threshold - 0.) < 1e-10 )
-    del m
-    m = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g, 1e-2)
-    self.assertTrue(m.dim_d == C_dim_d)
-    self.assertTrue(m.dim_f == C_dim_f)
-    self.assertTrue(m.dim_g == C_dim_g)
-    self.assertTrue( abs(m.variance_threshold - 1e-2) < 1e-10 )
-    del m
-
-    # Defines base machine
-    m = bob.machine.PLDABase()
-    m.resize(C_dim_d, C_dim_f, C_dim_g)
-    # Sets the current mu, F, G and sigma 
-    m.mu = mu
-    m.f = C_F
-    m.g = C_G
-    m.sigma = sigma
-    gamma3 = m.get_add_gamma(3).copy()
-    constTerm3 = m.get_add_log_like_const_term(3)
-    
-    # Compares precomputed values to matlab reference
-    for ii in range(m.__alpha__.shape[0]):
-      for jj in range(m.__alpha__.shape[1]):
-        absdiff = abs(m.__alpha__[ii,jj]- alpha_ref[ii,jj])
-        assert absdiff < 1e-10, 'PLDABase alpha matrix does not match reference at (%d,%d) to 10^-10: |%g-%g| = %g' % (ii, jj, m.__alpha__[ii,jj], alpha_ref[ii,jj], absdiff)
-    print(m.__alpha__ - alpha_ref)
-    self.assertTrue(equals(m.__alpha__, alpha_ref, 1e-10))
-    self.assertTrue(equals(m.__beta__, beta_ref, 1e-10))
-    self.assertTrue(equals(gamma3, gamma3_ref, 1e-10))
-
-    # Compares precomputed values to the ones returned by python implementation
-    self.assertTrue(equals(m.__isigma__, compute_i_sigma(sigma), 1e-10))
-    self.assertTrue(equals(m.__alpha__, compute_alpha(C_G,sigma), 1e-10))
-    self.assertTrue(equals(m.__beta__, compute_beta(C_G,sigma), 1e-10))
-    self.assertTrue(equals(m.get_add_gamma(3), compute_gamma(C_F,C_G,sigma,3), 1e-10))
-    self.assertTrue(m.has_gamma(3))
-    self.assertTrue(equals(m.get_gamma(3), compute_gamma(C_F,C_G,sigma,3), 1e-10))
-    self.assertTrue(equals(m.__ft_beta__, compute_ft_beta(C_F,C_G,sigma), 1e-10))
-    self.assertTrue(equals(m.__gt_i_sigma__, compute_gt_i_sigma(C_G,sigma), 1e-10))
-    self.assertTrue(math.fabs(m.__logdet_alpha__ - compute_logdet_alpha(C_G,sigma)) < 1e-10)
-    self.assertTrue(math.fabs(m.__logdet_sigma__ - compute_logdet_sigma(sigma)) < 1e-10)
-    self.assertTrue(abs(m.get_add_log_like_const_term(3) - compute_loglike_constterm(C_F,C_G,sigma,3)) < 1e-10)
-    self.assertTrue(m.has_log_like_const_term(3))
-    self.assertTrue(abs(m.get_log_like_const_term(3) - compute_loglike_constterm(C_F,C_G,sigma,3)) < 1e-10)
-
-    # Defines base machine
-    del m
-    m = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g)
-    # Sets the current mu, F, G and sigma 
-    m.mu = mu
-    m.f = C_F
-    m.g = C_G
-    m.sigma = sigma
-    gamma3 = m.get_add_gamma(3).copy()
-    constTerm3 = m.get_add_log_like_const_term(3)
-    
-    # Compares precomputed values to matlab reference
-    self.assertTrue(equals(m.__alpha__, alpha_ref, 1e-10))
-    self.assertTrue(equals(m.__beta__, beta_ref, 1e-10))
-    self.assertTrue(equals(gamma3, gamma3_ref, 1e-10))
-
-    # values before being saved
-    isigma = m.__isigma__.copy()
-    alpha = m.__alpha__.copy()
-    beta = m.__beta__.copy()
-    FtBeta = m.__ft_beta__.copy()
-    GtISigma = m.__gt_i_sigma__.copy()
-    logdetAlpha = m.__logdet_alpha__
-    logdetSigma = m.__logdet_sigma__
-
-    # Saves to file, loads and compares to original
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.PLDABase(bob.io.HDF5File(filename))
-
-    # Compares the values loaded with the former ones
-    self.assertTrue(m_loaded == m)
-    self.assertFalse(m_loaded != m)
-    self.assertTrue(equals(m_loaded.mu, mu, 1e-10))
-    self.assertTrue(equals(m_loaded.f, C_F, 1e-10))
-    self.assertTrue(equals(m_loaded.g, C_G, 1e-10))
-    self.assertTrue(equals(m_loaded.sigma, sigma, 1e-10))
-    self.assertTrue(equals(m_loaded.__isigma__, isigma, 1e-10))
-    self.assertTrue(equals(m_loaded.__alpha__, alpha, 1e-10))
-    self.assertTrue(equals(m_loaded.__beta__, beta, 1e-10))
-    self.assertTrue(equals(m_loaded.__ft_beta__, FtBeta, 1e-10))
-    self.assertTrue(equals(m_loaded.__gt_i_sigma__, GtISigma, 1e-10))
-    self.assertTrue(abs(m_loaded.__logdet_alpha__ - logdetAlpha) < 1e-10)
-    self.assertTrue(abs(m_loaded.__logdet_sigma__ - logdetSigma) < 1e-10)
-    self.assertTrue(m_loaded.has_gamma(3))
-    self.assertTrue(equals(m_loaded.get_gamma(3), gamma3_ref, 1e-10))
-    self.assertTrue(equals(m_loaded.get_add_gamma(3), gamma3_ref, 1e-10))
-    self.assertTrue(m_loaded.has_log_like_const_term(3))
-    self.assertTrue(abs(m_loaded.get_add_log_like_const_term(3) - constTerm3) < 1e-10)
-
-    # Compares the values loaded with the former ones when copying
-    m_copy = bob.machine.PLDABase(m_loaded)
-    self.assertTrue(m_loaded == m_copy)
-    self.assertFalse(m_loaded != m_copy)
-    # Test clear_maps method
-    self.assertTrue(m_copy.has_gamma(3))
-    self.assertTrue(m_copy.has_log_like_const_term(3))
-    m_copy.clear_maps()
-    self.assertFalse(m_copy.has_gamma(3))
-    self.assertFalse(m_copy.has_log_like_const_term(3))
-    
-    # Check variance flooring thresholds-related methods
-    v_zo = numpy.array([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01])
-    v_zo_ = 0.01
-    v_zzo = numpy.array([0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001])
-    v_zzo_ = 0.001
-    m_copy.variance_threshold = v_zo_
-    self.assertFalse(m_loaded == m_copy)
-    self.assertTrue(m_loaded != m_copy)
-    m_copy.variance_threshold = v_zzo_
-    m_copy.sigma = v_zo
-    self.assertTrue(equals(m_copy.sigma, v_zo, 1e-10))
-    m_copy.variance_threshold = v_zo_
-    m_copy.sigma = v_zzo
-    self.assertTrue(equals(m_copy.sigma, v_zo, 1e-10))
-    m_copy.variance_threshold = v_zzo_
-    m_copy.sigma = v_zzo
-    self.assertTrue(equals(m_copy.sigma, v_zzo, 1e-10))
-    m_copy.variance_threshold = v_zo_
-    self.assertTrue(equals(m_copy.sigma, v_zo, 1e-10)) 
-   
-    # Clean-up
-    os.unlink(filename)
-
-
-  def test02_plda_basemachine_loglikelihood_pointestimate(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    sigma = numpy.ndarray(C_dim_d, 'float64')
-    sigma.fill(0.01)
-    mu = numpy.ndarray(C_dim_d, 'float64')
-    mu.fill(0)
-    xij = numpy.array([0.7, 1.3, 2.5, 0.3, 1.3, 2.7, 0.9])
-    hi = numpy.array([-0.5, 0.5])
-    wij = numpy.array([-0.1, 0.2, 0.3])
-    
-    m = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g)
-    # Sets the current mu, F, G and sigma 
-    m.mu = mu
-    m.f = C_F
-    m.g = C_G
-    m.sigma = sigma
-
-    self.assertTrue(equals(m.compute_log_likelihood_point_estimate(xij, hi, wij), 
-                           compute_log_likelihood_point_estimate(xij, mu, C_F, C_G, sigma, hi, wij), 1e-6))
-
-
-  def test03_plda_machine(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    sigma = numpy.ndarray(C_dim_d, 'float64')
-    sigma.fill(0.01)
-    mu = numpy.ndarray(C_dim_d, 'float64')
-    mu.fill(0)
-
-    # Defines base machine
-    mb = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g)
-    # Sets the current mu, F, G and sigma 
-    mb.mu = mu
-    mb.f = C_F
-    mb.g = C_G
-    mb.sigma = sigma
-
-    # Test constructors and dim getters
-    m = bob.machine.PLDAMachine(mb)
-    self.assertTrue(m.dim_d == C_dim_d)
-    self.assertTrue(m.dim_f == C_dim_f)
-    self.assertTrue(m.dim_g == C_dim_g)
-
-    m0 = bob.machine.PLDAMachine()
-    m0.plda_base = mb
-    self.assertTrue(m0.dim_d == C_dim_d)
-    self.assertTrue(m0.dim_f == C_dim_f)
-    self.assertTrue(m0.dim_g == C_dim_g)
- 
-    # Defines machine
-    n_samples = 2
-    WSumXitBetaXi = 0.37
-    weightedSum = numpy.array([1.39,0.54], 'float64')
-    log_likelihood = -0.22
-
-    m.n_samples = n_samples
-    m.w_sum_xit_beta_xi = WSumXitBetaXi
-    m.weighted_sum = weightedSum
-    m.log_likelihood = log_likelihood
-
-    gamma3 = m.get_add_gamma(3).copy()
-    constTerm3 = m.get_add_log_like_const_term(3)
-
-    # Saves to file, loads and compares to original
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.PLDAMachine(bob.io.HDF5File(filename), mb)
-
-    # Compares the values loaded with the former ones
-    self.assertTrue(m_loaded == m)
-    self.assertFalse(m_loaded != m)
-    self.assertTrue(abs(m_loaded.n_samples - n_samples) < 1e-10)
-    self.assertTrue(abs(m_loaded.w_sum_xit_beta_xi - WSumXitBetaXi) < 1e-10)
-    self.assertTrue(equals(m_loaded.weighted_sum, weightedSum, 1e-10))
-    self.assertTrue(abs(m_loaded.log_likelihood - log_likelihood) < 1e-10) 
-    self.assertTrue(m_loaded.has_gamma(3))
-    self.assertTrue(equals(m_loaded.get_add_gamma(3), gamma3, 1e-10))
-    self.assertTrue(equals(m_loaded.get_gamma(3), gamma3, 1e-10))
-    self.assertTrue(m_loaded.has_log_like_const_term(3))
-    self.assertTrue(abs(m_loaded.get_add_log_like_const_term(3) - constTerm3) < 1e-10)
-    self.assertTrue(abs(m_loaded.get_log_like_const_term(3) - constTerm3) < 1e-10)
-
-    # Test clear_maps method
-    self.assertTrue(m_loaded.has_gamma(3))
-    self.assertTrue(m_loaded.has_log_like_const_term(3))
-    m_loaded.clear_maps()
-    self.assertFalse(m_loaded.has_gamma(3))
-    self.assertFalse(m_loaded.has_log_like_const_term(3))
-
-    # Check exceptions
-    m_loaded2 = bob.machine.PLDAMachine()
-    m_loaded2.load(bob.io.HDF5File(filename))
-    self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_d')
-    self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_f')
-    self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_g')
-    self.assertRaises(RuntimeError, m_loaded2.forward, [1.])
-    self.assertRaises(RuntimeError, m_loaded2.compute_log_likelihood, [1.])
- 
-    # Clean-up
-    os.unlink(filename)
-
-
-  def test04_plda_machine_log_likelihood_Python(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    sigma = numpy.ndarray(C_dim_d, 'float64')
-    sigma.fill(0.01)
-    mu = numpy.ndarray(C_dim_d, 'float64')
-    mu.fill(0)
-
-    # Defines base machine
-    mb = bob.machine.PLDABase(C_dim_d, C_dim_f, C_dim_g)
-    # Sets the current mu, F, G and sigma 
-    mb.mu = mu
-    mb.f = C_F
-    mb.g = C_G
-    mb.sigma = sigma
-
-    # Defines machine
-    m = bob.machine.PLDAMachine(mb)
-
-    # Defines (random) samples and check compute_log_likelihood method
-    ar_e = numpy.random.randn(2,C_dim_d)
-    ar_p = numpy.random.randn(C_dim_d)
-    ar_s = numpy.vstack([ar_e, ar_p])
-    self.assertTrue(abs(m.compute_log_likelihood(ar_s, False) - compute_log_likelihood(ar_s, mu, C_F, C_G, sigma)) < 1e-10)
-    ar_p2d = numpy.reshape(ar_p, (1,C_dim_d))
-    self.assertTrue(abs(m.compute_log_likelihood(ar_p, False) - compute_log_likelihood(ar_p2d, mu, C_F, C_G, sigma)) < 1e-10)
-
-    # Defines (random) samples and check forward method
-    ar2_e = numpy.random.randn(4,C_dim_d)
-    ar2_p = numpy.random.randn(C_dim_d)
-    ar2_s = numpy.vstack([ar2_e, ar2_p])
-    m.log_likelihood = m.compute_log_likelihood(ar2_e, False)
-    llr = m.compute_log_likelihood(ar2_s, True) - (m.compute_log_likelihood(ar2_s, False) + m.log_likelihood)
-    self.assertTrue(abs(m.forward(ar2_s) - llr) < 1e-10)
-    ar2_p2d = numpy.random.randn(3,C_dim_d)
-    ar2_s2d = numpy.vstack([ar2_e, ar2_p2d])
-    llr2d = m.compute_log_likelihood(ar2_s2d, True) - (m.compute_log_likelihood(ar2_s2d, False) + m.log_likelihood)
-    self.assertTrue(abs(m.forward(ar2_s2d) - llr2d) < 1e-10)
-
-
-  def test05_plda_machine_log_likelihood_Prince(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    D = 7
-    nf = 2
-    ng = 3
-
-    # initial values for F, G and sigma
-    G_init=numpy.array([-1.1424, -0.5044, -0.1917,
-      -0.6249,  0.1021, -0.8658,
-      -1.1687,  1.1963,  0.1807,
-      0.3926,  0.1203,  1.2665,
-      1.3018, -1.0368, -0.2512,
-      -0.5936, -0.8571, -0.2046,
-      0.4364, -0.1699, -2.2015]).reshape(D,ng)
-    # F <-> PCA on G
-    F_init=numpy.array([-0.054222647972093, -0.000000000783146, 
-      0.596449127693018,  0.000000006265167, 
-      0.298224563846509,  0.000000003132583, 
-      0.447336845769764,  0.000000009397750, 
-      -0.108445295944185, -0.000000001566292, 
-      -0.501559493741856, -0.000000006265167, 
-      -0.298224563846509, -0.000000003132583]).reshape(D,nf)
-    sigma_init = 0.01 * numpy.ones((D,), 'float64')
-    mean_zero = numpy.zeros((D,), 'float64')
-
-    # base machine
-    mb = bob.machine.PLDABase(D,nf,ng)
-    mb.sigma = sigma_init
-    mb.g = G_init
-    mb.f = F_init
-    mb.mu = mean_zero
-
-    # Data for likelihood computation
-    x1 = numpy.array([0.8032, 0.3503, 0.4587, 0.9511, 0.1330, 0.0703, 0.7061])
-    x2 = numpy.array([0.9317, 0.1089, 0.6517, 0.1461, 0.6940, 0.6256, 0.0437])
-    x3 = numpy.array([0.7979, 0.9862, 0.4367, 0.3447, 0.0488, 0.2252, 0.5810])
-    X = numpy.ndarray((3,D), 'float64')
-    X[0,:] = x1
-    X[1,:] = x2
-    X[2,:] = x3
-    a = []
-    a.append(x1)
-    a.append(x2)
-    a.append(x3)
-    a = numpy.array(a)
-
-    # reference likelihood from Prince implementation
-    ll_ref = -182.8880743535197
-
-    # machine
-    m = bob.machine.PLDAMachine(mb)
-    ll = m.compute_log_likelihood(X)
-    self.assertTrue(abs(ll - ll_ref) < 1e-10)
-
-    # log likelihood ratio
-    Y = numpy.ndarray((2,D), 'float64')
-    Y[0,:] = x1
-    Y[1,:] = x2
-    Z = numpy.ndarray((1,D), 'float64')
-    Z[0,:] = x3
-    llX = m.compute_log_likelihood(X)
-    llY = m.compute_log_likelihood(Y)
-    llZ = m.compute_log_likelihood(Z)
-    # reference obtained by computing the likelihood of [x1,x2,x3], [x1,x2] 
-    # and [x3] separately
-    llr_ref = -4.43695386675
-    self.assertTrue(abs((llX - (llY + llZ)) - llr_ref) < 1e-10)
+def test_plda_basemachine():
+
+  # Data used for performing the tests
+  sigma = numpy.ndarray(C_dim_d, 'float64')
+  sigma.fill(0.01)
+  mu = numpy.ndarray(C_dim_d, 'float64')
+  mu.fill(0)
+
+  # Defines reference results based on matlab
+  alpha_ref = numpy.array([ 0.002189051545735,  0.001127099941432,
+    -0.000145483208153, 0.001127099941432,  0.003549267943741,
+    -0.000552001405453, -0.000145483208153, -0.000552001405453,
+    0.001440505362615], 'float64').reshape(C_dim_g, C_dim_g)
+  beta_ref  = numpy.array([ 50.587191765140361, -14.512478352504877,
+    -0.294799164567830,  13.382002504394316,  9.202063877660278,
+    -43.182264846086497,  11.932345916716455, -14.512478352504878,
+    82.320149045633045, -12.605578822979698,  19.618675892079366,
+    13.033691341150439,  -8.004874490989799, -21.547363307109187,
+    -0.294799164567832, -12.605578822979696,  52.123885798398241,
+    4.363739008635009, 44.847177605628545,  16.438137537463710,
+    5.137421840557050, 13.382002504394316,  19.618675892079366,
+    4.363739008635011,  75.070401560513488, -4.515472972526140,
+    9.752862741017488,  34.196127678931106, 9.202063877660285,
+    13.033691341150439,  44.847177605628552,  -4.515472972526142,
+    56.189416227691098,  -7.536676357632515, -10.555735414707383,
+    -43.182264846086497,  -8.004874490989799,  16.438137537463703,
+    9.752862741017490, -7.536676357632518,  56.430571485722126,
+    9.471758169835317, 11.932345916716461, -21.547363307109187,
+    5.137421840557051,  34.196127678931099, -10.555735414707385,
+    9.471758169835320,  27.996266602110637], 'float64').reshape(C_dim_d, C_dim_d)
+  gamma3_ref = numpy.array([ 0.005318799462241, -0.000000012993151,
+    -0.000000012993151,  0.999999999999996], 'float64').reshape(C_dim_f, C_dim_f)
+
+  # Constructor tests
+  m = PLDABase()
+  assert m.dim_d == 0
+  assert m.dim_f == 0
+  assert m.dim_g == 0
+  del m
+  m = PLDABase(C_dim_d, C_dim_f, C_dim_g)
+  assert m.dim_d == C_dim_d
+  assert m.dim_f == C_dim_f
+  assert m.dim_g == C_dim_g
+  assert abs(m.variance_threshold - 0.) < 1e-10
+  del m
+  m = PLDABase(C_dim_d, C_dim_f, C_dim_g, 1e-2)
+  assert m.dim_d == C_dim_d
+  assert m.dim_f == C_dim_f
+  assert m.dim_g == C_dim_g
+  assert abs(m.variance_threshold - 1e-2) < 1e-10
+  del m
+
+  # Defines base machine
+  m = PLDABase()
+  m.resize(C_dim_d, C_dim_f, C_dim_g)
+  # Sets the current mu, F, G and sigma
+  m.mu = mu
+  m.f = C_F
+  m.g = C_G
+  m.sigma = sigma
+  gamma3 = m.get_add_gamma(3).copy()
+  constTerm3 = m.get_add_log_like_const_term(3)
+
+  # Compares precomputed values to matlab reference
+  for ii in range(m.__alpha__.shape[0]):
+    for jj in range(m.__alpha__.shape[1]):
+      absdiff = abs(m.__alpha__[ii,jj]- alpha_ref[ii,jj])
+      assert absdiff < 1e-10, 'PLDABase alpha matrix does not match reference at (%d,%d) to 10^-10: |%g-%g| = %g' % (ii, jj, m.__alpha__[ii,jj], alpha_ref[ii,jj], absdiff)
+  print(m.__alpha__ - alpha_ref)
+  assert equals(m.__alpha__, alpha_ref, 1e-10)
+  assert equals(m.__beta__, beta_ref, 1e-10)
+  assert equals(gamma3, gamma3_ref, 1e-10)
+
+  # Compares precomputed values to the ones returned by python implementation
+  assert equals(m.__isigma__, compute_i_sigma(sigma), 1e-10)
+  assert equals(m.__alpha__, compute_alpha(C_G,sigma), 1e-10)
+  assert equals(m.__beta__, compute_beta(C_G,sigma), 1e-10)
+  assert equals(m.get_add_gamma(3), compute_gamma(C_F,C_G,sigma,3), 1e-10)
+  assert m.has_gamma(3)
+  assert equals(m.get_gamma(3), compute_gamma(C_F,C_G,sigma,3), 1e-10)
+  assert equals(m.__ft_beta__, compute_ft_beta(C_F,C_G,sigma), 1e-10)
+  assert equals(m.__gt_i_sigma__, compute_gt_i_sigma(C_G,sigma), 1e-10)
+  assert math.fabs(m.__logdet_alpha__ - compute_logdet_alpha(C_G,sigma)) < 1e-10
+  assert math.fabs(m.__logdet_sigma__ - compute_logdet_sigma(sigma)) < 1e-10
+  assert abs(m.get_add_log_like_const_term(3) - compute_loglike_constterm(C_F,C_G,sigma,3)) < 1e-10
+  assert m.has_log_like_const_term(3)
+  assert abs(m.get_log_like_const_term(3) - compute_loglike_constterm(C_F,C_G,sigma,3)) < 1e-10
+
+  # Defines base machine
+  del m
+  m = PLDABase(C_dim_d, C_dim_f, C_dim_g)
+  # Sets the current mu, F, G and sigma
+  m.mu = mu
+  m.f = C_F
+  m.g = C_G
+  m.sigma = sigma
+  gamma3 = m.get_add_gamma(3).copy()
+  constTerm3 = m.get_add_log_like_const_term(3)
+
+  # Compares precomputed values to matlab reference
+  assert equals(m.__alpha__, alpha_ref, 1e-10)
+  assert equals(m.__beta__, beta_ref, 1e-10)
+  assert equals(gamma3, gamma3_ref, 1e-10)
+
+  # values before being saved
+  isigma = m.__isigma__.copy()
+  alpha = m.__alpha__.copy()
+  beta = m.__beta__.copy()
+  FtBeta = m.__ft_beta__.copy()
+  GtISigma = m.__gt_i_sigma__.copy()
+  logdetAlpha = m.__logdet_alpha__
+  logdetSigma = m.__logdet_sigma__
+
+  # Saves to file, loads and compares to original
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = PLDABase(xbob.io.base.HDF5File(filename))
+
+  # Compares the values loaded with the former ones
+  assert m_loaded == m
+  assert (m_loaded != m) is False
+  assert equals(m_loaded.mu, mu, 1e-10)
+  assert equals(m_loaded.f, C_F, 1e-10)
+  assert equals(m_loaded.g, C_G, 1e-10)
+  assert equals(m_loaded.sigma, sigma, 1e-10)
+  assert equals(m_loaded.__isigma__, isigma, 1e-10)
+  assert equals(m_loaded.__alpha__, alpha, 1e-10)
+  assert equals(m_loaded.__beta__, beta, 1e-10)
+  assert equals(m_loaded.__ft_beta__, FtBeta, 1e-10)
+  assert equals(m_loaded.__gt_i_sigma__, GtISigma, 1e-10)
+  assert abs(m_loaded.__logdet_alpha__ - logdetAlpha) < 1e-10
+  assert abs(m_loaded.__logdet_sigma__ - logdetSigma) < 1e-10
+  assert m_loaded.has_gamma(3)
+  assert equals(m_loaded.get_gamma(3), gamma3_ref, 1e-10)
+  assert equals(m_loaded.get_add_gamma(3), gamma3_ref, 1e-10)
+  assert m_loaded.has_log_like_const_term(3)
+  assert abs(m_loaded.get_add_log_like_const_term(3) - constTerm3) < 1e-10
+
+  # Compares the values loaded with the former ones when copying
+  m_copy = PLDABase(m_loaded)
+  assert m_loaded == m_copy
+  assert (m_loaded != m_copy) is False
+  # Test clear_maps method
+  assert m_copy.has_gamma(3)
+  assert m_copy.has_log_like_const_term(3)
+  m_copy.clear_maps()
+  assert (m_copy.has_gamma(3)) is False
+  assert (m_copy.has_log_like_const_term(3)) is False
+
+  # Check variance flooring thresholds-related methods
+  v_zo = numpy.array([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01])
+  v_zo_ = 0.01
+  v_zzo = numpy.array([0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001])
+  v_zzo_ = 0.001
+  m_copy.variance_threshold = v_zo_
+  assert (m_loaded == m_copy) is False
+  assert m_loaded != m_copy
+  m_copy.variance_threshold = v_zzo_
+  m_copy.sigma = v_zo
+  assert equals(m_copy.sigma, v_zo, 1e-10)
+  m_copy.variance_threshold = v_zo_
+  m_copy.sigma = v_zzo
+  assert equals(m_copy.sigma, v_zo, 1e-10)
+  m_copy.variance_threshold = v_zzo_
+  m_copy.sigma = v_zzo
+  assert equals(m_copy.sigma, v_zzo, 1e-10)
+  m_copy.variance_threshold = v_zo_
+  assert equals(m_copy.sigma, v_zo, 1e-10)
+
+  # Clean-up
+  os.unlink(filename)
+
+
+def test_plda_basemachine_loglikelihood_pointestimate():
+
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  sigma = numpy.ndarray(C_dim_d, 'float64')
+  sigma.fill(0.01)
+  mu = numpy.ndarray(C_dim_d, 'float64')
+  mu.fill(0)
+  xij = numpy.array([0.7, 1.3, 2.5, 0.3, 1.3, 2.7, 0.9])
+  hi = numpy.array([-0.5, 0.5])
+  wij = numpy.array([-0.1, 0.2, 0.3])
+
+  m = PLDABase(C_dim_d, C_dim_f, C_dim_g)
+  # Sets the current mu, F, G and sigma
+  m.mu = mu
+  m.f = C_F
+  m.g = C_G
+  m.sigma = sigma
+
+  self.assertTrue(equals(m.compute_log_likelihood_point_estimate(xij, hi, wij),
+                         compute_log_likelihood_point_estimate(xij, mu, C_F, C_G, sigma, hi, wij), 1e-6))
+
+
+def test_plda_machine():
+
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  sigma = numpy.ndarray(C_dim_d, 'float64')
+  sigma.fill(0.01)
+  mu = numpy.ndarray(C_dim_d, 'float64')
+  mu.fill(0)
+
+  # Defines base machine
+  mb = PLDABase(C_dim_d, C_dim_f, C_dim_g)
+  # Sets the current mu, F, G and sigma
+  mb.mu = mu
+  mb.f = C_F
+  mb.g = C_G
+  mb.sigma = sigma
+
+  # Test constructors and dim getters
+  m = PLDAMachine(mb)
+  assert m.dim_d == C_dim_d
+  assert m.dim_f == C_dim_f
+  assert m.dim_g == C_dim_g
+
+  m0 = PLDAMachine()
+  m0.plda_base = mb
+  assert m0.dim_d == C_dim_d
+  assert m0.dim_f == C_dim_f
+  assert m0.dim_g == C_dim_g
+
+  # Defines machine
+  n_samples = 2
+  WSumXitBetaXi = 0.37
+  weightedSum = numpy.array([1.39,0.54], 'float64')
+  log_likelihood = -0.22
+
+  m.n_samples = n_samples
+  m.w_sum_xit_beta_xi = WSumXitBetaXi
+  m.weighted_sum = weightedSum
+  m.log_likelihood = log_likelihood
+
+  gamma3 = m.get_add_gamma(3).copy()
+  constTerm3 = m.get_add_log_like_const_term(3)
+
+  # Saves to file, loads and compares to original
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = PLDAMachine(xbob.io.base.HDF5File(filename), mb)
+
+  # Compares the values loaded with the former ones
+  assert m_loaded == m
+  assert (m_loaded != m) is False
+  assert abs(m_loaded.n_samples - n_samples) < 1e-10
+  assert abs(m_loaded.w_sum_xit_beta_xi - WSumXitBetaXi) < 1e-10
+  assert equals(m_loaded.weighted_sum, weightedSum, 1e-10)
+  assert abs(m_loaded.log_likelihood - log_likelihood) < 1e-10
+  assert m_loaded.has_gamma(3)
+  assert equals(m_loaded.get_add_gamma(3), gamma3, 1e-10)
+  assert equals(m_loaded.get_gamma(3), gamma3, 1e-10)
+  assert m_loaded.has_log_like_const_term(3)
+  assert abs(m_loaded.get_add_log_like_const_term(3) - constTerm3) < 1e-10
+  assert abs(m_loaded.get_log_like_const_term(3) - constTerm3) < 1e-10
+
+  # Test clear_maps method
+  assert m_loaded.has_gamma(3)
+  assert m_loaded.has_log_like_const_term(3)
+  m_loaded.clear_maps()
+  assert (m_loaded.has_gamma(3)) is False
+  assert (m_loaded.has_log_like_const_term(3)) is False
+
+  # Check exceptions
+  m_loaded2 = PLDAMachine()
+  m_loaded2.load(xbob.io.base.HDF5File(filename))
+  self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_d')
+  self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_f')
+  self.assertRaises(RuntimeError, getattr, m_loaded2, 'dim_g')
+  self.assertRaises(RuntimeError, m_loaded2.forward, [1.])
+  self.assertRaises(RuntimeError, m_loaded2.compute_log_likelihood, [1.])
+
+  # Clean-up
+  os.unlink(filename)
+
+
+def test_plda_machine_log_likelihood_Python():
+
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  sigma = numpy.ndarray(C_dim_d, 'float64')
+  sigma.fill(0.01)
+  mu = numpy.ndarray(C_dim_d, 'float64')
+  mu.fill(0)
+
+  # Defines base machine
+  mb = PLDABase(C_dim_d, C_dim_f, C_dim_g)
+  # Sets the current mu, F, G and sigma
+  mb.mu = mu
+  mb.f = C_F
+  mb.g = C_G
+  mb.sigma = sigma
+
+  # Defines machine
+  m = PLDAMachine(mb)
+
+  # Defines (random) samples and check compute_log_likelihood method
+  ar_e = numpy.random.randn(2,C_dim_d)
+  ar_p = numpy.random.randn(C_dim_d)
+  ar_s = numpy.vstack([ar_e, ar_p])
+  assert abs(m.compute_log_likelihood(ar_s, False) - compute_log_likelihood(ar_s, mu, C_F, C_G, sigma)) < 1e-10
+  ar_p2d = numpy.reshape(ar_p, (1,C_dim_d))
+  assert abs(m.compute_log_likelihood(ar_p, False) - compute_log_likelihood(ar_p2d, mu, C_F, C_G, sigma)) < 1e-10
+
+  # Defines (random) samples and check forward method
+  ar2_e = numpy.random.randn(4,C_dim_d)
+  ar2_p = numpy.random.randn(C_dim_d)
+  ar2_s = numpy.vstack([ar2_e, ar2_p])
+  m.log_likelihood = m.compute_log_likelihood(ar2_e, False)
+  llr = m.compute_log_likelihood(ar2_s, True) - (m.compute_log_likelihood(ar2_s, False) + m.log_likelihood)
+  assert abs(m.forward(ar2_s) - llr) < 1e-10
+  ar2_p2d = numpy.random.randn(3,C_dim_d)
+  ar2_s2d = numpy.vstack([ar2_e, ar2_p2d])
+  llr2d = m.compute_log_likelihood(ar2_s2d, True) - (m.compute_log_likelihood(ar2_s2d, False) + m.log_likelihood)
+  assert abs(m.forward(ar2_s2d) - llr2d) < 1e-10
+
+def test_plda_machine_log_likelihood_Prince():
+
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  D = 7
+  nf = 2
+  ng = 3
+
+  # initial values for F, G and sigma
+  G_init=numpy.array([-1.1424, -0.5044, -0.1917,
+    -0.6249,  0.1021, -0.8658,
+    -1.1687,  1.1963,  0.1807,
+    0.3926,  0.1203,  1.2665,
+    1.3018, -1.0368, -0.2512,
+    -0.5936, -0.8571, -0.2046,
+    0.4364, -0.1699, -2.2015]).reshape(D,ng)
+  # F <-> PCA on G
+  F_init=numpy.array([-0.054222647972093, -0.000000000783146,
+    0.596449127693018,  0.000000006265167,
+    0.298224563846509,  0.000000003132583,
+    0.447336845769764,  0.000000009397750,
+    -0.108445295944185, -0.000000001566292,
+    -0.501559493741856, -0.000000006265167,
+    -0.298224563846509, -0.000000003132583]).reshape(D,nf)
+  sigma_init = 0.01 * numpy.ones((D,), 'float64')
+  mean_zero = numpy.zeros((D,), 'float64')
+
+  # base machine
+  mb = PLDABase(D,nf,ng)
+  mb.sigma = sigma_init
+  mb.g = G_init
+  mb.f = F_init
+  mb.mu = mean_zero
+
+  # Data for likelihood computation
+  x1 = numpy.array([0.8032, 0.3503, 0.4587, 0.9511, 0.1330, 0.0703, 0.7061])
+  x2 = numpy.array([0.9317, 0.1089, 0.6517, 0.1461, 0.6940, 0.6256, 0.0437])
+  x3 = numpy.array([0.7979, 0.9862, 0.4367, 0.3447, 0.0488, 0.2252, 0.5810])
+  X = numpy.ndarray((3,D), 'float64')
+  X[0,:] = x1
+  X[1,:] = x2
+  X[2,:] = x3
+  a = []
+  a.append(x1)
+  a.append(x2)
+  a.append(x3)
+  a = numpy.array(a)
+
+  # reference likelihood from Prince implementation
+  ll_ref = -182.8880743535197
+
+  # machine
+  m = PLDAMachine(mb)
+  ll = m.compute_log_likelihood(X)
+  assert abs(ll - ll_ref) < 1e-10
+
+  # log likelihood ratio
+  Y = numpy.ndarray((2,D), 'float64')
+  Y[0,:] = x1
+  Y[1,:] = x2
+  Z = numpy.ndarray((1,D), 'float64')
+  Z[0,:] = x3
+  llX = m.compute_log_likelihood(X)
+  llY = m.compute_log_likelihood(Y)
+  llZ = m.compute_log_likelihood(Z)
+  # reference obtained by computing the likelihood of [x1,x2,x3], [x1,x2]
+  # and [x3] separately
+  llr_ref = -4.43695386675
+  assert abs((llX - (llY + llZ)) - llr_ref) < 1e-10
diff --git a/xbob/learn/misc/test_plda_trainer.py b/xbob/learn/misc/test_plda_trainer.py
index d1d403c..7ac737e 100644
--- a/xbob/learn/misc/test_plda_trainer.py
+++ b/xbob/learn/misc/test_plda_trainer.py
@@ -3,14 +3,16 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Fri Oct 14 18:07:56 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests PLDA trainer
 """
 
-import sys, unittest
-import bob
-import numpy, numpy.linalg
+import sys
+import numpy
+import numpy.linalg
+
+from . import PLDATrainer, PLDABase, PLDAMachine
 
 class PythonPLDATrainer():
   """A simplified (and slower) version of the PLDATrainer"""
@@ -29,7 +31,7 @@ class PythonPLDATrainer():
     self.m_z_second_order = []
     self.m_sum_z_second_order = numpy.ndarray(shape=(0,0), dtype=numpy.float64)
 
-  def reset(self):
+  def reset():
     """Resets our internal state"""
     self.m_convergence_threshold = 0.001
     self.m_max_iterations = 10
@@ -313,422 +315,419 @@ class PythonPLDATrainer():
       i += 1
 
 
-class PLDATrainerTest(unittest.TestCase):
-  """Performs various PLDA trainer tests."""
-
-  def test01_plda_EM_vs_Python(self):
-
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    D = 7
-    nf = 2
-    ng = 3
-
-    # first identity (4 samples)
-    a = numpy.array([
-      [1,2,3,4,5,6,7],
-      [7,8,3,3,1,8,2],
-      [3,2,1,4,5,1,7],
-      [9,0,3,2,1,4,6],
-      ], dtype='float64')
-
-    # second identity (3 samples)
-    b = numpy.array([
-      [5,6,3,4,2,0,2],
-      [1,7,8,9,4,4,8],
-      [8,7,2,5,1,1,1],
-      ], dtype='float64')
-
-    # list of arrays (training data)
-    l = [a,b]
-
-    # initial values for F, G and sigma
-    G_init=numpy.array([-1.1424, -0.5044, -0.1917,
-      -0.6249,  0.1021, -0.8658,
-      -1.1687,  1.1963,  0.1807,
-      0.3926,  0.1203,  1.2665,
-      1.3018, -1.0368, -0.2512,
-      -0.5936, -0.8571, -0.2046,
-      0.4364, -0.1699, -2.2015]).reshape(D,ng)
-
-    # F <-> PCA on G
-    F_init=numpy.array([-0.054222647972093, -0.000000000783146,
-      0.596449127693018,  0.000000006265167,
-      0.298224563846509,  0.000000003132583,
-      0.447336845769764,  0.000000009397750,
-      -0.108445295944185, -0.000000001566292,
-      -0.501559493741856, -0.000000006265167,
-      -0.298224563846509, -0.000000003132583]).reshape(D,nf)
-    sigma_init = 0.01 * numpy.ones(D, 'float64')
-
-    # Runs the PLDA trainer EM-steps (2 steps)
-    # Defines base trainer and machine
-    t = bob.trainer.PLDATrainer(10)
-    t_py = PythonPLDATrainer()
-    m = bob.machine.PLDABase(D,nf,ng)
-    m_py = bob.machine.PLDABase(D,nf,ng)
-
-    # Sets the same initialization methods
-    t.init_f_method = bob.trainer.PLDATrainer.BETWEEN_SCATTER
-    t.init_g_method = bob.trainer.PLDATrainer.WITHIN_SCATTER
-    t.init_sigma_method = bob.trainer.PLDATrainer.VARIANCE_DATA
-
-    t.train(m, l)
-    t_py.train(m_py, l)
-    self.assertTrue(numpy.allclose(m.mu, m_py.mu))
-    self.assertTrue(numpy.allclose(m.f, m_py.f))
-    self.assertTrue(numpy.allclose(m.g, m_py.g))
-    self.assertTrue(numpy.allclose(m.sigma, m_py.sigma))
-
-  def test02_plda_EM_vs_Prince(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    dim_d = 7
-    dim_f = 2
-    dim_g = 3
-
-    # first identity (4 samples)
-    a = numpy.array([
-      [1,2,3,4,5,6,7],
-      [7,8,3,3,1,8,2],
-      [3,2,1,4,5,1,7],
-      [9,0,3,2,1,4,6],
-      ], dtype='float64')
-
-    # second identity (3 samples)
-    b = numpy.array([
-      [5,6,3,4,2,0,2],
-      [1,7,8,9,4,4,8],
-      [8,7,2,5,1,1,1],
-      ], dtype='float64')
-
-    # list of arrays (training data)
-    l = [a,b]
-
-    # initial values for F, G and sigma
-    G_init=numpy.array([-1.1424, -0.5044, -0.1917,
-      -0.6249,  0.1021, -0.8658,
-      -1.1687,  1.1963,  0.1807,
-      0.3926,  0.1203,  1.2665,
-      1.3018, -1.0368, -0.2512,
-      -0.5936, -0.8571, -0.2046,
-      0.4364, -0.1699, -2.2015]).reshape(dim_d,dim_g)
-
-    # F <-> PCA on G
-    F_init=numpy.array([-0.054222647972093, -0.000000000783146,
-      0.596449127693018,  0.000000006265167,
-      0.298224563846509,  0.000000003132583,
-      0.447336845769764,  0.000000009397750,
-      -0.108445295944185, -0.000000001566292,
-      -0.501559493741856, -0.000000006265167,
-      -0.298224563846509, -0.000000003132583]).reshape(dim_d,dim_f)
-    sigma_init = 0.01 * numpy.ones(dim_d, 'float64')
-
-    # Defines reference results based on Princes'matlab implementation
-    # After 1 iteration
-    z_first_order_a_1 = numpy.array(
-      [-2.624115900658397, -0.000000034277848,  1.554823055585319,  0.627476234024656, -0.264705934182394,
-       -2.624115900658397, -0.000000034277848, -2.703482671599357, -1.533283607433197,  0.553725774828231,
-       -2.624115900658397, -0.000000034277848,  2.311647528461115,  1.266362142140170, -0.317378177105131,
-       -2.624115900658397, -0.000000034277848, -1.163402640008200, -0.372604542926019,  0.025152800097991
-      ]).reshape(4, dim_f+dim_g)
-    z_first_order_b_1 = numpy.array(
-      [ 3.494168818797438,  0.000000045643026,  0.111295550530958, -0.029241422535725,  0.257045446451067,
-        3.494168818797438,  0.000000045643026,  1.102110715965762,  1.481232954001794, -0.970661225144399,
-        3.494168818797438,  0.000000045643026, -1.212854031699468, -1.435946529317718,  0.717884143973377
-      ]).reshape(3, dim_f+dim_g)
-
-    z_second_order_sum_1 = numpy.array(
-      [64.203518285366087,  0.000000747228248,  0.002703277337642,  0.078542842475345,  0.020894328259862,
-        0.000000747228248,  6.999999999999980, -0.000000003955962,  0.000000002017232, -0.000000003741593,
-        0.002703277337642, -0.000000003955962, 19.136889380923918, 11.860493771107487, -4.584339465366988,
-        0.078542842475345,  0.000000002017232, 11.860493771107487,  8.771502339750128, -3.905706024997424,
-        0.020894328259862, -0.000000003741593, -4.584339465366988, -3.905706024997424,  2.011924970338584
-      ]).reshape(dim_f+dim_g, dim_f+dim_g)
-
-    sigma_1 = numpy.array(
-        [2.193659969999207, 3.748361365521041, 0.237835235737085,
-          0.558546035892629, 0.209272700958400, 1.717782807724451,
-          0.248414618308223])
-
-    F_1 = numpy.array(
-        [-0.059083416465692,  0.000000000751007,
-          0.600133217253169,  0.000000006957266,
-          0.302789123922871,  0.000000000218947,
-          0.454540641429714,  0.000000003342540,
-          -0.106608957780613, -0.000000001641389,
-          -0.494267694269430, -0.000000011059552,
-          -0.295956102084270, -0.000000006718366]).reshape(dim_d,dim_f)
-
-    G_1 = numpy.array(
-        [-1.836166150865047,  2.491475145758734,  5.095958946372235,
-          -0.608732205531767, -0.618128420353493, -1.085423135463635,
-          -0.697390472635929, -1.047900122276840, -6.080211153116984,
-          0.769509301515319, -2.763610156675313, -5.972172587527176,
-          1.332474692714491, -1.368103875407414, -2.096382536513033,
-          0.304135903830416, -5.168096082564016, -9.604769461465978,
-          0.597445549865284, -1.347101803379971, -5.900246013340080]).reshape(dim_d,dim_g)
-
-    # After 2 iterations
-    z_first_order_a_2 = numpy.array(
-        [-2.144344161196005, -0.000000027851878,  1.217776189037369,  0.232492571855061, -0.212892893868819,
-          -2.144344161196005, -0.000000027851878, -2.382647766948079, -1.759951013670071,  0.587213207926731,
-          -2.144344161196005, -0.000000027851878,  2.143294830538722,  0.909307594408923, -0.183752098508072,
-          -2.144344161196005, -0.000000027851878, -0.662558006326892,  0.717992497547010, -0.202897892977004
-      ]).reshape(4, dim_f+dim_g)
-    z_first_order_b_2 = numpy.array(
-        [ 2.695117129662246,  0.000000035005543, -0.156173294945791, -0.123083763746364,  0.271123341933619,
-          2.695117129662246,  0.000000035005543,  0.690321563509753,  0.944473716646212, -0.850835940962492,
-          2.695117129662246,  0.000000035005543, -0.930970138998433, -0.949736472690315,  0.594216348861889
-      ]).reshape(3, dim_f+dim_g)
-
-    z_second_order_sum_2 = numpy.array(
-        [41.602421167226410,  0.000000449434708, -1.513391506933811, -0.477818674270533,  0.059260102368316,
-          0.000000449434708,  7.000000000000005, -0.000000023255959, -0.000000005157439, -0.000000003230262,
-          -1.513391506933810, -0.000000023255959, 14.399631061987494,  8.068678077509025, -3.227586434905497,
-          -0.477818674270533, -0.000000005157439,  8.068678077509025,  7.263248678863863, -3.060665688064639,
-          0.059260102368316, -0.000000003230262, -3.227586434905497, -3.060665688064639,  1.705174220723198
-      ]).reshape(dim_f+dim_g, dim_f+dim_g)
-
-    sigma_2 = numpy.array(
-      [1.120493935052524, 1.777598857891599, 0.197579528599150,
-        0.407657093211478, 0.166216300651473, 1.044336960403809,
-        0.287856936559308])
-
-    F_2 = numpy.array(
-      [-0.111956311978966,  0.000000000781025,
-        0.702502767389263,  0.000000007683917,
-        0.337823622542517,  0.000000000637302,
-        0.551363737526339,  0.000000004854293,
-       -0.096561040511417, -0.000000001716011,
-       -0.661587484803602, -0.000000012394362,
-       -0.346593051621620, -0.000000007134046]).reshape(dim_d,dim_f)
-
-    G_2 = numpy.array(
-      [-2.266404374274820,  4.089199685832099,  7.023039382876370,
-        0.094887459097613, -3.226829318470136, -3.452279917194724,
-       -0.498398131733141, -1.651712333649899, -6.548008210704172,
-        0.574932298590327, -2.198978667003715, -5.131253543126156,
-        1.415857426810629, -1.627795701160212, -2.509013676007012,
-       -0.543552834305580, -3.215063993186718, -7.006305082499653,
-        0.562108137758111, -0.785296641855087, -5.318335345720314]).reshape(dim_d,dim_g)
-
-    # Runs the PLDA trainer EM-steps (2 steps)
-
-    # Defines base trainer and machine
-    t = bob.trainer.PLDATrainer()
-    t0 = bob.trainer.PLDATrainer(t)
-    m = bob.machine.PLDABase(dim_d,dim_f,dim_g)
-    t.initialize(m,l)
-    m.sigma = sigma_init
-    m.g = G_init
-    m.f = F_init
-
-    # Defines base trainer and machine (for Python implementation
-    t_py = PythonPLDATrainer()
-    m_py = bob.machine.PLDABase(dim_d,dim_f,dim_g)
-    t_py.initialize(m_py,l)
-    m_py.sigma = sigma_init
-    m_py.g = G_init
-    m_py.f = F_init
-
-    # E-step 1
-    t.e_step(m,l)
-    t_py.e_step(m_py,l)
-    # Compares statistics to Prince matlab reference
-    self.assertTrue(numpy.allclose(t.z_first_order[0], z_first_order_a_1, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], z_first_order_b_1, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, z_second_order_sum_1, 1e-10))
-    # Compares statistics against the ones of the python implementation
-    self.assertTrue(numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10))
-
-    # M-step 1
-    t.m_step(m,l)
-    t_py.m_step(m_py,l)
-    # Compares F, G and sigma to Prince matlab reference
-    self.assertTrue(numpy.allclose(m.f, F_1, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, G_1, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, sigma_1, 1e-10))
-    # Compares F, G and sigma to the ones of the python implementation
-    self.assertTrue(numpy.allclose(m.f, m_py.f, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, m_py.g, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, m_py.sigma, 1e-10))
-
-    # E-step 2
-    t.e_step(m,l)
-    t_py.e_step(m_py,l)
-    # Compares statistics to Prince matlab reference
-    self.assertTrue(numpy.allclose(t.z_first_order[0], z_first_order_a_2, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], z_first_order_b_2, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, z_second_order_sum_2, 1e-10))
-    # Compares statistics against the ones of the python implementation
-    self.assertTrue(numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10))
-
-    # M-step 2
-    t.m_step(m,l)
-    t_py.m_step(m_py,l)
-    # Compares F, G and sigma to Prince matlab reference
-    self.assertTrue(numpy.allclose(m.f, F_2, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, G_2, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, sigma_2, 1e-10))
-    # Compares F, G and sigma to the ones of the python implementation
-    self.assertTrue(numpy.allclose(m.f, m_py.f, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, m_py.g, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, m_py.sigma, 1e-10))
-
-
-    # Test the second order statistics computation
-    # Calls the initialization methods and resets randomly initialized values
-    # to new reference ones (to make the tests deterministic)
-    t.use_sum_second_order = False
-    t.initialize(m,l)
-    m.sigma = sigma_init
-    m.g = G_init
-    m.f = F_init
-    t_py.initialize(m_py,l)
-    m_py.sigma = sigma_init
-    m_py.g = G_init
-    m_py.f = F_init
-
-    # E-step 1
-    t.e_step(m,l)
-    t_py.e_step(m_py,l)
-    # Compares statistics to Prince matlab reference
-    self.assertTrue(numpy.allclose(t.z_first_order[0], z_first_order_a_1, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], z_first_order_b_1, 1e-10))
-    # Compares statistics against the ones of the python implementation
-    self.assertTrue(numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order[0], t_py.m_z_second_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order[1], t_py.m_z_second_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10))
-
-    # M-step 1
-    t.m_step(m,l)
-    t_py.m_step(m_py,l)
-    # Compares F, G and sigma to the ones of the python implementation
-    self.assertTrue(numpy.allclose(m.f, m_py.f, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, m_py.g, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, m_py.sigma, 1e-10))
-
-    # E-step 2
-    t.e_step(m,l)
-    t_py.e_step(m_py,l)
-    # Compares statistics to Prince matlab reference
-    self.assertTrue(numpy.allclose(t.z_first_order[0], z_first_order_a_2, 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], z_first_order_b_2, 1e-10))
-    # Compares statistics against the ones of the python implementation
-    self.assertTrue(numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order[0], t_py.m_z_second_order[0], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order[1], t_py.m_z_second_order[1], 1e-10))
-    self.assertTrue(numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10))
-
-    # M-step 2
-    t.m_step(m,l)
-    t_py.m_step(m_py,l)
-    # Compares F, G and sigma to the ones of the python implementation
-    self.assertTrue(numpy.allclose(m.f, m_py.f, 1e-10))
-    self.assertTrue(numpy.allclose(m.g, m_py.g, 1e-10))
-    self.assertTrue(numpy.allclose(m.sigma, m_py.sigma, 1e-10))
-
-
-  def test03_plda_enrollment(self):
-    # Data used for performing the tests
-    # Features and subspaces dimensionality
-    dim_d = 7
-    dim_f = 2
-    dim_g = 3
-
-    # initial values for F, G and sigma
-    G_init=numpy.array([-1.1424, -0.5044, -0.1917,
-      -0.6249,  0.1021, -0.8658,
-      -1.1687,  1.1963,  0.1807,
-      0.3926,  0.1203,  1.2665,
-      1.3018, -1.0368, -0.2512,
-      -0.5936, -0.8571, -0.2046,
-      0.4364, -0.1699, -2.2015]).reshape(dim_d,dim_g)
-    # F <-> PCA on G
-    F_init=numpy.array([-0.054222647972093, -0.000000000783146,
-      0.596449127693018,  0.000000006265167,
-      0.298224563846509,  0.000000003132583,
-      0.447336845769764,  0.000000009397750,
-      -0.108445295944185, -0.000000001566292,
-      -0.501559493741856, -0.000000006265167,
-      -0.298224563846509, -0.000000003132583]).reshape(dim_d,dim_f)
-    sigma_init = 0.01 * numpy.ones((dim_d,), 'float64')
-    mean_zero = numpy.zeros((dim_d,), 'float64')
-
-    # base machine
-    mb = bob.machine.PLDABase(dim_d,dim_f,dim_g)
-    mb.sigma = sigma_init
-    mb.g = G_init
-    mb.f = F_init
-    mb.mu = mean_zero
-
-    # Data for likelihood computation
-    x1 = numpy.array([0.8032, 0.3503, 0.4587, 0.9511, 0.1330, 0.0703, 0.7061])
-    x2 = numpy.array([0.9317, 0.1089, 0.6517, 0.1461, 0.6940, 0.6256, 0.0437])
-    x3 = numpy.array([0.7979, 0.9862, 0.4367, 0.3447, 0.0488, 0.2252, 0.5810])
-    a_enrol = []
-    a_enrol.append(x1)
-    a_enrol.append(x2)
-    a_enrol = numpy.array(a_enrol)
-
-    # reference likelihood from Prince implementation
-    ll_ref = -182.8880743535197
-
-    # Computes the likelihood using x1 and x2 as enrollment samples
-    # and x3 as a probe sample
-    m = bob.machine.PLDAMachine(mb)
-    t = bob.trainer.PLDATrainer()
-    t.enrol(m, a_enrol)
-    ll = m.compute_log_likelihood(x3)
-    self.assertTrue(abs(ll - ll_ref) < 1e-10)
-
-    # reference obtained by computing the likelihood of [x1,x2,x3], [x1,x2]
-    # and [x3] separately
-    llr_ref = -4.43695386675
-    llr = m.forward(x3)
-    self.assertTrue(abs(llr - llr_ref) < 1e-10)
-    #
-    llr_separate = m.compute_log_likelihood(numpy.array([x1,x2,x3]), False) - \
-      (m.compute_log_likelihood(numpy.array([x1,x2]), False) + m.compute_log_likelihood(numpy.array([x3]), False))
-    self.assertTrue(abs(llr - llr_separate) < 1e-10)
-
-  def test04_plda_comparisons(self):
-
-    t1 = bob.trainer.PLDATrainer()
-    t2 = bob.trainer.PLDATrainer()
-    t2.rng = t1.rng
-    self.assertTrue(  t1 == t2 )
-    self.assertFalse( t1 != t2 )
-    self.assertTrue(  t1.is_similar_to(t2) )
-
-    training_set = [numpy.array([[1,2,3,4]], numpy.float64), numpy.array([[3,4,3,4]], numpy.float64)]
-    m = bob.machine.PLDABase(4,1,1,1e-8)
-    t1.rng.seed(37)
-    t1.initialize(m, training_set)
-    t1.e_step(m, training_set)
-    t1.m_step(m, training_set)
-    self.assertFalse( t1 == t2 )
-    self.assertTrue(  t1 != t2 )
-    self.assertFalse( t1.is_similar_to(t2) )
-    t2.rng.seed(37)
-    t2.initialize(m, training_set)
-    t2.e_step(m, training_set)
-    t2.m_step(m, training_set)
-    self.assertTrue(  t1 == t2 )
-    self.assertFalse( t1 != t2 )
-    self.assertTrue(  t1.is_similar_to(t2) )
-    t2.rng.seed(77)
-    t2.initialize(m, training_set)
-    t2.e_step(m, training_set)
-    t2.m_step(m, training_set)
-    self.assertFalse( t1 == t2 )
-    self.assertTrue(  t1 != t2 )
-    self.assertFalse( t1.is_similar_to(t2) )
+def test_plda_EM_vs_Python():
+
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  D = 7
+  nf = 2
+  ng = 3
+
+  # first identity (4 samples)
+  a = numpy.array([
+    [1,2,3,4,5,6,7],
+    [7,8,3,3,1,8,2],
+    [3,2,1,4,5,1,7],
+    [9,0,3,2,1,4,6],
+    ], dtype='float64')
+
+  # second identity (3 samples)
+  b = numpy.array([
+    [5,6,3,4,2,0,2],
+    [1,7,8,9,4,4,8],
+    [8,7,2,5,1,1,1],
+    ], dtype='float64')
+
+  # list of arrays (training data)
+  l = [a,b]
+
+  # initial values for F, G and sigma
+  G_init=numpy.array([-1.1424, -0.5044, -0.1917,
+    -0.6249,  0.1021, -0.8658,
+    -1.1687,  1.1963,  0.1807,
+    0.3926,  0.1203,  1.2665,
+    1.3018, -1.0368, -0.2512,
+    -0.5936, -0.8571, -0.2046,
+    0.4364, -0.1699, -2.2015]).reshape(D,ng)
+
+  # F <-> PCA on G
+  F_init=numpy.array([-0.054222647972093, -0.000000000783146,
+    0.596449127693018,  0.000000006265167,
+    0.298224563846509,  0.000000003132583,
+    0.447336845769764,  0.000000009397750,
+    -0.108445295944185, -0.000000001566292,
+    -0.501559493741856, -0.000000006265167,
+    -0.298224563846509, -0.000000003132583]).reshape(D,nf)
+  sigma_init = 0.01 * numpy.ones(D, 'float64')
+
+  # Runs the PLDA trainer EM-steps (2 steps)
+  # Defines base trainer and machine
+  t = PLDATrainer(10)
+  t_py = PythonPLDATrainer()
+  m = PLDABase(D,nf,ng)
+  m_py = PLDABase(D,nf,ng)
+
+  # Sets the same initialization methods
+  t.init_f_method = PLDATrainer.BETWEEN_SCATTER
+  t.init_g_method = PLDATrainer.WITHIN_SCATTER
+  t.init_sigma_method = PLDATrainer.VARIANCE_DATA
+
+  t.train(m, l)
+  t_py.train(m_py, l)
+  assert numpy.allclose(m.mu, m_py.mu)
+  assert numpy.allclose(m.f, m_py.f)
+  assert numpy.allclose(m.g, m_py.g)
+  assert numpy.allclose(m.sigma, m_py.sigma)
+
+def test_plda_EM_vs_Prince():
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  dim_d = 7
+  dim_f = 2
+  dim_g = 3
+
+  # first identity (4 samples)
+  a = numpy.array([
+    [1,2,3,4,5,6,7],
+    [7,8,3,3,1,8,2],
+    [3,2,1,4,5,1,7],
+    [9,0,3,2,1,4,6],
+    ], dtype='float64')
+
+  # second identity (3 samples)
+  b = numpy.array([
+    [5,6,3,4,2,0,2],
+    [1,7,8,9,4,4,8],
+    [8,7,2,5,1,1,1],
+    ], dtype='float64')
+
+  # list of arrays (training data)
+  l = [a,b]
+
+  # initial values for F, G and sigma
+  G_init=numpy.array([-1.1424, -0.5044, -0.1917,
+    -0.6249,  0.1021, -0.8658,
+    -1.1687,  1.1963,  0.1807,
+    0.3926,  0.1203,  1.2665,
+    1.3018, -1.0368, -0.2512,
+    -0.5936, -0.8571, -0.2046,
+    0.4364, -0.1699, -2.2015]).reshape(dim_d,dim_g)
+
+  # F <-> PCA on G
+  F_init=numpy.array([-0.054222647972093, -0.000000000783146,
+    0.596449127693018,  0.000000006265167,
+    0.298224563846509,  0.000000003132583,
+    0.447336845769764,  0.000000009397750,
+    -0.108445295944185, -0.000000001566292,
+    -0.501559493741856, -0.000000006265167,
+    -0.298224563846509, -0.000000003132583]).reshape(dim_d,dim_f)
+  sigma_init = 0.01 * numpy.ones(dim_d, 'float64')
+
+  # Defines reference results based on Princes'matlab implementation
+  # After 1 iteration
+  z_first_order_a_1 = numpy.array(
+    [-2.624115900658397, -0.000000034277848,  1.554823055585319,  0.627476234024656, -0.264705934182394,
+     -2.624115900658397, -0.000000034277848, -2.703482671599357, -1.533283607433197,  0.553725774828231,
+     -2.624115900658397, -0.000000034277848,  2.311647528461115,  1.266362142140170, -0.317378177105131,
+     -2.624115900658397, -0.000000034277848, -1.163402640008200, -0.372604542926019,  0.025152800097991
+    ]).reshape(4, dim_f+dim_g)
+  z_first_order_b_1 = numpy.array(
+    [ 3.494168818797438,  0.000000045643026,  0.111295550530958, -0.029241422535725,  0.257045446451067,
+      3.494168818797438,  0.000000045643026,  1.102110715965762,  1.481232954001794, -0.970661225144399,
+      3.494168818797438,  0.000000045643026, -1.212854031699468, -1.435946529317718,  0.717884143973377
+    ]).reshape(3, dim_f+dim_g)
+
+  z_second_order_sum_1 = numpy.array(
+    [64.203518285366087,  0.000000747228248,  0.002703277337642,  0.078542842475345,  0.020894328259862,
+      0.000000747228248,  6.999999999999980, -0.000000003955962,  0.000000002017232, -0.000000003741593,
+      0.002703277337642, -0.000000003955962, 19.136889380923918, 11.860493771107487, -4.584339465366988,
+      0.078542842475345,  0.000000002017232, 11.860493771107487,  8.771502339750128, -3.905706024997424,
+      0.020894328259862, -0.000000003741593, -4.584339465366988, -3.905706024997424,  2.011924970338584
+    ]).reshape(dim_f+dim_g, dim_f+dim_g)
+
+  sigma_1 = numpy.array(
+      [2.193659969999207, 3.748361365521041, 0.237835235737085,
+        0.558546035892629, 0.209272700958400, 1.717782807724451,
+        0.248414618308223])
+
+  F_1 = numpy.array(
+      [-0.059083416465692,  0.000000000751007,
+        0.600133217253169,  0.000000006957266,
+        0.302789123922871,  0.000000000218947,
+        0.454540641429714,  0.000000003342540,
+        -0.106608957780613, -0.000000001641389,
+        -0.494267694269430, -0.000000011059552,
+        -0.295956102084270, -0.000000006718366]).reshape(dim_d,dim_f)
+
+  G_1 = numpy.array(
+      [-1.836166150865047,  2.491475145758734,  5.095958946372235,
+        -0.608732205531767, -0.618128420353493, -1.085423135463635,
+        -0.697390472635929, -1.047900122276840, -6.080211153116984,
+        0.769509301515319, -2.763610156675313, -5.972172587527176,
+        1.332474692714491, -1.368103875407414, -2.096382536513033,
+        0.304135903830416, -5.168096082564016, -9.604769461465978,
+        0.597445549865284, -1.347101803379971, -5.900246013340080]).reshape(dim_d,dim_g)
+
+  # After 2 iterations
+  z_first_order_a_2 = numpy.array(
+      [-2.144344161196005, -0.000000027851878,  1.217776189037369,  0.232492571855061, -0.212892893868819,
+        -2.144344161196005, -0.000000027851878, -2.382647766948079, -1.759951013670071,  0.587213207926731,
+        -2.144344161196005, -0.000000027851878,  2.143294830538722,  0.909307594408923, -0.183752098508072,
+        -2.144344161196005, -0.000000027851878, -0.662558006326892,  0.717992497547010, -0.202897892977004
+    ]).reshape(4, dim_f+dim_g)
+  z_first_order_b_2 = numpy.array(
+      [ 2.695117129662246,  0.000000035005543, -0.156173294945791, -0.123083763746364,  0.271123341933619,
+        2.695117129662246,  0.000000035005543,  0.690321563509753,  0.944473716646212, -0.850835940962492,
+        2.695117129662246,  0.000000035005543, -0.930970138998433, -0.949736472690315,  0.594216348861889
+    ]).reshape(3, dim_f+dim_g)
+
+  z_second_order_sum_2 = numpy.array(
+      [41.602421167226410,  0.000000449434708, -1.513391506933811, -0.477818674270533,  0.059260102368316,
+        0.000000449434708,  7.000000000000005, -0.000000023255959, -0.000000005157439, -0.000000003230262,
+        -1.513391506933810, -0.000000023255959, 14.399631061987494,  8.068678077509025, -3.227586434905497,
+        -0.477818674270533, -0.000000005157439,  8.068678077509025,  7.263248678863863, -3.060665688064639,
+        0.059260102368316, -0.000000003230262, -3.227586434905497, -3.060665688064639,  1.705174220723198
+    ]).reshape(dim_f+dim_g, dim_f+dim_g)
+
+  sigma_2 = numpy.array(
+    [1.120493935052524, 1.777598857891599, 0.197579528599150,
+      0.407657093211478, 0.166216300651473, 1.044336960403809,
+      0.287856936559308])
+
+  F_2 = numpy.array(
+    [-0.111956311978966,  0.000000000781025,
+      0.702502767389263,  0.000000007683917,
+      0.337823622542517,  0.000000000637302,
+      0.551363737526339,  0.000000004854293,
+     -0.096561040511417, -0.000000001716011,
+     -0.661587484803602, -0.000000012394362,
+     -0.346593051621620, -0.000000007134046]).reshape(dim_d,dim_f)
+
+  G_2 = numpy.array(
+    [-2.266404374274820,  4.089199685832099,  7.023039382876370,
+      0.094887459097613, -3.226829318470136, -3.452279917194724,
+     -0.498398131733141, -1.651712333649899, -6.548008210704172,
+      0.574932298590327, -2.198978667003715, -5.131253543126156,
+      1.415857426810629, -1.627795701160212, -2.509013676007012,
+     -0.543552834305580, -3.215063993186718, -7.006305082499653,
+      0.562108137758111, -0.785296641855087, -5.318335345720314]).reshape(dim_d,dim_g)
+
+  # Runs the PLDA trainer EM-steps (2 steps)
+
+  # Defines base trainer and machine
+  t = PLDATrainer()
+  t0 = PLDATrainer(t)
+  m = PLDABase(dim_d,dim_f,dim_g)
+  t.initialize(m,l)
+  m.sigma = sigma_init
+  m.g = G_init
+  m.f = F_init
+
+  # Defines base trainer and machine (for Python implementation
+  t_py = PythonPLDATrainer()
+  m_py = PLDABase(dim_d,dim_f,dim_g)
+  t_py.initialize(m_py,l)
+  m_py.sigma = sigma_init
+  m_py.g = G_init
+  m_py.f = F_init
+
+  # E-step 1
+  t.e_step(m,l)
+  t_py.e_step(m_py,l)
+  # Compares statistics to Prince matlab reference
+  assert numpy.allclose(t.z_first_order[0], z_first_order_a_1, 1e-10)
+  assert numpy.allclose(t.z_first_order[1], z_first_order_b_1, 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, z_second_order_sum_1, 1e-10)
+  # Compares statistics against the ones of the python implementation
+  assert numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10)
+  assert numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10)
+
+  # M-step 1
+  t.m_step(m,l)
+  t_py.m_step(m_py,l)
+  # Compares F, G and sigma to Prince matlab reference
+  assert numpy.allclose(m.f, F_1, 1e-10)
+  assert numpy.allclose(m.g, G_1, 1e-10)
+  assert numpy.allclose(m.sigma, sigma_1, 1e-10)
+  # Compares F, G and sigma to the ones of the python implementation
+  assert numpy.allclose(m.f, m_py.f, 1e-10)
+  assert numpy.allclose(m.g, m_py.g, 1e-10)
+  assert numpy.allclose(m.sigma, m_py.sigma, 1e-10)
+
+  # E-step 2
+  t.e_step(m,l)
+  t_py.e_step(m_py,l)
+  # Compares statistics to Prince matlab reference
+  assert numpy.allclose(t.z_first_order[0], z_first_order_a_2, 1e-10)
+  assert numpy.allclose(t.z_first_order[1], z_first_order_b_2, 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, z_second_order_sum_2, 1e-10)
+  # Compares statistics against the ones of the python implementation
+  assert numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10)
+  assert numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10)
+
+  # M-step 2
+  t.m_step(m,l)
+  t_py.m_step(m_py,l)
+  # Compares F, G and sigma to Prince matlab reference
+  assert numpy.allclose(m.f, F_2, 1e-10)
+  assert numpy.allclose(m.g, G_2, 1e-10)
+  assert numpy.allclose(m.sigma, sigma_2, 1e-10)
+  # Compares F, G and sigma to the ones of the python implementation
+  assert numpy.allclose(m.f, m_py.f, 1e-10)
+  assert numpy.allclose(m.g, m_py.g, 1e-10)
+  assert numpy.allclose(m.sigma, m_py.sigma, 1e-10)
+
+
+  # Test the second order statistics computation
+  # Calls the initialization methods and resets randomly initialized values
+  # to new reference ones (to make the tests deterministic)
+  t.use_sum_second_order = False
+  t.initialize(m,l)
+  m.sigma = sigma_init
+  m.g = G_init
+  m.f = F_init
+  t_py.initialize(m_py,l)
+  m_py.sigma = sigma_init
+  m_py.g = G_init
+  m_py.f = F_init
+
+  # E-step 1
+  t.e_step(m,l)
+  t_py.e_step(m_py,l)
+  # Compares statistics to Prince matlab reference
+  assert numpy.allclose(t.z_first_order[0], z_first_order_a_1, 1e-10)
+  assert numpy.allclose(t.z_first_order[1], z_first_order_b_1, 1e-10)
+  # Compares statistics against the ones of the python implementation
+  assert numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10)
+  assert numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order[0], t_py.m_z_second_order[0], 1e-10)
+  assert numpy.allclose(t.z_second_order[1], t_py.m_z_second_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10)
+
+  # M-step 1
+  t.m_step(m,l)
+  t_py.m_step(m_py,l)
+  # Compares F, G and sigma to the ones of the python implementation
+  assert numpy.allclose(m.f, m_py.f, 1e-10)
+  assert numpy.allclose(m.g, m_py.g, 1e-10)
+  assert numpy.allclose(m.sigma, m_py.sigma, 1e-10)
+
+  # E-step 2
+  t.e_step(m,l)
+  t_py.e_step(m_py,l)
+  # Compares statistics to Prince matlab reference
+  assert numpy.allclose(t.z_first_order[0], z_first_order_a_2, 1e-10)
+  assert numpy.allclose(t.z_first_order[1], z_first_order_b_2, 1e-10)
+  # Compares statistics against the ones of the python implementation
+  assert numpy.allclose(t.z_first_order[0], t_py.m_z_first_order[0], 1e-10)
+  assert numpy.allclose(t.z_first_order[1], t_py.m_z_first_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order[0], t_py.m_z_second_order[0], 1e-10)
+  assert numpy.allclose(t.z_second_order[1], t_py.m_z_second_order[1], 1e-10)
+  assert numpy.allclose(t.z_second_order_sum, t_py.m_sum_z_second_order, 1e-10)
+
+  # M-step 2
+  t.m_step(m,l)
+  t_py.m_step(m_py,l)
+  # Compares F, G and sigma to the ones of the python implementation
+  assert numpy.allclose(m.f, m_py.f, 1e-10)
+  assert numpy.allclose(m.g, m_py.g, 1e-10)
+  assert numpy.allclose(m.sigma, m_py.sigma, 1e-10)
+
+
+def test_plda_enrollment():
+  # Data used for performing the tests
+  # Features and subspaces dimensionality
+  dim_d = 7
+  dim_f = 2
+  dim_g = 3
+
+  # initial values for F, G and sigma
+  G_init=numpy.array([-1.1424, -0.5044, -0.1917,
+    -0.6249,  0.1021, -0.8658,
+    -1.1687,  1.1963,  0.1807,
+    0.3926,  0.1203,  1.2665,
+    1.3018, -1.0368, -0.2512,
+    -0.5936, -0.8571, -0.2046,
+    0.4364, -0.1699, -2.2015]).reshape(dim_d,dim_g)
+  # F <-> PCA on G
+  F_init=numpy.array([-0.054222647972093, -0.000000000783146,
+    0.596449127693018,  0.000000006265167,
+    0.298224563846509,  0.000000003132583,
+    0.447336845769764,  0.000000009397750,
+    -0.108445295944185, -0.000000001566292,
+    -0.501559493741856, -0.000000006265167,
+    -0.298224563846509, -0.000000003132583]).reshape(dim_d,dim_f)
+  sigma_init = 0.01 * numpy.ones((dim_d,), 'float64')
+  mean_zero = numpy.zeros((dim_d,), 'float64')
+
+  # base machine
+  mb = PLDABase(dim_d,dim_f,dim_g)
+  mb.sigma = sigma_init
+  mb.g = G_init
+  mb.f = F_init
+  mb.mu = mean_zero
+
+  # Data for likelihood computation
+  x1 = numpy.array([0.8032, 0.3503, 0.4587, 0.9511, 0.1330, 0.0703, 0.7061])
+  x2 = numpy.array([0.9317, 0.1089, 0.6517, 0.1461, 0.6940, 0.6256, 0.0437])
+  x3 = numpy.array([0.7979, 0.9862, 0.4367, 0.3447, 0.0488, 0.2252, 0.5810])
+  a_enrol = []
+  a_enrol.append(x1)
+  a_enrol.append(x2)
+  a_enrol = numpy.array(a_enrol)
+
+  # reference likelihood from Prince implementation
+  ll_ref = -182.8880743535197
+
+  # Computes the likelihood using x1 and x2 as enrollment samples
+  # and x3 as a probe sample
+  m = PLDAMachine(mb)
+  t = PLDATrainer()
+  t.enrol(m, a_enrol)
+  ll = m.compute_log_likelihood(x3)
+  assert abs(ll - ll_ref) < 1e-10
+
+  # reference obtained by computing the likelihood of [x1,x2,x3], [x1,x2]
+  # and [x3] separately
+  llr_ref = -4.43695386675
+  llr = m.forward(x3)
+  assert abs(llr - llr_ref) < 1e-10
+  #
+  llr_separate = m.compute_log_likelihood(numpy.array([x1,x2,x3]), False) - \
+    (m.compute_log_likelihood(numpy.array([x1,x2]), False) + m.compute_log_likelihood(numpy.array([x3]), False))
+  assert abs(llr - llr_separate) < 1e-10
+
+def test_plda_comparisons():
+
+  t1 = PLDATrainer()
+  t2 = PLDATrainer()
+  t2.rng = t1.rng
+  assert t1 == t2
+  assert (t1 != t2 ) is False
+  assert t1.is_similar_to(t2)
+
+  training_set = [numpy.array([[1,2,3,4]], numpy.float64), numpy.array([[3,4,3,4]], numpy.float64)]
+  m = PLDABase(4,1,1,1e-8)
+  t1.rng.seed(37)
+  t1.initialize(m, training_set)
+  t1.e_step(m, training_set)
+  t1.m_step(m, training_set)
+  assert (t1 == t2 ) is False
+  assert t1 != t2
+  assert (t1.is_similar_to(t2) ) is False
+  t2.rng.seed(37)
+  t2.initialize(m, training_set)
+  t2.e_step(m, training_set)
+  t2.m_step(m, training_set)
+  assert t1 == t2
+  assert (t1 != t2 ) is False
+  assert t1.is_similar_to(t2)
+  t2.rng.seed(77)
+  t2.initialize(m, training_set)
+  t2.e_step(m, training_set)
+  t2.m_step(m, training_set)
+  assert (t1 == t2 ) is False
+  assert t1 != t2
+  assert (t1.is_similar_to(t2) ) is False
diff --git a/xbob/learn/misc/test_wiener.py b/xbob/learn/misc/test_wiener.py
index d1de97d..15e3028 100644
--- a/xbob/learn/misc/test_wiener.py
+++ b/xbob/learn/misc/test_wiener.py
@@ -2,108 +2,106 @@
 # vim: set fileencoding=utf-8 :
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests the WienerMachine
 """
 
-import os, sys
-import unittest
-import math
-import bob
-import numpy, numpy.random
+import os
+import numpy
 import tempfile
+import nose.tools
 
-class WienerMachineTest(unittest.TestCase):
-  """Performs various WienerMachine tests."""
-
-  def test01_initialization(self):
-
-    # Getters/Setters
-    m = bob.machine.WienerMachine(5,4,0.5)
-    self.assertEqual(m.height, 5)
-    self.assertEqual(m.width, 4)
-    self.assertEqual(m.shape, (5,4))
-    m.height = 8
-    m.width = 7
-    self.assertEqual(m.height, 8)
-    self.assertEqual(m.width, 7)
-    self.assertEqual(m.shape, (8,7))
-    m.shape = (5,6)
-    self.assertEqual(m.height, 5)
-    self.assertEqual(m.width, 6)
-    self.assertEqual(m.shape, (5,6))
-    ps1 = 0.2 + numpy.fabs(numpy.random.randn(5,6))
-    ps2 = 0.2 + numpy.fabs(numpy.random.randn(5,6))
-    m.ps = ps1
-    self.assertTrue( numpy.allclose(m.ps, ps1) )
-    m.ps = ps2
-    self.assertTrue( numpy.allclose(m.ps, ps2) )
-    pn1 = 0.5
-    m.pn = pn1
-    self.assertTrue( abs(m.pn - pn1) < 1e-5 )
-    var_thd = 1e-5
-    m.variance_threshold = var_thd
-    self.assertTrue( abs(m.variance_threshold - var_thd) < 1e-5 )
-
-    # Comparison operators
-    m2 = bob.machine.WienerMachine(m)
-    self.assertTrue( m == m2 )
-    self.assertFalse( m != m2 )
-    m3 = bob.machine.WienerMachine(ps2, pn1)
-    m3.variance_threshold = var_thd
-    self.assertTrue( m == m3 )
-    self.assertFalse( m != m3 )
-
-    # Computation of the Wiener filter W
-    w_py = 1 / (1. + m.pn / m.ps)
-    self.assertTrue( numpy.allclose(m.w, w_py) )
-
-
-  def test02_load_save(self):
-
-    m = bob.machine.WienerMachine(5,4,0.5)
-    
-    # Save and read from file
-    filename = str(tempfile.mkstemp(".hdf5")[1])
-    m.save(bob.io.HDF5File(filename, 'w'))
-    m_loaded = bob.machine.WienerMachine(bob.io.HDF5File(filename))
-    self.assertTrue( m == m_loaded )
-    self.assertFalse( m != m_loaded )
-    self.assertTrue(m.is_similar_to(m_loaded))
-    # Make them different
-    m_loaded.variance_threshold = 0.001
-    self.assertFalse( m == m_loaded )
-    self.assertTrue( m != m_loaded )
-
-    # Clean-up
-    os.unlink(filename)
-
-  
-  def test03_forward(self):
-
-    ps = 0.2 + numpy.fabs(numpy.random.randn(5,6))
-    pn = 0.5
-    m = bob.machine.WienerMachine(ps,pn)
-   
-    # Python way
-    sample = numpy.random.randn(5,6)
-    sample_fft = bob.sp.fft(sample.astype(numpy.complex128))
-    w = m.w
-    sample_fft_filtered = sample_fft * m.w
-    sample_filtered_py = numpy.absolute(bob.sp.ifft(sample_fft_filtered))
-
-    # Bob c++ way
-    sample_filtered0 = m.forward(sample) 
-    sample_filtered1 = m(sample) 
-    sample_filtered2 = numpy.zeros((5,6),numpy.float64)
-    m.forward_(sample, sample_filtered2)
-    sample_filtered3 = numpy.zeros((5,6),numpy.float64)
-    m.forward(sample, sample_filtered3)
-    sample_filtered4 = numpy.zeros((5,6),numpy.float64)
-    m(sample, sample_filtered4)
-    self.assertTrue( numpy.allclose(sample_filtered0, sample_filtered_py) )
-    self.assertTrue( numpy.allclose(sample_filtered1, sample_filtered_py) )
-    self.assertTrue( numpy.allclose(sample_filtered2, sample_filtered_py) )
-    self.assertTrue( numpy.allclose(sample_filtered3, sample_filtered_py) )
-    self.assertTrue( numpy.allclose(sample_filtered4, sample_filtered_py) )
+import xbob.sp
+import xbob.io.base
+
+from . import WienerMachine
+
+def test_initialization():
+
+  # Getters/Setters
+  m = WienerMachine(5,4,0.5)
+  nose.tools.eq_(m.height, 5)
+  nose.tools.eq_(m.width, 4)
+  nose.tools.eq_(m.shape, (5,4))
+  m.height = 8
+  m.width = 7
+  nose.tools.eq_(m.height, 8)
+  nose.tools.eq_(m.width, 7)
+  nose.tools.eq_(m.shape, (8,7))
+  m.shape = (5,6)
+  nose.tools.eq_(m.height, 5)
+  nose.tools.eq_(m.width, 6)
+  nose.tools.eq_(m.shape, (5,6))
+  ps1 = 0.2 + numpy.fabs(numpy.random.randn(5,6))
+  ps2 = 0.2 + numpy.fabs(numpy.random.randn(5,6))
+  m.ps = ps1
+  assert numpy.allclose(m.ps, ps1)
+  m.ps = ps2
+  assert numpy.allclose(m.ps, ps2)
+  pn1 = 0.5
+  m.pn = pn1
+  assert abs(m.pn - pn1) < 1e-5
+  var_thd = 1e-5
+  m.variance_threshold = var_thd
+  assert abs(m.variance_threshold - var_thd) < 1e-5
+
+  # Comparison operators
+  m2 = WienerMachine(m)
+  assert m == m2
+  assert (m != m2 ) is False
+  m3 = WienerMachine(ps2, pn1)
+  m3.variance_threshold = var_thd
+  assert m == m3
+  assert (m != m3 ) is False
+
+  # Computation of the Wiener filter W
+  w_py = 1 / (1. + m.pn / m.ps)
+  assert numpy.allclose(m.w, w_py)
+
+def test_load_save():
+
+  m = WienerMachine(5,4,0.5)
+
+  # Save and read from file
+  filename = str(tempfile.mkstemp(".hdf5")[1])
+  m.save(xbob.io.base.HDF5File(filename, 'w'))
+  m_loaded = WienerMachine(xbob.io.base.HDF5File(filename))
+  assert m == m_loaded
+  assert (m != m_loaded ) is False
+  assert m.is_similar_to(m_loaded)
+  # Make them different
+  m_loaded.variance_threshold = 0.001
+  assert (m == m_loaded ) is False
+  assert m != m_loaded
+
+  # Clean-up
+  os.unlink(filename)
+
+def test_forward():
+
+  ps = 0.2 + numpy.fabs(numpy.random.randn(5,6))
+  pn = 0.5
+  m = WienerMachine(ps,pn)
+
+  # Python way
+  sample = numpy.random.randn(5,6)
+  sample_fft = xbob.sp.fft(sample.astype(numpy.complex128))
+  w = m.w
+  sample_fft_filtered = sample_fft * m.w
+  sample_filtered_py = numpy.absolute(xbob.sp.ifft(sample_fft_filtered))
+
+  # Bob c++ way
+  sample_filtered0 = m.forward(sample)
+  sample_filtered1 = m(sample)
+  sample_filtered2 = numpy.zeros((5,6),numpy.float64)
+  m.forward_(sample, sample_filtered2)
+  sample_filtered3 = numpy.zeros((5,6),numpy.float64)
+  m.forward(sample, sample_filtered3)
+  sample_filtered4 = numpy.zeros((5,6),numpy.float64)
+  m(sample, sample_filtered4)
+  assert numpy.allclose(sample_filtered0, sample_filtered_py)
+  assert numpy.allclose(sample_filtered1, sample_filtered_py)
+  assert numpy.allclose(sample_filtered2, sample_filtered_py)
+  assert numpy.allclose(sample_filtered3, sample_filtered_py)
+  assert numpy.allclose(sample_filtered4, sample_filtered_py)
diff --git a/xbob/learn/misc/test_wiener_trainer.py b/xbob/learn/misc/test_wiener_trainer.py
index 70a4ba8..676429f 100644
--- a/xbob/learn/misc/test_wiener_trainer.py
+++ b/xbob/learn/misc/test_wiener_trainer.py
@@ -2,17 +2,15 @@
 # vim: set fileencoding=utf-8 :
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests the WienerTrainer
 """
 
-import os, sys
-import unittest
-import math
-import bob
-import numpy, numpy.random
-import tempfile
+import numpy
+import xbob.sp
+
+from . import WienerMachine, WienerTrainer
 
 def train_wiener_ps(training_set):
 
@@ -24,7 +22,7 @@ def train_wiener_ps(training_set):
 
   for n in range(n_samples):
     sample = (training_set[n,:,:]).astype(numpy.complex128)
-    training_fftabs[n,:,:] = numpy.absolute(bob.sp.fft(sample))
+    training_fftabs[n,:,:] = numpy.absolute(xbob.sp.fft(sample))
 
   mean = numpy.mean(training_fftabs, axis=0)
 
@@ -37,42 +35,39 @@ def train_wiener_ps(training_set):
   return var_ps
 
 
-class WienerTrainerTest(unittest.TestCase):
-  """Performs various WienerTrainer tests."""
-
-  def test01_initialization(self):
-
-    # Constructors and comparison operators
-    t1 = bob.trainer.WienerTrainer()
-    t2 = bob.trainer.WienerTrainer()
-    t3 = bob.trainer.WienerTrainer(t2)
-    t4 = t3
-    self.assertTrue( t1 == t2)
-    self.assertFalse( t1 != t2)
-    self.assertTrue( t1.is_similar_to(t2) )
-    self.assertTrue( t1 == t3)
-    self.assertFalse( t1 != t3)
-    self.assertTrue( t1.is_similar_to(t3) )
-    self.assertTrue( t1 == t4)
-    self.assertFalse( t1 != t4)
-    self.assertTrue( t1.is_similar_to(t4) )
-
-
-  def test02_train(self):
-
-    n_samples = 20
-    height = 5
-    width = 6
-    training_set = 0.2 + numpy.fabs(numpy.random.randn(n_samples, height, width))
-
-    # Python implementation
-    var_ps = train_wiener_ps(training_set)
-    # Bob C++ implementation (variant 1) + comparison against python one
-    t = bob.trainer.WienerTrainer()
-    m1 = t.train(training_set)
-    self.assertTrue( numpy.allclose(var_ps, m1.ps) )
-    # Bob C++ implementation (variant 2) + comparison against python one
-    m2 = bob.machine.WienerMachine(height, width, 0.)
-    t.train(m2, training_set)
-    self.assertTrue( numpy.allclose(var_ps, m2.ps) )
+def test_initialization():
+
+  # Constructors and comparison operators
+  t1 = WienerTrainer()
+  t2 = WienerTrainer()
+  t3 = WienerTrainer(t2)
+  t4 = t3
+  assert t1 == t2
+  assert (t1 != t2) is False
+  assert t1.is_similar_to(t2)
+  assert t1 == t3
+  assert (t1 != t3) is False
+  assert t1.is_similar_to(t3)
+  assert t1 == t4
+  assert (t1 != t4) is False
+  assert t1.is_similar_to(t4)
+
+
+def test_train():
+
+  n_samples = 20
+  height = 5
+  width = 6
+  training_set = 0.2 + numpy.fabs(numpy.random.randn(n_samples, height, width))
+
+  # Python implementation
+  var_ps = train_wiener_ps(training_set)
+  # Bob C++ implementation (variant 1) + comparison against python one
+  t = WienerTrainer()
+  m1 = t.train(training_set)
+  assert numpy.allclose(var_ps, m1.ps)
+  # Bob C++ implementation (variant 2) + comparison against python one
+  m2 = WienerMachine(height, width, 0.)
+  t.train(m2, training_set)
+  assert numpy.allclose(var_ps, m2.ps)
 
diff --git a/xbob/learn/misc/test_ztnorm.py b/xbob/learn/misc/test_ztnorm.py
index 9de2c37..0af05f2 100644
--- a/xbob/learn/misc/test_ztnorm.py
+++ b/xbob/learn/misc/test_ztnorm.py
@@ -4,20 +4,17 @@
 # Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 # Tue Jul 19 15:33:20 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests on the ZTNorm function
 """
 
-import os, sys
-import unittest
 import numpy
-import bob
-import pkg_resources
 
-def F(f):
-  """Returns the test file on the "data" subdirectory"""
-  return pkg_resources.resource_filename(__name__, os.path.join('data', f))
+from xbob.io.base.test_utils import datafile
+import xbob.io.base
+
+from . import znorm, tnorm, ztnorm
 
 def sameValue(vect_A, vect_B):
   sameMatrix = numpy.zeros((vect_A.shape[0], vect_B.shape[0]), 'bool')
@@ -27,7 +24,7 @@ def sameValue(vect_A, vect_B):
       sameMatrix[j, i] = (vect_A[j] == vect_B[i])
 
   return sameMatrix
- 
+
 def tnorm(A, C):
   Cmean = numpy.mean(C, axis=0)
   if C.shape[1] > 1:
@@ -35,92 +32,90 @@ def tnorm(A, C):
   else:
     Cstd = numpy.ones(shape=(C.shape[1],), dtype=numpy.float64)
   return (A - numpy.tile(Cmean.reshape(1,C.shape[1]), (A.shape[0],1))) / numpy.tile(Cstd.reshape(1,C.shape[1]), (A.shape[0],1))
-  
+
 def znorm(A, B):
   Bmean = numpy.mean(B, axis=1)
   if B.shape[1] > 1:
     Bstd = numpy.sqrt(numpy.sum((B - numpy.tile(Bmean.reshape(B.shape[0],1), (1,B.shape[1]))) ** 2, axis=1) / (B.shape[1]-1))
   else:
     Bstd = numpy.ones(shape=(B.shape[0],), dtype=numpy.float64)
-  
+
   return (A - numpy.tile(Bmean.reshape(B.shape[0],1), (1,A.shape[1]))) / numpy.tile(Bstd.reshape(B.shape[0],1), (1,A.shape[1]))
- 
-class ZTNormTest(unittest.TestCase):
-  """Performs various ZTNorm tests."""
-
-  def test01_ztnorm_simple(self):
-    # 3x5
-    my_A = numpy.array([[1, 2, 3, 4, 5],
-                        [6, 7, 8, 9, 8],
-                        [7, 6, 5, 4, 3]],'float64')
-    # 3x4
-    my_B = numpy.array([[5, 4, 7, 8],[9, 8, 7, 4],[5, 6, 3, 2]],'float64')
-    # 2x5
-    my_C = numpy.array([[5, 4, 3, 2, 1],[2, 1, 2, 3, 4]],'float64')
-    # 2x4
-    my_D = numpy.array([[8, 6, 4, 2],[0, 2, 4, 6]],'float64')
-    
-    # 4x1
-    znorm_id = numpy.array([1, 2, 3, 4],'uint32')
-    # 2x1
-    tnorm_id = numpy.array([1, 5],'uint32')
-
-    scores = bob.machine.ztnorm(my_A, my_B, my_C, my_D,
-        sameValue(tnorm_id, znorm_id))
-
-    ref_scores = numpy.array([[-4.45473107e+00, -3.29289322e+00, -1.50519101e+01, -8.42086557e-01, 6.46544511e-03], [-8.27619927e-01,  7.07106781e-01,  1.13757710e+01,  2.01641412e+00, 7.63765080e-01], [ 2.52913570e+00,  2.70710678e+00,  1.24400233e+01,  7.07106781e-01, 6.46544511e-03]], 'float64')
-    
-    self.assertTrue((abs(scores - ref_scores) < 1e-7).all())
-
-  def test02_ztnorm_big(self):
-    my_A = bob.io.load(F("ztnorm_eval_eval.mat"))
-    my_B = bob.io.load(F("ztnorm_znorm_eval.mat"))
-    my_C = bob.io.load(F("ztnorm_eval_tnorm.mat"))
-    my_D = bob.io.load(F("ztnorm_znorm_tnorm.mat"))
-
-    # ZT-Norm
-    ref_scores = bob.io.load(F("ztnorm_result.mat"))
-    scores = bob.machine.ztnorm(my_A, my_B, my_C, my_D)
-    self.assertTrue((abs(scores - ref_scores) < 1e-7).all()) 
-
-    # T-Norm
-    scores = bob.machine.tnorm(my_A, my_C)
-    scores_py = tnorm(my_A, my_C)
-    self.assertTrue((abs(scores - scores_py) < 1e-7).all()) 
-
-    # Z-Norm
-    scores = bob.machine.znorm(my_A, my_B)
-    scores_py = znorm(my_A, my_B)
-    self.assertTrue((abs(scores - scores_py) < 1e-7).all()) 
-
-  def test03_tnorm_simple(self):
-    # 3x5
-    my_A = numpy.array([[1, 2, 3, 4, 5],
-                        [6, 7, 8, 9, 8],
-                        [7, 6, 5, 4, 3]],'float64')
-    # 2x5
-    my_C = numpy.array([[5, 4, 3, 2, 1],[2, 1, 2, 3, 4]],'float64')
-    
-    zC = bob.machine.tnorm(my_A, my_C)
-    zC_py = tnorm(my_A, my_C)
-    self.assertTrue((abs(zC - zC_py) < 1e-7).all())
-
-    empty = numpy.zeros(shape=(0,0), dtype=numpy.float64)
-    zC = bob.machine.ztnorm(my_A, empty, my_C, empty)
-    self.assertTrue((abs(zC - zC_py) < 1e-7).all())
-
-  def test04_znorm_simple(self):
-    # 3x5
-    my_A = numpy.array([[1, 2, 3, 4, 5],
-                        [6, 7, 8, 9, 8],
-                        [7, 6, 5, 4, 3]], numpy.float64)
-    # 3x4
-    my_B = numpy.array([[5, 4, 7, 8],[9, 8, 7, 4],[5, 6, 3, 2]], numpy.float64)
- 
-    zA = bob.machine.znorm(my_A, my_B)
-    zA_py = znorm(my_A, my_B)
-    self.assertTrue((abs(zA - zA_py) < 1e-7).all())
-
-    empty = numpy.zeros(shape=(0,0), dtype=numpy.float64)
-    zA = bob.machine.ztnorm(my_A, my_B, empty, empty)
-    self.assertTrue((abs(zA - zA_py) < 1e-7).all())
+
+
+def test_ztnorm_simple():
+  # 3x5
+  my_A = numpy.array([[1, 2, 3, 4, 5],
+                      [6, 7, 8, 9, 8],
+                      [7, 6, 5, 4, 3]],'float64')
+  # 3x4
+  my_B = numpy.array([[5, 4, 7, 8],[9, 8, 7, 4],[5, 6, 3, 2]],'float64')
+  # 2x5
+  my_C = numpy.array([[5, 4, 3, 2, 1],[2, 1, 2, 3, 4]],'float64')
+  # 2x4
+  my_D = numpy.array([[8, 6, 4, 2],[0, 2, 4, 6]],'float64')
+
+  # 4x1
+  znorm_id = numpy.array([1, 2, 3, 4],'uint32')
+  # 2x1
+  tnorm_id = numpy.array([1, 5],'uint32')
+
+  scores = ztnorm(my_A, my_B, my_C, my_D,
+      sameValue(tnorm_id, znorm_id))
+
+  ref_scores = numpy.array([[-4.45473107e+00, -3.29289322e+00, -1.50519101e+01, -8.42086557e-01, 6.46544511e-03], [-8.27619927e-01,  7.07106781e-01,  1.13757710e+01,  2.01641412e+00, 7.63765080e-01], [ 2.52913570e+00,  2.70710678e+00,  1.24400233e+01,  7.07106781e-01, 6.46544511e-03]], 'float64')
+
+  assert (abs(scores - ref_scores) < 1e-7).all()
+
+def test_ztnorm_big():
+  my_A = xbob.io.base.load(datafile("ztnorm_eval_eval.mat", __name__))
+  my_B = xbob.io.base.load(datafile("ztnorm_znorm_eval.mat", __name__))
+  my_C = xbob.io.base.load(datafile("ztnorm_eval_tnorm.mat", __name__))
+  my_D = xbob.io.base.load(datafile("ztnorm_znorm_tnorm.mat", __name__))
+
+  # ZT-Norm
+  ref_scores = xbob.io.base.load(datafile("ztnorm_result.mat", __name__))
+  scores = ztnorm(my_A, my_B, my_C, my_D)
+  assert (abs(scores - ref_scores) < 1e-7).all()
+
+  # T-Norm
+  scores = tnorm(my_A, my_C)
+  scores_py = tnorm(my_A, my_C)
+  assert (abs(scores - scores_py) < 1e-7).all()
+
+  # Z-Norm
+  scores = znorm(my_A, my_B)
+  scores_py = znorm(my_A, my_B)
+  assert (abs(scores - scores_py) < 1e-7).all()
+
+def test_tnorm_simple():
+  # 3x5
+  my_A = numpy.array([[1, 2, 3, 4, 5],
+                      [6, 7, 8, 9, 8],
+                      [7, 6, 5, 4, 3]],'float64')
+  # 2x5
+  my_C = numpy.array([[5, 4, 3, 2, 1],[2, 1, 2, 3, 4]],'float64')
+
+  zC = tnorm(my_A, my_C)
+  zC_py = tnorm(my_A, my_C)
+  assert (abs(zC - zC_py) < 1e-7).all()
+
+  empty = numpy.zeros(shape=(0,0), dtype=numpy.float64)
+  zC = ztnorm(my_A, empty, my_C, empty)
+  assert (abs(zC - zC_py) < 1e-7).all()
+
+def test_znorm_simple():
+  # 3x5
+  my_A = numpy.array([[1, 2, 3, 4, 5],
+                      [6, 7, 8, 9, 8],
+                      [7, 6, 5, 4, 3]], numpy.float64)
+  # 3x4
+  my_B = numpy.array([[5, 4, 7, 8],[9, 8, 7, 4],[5, 6, 3, 2]], numpy.float64)
+
+  zA = znorm(my_A, my_B)
+  zA_py = znorm(my_A, my_B)
+  assert (abs(zA - zA_py) < 1e-7).all()
+
+  empty = numpy.zeros(shape=(0,0), dtype=numpy.float64)
+  zA = ztnorm(my_A, my_B, empty, empty)
+  assert (abs(zA - zA_py) < 1e-7).all()
-- 
GitLab