public static List <Minutia> ExtractMinutiae(double[,] imgBytes)
        {
            var lsEnhanced = SymmetryHelper.EstimateLS(imgBytes, sigma1, sigma2);
            var psEnhanced = SymmetryHelper.EstimatePS(imgBytes, sigma1, sigma2);
            //ImageHelper.SaveComplexArrayAsHSV(lsEnhanced,"C:\\temp\\lsenh.png");

            //ImageHelper.SaveArray(NormalizeArray(psEnhanced.Select2D(x=>x.Magnitude)), "C:\\temp\\psenh.png");

            var psi = KernelHelper.Zip2D(psEnhanced,
                                         lsEnhanced.Select2D(x => x.Magnitude), (x, y) =>
                                         x * (1.0d - y));

            return(SearchMinutiae(psi, lsEnhanced, psEnhanced));
        }
        // (x, y) pairs of continious vector field
        private static Tuple <double, double>[,] GenerateLowPassFilteredContiniousVectorField(int[,] image)
        {
            int maxY = image.GetLength(0);
            int maxX = image.GetLength(1);
            var cvf  = new Tuple <double, double> [maxY, maxX];
            var lsq  = GenerateLeastSquareEstimate(image);

            for (int i = 0; i < maxY; i++)
            {
                for (int j = 0; j < maxX; j++)
                {
                    cvf[i, j] = new Tuple <double, double>(Math.Cos(2 * lsq[i, j]), Math.Sin(2 * lsq[i, j]));
                }
            }
            return(KernelHelper.Zip2D(GenerateBlur(cvf.Select2D(x => x.Item1)), GenerateBlur(cvf.Select2D(x => x.Item2)),
                                      (x, y) => new Tuple <double, double>(x, y)));
        }
        public static Complex[,] EstimateLS(double[,] l1, double Sigma1, double Sigma2)
        {
            var kernelX = KernelHelper.MakeKernel((x, y) => Gaussian.Gaussian2D(x, y, Sigma1) * x, KernelHelper.GetKernelSizeForGaussianSigma(Sigma1));
            var resultX = ConvolutionHelper.Convolve(l1, kernelX);
            var kernelY = KernelHelper.MakeKernel((x, y) => Gaussian.Gaussian2D(x, y, Sigma1) * -y, KernelHelper.GetKernelSizeForGaussianSigma(Sigma1));
            var resultY = ConvolutionHelper.Convolve(l1, kernelY);


            var preZ = KernelHelper.MakeComplexFromDouble(resultX, resultY);

            var z = preZ.Select2D(x => x * x);

            var kernel2 = KernelHelper.MakeComplexKernel((x, y) => Gaussian.Gaussian2D(x, y, Sigma2), (x, y) => 0,
                                                         KernelHelper.GetKernelSizeForGaussianSigma(Sigma2));

            var I20 = ConvolutionHelper.ComplexConvolve(z, kernel2);

            var I11 = ConvolutionHelper.Convolve(z.Select2D(x => x.Magnitude), kernel2.Select2D(x => x.Real));

            Complex[,] LS = KernelHelper.Zip2D(I20, I11, (x, y) => x / y);

            return(LS);
        }