/// <summary>
        /// Construit un classificateur de test en cherchant la meilleure
        /// valeur du seuil.
        /// FeatureIndex est le numero d'index de la caractéristique.
        /// </summary>
        public static TestWeakClassifier Train(TestImage[] Tests, double ValidWeight,
		                                       FeatureValues Feature)
        {
            if (Feature.Values == null) // Uncached values
                Feature.Values = FeatureValue.ComputeAllValuesSorted(Tests, Feature.Feature);

            // With the default values, the positive classifier says always
            // no. So it scores wrong for all valid tests.
            var positiveError = ValidWeight;

            // Iterate all feature's values, ascending
            var best = new TestWeakClassifier(Feature, Feature.Values[0].Value, 1, positiveError);

            // Select the threshold with the lowest error weight
            for (var iTest = 0; iTest < Feature.Values.Length; iTest++) {
                if (Tests[Feature.Values[iTest].TestIndex].Valid) {
                    positiveError -= Tests[Feature.Values[iTest].TestIndex].Weight;

                    if (positiveError < best.Errors) {
                        best = new TestWeakClassifier(Feature, Feature.Values[iTest].Value + 1,
                                                      1, positiveError);
                    }
                } else {
                    positiveError += Tests[Feature.Values[iTest].TestIndex].Weight;

                    var negativeError = 1.0 - positiveError;

                    if (negativeError < best.Errors) {
                        best = new TestWeakClassifier(Feature, Feature.Values[iTest].Value - 1,
                                                      -1, negativeError);
                    }
                }
            }

            return best;
        }
        /// <summary>
        /// Charge un ensemble d'images de test d'apprentissage, initialise
        /// les poids des tests et calcule les valeurs de chaque caractéristique.
        /// </summary>
        private static Tuple<TestImage[], FeatureValues[]> LoadTestsSet(string TestsDir)
        {
            var goodDir = Path.Combine(TestsDir, "good");
            var badDir = Path.Combine(TestsDir, "bad");

            var good = LoadImages(goodDir);
            var bad = LoadImages(badDir);

            // Init default weights values
            var goodWeight = 1.0 / (2 * good.Length);
            var badWeight = 1.0 / (2 * bad.Length);

            var tests = new TestImage[good.Length + bad.Length];

            // Save default weights and status (valid/invalid) and
            // fusion good & bad sets
            for (var i = 0; i < good.Length; i++) {
                tests[i] = new TestImage(good[i], goodWeight, true);
            }
            for (var i = good.Length; i < good.Length + bad.Length; i++) {
                tests[i] = new TestImage(bad[i - good.Length], badWeight, false);
            }

            // Compute features's values
            var featuresValues = ComputeFeaturesValues(tests);

            return Tuple.Create(tests, featuresValues);
        }
        /// <summary>
        /// Retourne un tableau a deux dimensions contenenant les valeurs
        /// calculées de toutes les caractéristiques pour tous les tests.
        /// int[feature,test].
        /// </summary>
        private static FeatureValues[] ComputeFeaturesValues(TestImage[] Tests)
        {
            // TODO: Windows compatibility
            if (Environment.OSVersion.Platform != PlatformID.Unix)
                    throw new NotImplementedException();

            Func<int> AvailableMemory = () =>
            {
                return File.ReadAllLines("/proc/meminfo")
                    .Where(elem =>
                        elem.StartsWith("MemFree")
                        || elem.StartsWith("Buffers")
                        || elem.StartsWith("Cached"))
                    .Select(elem => elem.Split(' '))
                    .Aggregate(0, (acc, values) => acc + int.Parse(values[values.Length - 2]));
            };

            // List all features of a standard 24x24 window
            var features = Window.ListFeatures().ToArray();

            var featuresValues = new FeatureValues[features.Length];

            Parallel.For(0, features.Length, (iFeature) => {
            //for (var iFeature = 0; iFeature < FeaturesList.Length; iFeature++) {
                if (AvailableMemory() > Config.MinFreeMemory) {
                    var values = FeatureValue.ComputeAllValuesSorted(Tests, features[iFeature]);

                    featuresValues[iFeature] = new FeatureValues(features[iFeature], values);
                } else
                    featuresValues[iFeature] = new FeatureValues(features[iFeature], null);
            });
            //}

            return featuresValues;
        }