//Union-Find solution
        public int FindCircleNum(int[][] M)
        {
            int n  = M.Length;
            var uf = new UnionFind(n);

            for (int i = 0; i < M.Length; i++)
            {
                for (int j = 0; j < M[0].Length; j++)
                {
                    if (M[i][j] == 1 && i != j) // i != j (can't be friend to oneself)
                    {
                        uf.Union(i, j);
                    }
                }
            }
            return(uf.ConnectedComponents());
        }
        private void RemoveBackground(Color[,] image, CIELAB[,] imageLAB)
        {
            //check perimeter to see if it's mostly black or white

            //RGB to LAB
            CIELAB black = new CIELAB(0, 0, 0);
            CIELAB white = new CIELAB(100, 0, 0);

            int width = image.GetLength(0);
            int height = image.GetLength(1);

            CIELAB[,] labs = imageLAB;//Util.Map<Color, CIELAB>(image, (c) => Util.RGBtoLAB(c));

            int numBlack = 0;
            int numWhite = 0;
            int thresh = 3 * 3;
            List<Point> perimeterIdx = new List<Point>();
            double totalPerimeter = 4 * width + 4 * height;
            double bgThresh = totalPerimeter*0.75;

            for (int i = 0; i < width; i++)
            {
                //top
                for (int j = 0; j < 2; j++)
                {
                    if (black.SqDist(labs[i, j])<thresh)
                        numBlack++;
                    if (white.SqDist(labs[i, j]) < thresh)
                        numWhite++;
                    perimeterIdx.Add(new Point(i, j));
                }

                //bottom
                for (int j = height - 2; j < height; j++)
                {
                    perimeterIdx.Add(new Point(i, j));
                    if (black.SqDist(labs[i, j]) < thresh)
                        numBlack++;
                    if (white.SqDist(labs[i, j]) < thresh)
                        numWhite++;
                }
            }

            for (int j = 0; j < height; j++)
            {
                //left
                for (int i=0; i<2; i++)
                {
                    perimeterIdx.Add(new Point(i,j));
                    if (black.SqDist(labs[i, j]) < thresh)
                        numBlack++;
                    if (white.SqDist(labs[i, j]) < thresh)
                        numWhite++;
                }

                //right
                for (int i=width-2; i<width; i++)
                {
                    perimeterIdx.Add(new Point(i, j));
                    if (black.SqDist(labs[i, j]) < thresh)
                        numBlack++;
                    if (white.SqDist(labs[i, j]) < thresh)
                        numWhite++;
                }
            }

            if (numBlack >= bgThresh || numWhite >= bgThresh)
            {
                //connected components
                UnionFind<CIELAB> uf = new UnionFind<CIELAB>((a,b) => a.SqDist(b)<thresh);
                int[,] cc = uf.ConnectedComponents(labs);

                SortedSet<int> ids = new SortedSet<int>();

                //go around the perimeter to collect the right ids
                foreach (Point p in perimeterIdx)
                {
                    if (numWhite > numBlack)
                    {
                        if (labs[p.X, p.Y].SqDist(white) < thresh)
                            ids.Add(cc[p.X, p.Y]);
                    }
                    else
                    {
                        if (labs[p.X, p.Y].SqDist(black) < thresh)
                            ids.Add(cc[p.X, p.Y]);
                    }
                }

                //fill the bitmap with transparency
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        if (ids.Contains(cc[i, j]))
                            image[i, j] = Color.FromArgb(0, 0, 0, 0);
                    }
                }
            }
        }
        private bool ProcessImage(Color[,] image, CIELAB[,] imageLAB)
        {
            double thresh = 5;
            UnionFind<CIELAB> uf = new UnionFind<CIELAB>((a,b)=>(a.SqDist(b)<=thresh));
            int[,] assignments = uf.ConnectedComponents(imageLAB);//Util.Map<Color, CIELAB>(image, Util.RGBtoLAB));
            int numC = -1;
            for(int i=0; i<image.GetLength(0); i++)
                for (int j=0; j<image.GetLength(1); j++)
                    numC = Math.Max(numC, assignments[i,j]+1);
            if (numC >= 2)
                RemoveBackground(image, imageLAB);

            //if it is a black and white image (with num connected components >= 2), it's not a valid color image
            return !(isBlackWhite(image, imageLAB) && numC >= 2);
        }