private SortedSet <int> MergeNeighbors(int i, int j)
        {
            PixelCluster    a      = clusters[i];
            PixelCluster    b      = clusters[j];
            SortedSet <int> merged = Simplify(a.neighbors);

            merged.UnionWith(Simplify(b.neighbors));
            merged.Remove(a.id);
            merged.Remove(b.id);

            return(merged);
        }
        //follow the parent pointer until we reach the root
        private int Find(int id)
        {
            PixelCluster p = clusters[id];

            if (p.id == p.parentId)
            {
                return(p.id);
            }

            int parentId = p.parentId;

            while (parentId != clusters[parentId].parentId)
            {
                parentId = clusters[parentId].parentId;
            }
            //compress the path
            p.parentId = parentId;

            return(parentId);
        }
        //Cluster the final clusters into color space
        public void ClusterColorSpace()
        {
            double maxDist    = 20 * 20;
            int    minRegions = 5;

            SortedSet <int> activeClusterIds = new SortedSet <int>(rootIds);
            String          logFile          = "colorlog.txt";
            StreamWriter    log = File.AppendText(logFile);

            log.WriteLine("\n\tCluster ColorSpace Run " + DateTime.Now.ToString());
            log.Flush();


            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue <Tuple <int, int>, double> pq = new PriorityQueue <Tuple <int, int>, double>();

            int counter = activeClusterIds.Last() + 1;

            int[] ids = activeClusterIds.ToArray <int>();

            //Calculate the initial distances
            for (int i = 0; i < ids.Count(); i++)
            {
                for (int j = i + 1; j < ids.Count(); j++)
                {
                    //log.WriteLine(ids[i] + ", " + ids[j] + " dist " + -1 * clusters[ids[i]].lab.SqDist(clusters[ids[j]].lab));
                    //log.Flush();

                    //pq.Enqueue(new Tuple<int, int>(ids[i], ids[j]), -1 * clusters[ids[i]].lab.SqDist(clusters[ids[j]].lab));
                    PixelCluster a = clusters[ids[i]];
                    PixelCluster b = clusters[ids[j]];

                    double newDist = a.lab.SqDist(b.lab);

                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    pq.Enqueue(new Tuple <int, int>(ids[i], ids[j]), -1 * newDist);
                }
            }

            Stopwatch timer = new Stopwatch();

            timer.Start();

            while (activeClusterIds.Count > minRegions)
            {
                //Find the pair with the smallest distance
                KeyValuePair <Tuple <int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple <int, int> pair     = result.Key;
                double           bestDist = -1 * result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                {
                    break;
                }

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id, we don't care about the neighbors
                PixelCluster merged = new PixelCluster();
                merged.id       = counter++;
                merged.lab      = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count    = a.count + b.count;
                merged.children = new int[] { a.id, b.id };
                merged.parentId = merged.id;
                a.parentId      = merged.id;
                b.parentId      = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in activeClusterIds)
                {
                    if (i == merged.id)
                    {
                        continue;
                    }

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    PixelCluster c       = clusters[i];
                    double       newDist = merged.lab.SqDist(c.lab);


                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2*a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                    {
                        pq.Enqueue(new Tuple <int, int>(c.id, merged.id), -1 * newDist);
                    }
                    else
                    {
                        pq.Enqueue(new Tuple <int, int>(merged.id, c.id), -1 * newDist);
                    }
                }

                //Remove the old clusters
                foreach (int i in activeClusterIds)
                {
                    if (i > a.id)
                    {
                        pq.Remove(new Tuple <int, int>(a.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, a.id));
                    }
                }
                foreach (int i in activeClusterIds)
                {
                    if (i > b.id)
                    {
                        pq.Remove(new Tuple <int, int>(b.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, b.id));
                    }
                }



                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count);
                    log.Flush();
                    timer.Restart();
                }
            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }
        /**
         * Now the Agglomerative Clustering. May want to pass in additional parameters, like the termination
         * criteria and parameters.
         * Assumes list of colors are in row-order
         **/
        public void Cluster(List <CIELAB> colors, int width, int height)
        {
            double          maxDist          = 50 * 10;
            SortedSet <int> activeClusterIds = new SortedSet <int>();
            String          logFile          = "log.txt";
            StreamWriter    log = File.AppendText(logFile);

            log.WriteLine("\n\tCluster Spatial Run " + DateTime.Now.ToString());
            log.Flush();


            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue <Tuple <int, int>, double> pq = new PriorityQueue <Tuple <int, int>, double>();

            clusters = new Dictionary <int, PixelCluster>();

            int counter = 0;

            //Initialize the clusters in row-order
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    activeClusterIds.Add(counter);
                    PixelCluster p = new PixelCluster(counter, colors[width * j + i]);
                    counter++;

                    //Initialize the 4-neighbors
                    if (i > 0)
                    {
                        p.neighbors.Add(ToIndex(i - 1, j, width));
                    }
                    if (j > 0)
                    {
                        p.neighbors.Add(ToIndex(i, j - 1, width));
                    }
                    if (i < width - 1)
                    {
                        p.neighbors.Add(ToIndex(i + 1, j, width));
                    }
                    if (j < height - 1)
                    {
                        p.neighbors.Add(ToIndex(i, j + 1, width));
                    }

                    clusters.Add(p.id, p);
                }
            }

            foreach (int i in activeClusterIds)
            {
                //calculate distances to neighbors larger than current id
                SortedSet <int> neighbors = Simplify(clusters[i].neighbors);
                foreach (int j in neighbors)
                {
                    if (i < j)
                    {
                        pq.Enqueue(new Tuple <int, int>(i, j), -1 * clusters[i].lab.SqDist(clusters[j].lab));
                    }
                }
            }

            Stopwatch timer = new Stopwatch();

            timer.Start();

            while (activeClusterIds.Count > 1)
            {
                //Find the pair with the smallest distance
                KeyValuePair <Tuple <int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple <int, int> pair     = result.Key;
                double           bestDist = -1 * result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                {
                    break;
                }

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id
                PixelCluster merged = new PixelCluster();
                merged.id        = counter++;
                merged.lab       = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count     = a.count + b.count;
                merged.children  = new int[] { a.id, b.id };
                merged.neighbors = MergeNeighbors(a.id, b.id);
                merged.parentId  = merged.id;
                a.parentId       = merged.id;
                b.parentId       = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in merged.neighbors)
                {
                    //Debug.Assert(i != merged.id && activeClusterIds.Contains(i));

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    PixelCluster c       = clusters[i];
                    double       newDist = merged.lab.SqDist(c.lab);


                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                    {
                        pq.Enqueue(new Tuple <int, int>(c.id, merged.id), -1 * newDist);
                    }
                    else
                    {
                        pq.Enqueue(new Tuple <int, int>(merged.id, c.id), -1 * newDist);
                    }
                }

                //Remove the old clusters
                foreach (int i in a.neighbors)
                {
                    if (i > a.id)
                    {
                        pq.Remove(new Tuple <int, int>(a.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, a.id));
                    }
                }
                foreach (int i in b.neighbors)
                {
                    if (i > b.id)
                    {
                        pq.Remove(new Tuple <int, int>(b.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, b.id));
                    }
                }



                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count);
                    log.Flush();
                    timer.Restart();
                }
            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }
        public void ClusterColors(List <CIELAB> colors, int width, int height)
        {
            //Bin the colors
            int             minRegions       = 5;
            double          maxDist          = 10 * 10;
            SortedSet <int> activeClusterIds = new SortedSet <int>();
            String          logFile          = "log-colorspace.txt";
            StreamWriter    log = File.AppendText(logFile);

            log.WriteLine("\n\tCluster Color Space " + DateTime.Now.ToString());
            log.Flush();

            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue <Tuple <int, int>, double> pq = new PriorityQueue <Tuple <int, int>, double>();

            clusters = new Dictionary <int, PixelCluster>();
            int counter = 0;

            foreach (CIELAB color in colors)
            {
                //bin it into one of the clusters
                //index is a first, then b, then L
                int id = GetBinId(color);

                if (id > counter)
                {
                    counter = id;
                }

                if (!clusters.ContainsKey(id))
                {
                    clusters.Add(id, new PixelCluster(id, color));
                }
                else
                {
                    clusters[id].lab = (clusters[id].lab * clusters[id].count + color) / (clusters[id].count + 1);
                    clusters[id].count++;
                }
            }
            counter++;

            activeClusterIds = new SortedSet <int>(clusters.Keys);

            List <int> ids = activeClusterIds.ToList <int>();

            for (int i = 0; i < ids.Count(); i++)
            {
                PixelCluster a = clusters[ids[i]];

                //calculate distances to neighbors larger than current id
                for (int j = i + 1; j < ids.Count(); j++)
                {
                    PixelCluster b = clusters[ids[j]];

                    double newDist = a.lab.SqDist(b.lab);
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));
                    pq.Enqueue(new Tuple <int, int>(a.id, b.id), -1 * newDist);
                }
            }

            Stopwatch timer = new Stopwatch();

            timer.Start();

            while (activeClusterIds.Count > minRegions)
            {
                //Find the pair with the smallest distance
                KeyValuePair <Tuple <int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple <int, int> pair     = result.Key;
                double           bestDist = -1 * result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                {
                    break;
                }

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id, don't care about neighbors
                PixelCluster merged = new PixelCluster();
                merged.id       = counter++;
                merged.lab      = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count    = a.count + b.count;
                merged.children = new int[] { a.id, b.id };
                merged.parentId = merged.id;
                a.parentId      = merged.id;
                b.parentId      = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in activeClusterIds)
                {
                    //Debug.Assert(i != merged.id && activeClusterIds.Contains(i));

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    if (i == merged.id)
                    {
                        continue;
                    }

                    PixelCluster c       = clusters[i];
                    double       newDist = merged.lab.SqDist(c.lab);


                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                    {
                        pq.Enqueue(new Tuple <int, int>(c.id, merged.id), -1 * newDist);
                    }
                    else
                    {
                        pq.Enqueue(new Tuple <int, int>(merged.id, c.id), -1 * newDist);
                    }
                }

                //Remove the old clusters
                foreach (int i in a.neighbors)
                {
                    if (i > a.id)
                    {
                        pq.Remove(new Tuple <int, int>(a.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, a.id));
                    }
                }
                foreach (int i in b.neighbors)
                {
                    if (i > b.id)
                    {
                        pq.Remove(new Tuple <int, int>(b.id, i));
                    }
                    else
                    {
                        pq.Remove(new Tuple <int, int>(i, b.id));
                    }
                }



                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count);
                    log.Flush();
                    timer.Restart();
                }
            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }
        /**
         * Now the Agglomerative Clustering. May want to pass in additional parameters, like the termination
         * criteria and parameters.
         * Assumes list of colors are in row-order
         **/
        public void Cluster(List<CIELAB> colors, int width, int height)
        {
            double maxDist = 50*10;
            SortedSet<int> activeClusterIds = new SortedSet<int>();
            String logFile = "log.txt";
            StreamWriter log = File.AppendText(logFile);
            log.WriteLine("\n\tCluster Spatial Run " + DateTime.Now.ToString());
            log.Flush();

            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue<Tuple<int, int>, double> pq = new PriorityQueue<Tuple<int, int>, double>();

            clusters = new Dictionary<int, PixelCluster>();

            int counter = 0;

            //Initialize the clusters in row-order
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    activeClusterIds.Add(counter);
                    PixelCluster p = new PixelCluster(counter, colors[width * j + i]);
                    counter++;

                    //Initialize the 4-neighbors
                    if (i > 0)
                        p.neighbors.Add(ToIndex(i - 1, j, width));
                    if (j > 0)
                        p.neighbors.Add(ToIndex(i, j - 1, width));
                    if (i < width - 1)
                        p.neighbors.Add(ToIndex(i + 1, j, width));
                    if (j < height - 1)
                        p.neighbors.Add(ToIndex(i, j + 1, width));

                    clusters.Add(p.id, p);
                }
            }

            foreach (int i in activeClusterIds)
            {
                //calculate distances to neighbors larger than current id
                SortedSet<int> neighbors = Simplify(clusters[i].neighbors);
                foreach (int j in neighbors)
                {
                    if (i < j)
                    {
                        pq.Enqueue(new Tuple<int, int>(i, j), -1*clusters[i].lab.SqDist(clusters[j].lab));
                    }
                }

            }

            Stopwatch timer = new Stopwatch();
            timer.Start();

            while (activeClusterIds.Count > 1)
            {

                //Find the pair with the smallest distance
                KeyValuePair<Tuple<int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple<int, int> pair = result.Key;
                double bestDist = -1*result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                    break;

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id
                PixelCluster merged = new PixelCluster();
                merged.id = counter++;
                merged.lab = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count = a.count + b.count;
                merged.children = new int[] { a.id, b.id };
                merged.neighbors = MergeNeighbors(a.id, b.id);
                merged.parentId = merged.id;
                a.parentId = merged.id;
                b.parentId = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in merged.neighbors)
                {
                    //Debug.Assert(i != merged.id && activeClusterIds.Contains(i));

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    PixelCluster c = clusters[i];
                    double newDist = merged.lab.SqDist(c.lab);

                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                        pq.Enqueue(new Tuple<int, int>(c.id, merged.id), -1 * newDist);
                    else
                        pq.Enqueue(new Tuple<int, int>(merged.id, c.id), -1 * newDist);

                }

                //Remove the old clusters
                foreach (int i in a.neighbors)
                {
                    if (i > a.id)
                        pq.Remove(new Tuple<int, int>(a.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, a.id));
                }
                foreach (int i in b.neighbors)
                {
                    if (i > b.id)
                        pq.Remove(new Tuple<int, int>(b.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, b.id));
                }

                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds/1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count );
                    log.Flush();
                    timer.Restart();
                }

            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds/1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }
        //Cluster the final clusters into color space
        public void ClusterColorSpace()
        {
            double maxDist = 20*20;
            int minRegions = 5;

            SortedSet<int> activeClusterIds = new SortedSet<int>(rootIds);
            String logFile = "colorlog.txt";
            StreamWriter log = File.AppendText(logFile);
            log.WriteLine("\n\tCluster ColorSpace Run " + DateTime.Now.ToString());
            log.Flush();

            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue<Tuple<int, int>, double> pq = new PriorityQueue<Tuple<int, int>, double>();

            int counter = activeClusterIds.Last()+1;

            int[] ids = activeClusterIds.ToArray<int>();

            //Calculate the initial distances
            for (int i = 0; i < ids.Count(); i++)
            {
                for (int j = i+1; j < ids.Count(); j++)
                {
                    //log.WriteLine(ids[i] + ", " + ids[j] + " dist " + -1 * clusters[ids[i]].lab.SqDist(clusters[ids[j]].lab));
                    //log.Flush();

                    //pq.Enqueue(new Tuple<int, int>(ids[i], ids[j]), -1 * clusters[ids[i]].lab.SqDist(clusters[ids[j]].lab));
                    PixelCluster a = clusters[ids[i]];
                    PixelCluster b = clusters[ids[j]];

                    double newDist = a.lab.SqDist(b.lab);

                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    pq.Enqueue(new Tuple<int, int>(ids[i], ids[j]), -1 * newDist);
                }
            }

            Stopwatch timer = new Stopwatch();
            timer.Start();

            while (activeClusterIds.Count > minRegions)
            {

                //Find the pair with the smallest distance
                KeyValuePair<Tuple<int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple<int, int> pair = result.Key;
                double bestDist = -1 * result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                    break;

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id, we don't care about the neighbors
                PixelCluster merged = new PixelCluster();
                merged.id = counter++;
                merged.lab = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count = a.count + b.count;
                merged.children = new int[] { a.id, b.id };
                merged.parentId = merged.id;
                a.parentId = merged.id;
                b.parentId = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in activeClusterIds)
                {
                    if (i == merged.id)
                        continue;

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    PixelCluster c = clusters[i];
                    double newDist = merged.lab.SqDist(c.lab);

                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2*a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                        pq.Enqueue(new Tuple<int, int>(c.id, merged.id), -1 * newDist);
                    else
                        pq.Enqueue(new Tuple<int, int>(merged.id, c.id), -1 * newDist);

                }

                //Remove the old clusters
                foreach (int i in activeClusterIds)
                {
                    if (i > a.id)
                        pq.Remove(new Tuple<int, int>(a.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, a.id));
                }
                foreach (int i in activeClusterIds)
                {
                    if (i > b.id)
                        pq.Remove(new Tuple<int, int>(b.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, b.id));
                }

                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count);
                    log.Flush();
                    timer.Restart();
                }

            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }
        public void ClusterColors(List<CIELAB> colors, int width, int height)
        {
            //Bin the colors
            int minRegions = 5;
            double maxDist = 10*10;
            SortedSet<int> activeClusterIds = new SortedSet<int>();
            String logFile = "log-colorspace.txt";
            StreamWriter log = File.AppendText(logFile);
            log.WriteLine("\n\tCluster Color Space " + DateTime.Now.ToString());
            log.Flush();

            //the smaller id comes first in the dictionary for pairwise distances
            PriorityQueue<Tuple<int, int>, double> pq = new PriorityQueue<Tuple<int, int>, double>();

            clusters = new Dictionary<int, PixelCluster>();
            int counter = 0;

            foreach (CIELAB color in colors)
            {
                //bin it into one of the clusters
                //index is a first, then b, then L
                int id = GetBinId(color);

                if (id > counter)
                    counter = id;

                if (!clusters.ContainsKey(id))
                {
                    clusters.Add(id, new PixelCluster(id, color));
                }
                else
                {
                    clusters[id].lab = (clusters[id].lab * clusters[id].count + color) / (clusters[id].count + 1);
                    clusters[id].count++;
                }
            }
            counter++;

            activeClusterIds = new SortedSet<int>(clusters.Keys);

            List<int> ids = activeClusterIds.ToList<int>();
            for (int i=0; i<ids.Count(); i++)
            {
                PixelCluster a = clusters[ids[i]];

                //calculate distances to neighbors larger than current id
                for (int j=i+1; j<ids.Count(); j++)
                {
                    PixelCluster b = clusters[ids[j]];

                    double newDist = a.lab.SqDist(b.lab);
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));
                    pq.Enqueue(new Tuple<int, int>(a.id, b.id), -1*newDist);
                }

            }

            Stopwatch timer = new Stopwatch();
            timer.Start();

            while (activeClusterIds.Count > minRegions)
            {
                //Find the pair with the smallest distance
                KeyValuePair<Tuple<int, int>, double> result = BestPair(pq, activeClusterIds);
                Tuple<int, int> pair = result.Key;
                double bestDist = -1 * result.Value;

                Console.WriteLine("num clusters: " + activeClusterIds.Count());

                if (bestDist > maxDist)
                    break;

                PixelCluster a = clusters[pair.Item1];
                PixelCluster b = clusters[pair.Item2];

                //Create a new cluster with unique id, don't care about neighbors
                PixelCluster merged = new PixelCluster();
                merged.id = counter++;
                merged.lab = (a.lab * a.count + b.lab * b.count) / (a.count + b.count);
                merged.count = a.count + b.count;
                merged.children = new int[] { a.id, b.id };
                merged.parentId = merged.id;
                a.parentId = merged.id;
                b.parentId = merged.id;
                clusters.Add(merged.id, merged);

                //Update the active cluster set
                activeClusterIds.Remove(a.id);
                activeClusterIds.Remove(b.id);
                activeClusterIds.Add(merged.id);

                double totalCount = a.count + b.count;

                //Update the distances, based on old distances
                foreach (int i in activeClusterIds)
                {
                    //Debug.Assert(i != merged.id && activeClusterIds.Contains(i));

                    //TODO: Ward's method with minimum variance
                    //For now, just use the dist between the centroids
                    if (i == merged.id)
                        continue;

                    PixelCluster c = clusters[i];
                    double newDist = merged.lab.SqDist(c.lab);

                    //Add in Ward's variance  (variation in Color Segmentation using Region Merging)
                    //http://www.mathworks.com/help/toolbox/stats/linkage.html
                    //newDist = newDist * Math.Sqrt(2 * a.count * b.count / (a.count + b.count));

                    if (c.id < merged.id)
                        pq.Enqueue(new Tuple<int, int>(c.id, merged.id), -1 * newDist);
                    else
                        pq.Enqueue(new Tuple<int, int>(merged.id, c.id), -1 * newDist);

                }

                //Remove the old clusters
                foreach (int i in a.neighbors)
                {
                    if (i > a.id)
                        pq.Remove(new Tuple<int, int>(a.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, a.id));
                }
                foreach (int i in b.neighbors)
                {
                    if (i > b.id)
                        pq.Remove(new Tuple<int, int>(b.id, i));
                    else
                        pq.Remove(new Tuple<int, int>(i, b.id));
                }

                //Repeat until termination criteria

                if (activeClusterIds.Count() % 1000 == 0)
                {
                    //write to file
                    log.WriteLine("Merge loop: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count + " merged # neighbors: " + merged.neighbors.Count);
                    log.Flush();
                    timer.Restart();
                }

            }
            rootIds = activeClusterIds;
            timer.Stop();
            log.WriteLine("End: " + timer.ElapsedMilliseconds / 1000.0 + " # Clusters: " + activeClusterIds.Count() + " pqCount: " + pq.Count);
            log.WriteLine("End Time: " + DateTime.Now.ToString());
            log.Flush();
            log.Close();
        }