Ejemplo n.º 1
0
        public BinaryMap DetectLowContrast(byte[,] contrast, BlockMap blocks)
        {
            List<byte> sortedContrast = new List<byte>();
            foreach (byte contrastItem in contrast)
                sortedContrast.Add(contrastItem);
            sortedContrast.Sort();
            sortedContrast.Reverse();

            int pixelsPerBlock = Calc.GetArea(blocks.PixelCount) / blocks.AllBlocks.TotalArea;
            int sampleCount = Math.Min(sortedContrast.Count, SampleSize / pixelsPerBlock);
            int consideredBlocks = Math.Max(Convert.ToInt32(sampleCount * SampleFraction), 1);

            int averageContrast = 0;
            for (int i = 0; i < consideredBlocks; ++i)
                averageContrast += sortedContrast[i];
            averageContrast /= consideredBlocks;
            byte limit = Convert.ToByte(averageContrast * RelativeLimit);

            BinaryMap result = new BinaryMap(blocks.BlockCount.Width, blocks.BlockCount.Height);
            for (int y = 0; y < result.Height; ++y)
                for (int x = 0; x < result.Width; ++x)
                    if (contrast[y, x] < limit)
                        result.SetBitOne(x, y);
            Logger.Log(result);
            return result;
        }
Ejemplo n.º 2
0
 public float[,] Smooth(float[,] input, byte[,] orientation, BinaryMap mask, BlockMap blocks)
 {
     Point[][] lines = Lines.Construct();
     float[,] output = new float[input.GetLength(0), input.GetLength(1)];
     Parallel.ForEach(blocks.AllBlocks, delegate(Point block)
     {
         if (mask.GetBit(block))
         {
             Point[] line = lines[Angle.Quantize(Angle.Add(orientation[block.Y, block.X], AngleOffset), lines.Length)];
             foreach (Point linePoint in line)
             {
                 RectangleC target = blocks.BlockAreas[block];
                 RectangleC source = target.GetShifted(linePoint);
                 source.Clip(new RectangleC(blocks.PixelCount));
                 target = source.GetShifted(Calc.Negate(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];
             }
             RectangleC 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] *= 1f / line.Length;
         }
     });
     Logger.Log(output);
     return output;
 }
Ejemplo n.º 3
0
        float[,] PerformEqualization(BlockMap blocks, byte[,] image, float[, ,] equalization, BinaryMap blockMask)
        {
            float[,] result = new float[blocks.PixelCount.Height, blocks.PixelCount.Width];
            Parallel.ForEach(blocks.AllBlocks, delegate(Point block)
            {
                if (blockMask.GetBit(block))
                {
                    RectangleC area = blocks.BlockAreas[block];
                    for (int y = area.Bottom; y < area.Top; ++y)
                        for (int x = area.Left; x < area.Right; ++x)
                        {
                            byte pixel = image[y, x];

                            float bottomLeft = equalization[block.Y, block.X, pixel];
                            float bottomRight = equalization[block.Y, block.X + 1, pixel];
                            float topLeft = equalization[block.Y + 1, block.X, pixel];
                            float topRight = equalization[block.Y + 1, block.X + 1, pixel];

                            PointF fraction = area.GetFraction(new Point(x, y));
                            result[y, x] = Calc.Interpolate(topLeft, topRight, bottomLeft, bottomRight, fraction);
                        }
                }
            });
            Logger.Log(result);
            return result;
        }
Ejemplo n.º 4
0
 public byte[,] Detect(float[,] image, BinaryMap mask, BlockMap blocks)
 {
     PointF[,] accumulated = AccumulateOrientations(image, mask, blocks);
     PointF[,] byBlock = SumBlocks(accumulated, blocks, mask);
     PointF[,] smooth = Smooth(byBlock, mask);
     byte[,] angles = ToAngles(smooth, mask);
     Logger.Log(angles);
     return angles;
 }
Ejemplo n.º 5
0
 public short[, ,] Analyze(BlockMap blocks, byte[,] image)
 {
     short[, ,] histogram = new short[blocks.BlockCount.Height, blocks.BlockCount.Width, 256];
     Parallel.ForEach(blocks.AllBlocks, delegate(Point block)
     {
         RectangleC area = blocks.BlockAreas[block];
         for (int y = area.Bottom; y < area.Top; ++y)
             for (int x = area.Left; x < area.Right; ++x)
                 ++histogram[block.Y, block.X, image[y, x]];
     });
     return histogram;
 }
Ejemplo n.º 6
0
        public BinaryMap FillBlocks(BlockMap blocks)
        {
            BinaryMap result = new BinaryMap(blocks.PixelCount);

            Parallel.For(0, blocks.BlockCount.Height, delegate(int blockY)
            {
                for (int blockX = 0; blockX < blocks.BlockCount.Width; ++blockX)
                {
                    if (GetBit(blockX, blockY))
                    {
                        result.Fill(blocks.BlockAreas[blockY, blockX]);
                    }
                }
            });
            return(result);
        }
Ejemplo n.º 7
0
        public BinaryMap FillCornerAreas(BlockMap blocks)
        {
            BinaryMap result = new BinaryMap(blocks.PixelCount);

            Parallel.For(0, blocks.CornerCount.Height, delegate(int cornerY)
            {
                for (int cornerX = 0; cornerX < blocks.CornerCount.Width; ++cornerX)
                {
                    if (GetBit(cornerX, cornerY))
                    {
                        result.Fill(blocks.CornerAreas[cornerY, cornerX]);
                    }
                }
            });
            return(result);
        }
Ejemplo n.º 8
0
 public short[, ,] SmoothAroundCorners(BlockMap blocks, short[, ,] input)
 {
     Point[] blocksAround = new Point[] { new Point(0, 0), new Point(-1, 0), new Point(0, -1), new Point(-1, -1) };
     short[, ,] output = new short[blocks.CornerCount.Height, blocks.CornerCount.Width, 256];
     Parallel.ForEach(blocks.AllCorners, delegate(Point corner)
     {
         foreach (Point relative in blocksAround)
         {
             Point block = Calc.Add(corner, relative);
             if (blocks.AllBlocks.Contains(block))
             {
                 for (int i = 0; i < 256; ++i)
                     output[corner.Y, corner.X, i] += input[block.Y, block.X, i];
             }
         }
     });
     return output;
 }
Ejemplo n.º 9
0
        float[, ,] ComputeEqualization(BlockMap blocks, short[, ,] histogram, BinaryMap blockMask)
        {
            float widthMax = RangeSize / 256f * MaxScaling;
            float widthMin = RangeSize / 256f * MinScaling;

            float[] limitedMin = new float[256];
            float[] limitedMax = new float[256];
            for (int i = 0; i < 256; ++i)
            {
                limitedMin[i] = Math.Max(i * widthMin + RangeMin, RangeMax - (255 - i) * widthMax);
                limitedMax[i] = Math.Min(i * widthMax + RangeMin, RangeMax - (255 - i) * widthMin);
            }

            float[, ,] equalization = new float[blocks.CornerCount.Height, blocks.CornerCount.Width, 256];
            Parallel.ForEach(blocks.AllCorners, delegate(Point corner)
            {
                if (blockMask.GetBitSafe(corner.X, corner.Y, false)
                    || blockMask.GetBitSafe(corner.X - 1, corner.Y, false)
                    || blockMask.GetBitSafe(corner.X, corner.Y - 1, false)
                    || blockMask.GetBitSafe(corner.X - 1, corner.Y - 1, false))
                {
                    int area = 0;
                    for (int i = 0; i < 256; ++i)
                        area += histogram[corner.Y, corner.X, i];
                    float widthWeigth = RangeSize / area;

                    float top = RangeMin;
                    for (int i = 0; i < 256; ++i)
                    {
                        float width = histogram[corner.Y, corner.X, i] * widthWeigth;
                        float equalized = top + ToFloatTable[i] * width;
                        top += width;

                        float limited = equalized;
                        if (limited < limitedMin[i])
                            limited = limitedMin[i];
                        if (limited > limitedMax[i])
                            limited = limitedMax[i];
                        equalization[corner.Y, corner.X, i] = limited;
                    }
                }
            });
            return equalization;
        }
Ejemplo n.º 10
0
 public short[, ,] Smooth(BlockMap blocks, short[, ,] input)
 {
     short[, ,] output = new short[blocks.CornerCount.Height, blocks.CornerCount.Width, 256];
     Parallel.ForEach(blocks.AllCorners, delegate(Point corner)
     {
         for (int i = 0; i < 256; ++i)
             output[corner.Y, corner.X, i] = input[corner.Y, corner.X, i];
         foreach (Point neigborRelative in Neighborhood.CornerNeighbors)
         {
             Point neighbor = Calc.Add(corner, neigborRelative);
             if (blocks.AllCorners.Contains(neighbor))
             {
                 for (int i = 0; i < 256; ++i)
                     output[corner.Y, corner.X, i] += input[neighbor.Y, neighbor.X, i];
             }
         }
     });
     return output;
 }
Ejemplo n.º 11
0
 public BinaryMap Binarize(float[,] input, float[,] baseline, BinaryMap mask, BlockMap blocks)
 {
     BinaryMap binarized = new BinaryMap(input.GetLength(1), input.GetLength(0));
     Parallel.For(0, blocks.AllBlocks.Height, delegate(int blockY)
     {
         for (int blockX = 0; blockX < blocks.AllBlocks.Width; ++blockX)
         {
             if (mask.GetBit(blockX, blockY))
             {
                 RectangleC 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.SetBitOne(x, y);
             }
         }
     });
     Logger.Log(binarized);
     return binarized;
 }
Ejemplo n.º 12
0
        public byte[,] Compute(BlockMap blocks, short[, ,] histogram)
        {
            byte[,] result = new byte[blocks.BlockCount.Height, blocks.BlockCount.Width];
            Parallel.ForEach(blocks.AllBlocks, delegate(Point block)
            {
                int area = 0;
                for (int i = 0; i < 256; ++i)
                    area += histogram[block.Y, block.X, i];
                int clipLimit = Convert.ToInt32(area * ClipFraction);

                int accumulator = 0;
                int lowerBound = 255;
                for (int i = 0; i < 256; ++i)
                {
                    accumulator += histogram[block.Y, block.X, i];
                    if (accumulator > clipLimit)
                    {
                        lowerBound = i;
                        break;
                    }
                }

                accumulator = 0;
                int upperBound = 0;
                for (int i = 255; i >= 0; --i)
                {
                    accumulator += histogram[block.Y, block.X, i];
                    if (accumulator > clipLimit)
                    {
                        upperBound = i;
                        break;
                    }
                }

                result[block.Y, block.X] = (byte)(upperBound - lowerBound);
            });
            Logger.Log(result);
            return result;
        }
Ejemplo n.º 13
0
        PointF[,] AccumulateOrientations(float[,] input, BinaryMap mask, BlockMap blocks)
        {
            List<List<NeighborInfo>> neighbors = PrepareNeighbors();

            PointF[,] orientation = new PointF[input.GetLength(0), input.GetLength(1)];
            Parallel.For(0, mask.Height, delegate(int blockY)
            {
                Range validMaskRange = GetMaskLineRange(mask, blockY);
                if (validMaskRange.Length > 0)
                {
                    Range validXRange = new Range(blocks.BlockAreas[blockY, validMaskRange.Begin].Left,
                        blocks.BlockAreas[blockY, validMaskRange.End - 1].Right);
                    for (int y = blocks.BlockAreas[blockY, 0].Bottom; y < blocks.BlockAreas[blockY, 0].Top; ++y)
                    {
                        foreach (NeighborInfo neighbor in neighbors[y % neighbors.Count])
                        {
                            int radius = Math.Max(Math.Abs(neighbor.Position.X), Math.Abs(neighbor.Position.Y));
                            if (y - radius >= 0 && y + radius < input.GetLength(0))
                            {
                                Range xRange = new Range(Math.Max(radius, validXRange.Begin),
                                    Math.Min(input.GetLength(1) - radius, validXRange.End));
                                for (int x = xRange.Begin; x < xRange.End; ++x)
                                {
                                    float before = input[y - neighbor.Position.Y, x - neighbor.Position.X];
                                    float at = input[y, x];
                                    float after = input[y + neighbor.Position.Y, x + neighbor.Position.X];
                                    float strength = at - Math.Max(before, after);
                                    if (strength > 0)
                                        orientation[y, x] = Calc.Add(orientation[y, x], Calc.Multiply(strength, neighbor.Orientation));
                                }
                            }
                        }
                    }
                }
            });
            Logger.Log("Raw", orientation);
            return orientation;
        }
Ejemplo n.º 14
0
 public BinaryMap FillBlocks(BlockMap blocks)
 {
     BinaryMap result = new BinaryMap(blocks.PixelCount);
     Parallel.For(0, blocks.BlockCount.Height, delegate(int blockY)
     {
         for (int blockX = 0; blockX < blocks.BlockCount.Width; ++blockX)
             if (GetBit(blockX, blockY))
                 result.Fill(blocks.BlockAreas[blockY, blockX]);
     });
     return result;
 }
Ejemplo n.º 15
0
 public void LogRectangleGrid(BlockMap.RectangleGrid grid, BinaryWriter binWrite, TextWriter tw, string name)
 {
     LogPointGrid(grid.Corners, binWrite, tw, name);
 }
Ejemplo n.º 16
0
 PointF[,] SumBlocks(PointF[,] orientation, BlockMap blocks, BinaryMap mask)
 {
     PointF[,] sums = new PointF[blocks.BlockCount.Height, blocks.BlockCount.Width];
     Parallel.ForEach(blocks.AllBlocks, delegate(Point block)
     {
         if (mask.GetBit(block))
         {
             PointF sum = new PointF();
             RectangleC area = blocks.BlockAreas[block];
             for (int y = area.Bottom; y < area.Top; ++y)
                 for (int x = area.Left; x < area.Right; ++x)
                     sum = Calc.Add(sum, orientation[y, x]);
             sums[block.Y, block.X] = sum;
         }
     });
     return sums;
 }
Ejemplo n.º 17
0
 public float[,] Equalize(BlockMap blocks, byte[,] image, short[, ,] histogram, BinaryMap blockMask)
 {
     float[, ,] equalization = ComputeEqualization(blocks, histogram, blockMask);
     return PerformEqualization(blocks, image, equalization, blockMask);
 }
Ejemplo n.º 18
0
 public void LogPointGrid(BlockMap.PointGrid grid, BinaryWriter binWrite, TextWriter tw, string name)
 {
     binWrite.Write(grid.AllX.Count());
     tw.WriteLine("{1} AllX Length: {0}", grid.AllX.Count(), name);
     foreach (var i in grid.AllX)
     {
         binWrite.Write(i);
         tw.WriteLine(i);
     }
     binWrite.Write(grid.AllY.Count());
     tw.WriteLine("{1} AllY Length: {0}", grid.AllY.Count(), name);
     foreach (var i in grid.AllY)
     {
         binWrite.Write(i);
         tw.WriteLine(i);
     }
 }
Ejemplo n.º 19
0
        public TemplateBuilder Extract(byte[,] invertedImage, int dpi)
        {
            TemplateBuilder template = null;
            DpiAdjuster.Adjust(this, dpi, delegate()
            {
                byte[,] image = ImageInverter.GetInverted(invertedImage);

                BlockMap blocks = new BlockMap(new Size(image.GetLength(1), image.GetLength(0)), BlockSize);
                Logger.Log("BlockMap", blocks);

                short[, ,] histogram = Histogram.Analyze(blocks, image);
                short[, ,] smoothHistogram = Histogram.SmoothAroundCorners(blocks, histogram);
                BinaryMap mask = Mask.ComputeMask(blocks, histogram);
                float[,] equalized = Equalizer.Equalize(blocks, image, smoothHistogram, mask);

                byte[,] orientation = Orientation.Detect(equalized, mask, blocks);
                float[,] smoothed = RidgeSmoother.Smooth(equalized, orientation, mask, blocks);
                float[,] orthogonal = OrthogonalSmoother.Smooth(smoothed, orientation, mask, blocks);

                BinaryMap binary = Binarizer.Binarize(smoothed, orthogonal, mask, blocks);
                binary.AndNot(BinarySmoother.Filter(binary.GetInverted()));
                binary.Or(BinarySmoother.Filter(binary));
                Logger.Log("BinarySmoothingResult", binary);
                CrossRemover.Remove(binary);

                BinaryMap pixelMask = mask.FillBlocks(blocks);
                BinaryMap innerMask = InnerMask.Compute(pixelMask);

                BinaryMap inverted = binary.GetInverted();
                inverted.And(pixelMask);

                SkeletonBuilder ridges = null;
                SkeletonBuilder valleys = null;
                Parallel.Invoke(
                    () => { ridges = ProcessSkeleton("Ridges", binary); },
                    () => { valleys = ProcessSkeleton("Valleys", inverted); });

                template = new TemplateBuilder();
                template.OriginalDpi = dpi;
                template.OriginalWidth = invertedImage.GetLength(1);
                template.OriginalHeight = invertedImage.GetLength(0);

                MinutiaCollector.Collect(ridges, TemplateBuilder.MinutiaType.Ending, template);
                MinutiaCollector.Collect(valleys, TemplateBuilder.MinutiaType.Bifurcation, template);
                MinutiaMask.Filter(template, innerMask);
                StandardDpiScaling.Scale(template);
                MinutiaCloudRemover.Filter(template);
                UniqueMinutiaSorter.Filter(template);
                MinutiaSorter.Shuffle(template);
                Logger.Log("FinalTemplate", template);
            });
            return template;
        }
Ejemplo n.º 20
0
 public BinaryMap FillCornerAreas(BlockMap blocks)
 {
     BinaryMap result = new BinaryMap(blocks.PixelCount);
     Parallel.For(0, blocks.CornerCount.Height, delegate(int cornerY)
     {
         for (int cornerX = 0; cornerX < blocks.CornerCount.Width; ++cornerX)
             if (GetBit(cornerX, cornerY))
                 result.Fill(blocks.CornerAreas[cornerY, cornerX]);
     });
     return result;
 }
Ejemplo n.º 21
0
        public TemplateBuilder Extract(byte[,] invertedImage, int dpi)
        {
            TemplateBuilder template = null;
            DpiAdjuster.Adjust(this, dpi, delegate ()
            {
                byte[,] image = ImageInverter.GetInverted(invertedImage);

                BlockMap blocks = new BlockMap(new Size(image.GetLength(1), image.GetLength(0)), BlockSize);
                Logger.Log("BlockMap", blocks);
                //Testing Start
                var outFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "bloackMapOut" + DateTime.Now.Second + ".bin");
                var outFileText = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                    "blockMapTextOut" + DateTime.Now.Second + ".txt");
                var file = new FileStream(outFileDir, FileMode.CreateNew);
                var binWrite = new BinaryWriter(file);
                TextWriter tw = new StreamWriter(outFileText);
                LogSize(blocks.PixelCount, binWrite, tw, "PixelCount");
                LogSize(blocks.BlockCount, binWrite, tw, "BlockCount");
                LogSize(blocks.CornerCount, binWrite, tw, "CornerCount");
                LogRectangleC(blocks.AllBlocks, binWrite, tw, "AllBlocks");
                LogRectangleC(blocks.AllCorners, binWrite, tw, "AllCorners");
                LogPointGrid(blocks.Corners, binWrite, tw, "Corners");
                LogRectangleGrid(blocks.BlockAreas, binWrite, tw, "BlockAreas");
                LogPointGrid(blocks.BlockCenters, binWrite, tw, "BlockCenters");
                LogRectangleGrid(blocks.CornerAreas, binWrite, tw, "CornerAreas");
                binWrite.Close();
                tw.Close();
                //Testing End

                short[,,] histogram = Histogram.Analyze(blocks, image);
                ////testing
                //var outFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "histogramImageInput" + ".bin");
                //var file = new FileStream(outFileDir, FileMode.CreateNew);
                //var binWrite = new BinaryWriter(file);
                //binWrite.Write(image.GetLength(0));
                //binWrite.Write(image.GetLength(1));
                //for (var i = 0; i < image.GetLength(0); i++)
                //{
                //    for (var j = 0; j < image.GetLength(1); j++)
                //    {
                //        binWrite.Write(image[i, j]);
                //    }
                //}
                //binWrite.Close();
                ////End testing

                ////Testing Start
                //Count++;
                //var outFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "histogramOut" + Count + ".bin");
                //var outFileText = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                //    "histogramTextOut" + Count + ".txt");
                //var file = new FileStream(outFileDir, FileMode.CreateNew);
                //var binWrite = new BinaryWriter(file);
                //TextWriter tw = new StreamWriter(outFileText);
                //binWrite.Write(histogram.GetLength(0));
                //tw.WriteLine(histogram.GetLength(0));
                //binWrite.Write(histogram.GetLength(1));
                //tw.WriteLine(histogram.GetLength(1));
                //binWrite.Write(histogram.GetLength(2));
                //tw.WriteLine(histogram.GetLength(2));
                //for (var i = 0; i < histogram.GetLength(0); i++)
                //{
                //    for (var j = 0; j < histogram.GetLength(1); j++)
                //    {
                //        for (var k = 0; k < histogram.GetLength(2); k++)
                //        {
                //            binWrite.Write(histogram[i, j, k]);
                //            tw.WriteLine(histogram[i, j, k]);
                //        }
                //    }
                //}
                //binWrite.Close();
                //file.Close();
                //tw.Close();
                //Testing Finish

                //Testing Start
                //outFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "bloackMapOutPostHis" + DateTime.Now.Second + ".bin");
                //outFileText = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                //   "blockMapTextOutPostHis" + DateTime.Now.Second + ".txt");
                //file = new FileStream(outFileDir, FileMode.CreateNew);
                //binWrite = new BinaryWriter(file);
                //tw = new StreamWriter(outFileText);
                //LogSize(blocks.PixelCount, binWrite, tw);
                //LogSize(blocks.BlockCount, binWrite, tw);
                //LogSize(blocks.CornerCount, binWrite, tw);
                //LogRectangleC(blocks.AllBlocks, binWrite, tw);
                //LogRectangleC(blocks.AllCorners, binWrite, tw);
                //LogPointGrid(blocks.Corners, binWrite, tw);
                //LogRectangleGrid(blocks.BlockAreas, binWrite, tw);
                //LogPointGrid(blocks.BlockCenters, binWrite, tw);
                //LogRectangleGrid(blocks.CornerAreas, binWrite, tw);
                //binWrite.Close();
                //tw.Close();
                //Testing End

                short[,,] smoothHistogram = Histogram.SmoothAroundCorners(blocks, histogram);
                BinaryMap mask = Mask.ComputeMask(blocks, histogram);
                float[,] equalized = Equalizer.Equalize(blocks, image, smoothHistogram, mask);

                byte[,] orientation = Orientation.Detect(equalized, mask, blocks);
                float[,] smoothed = RidgeSmoother.Smooth(equalized, orientation, mask, blocks);
                float[,] orthogonal = OrthogonalSmoother.Smooth(smoothed, orientation, mask, blocks);

                BinaryMap binary = Binarizer.Binarize(smoothed, orthogonal, mask, blocks);
                binary.AndNot(BinarySmoother.Filter(binary.GetInverted()));
                binary.Or(BinarySmoother.Filter(binary));
                Logger.Log("BinarySmoothingResult", binary);
                CrossRemover.Remove(binary);

                BinaryMap pixelMask = mask.FillBlocks(blocks);
                BinaryMap innerMask = InnerMask.Compute(pixelMask);

                BinaryMap inverted = binary.GetInverted();
                inverted.And(pixelMask);

                SkeletonBuilder ridges = null;
                SkeletonBuilder valleys = null;

                Parallel.Invoke(
                    () => { ridges = ProcessSkeleton("Ridges", binary); },
                    () => { valleys = ProcessSkeleton("Valleys", inverted); });

                template = new TemplateBuilder();
                template.OriginalDpi = dpi;
                template.OriginalWidth = invertedImage.GetLength(1);
                template.OriginalHeight = invertedImage.GetLength(0);

                MinutiaCollector.Collect(ridges, TemplateBuilder.MinutiaType.Ending, template);
                MinutiaCollector.Collect(valleys, TemplateBuilder.MinutiaType.Bifurcation, template);
                MinutiaMask.Filter(template, innerMask);
                StandardDpiScaling.Scale(template);
                MinutiaCloudRemover.Filter(template);
                UniqueMinutiaSorter.Filter(template);
                MinutiaSorter.Shuffle(template);
                Logger.Log("FinalTemplate", template);
            });
            return template;
        }