/// <summary>
        /// Superimpose a monochrome image, and return the resulting image.
        /// </summary>
        /// <param name="img">A monochrome image.</param>
        /// <param name="reference">A reference to img1 for downsampling and rotation.</param>
        /// <returns></returns>
        public static Image SaveSuperimposedImage(Image img, Image reference)
        {
            //If image isn't monochrome format, throw exception
            if (img.PixelFormat != PixelFormat.Format1bppIndexed)
            {
                throw new Exception(imageFormatNotMonochrome);
            }

            //Construct two ImageAsPointCollection instances.
            ImageAsPointCollection pointCollection1 = new ImageAsPointCollection(img);
            ImageAsPointCollection pointCollection2 = new ImageAsPointCollection(reference);

            //Downsample
            pointCollection1 = DownSampling(pointCollection1, Math.Min(pointCollection1.listOfPoints.Count, pointCollection2.listOfPoints.Count));
            pointCollection2 = DownSampling(pointCollection2, Math.Min(pointCollection1.listOfPoints.Count, pointCollection2.listOfPoints.Count));

            //Translate
            pointCollection1 = Translate(pointCollection1);
            pointCollection2 = Translate(pointCollection2);

            //Uniform scale
            pointCollection1 = UniformScale(pointCollection1);
            pointCollection2 = UniformScale(pointCollection2);

            //Rotate
            pointCollection1 = RotateToReference(pointCollection2, pointCollection1);

            Image superimposedImage1 = pointCollection1.ToMonoImage();

            return superimposedImage1;
        }
        private static string imageNotSameDimension = "Images have different sizes; computation aborted."; //Exception message.

        #endregion Fields

        #region Methods

        /// <summary>
        /// Given two images, compute their square differences.
        /// No superimposition function is applied in this step.
        /// To compare superimposed images, they must be superimposed before input into this function.
        /// </summary>
        /// <param name="img1">A monochrome image. Must have same dimensions with img2.</param>
        /// <param name="img2">A monochrome image. Must have same dimensions with img1.</param>
        /// <returns></returns>
        public static double ComputeHausdroffDistance(Image img1, Image img2)
        {
            //If any image isn't monochrome format, throw exception
            if (img1.PixelFormat != PixelFormat.Format1bppIndexed || img2.PixelFormat != PixelFormat.Format1bppIndexed)
            {
                throw new Exception(imageFormatNotMonochrome);
            }

            //If images aren't of same dimension, throw exception
            if (img1.Width != img2.Width || img1.Height != img2.Height)
            {
                throw new Exception(imageNotSameDimension);
            }

            //Construct two ImageAsPointCollection instances.
            ImageAsPointCollection pointCollection1 = new ImageAsPointCollection(img1);
            ImageAsPointCollection pointCollection2 = new ImageAsPointCollection(img2);

            //Calculate difference of two superimposed images.
            double difference = 0;
            for(int i = 0; i < pointCollection1.listOfPoints.Count; i++)
            {
                double min_difference = double.MaxValue;
                for(int j = 0; j < pointCollection2.listOfPoints.Count; j++)
                {
                    double temp_difference = Math.Pow((pointCollection1.listOfPoints[i].x - pointCollection2.listOfPoints[j].x), 2)
                         + Math.Pow((pointCollection1.listOfPoints[i].y - pointCollection2.listOfPoints[j].y), 2);
                    if (temp_difference < min_difference)
                        min_difference = temp_difference;
                }
                difference += min_difference;
            }
            difference = Math.Sqrt(difference/(pointCollection1.listOfPoints.Count * pointCollection2.listOfPoints.Count));

            return difference;
        }
        /// <summary>
        /// Calculates the root mean square distance (RMSD) of a set of points,
        /// then scale them.
        /// </summary>
        /// <param name="original"></param>
        /// <returns></returns>
        private static ImageAsPointCollection UniformScale(ImageAsPointCollection original)
        {
            ImageAsPointCollection result = original;

            //Get list's length
            int k = original.listOfPoints.Count;

            //Calculate sum of x's and y's
            double x_sum = 0;
            double y_sum = 0;
            for (int i = 0; i < k; i++)
            {
                x_sum += original.listOfPoints[i].x;
                y_sum += original.listOfPoints[i].y;
            }

            //Calculate averages of x's and y's
            double x_bar = x_sum / k;
            double y_bar = y_sum / k;

            //Calculate RMSD
            double rmsd = 0;
            for (int i = 0; i < k; i++)
            {
                rmsd += (original.listOfPoints[i].x - x_bar) * (original.listOfPoints[i].x - x_bar);
                rmsd += (original.listOfPoints[i].y - y_bar) * (original.listOfPoints[i].y - y_bar);
            }
            rmsd = Math.Sqrt(rmsd / k);

            //Apply scaling
            for (int i = 0; i < k; i++)
            {
                result.listOfPoints[i].x = ((original.listOfPoints[i].x - x_bar)/rmsd);
                result.listOfPoints[i].y = ((original.listOfPoints[i].y - y_bar)/rmsd);
            }

            return result;
        }
        /// <summary>
        /// Takes an ImageAsPointCollection instance, return its translated version.
        /// </summary>
        /// <param name="original"></param>
        /// <returns></returns>
        private static ImageAsPointCollection Translate(ImageAsPointCollection original)
        {
            ImageAsPointCollection result = original;
            //Get list's length
            int k = original.listOfPoints.Count;

            double x_sum = 0;
            double y_sum = 0;

            for (int i = 0; i < k; i++)
            {
                x_sum += original.listOfPoints[i].x;
                y_sum += original.listOfPoints[i].y;
            }

            double x_bar = x_sum / k;
            double y_bar = y_sum / k;

            for(int i = 0; i < k; i++)
            {
                result.listOfPoints[i].x = (original.listOfPoints[i].x - x_bar);
                result.listOfPoints[i].y = (original.listOfPoints[i].y - y_bar);
            }

            return result;
        }
        /// <summary>
        /// Rotates original image relative to reference image.
        /// </summary>
        /// <param name="original"></param>
        /// <param name="reference"></param>
        /// <returns></returns>
        private static ImageAsPointCollection RotateToReference(ImageAsPointCollection reference, ImageAsPointCollection original)
        {
            ImageAsPointCollection result = original;

            //Get list's length
            int k = Math.Min(reference.listOfPoints.Count, original.listOfPoints.Count);

            //Calculate theta
            double numerator = 0;
            double denominator = 0;
            for(int i = 0; i < k; i++)
            {
                numerator += original.listOfPoints[i].x * reference.listOfPoints[i].y - original.listOfPoints[i].y * reference.listOfPoints[i].x;
                denominator += original.listOfPoints[i].x * reference.listOfPoints[i].x + original.listOfPoints[i].y * reference.listOfPoints[i].y;
            }
            double theta = Math.Atan(numerator/denominator);

            //Do rotation
            for(int i = 0; i < result.listOfPoints.Count; i++)
            {
                result.listOfPoints[i].x = (Math.Cos(theta) * original.listOfPoints[i].x - Math.Sin(theta) * original.listOfPoints[i].y);
                result.listOfPoints[i].y = (Math.Sin(theta) * original.listOfPoints[i].x + Math.Cos(theta) * original.listOfPoints[i].y);
            }

            return result;
        }
        //--------------------------------------------Helper methods--------------------------------------------
        /// <summary>
        /// Downsample a set of data to a desired size.
        /// </summary>
        /// <param name="original"></param>
        /// <param name="desiredNumberOfPoints"></param>
        /// <returns></returns>
        private static ImageAsPointCollection DownSampling(ImageAsPointCollection original, int desiredNumberOfPoints)
        {
            ImageAsPointCollection result = original;
            //Get list's length
            int k = original.listOfPoints.Count;

            if(k <= desiredNumberOfPoints)
            {
                return original;
            }

            while(result.listOfPoints.Count > desiredNumberOfPoints)
            {
                for(int i = 0; i < result.listOfPoints.Count; i += (int)((double)result.listOfPoints.Count/desiredNumberOfPoints))
                {
                    result.listOfPoints.RemoveAt(i);
                    if(result.listOfPoints.Count <= desiredNumberOfPoints)
                    {
                        return result;
                    }
                }
            }

            return result;
        }
        /// <summary>
        /// Returns the translated version of a given image.
        /// </summary>
        /// <param name="img">A monochrome image.</param>
        /// <returns></returns>
        public static Image SaveTranslatedImage(Image img)
        {
            //If image isn't monochrome format, throw exception
            if (img.PixelFormat != PixelFormat.Format1bppIndexed)
            {
                throw new Exception(imageFormatNotMonochrome);
            }

            //Construct ImageAsPointCollection instance.
            ImageAsPointCollection pointCollection1 = new ImageAsPointCollection(img);

            //Translate
            pointCollection1 = Translate(pointCollection1);

            Image superimposedImage1 = pointCollection1.ToMonoImage();

            return superimposedImage1;
        }