public TestWeakClassifier(FeatureValues Feature, int Threshold, sbyte Parity, double Errors)
        {
            this.Feature = Feature;

            this.Threshold = Threshold;
            this.Parity = Parity;

            this.Errors = Errors;
        }
        /// <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>
        /// 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;
        }