private static double ComputeSSIM(Grid img1, Grid img2)
        {
            // uses notation from paper
            // automatic downsampling
            int f = (int)Math.Max(1, Math.Round(Math.Min(img1.Width, img1.Height) / 256.0));
            if (f > 1)
            {
                // downsampling by f
                // use a simple low-pass filter and subsample by f
                img1 = Grid.SubSample(img1, f);
                img2 = Grid.SubSample(img2, f);
            }

            // normalize window - todo - do in window set {}
            double scale = 1.0 / window.Total;
            Grid.Op((i, j) => window[i, j] * scale, window);

            // image statistics
            var mu1 = Grid.Filter(img1, window);
            var mu2 = Grid.Filter(img2, window);

            var mu1mu2 = mu1 * mu2;
            var mu1SQ = mu1 * mu1;
            var mu2SQ = mu2 * mu2;

            var sigma12 = Grid.Filter(img1 * img2, window) - mu1mu2;
            var sigma1SQ = Grid.Filter(img1 * img1, window) - mu1SQ;
            var sigma2SQ = Grid.Filter(img2 * img2, window) - mu2SQ;

            // constants from the paper
            double C1 = K1 * L; C1 *= C1;
            double C2 = K2 * L; C2 *= C2;

            Grid ssimMap = null;
            if ((C1 > 0) && (C2 > 0))
            {
                ssimMap = Grid.Op((i, j) =>
                    (2 * mu1mu2[i, j] + C1) * (2 * sigma12[i, j] + C2) /
                    (mu1SQ[i, j] + mu2SQ[i, j] + C1) / (sigma1SQ[i, j] + sigma2SQ[i, j] + C2),
                    new Grid(mu1mu2.Width, mu1mu2.Height));
            }
            else
            {
                var num1 = Grid.Linear(2, mu1mu2, C1);
                var num2 = Grid.Linear(2, sigma12, C2);
                var den1 = Grid.Linear(1, mu1SQ + mu2SQ, C1);
                var den2 = Grid.Linear(1, sigma1SQ + sigma2SQ, C2);

                var den = den1 * den2; // total denominator
                ssimMap = new Grid(mu1.Width, mu1.Height);
                for (int i = 0; i < ssimMap.Width; ++i)
                    for (int j = 0; j < ssimMap.Height; ++j)
                    {
                        ssimMap[i, j] = 1;
                        if (den[i, j] > 0)
                        {
                            ssimMap[i, j] = num1[i, j] * num2[i, j] / (den1[i, j] * den2[i, j]);
                        }
                        else if ((den1[i, j] != 0) && (den2[i, j] == 0))
                        {
                            ssimMap[i, j] = num1[i, j] / den1[i, j];
                        }
                    }
            }

            // average all values
            return ssimMap.Total / (ssimMap.Width * ssimMap.Height);
        }
 public static Grid SubSample(Grid img, int skip)
 {
     int width = img.Width;
     int height = img.Height;
     double scale = 1.0 / (skip * skip);
     var ans = new Grid(width / skip, height / skip);
     for (int i = 0; i < width - skip; i += skip)
     {
         for (int j = 0; j < height - skip; j += skip)
         {
             double sum = 0;
             for (int x = i; x < i + skip; ++x)
             {
                 for (int y = j; y < j + skip; ++y)
                 {
                     sum += img[x, y];
                 }
             }
             ans[i / skip, j / skip] = sum * scale;
         }
     }
     return ans;
 }
 public static Grid Op(Func<int, int, double> f, Grid g)
 {
     int width = g._width;
     int height = g._height;
     for (int i = 0; i < width; i++)
     {
         for (int j = 0; j < height; j++)
         {
             g[i, j] = f(i, j);
         }
     }
     return g;
 }
 public static Grid Linear(double s, Grid a, double c)
 {
     return Grid.Op((i, j) => s * a[i, j] + c, new Grid(a.Width, a.Height));
 }
 public static Grid Filter(Grid firstGrid, Grid secondGrid)
 {
     int ax = firstGrid.Width;
     int ay = firstGrid.Height;
     int bx = secondGrid.Width;
     int by = secondGrid.Height;
     int bcx = (bx + 1) / 2, bcy = (by + 1) / 2;
     var thirdGrid = new Grid(ax - bx + 1, ay - by + 1);
     for (int i = bx - bcx + 1; i < ax - bx; ++i)
     {
         for (int j = by - bcy + 1; j < ay - by; ++j)
         {
             double sum = 0;
             for (int x = bcx - bx + 1 + i; x < 1 + i + bcx; ++x)
             {
                 for (int y = bcy - by + 1 + j; y < 1 + j + bcy; ++y)
                 {
                     sum += firstGrid[x, y] * secondGrid[bx - bcx - 1 - i + x, by - bcy - 1 - j + y];
                 }
             }
             thirdGrid[i - bcx, j - bcy] = sum;
         }
     }
     return thirdGrid;
 }
        public static Grid CreateGaussianGrid(int size, double sigma)
        {
            var filter = new Grid(size, size);
            double s2 = sigma * sigma, c = (size - 1) / 2.0, dx, dy;

            Grid.Op(
                (i, j) =>
                {
                    dx = i - c;
                    dy = j - c;
                    return Math.Exp(-(dx * dx + dy * dy) / (2 * s2));
                },
                filter
            );

            var scale = 1.0 / filter.Total;
            Grid.Op((i, j) => filter[i, j] * scale, filter);
            return filter;
        }