private static Task<ApplyResult> ApplyExtract_DoIt_Task(Convolution2D imageConv, FeatureRecognizer_Extract extract, FeatureRecognizer_Extract_Sub sub)
        {
            return Task.Run(() =>
            {
                VectorInt totalReduce = sub.Extract.GetReduction();

                Convolution2D finalImage = imageConv;
                if (extract.PreFilter != null)
                {
                    finalImage = Convolutions.Convolute(imageConv, extract.PreFilter);      //TODO: The final worker method shouldn't do this if too small.  I'm just doing it to show the user something
                    totalReduce += extract.PreFilter.GetReduction();
                }

                if (imageConv.Width <= totalReduce.X || imageConv.Height <= totalReduce.Y)
                {
                    // Too small, exit early
                    return new ApplyResult(finalImage, null, null);
                }

                // Apply convolutions
                Convolution2D filtered = Convolutions.Convolute(finalImage, sub.Extract);

                // Look at the brightest spots, and see if they are matches
                var matches = AnalyzeBrightSpots(filtered, sub.Results);

                return new ApplyResult(finalImage, filtered, matches);
            });
        }
        private async static void ApplyExtract_DoIt(Grid grid, FeatureRecognizer_Image image, FeatureRecognizer_Extract extract, FeatureRecognizer_Extract_Sub sub, ConvolutionResultNegPosColoring edgeColor, ContextMenu contextMenu)
        {
            // Source image
            BitmapSource bitmap;
            if (sub.InputWidth == image.Bitmap.PixelWidth && sub.InputHeight == image.Bitmap.PixelHeight)
            {
                bitmap = image.Bitmap;
            }
            else
            {
                bitmap = UtilityWPF.ResizeImage(image.Bitmap, sub.InputWidth, sub.InputHeight);
            }

            Convolution2D imageConv = ((BitmapCustomCachedBytes)UtilityWPF.ConvertToColorArray(bitmap, false, Colors.Transparent)).ToConvolution();

            // Convolute, look for matches
            var results = await ApplyExtract_DoIt_Task(imageConv, extract, sub);

            #region Show results

            // Left Image
            ApplyExtract_Draw_LeftImage(grid, results.ImageConv, extract.PreFilter, edgeColor);

            // Right Image
            if (results.Filtered != null)
            {
                ApplyExtract_Draw_RightImage(grid, results.Filtered, edgeColor);
            }

            // Matches
            if (results.Matches != null && results.Matches.Length > 0)
            {
                ApplyExtract_Draw_Matches(grid, results.Matches, results.Filtered.Size, sub, contextMenu);
            }

            #endregion
        }
        private static void ApplyExtract_Draw_Matches(Grid grid, Tuple<VectorInt, ApplyResult_Match[]>[] matches, VectorInt imageSize, FeatureRecognizer_Extract_Sub sub, ContextMenu contextMenu)
        {
            const int HITSSIZE_SMALL = 50;
            const int HITSSIZE_BIG = 70;

            StackPanel mainPanel = new StackPanel();

            Grid headerRow = new Grid();
            mainPanel.Children.Add(headerRow);

            #region Draw all hit positions

            var hits = matches.Select(o =>
                {
                    double weight = 0;
                    if (o.Item2 != null && o.Item2.Length > 0)
                    {
                        weight = o.Item2.Max(p => p.Weight);
                    }

                    return new Tuple<VectorInt, double>(o.Item1, weight);
                });

            Border hitsMap = GetResultPositionsImage(hits, imageSize, HITSSIZE_BIG);

            hitsMap.HorizontalAlignment = HorizontalAlignment.Left;
            hitsMap.VerticalAlignment = VerticalAlignment.Bottom;

            headerRow.Children.Add(hitsMap);

            #endregion
            #region Compare extracts

            Grid comparesGrid = new Grid();
            comparesGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
            comparesGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });

            comparesGrid.HorizontalAlignment = HorizontalAlignment.Right;
            comparesGrid.VerticalAlignment = VerticalAlignment.Bottom;

            headerRow.Children.Add(comparesGrid);

            // Label
            TextBlock comparesHeading = new TextBlock() { Text = "compare to", ToolTip = "These are results from the original extract, and can be used to compare the current image's extracts against", HorizontalAlignment = HorizontalAlignment.Center };

            Grid.SetRow(comparesHeading, 0);
            comparesGrid.Children.Add(comparesHeading);

            // Extracts
            StackPanel headerExtracts = new StackPanel()
            {
                Orientation = Orientation.Horizontal,
            };

            foreach (Convolution2D compareResult in sub.Results.Select(o => o.Result))
            {
                headerExtracts.Children.Add(Convolutions.GetThumbnail(compareResult, THUMBSIZE_EXTRACT, contextMenu));
            }

            Grid.SetRow(headerExtracts, 1);
            comparesGrid.Children.Add(headerExtracts);

            #endregion

            foreach (var match in matches)
            {
                //TODO: Make a grid
                StackPanel row = new StackPanel()
                {
                    Orientation = Orientation.Horizontal,
                    Margin = new Thickness(2),
                };

                #region Position

                StackPanel positionPanel = new StackPanel()
                {
                    VerticalAlignment = VerticalAlignment.Center,
                };

                row.Children.Add(positionPanel);

                // Point map
                Border pointMap = GetResultPositionsImage(new[] { Tuple.Create(match.Item1, 1d) }, imageSize, HITSSIZE_SMALL);
                pointMap.HorizontalAlignment = HorizontalAlignment.Left;
                positionPanel.Children.Add(pointMap);

                // Coordinates
                positionPanel.Children.Add(new TextBlock() { Text = match.Item1.ToString(), HorizontalAlignment = HorizontalAlignment.Center });

                #endregion

                foreach (var patch in match.Item2)
                {
                    #region Match Patch

                    StackPanel patchPanel = new StackPanel();
                    row.Children.Add(patchPanel);

                    // Show convolution
                    patchPanel.Children.Add(Convolutions.GetThumbnail(patch.Patch, THUMBSIZE_EXTRACT, contextMenu));

                    // Weight
                    patchPanel.Children.Add(new TextBlock()
                    {
                        HorizontalAlignment = HorizontalAlignment.Center,
                        Text = patch.IsMatch ? patch.Weight.ToStringSignificantDigits(2) : "no match",
                    });

                    #endregion
                }

                mainPanel.Children.Add(row);
            }

            #region Return border

            Border border = new Border()
            {
                BorderBrush = new SolidColorBrush(UtilityWPF.ColorFromHex("40000000")),
                BorderThickness = new Thickness(1),
                CornerRadius = new CornerRadius(4),
                Padding = new Thickness(4),
                Background = new SolidColorBrush(UtilityWPF.ColorFromHex("10000000")),
                Child = mainPanel,
            };

            Grid.SetColumn(border, 0);
            Grid.SetColumnSpan(border, 3);
            Grid.SetRow(border, 2);
            grid.Children.Add(border);

            #endregion
        }
        private void FinishBuildingExtract(ConvolutionBase2D filter, FeatureRecognizer_Extract_Sub[] subs, string imageID)
        {
            string uniqueID = Guid.NewGuid().ToString();

            // Determine filename
            string filename = "extract - " + uniqueID + ".xml";
            string fullFilename = System.IO.Path.Combine(_workingFolder, filename);

            // Add it
            FeatureRecognizer_Extract extract = new FeatureRecognizer_Extract()
            {
                Extracts = subs,
                PreFilter = filter,
                Control = Convolutions.GetThumbnail(subs[0].Extract, THUMBSIZE_EXTRACT, _extractContextMenu),
                ImageID = imageID,
                UniqueID = uniqueID,
                Filename = filename,
            };

            if (extract.PreFilter != null && extract.PreFilter is Convolution2D)
            {
                extract.PreFilterDNA_Single = ((Convolution2D)extract.PreFilter).ToDNA();
            }
            else if (extract.PreFilter != null && extract.PreFilter is ConvolutionSet2D)
            {
                extract.PreFilterDNA_Set = ((ConvolutionSet2D)extract.PreFilter).ToDNA();
            }

            // Copy to the working folder
            UtilityCore.SerializeToFile(fullFilename, extract);

            AddExtract(extract);

            // Update the session file
            SaveSession_SessionFile(_workingFolder);
        }