/// <summary>
        /// Convert image of double type to byte type using a clamp
        /// </summary>
        private static byte[,] Double2DtoByte2D(double[,] image)
        {
            var result = new byte[image.GetLength(0), image.GetLength(1)];

            for (var x = 0; x < image.GetLength(0); x++)
            {
                for (var y = 0; y < image.GetLength(1); y++)
                {
                    result[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(image[x, y]));
                }
            }
            return(result);
        }
        /// <summary>
        /// Perform Canny edge detection
        /// </summary>
        public static byte[,] CannyEdgeDetector(byte[,] input, int blur = 1, byte high = 90, byte low = 90)
        {
            var copy = BilateralFilter.BilateralFilter2D(input); // Preprocessing before detecting edges

            // ======================= Adapted Sobel edge detection from earlier implementation =======================
            var image1    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3V, 1);
            var image2    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3H, 1);
            var midImage1 = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage1.GetLength(0); x++)
            {
                for (var y = 0; y < midImage1.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image1[x, y] * image1[x, y] + image2[x, y] * image2[x, y]);
                    midImage1[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var image3    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3Minv, 1);
            var image4    = Convolution.Convolve(copy, EdgeDetection.SobelKernel3X3Minh, 1);
            var midImage2 = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = Math.Sqrt(image3[x, y] * image3[x, y] + image4[x, y] * image4[x, y]);
                    midImage2[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var imageG = new double[image1.GetLength(0), image1.GetLength(1)];

            for (var x = 0; x < midImage2.GetLength(0); x++)
            {
                for (var y = 0; y < midImage2.GetLength(1); y++)
                {
                    var final = midImage1[x, y] + midImage2[x, y];/*Math.Sqrt(midImage1[x, y] * midImage1[x, y] + midImage2[x, y] * midImage2[x, y]);*/
                    imageG[x, y] = (byte)EdgeDetection.ImageClamp2(Math.Round(final));
                }
            }

            var imageT = new int[midImage1.GetLength(0), midImage1.GetLength(1)];

            for (var x = 0; x < imageT.GetLength(0); x++)
            {
                for (var y = 0; y < imageT.GetLength(1); y++)
                {
                    imageT[x, y] = (int)Math.Round(Math.Atan2(midImage2[x, y], midImage2[x, y]) * (5.0 / Math.PI) + 5) % 5;
                }
            }

            // ========================================================================================================


            var suppressed = Suppress(imageG, imageT);                         //Perform Non-maximum suppression

            var highEdges = HelperFunctions.ThresholdDouble(suppressed, high); // Keep the strong edges in one map
            var lowEdges  = HelperFunctions.ThresholdDouble(suppressed, low);  // Keep weaker edges in another map

            var highEdgesByte = Double2DtoByte2D(highEdges);
            var lowEdgesByte  = Double2DtoByte2D(lowEdges);

            var CombinedThresholds = BinaryOperators.ORoperator(highEdgesByte, lowEdgesByte);

            // Hysteresis to trace edges
            return(Hysteresis(highEdgesByte, CombinedThresholds));
        }
        private void applyButton_Click(object sender, EventArgs e)
        {
            if (this._inputImage == null)
            {
                return;
            }
            this._outputImage?.Dispose();
            this._outputImage = new Bitmap(this._inputImage.Size.Width, this._inputImage.Size.Height);
            var image  = new Color[this._inputImage.Size.Width, this._inputImage.Size.Height];
            var image2 = new Color[this._inputImage2.Size.Width, this._inputImage2.Size.Height];

            // Setup progress bar
            this.progressBar.Visible = true;
            this.progressBar.Minimum = 1;
            this.progressBar.Maximum = this._inputImage.Size.Width * this._inputImage.Size.Height;
            this.progressBar.Value   = 1;
            this.progressBar.Step    = 1;

            // Copy input Bitmap to array
            for (var x = 0; x < this._inputImage.Size.Width; x++)
            {
                for (var y = 0; y < this._inputImage.Size.Height; y++)
                {
                    image[x, y] = this._inputImage.GetPixel(x, y);                           // Set pixel color in array at (x,y)
                }
            }
            // Copy input Bitmap to array for the control image
            for (var x = 0; x < this._inputImage2.Size.Width; x++)
            {
                for (var y = 0; y < this._inputImage2.Size.Height; y++)
                {
                    image2[x, y] = this._inputImage2.GetPixel(x, y);                         // Set pixel color in array at (x,y)
                }
            }
            //==================================================================================
            //================================= PIPELINE =======================================
            //==================================================================================
            //RESET VARS FROM PREVIOUS IMAGE:
            FloodFill.ObjectCount = 0;
            FloodFill.Objects     = new List <PictureObject>();

            //===================== Preprocessing: Filtering, Segmentation =====================
            var greyscale = HelperFunctions.Greyscalize(image);
            HelperFunctions.GlobalMask = HelperFunctions.Greyscalize(image2);

            //===================== Edge Detection =============================================
            var detectSides = CannyEdge.CannyEdgeDetector(greyscale, 1, GlobalThreshold.OtsuThreshold(greyscale));

            //===================== Find sides of road poles using Hough transform =============
            var lines      = HoughTransform.FoundLines(detectSides, 0, 0);
            var cartLines  = HoughExtensions.GetAllCartesianLines(lines, detectSides);
            var pairs      = HoughExtensions.GetPairs(cartLines, detectSides.GetLength(1));
            var roughPairs = HoughExtensions.GetRoughPairs(cartLines, detectSides.GetLength(1));

            //===================== Find lower and upper boundaries of poles ===================
            var workingImage = BilateralFilter.BilateralFilter2D(greyscale);
            workingImage = EdgeDetection.DetectEdges(HelperFunctions.Threshold(workingImage, GlobalThreshold.OtsuThreshold(workingImage)));
            detectSides  = HoughExtensions.CleanOutsidePairs(workingImage, roughPairs);
            detectSides  = FloodFill.MarkObjects(detectSides);
            detectSides  = FloodFill.filterObjects(detectSides);

            //==================================================================================
            //==================================================================================
            //==================================================================================

            var nDV = ValueCounter(detectSides);
            this.label2.Text        = "Number of distinct values: " + nDV;
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("[Done]              Final image with size " + detectSides.GetLength(0) + "x" + detectSides.GetLength(1) + " and " + nDV + " distinct Values");
            Console.ResetColor();

            var newImage = Colorize(detectSides);
            DrawBoundingBox(newImage);

            var outputResized = new Bitmap(this._outputImage, detectSides.GetLength(0), detectSides.GetLength(1));

            // Copy array to output Bitmap
            for (var x = 0; x < newImage.GetLength(0); x++)
            {
                for (var y = 0; y < newImage.GetLength(1); y++)
                {
                    outputResized.SetPixel(x, y, newImage[x, y]);                      // Set the pixel color at coordinate (x,y)
                }
            }
            var g = Graphics.FromImage(outputResized);

            DrawPairs(pairs, outputResized); // Draw pairs generated by hough transform
            FloodFill.DrawFinal(this._inputImage, pairs);

            pictureBox2.Image   = this._inputImage; // Display output image - replace InputImage with OutputResized to see hough pairs and object map with bounding boxes
            progressBar.Visible = true;             // Hide progress bar
        }