public static double Compute(double[] X, GaussianMixtureModel GMM)
        {
            // Initialisations
            int K = GMM.K;

            double[,] membership = new double[GMM.K, X.Length];
            int    count         = 0;
            double?logLikelihood = null;

            while (count++ < 100)
            {
                // Expectation - recalculate membership values for all k,x pairs
                int XLength = X.Length;
                for (int x = 0; x < XLength; x++)
                {
                    double total = 0.0;
                    for (int k = 0; k < K; k++)
                    {
                        membership[k, x] = GMM.MixingWeight[k] * GMM.SingleGaussianProbabilityEM(X[x], k);
                        total           += membership[k, x];
                    }
                    for (int k = 0; k < K; k++)
                    {
                        membership[k, x] /= total;
                    }
                }

                // Maximisation - recalculate GMM parameters based on the results of the expectation step
                for (int k = 0; k < K; k++)
                {
                    // Mean
                    double sumMembership  = 0.0;
                    double sumMembershipX = 0.0;
                    for (int x = 0; x < XLength; x++)
                    {
                        sumMembership  += membership[k, x];
                        sumMembershipX += membership[k, x] * X[x];
                    }
                    GMM.Mean[k] = sumMembershipX / sumMembership;

                    // Sigma
                    double Mean = GMM.Mean[k];
                    double sumMembershipXMinusMeanSquared = 0.0;
                    for (int x = 0; x < XLength; x++)
                    {
                        sumMembershipXMinusMeanSquared += membership[k, x] * Math.Pow(X[x] - Mean, 2.0);
                    }

                    // Prevent Sigma from going below 1 to avoid S.Ds of 0
                    GMM.Sigma[k] = Math.Max(Math.Sqrt(sumMembershipXMinusMeanSquared / sumMembership), 1);

                    // Mixing
                    GMM.MixingWeight[k] = sumMembership / XLength;
                }

                // Log likelihood
                double currentLikelihood = 1.0;
                for (int x = 0; x < XLength; x++)
                {
                    double value = X[x];
                    double total = 0.0;
                    for (int k = 0; k < K; k++)
                    {
                        total += GMM.MixingWeight[k] * GMM.SingleGaussianProbabilityEM(value, k);
                    }
                    currentLikelihood += Math.Log(total);
                }

                if (logLikelihood == null)
                {
                    logLikelihood = currentLikelihood;
                }
                else
                {
                    double ratio = currentLikelihood / (double)logLikelihood;
                    logLikelihood = currentLikelihood;
                    if (1 - ratio < 0.0001)
                    {
                        // Convergence
                        break;
                    }
                }
            }

            return(logLikelihood == null ? Double.NaN : (double)logLikelihood);
        }
        protected override void OnDoWork(DoWorkEventArgs e)
        {
            List <Tuple <EMPatch, GaussianMixtureModel> > currentMixtures = new List <Tuple <EMPatch, GaussianMixtureModel> >();

            int patchProgressCount = 0;
            int totalPatches       = Patches.Count;

            foreach (EMPatch patch in Patches)
            {
                // Check bounds
                if (patch.Left < 0 || patch.Right > Width || patch.Top < 0 || patch.Bottom > Height)
                {
                    throw new EMPatch.EMPatchException("Patch falls outside the bounds of the source image");
                }

                // Copy data
                int patchWidth = patch.Right - patch.Left;
                int patchHeight = patch.Bottom - patch.Top;
                int left = patch.Left, right = patch.Right, top = patch.Top, bottom = patch.Bottom;
                if (this.data == null || this.data.Length != patchWidth * patchHeight)
                {
                    this.data = new double[patchWidth * patchHeight];
                }

                int    i = 0;
                double max = Double.MinValue, min = Double.MaxValue;
                for (int pX = left; pX < right; pX++)
                {
                    for (int pY = top; pY < bottom; pY++)
                    {
                        data[i] = IntensityBuffer[pY * Width + pX];
                        if (data[i] < min)
                        {
                            min = data[i];
                        }
                        if (data[i] > max)
                        {
                            max = data[i];
                        }
                        i += 1;
                    }
                }

                GaussianMixtureModel GMM = null;

                // Run EM starting at the initial class count and increasing the class count if necessary
                for (int classCount = Configuration.InitialClassCount; classCount <= Configuration.MaximumClassCount; classCount++)
                {
                    // KMeans initialisation
                    GMM = GaussianMixtureModel.KMeansInitialisation(data, classCount, (int)min, (int)max);

                    // Expectation Maximisation
                    ExpectationMaximisation.Compute(data, GMM);

                    // Threshold
                    GMM.ThresholdAtPercentage(Configuration.BackgroundPercentage, Configuration.BackgroundExcessSigma);

                    // Check remaining classes for none (empty patch) or the correct number of classes
                    if (GMM.K - (GMM.ThresholdK + 1) >= Configuration.ExpectedRootClassCount ||
                        GMM.K - (GMM.ThresholdK + 1) == 0)
                    {
                        break;
                    }
                }

                // Add Patch, GMM pair into output array
                if (GMM == null)
                {
                    throw new InvalidOperationException("GMM cannot be null");
                }

                currentMixtures.Add(new Tuple <EMPatch, GaussianMixtureModel>(patch, GMM));

                // Signal progress changed
                base.OnProgressChanged(new ProgressChangedEventArgs((int)(++patchProgressCount * 100.0 / totalPatches), null));
            }

            // Assign this.Mixtures
            this.Mixtures = currentMixtures;
        }