static bool[,] Binarize(double[,] input, double[,] baseline, bool[,] mask, BlockMap blocks)
        {
            var size      = Point.SizeOf(input);
            var binarized = size.Allocate <bool>();

            for (int blockY = 0; blockY < blocks.AllBlocks.Height; ++blockY)
            {
                for (int blockX = 0; blockX < blocks.AllBlocks.Width; ++blockX)
                {
                    if (mask[blockY, blockX])
                    {
                        Rectangle rect = blocks.BlockAreas[blockY, blockX];
                        for (int y = rect.Bottom; y < rect.Top; ++y)
                        {
                            for (int x = rect.Left; x < rect.Right; ++x)
                            {
                                if (input[y, x] - baseline[y, x] > 0)
                                {
                                    binarized[y, x] = true;
                                }
                            }
                        }
                    }
                }
            }
            return(binarized);
        }
 static double[,] SmoothByOrientation(double[,] input, byte[,] orientation, bool[,] mask, BlockMap blocks, byte angle, Point[][] lines)
 {
     double[,] output = new double[input.GetLength(0), input.GetLength(1)];
     foreach (var block in blocks.AllBlocks)
     {
         if (block.Get(mask))
         {
             Point[] line = lines[Angle.Quantize(Angle.Add(orientation[block.Y, block.X], angle), lines.Length)];
             foreach (Point linePoint in line)
             {
                 Rectangle target = blocks.BlockAreas[block];
                 Rectangle source = target.GetShifted(linePoint);
                 source.Clip(new Rectangle(blocks.PixelCount));
                 target = source.GetShifted(-linePoint);
                 for (int y = target.Bottom; y < target.Top; ++y)
                 {
                     for (int x = target.Left; x < target.Right; ++x)
                     {
                         output[y, x] += input[y + linePoint.Y, x + linePoint.X];
                     }
                 }
             }
             Rectangle blockArea = blocks.BlockAreas[block];
             for (int y = blockArea.Bottom; y < blockArea.Top; ++y)
             {
                 for (int x = blockArea.Left; x < blockArea.Right; ++x)
                 {
                     output[y, x] *= 1.0 / line.Length;
                 }
             }
         }
     }
     return(output);
 }
        static PointF[,] SmoothOutOrientationMap(PointF[,] orientation, bool[,] mask)
        {
            const int radius = 1;
            var       size   = Point.SizeOf(mask);

            PointF[,] smoothed = size.Allocate <PointF>();
            for (int y = 0; y < size.Y; ++y)
            {
                for (int x = 0; x < size.X; ++x)
                {
                    if (mask[y, x])
                    {
                        Rectangle neighbors = Rectangle.Between(
                            new Point(Math.Max(0, x - radius), Math.Max(0, y - radius)),
                            new Point(Math.Min(size.X, x + radius + 1), Math.Min(size.Y, y + radius + 1)));
                        PointF sum = new PointF();
                        for (int ny = neighbors.Bottom; ny < neighbors.Top; ++ny)
                        {
                            for (int nx = neighbors.Left; nx < neighbors.Right; ++nx)
                            {
                                if (mask[ny, nx])
                                {
                                    sum += orientation[ny, nx];
                                }
                            }
                        }
                        smoothed[y, x] = sum;
                    }
                }
            }
            return(smoothed);
        }
 static PointF[,] AverageBlockOrientations(PointF[,] orientation, BlockMap blocks, bool[,] mask)
 {
     PointF[,] sums = new PointF[blocks.BlockCount.Y, blocks.BlockCount.X];
     foreach (var block in blocks.AllBlocks)
     {
         if (block.Get(mask))
         {
             PointF    sum  = new PointF();
             Rectangle area = blocks.BlockAreas[block];
             for (int y = area.Bottom; y < area.Top; ++y)
             {
                 for (int x = area.Left; x < area.Right; ++x)
                 {
                     sum += orientation[y, x];
                 }
             }
             sums[block.Y, block.X] = sum;
         }
     }
     return(sums);
 }
        static bool[,] ApplyVotingFilter(bool[,] input, int radius = 1, double majority = 0.51, int borderDist = 0)
        {
            var       size = Point.SizeOf(input);
            Rectangle rect = new Rectangle(new Point(borderDist, borderDist),
                                           new Point(size.X - 2 * borderDist, size.Y - 2 * borderDist));
            var output = size.Allocate <bool>();

            for (int y = rect.RangeY.Begin; y < rect.RangeY.End; ++y)
            {
                for (int x = rect.Left; x < rect.Right; ++x)
                {
                    Rectangle neighborhood = Rectangle.Between(
                        new Point(Math.Max(x - radius, 0), Math.Max(y - radius, 0)),
                        new Point(Math.Min(x + radius + 1, size.X), Math.Min(y + radius + 1, size.Y)));

                    int ones = 0;
                    for (int ny = neighborhood.Bottom; ny < neighborhood.Top; ++ny)
                    {
                        for (int nx = neighborhood.Left; nx < neighborhood.Right; ++nx)
                        {
                            if (input[ny, nx])
                            {
                                ++ones;
                            }
                        }
                    }

                    double voteWeight = 1.0 / neighborhood.TotalArea;
                    if (ones * voteWeight >= majority)
                    {
                        output[y, x] = true;
                    }
                }
            }
            return(output);
        }