diff --git a/CHANGELOG.md b/CHANGELOG.md index 40444c8..5004e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## /09/2021 - v2.21.2 + +- (Add) Allow to choose custom locations for "Send to" +- (Improvement) Better random generation for benchmark +- (Fix) Outline - Hollow areas: Not outlining the second closing contour for contours with child +- (Fix) Pixel editor - Eraser: It was selecting the whole blob even if have inner parents + ## 06/09/2021 - v2.21.1 - **(Add) Layer outline:** diff --git a/UVtools.Core/CoreSettings.cs b/UVtools.Core/CoreSettings.cs index 47f019c..62824f6 100644 --- a/UVtools.Core/CoreSettings.cs +++ b/UVtools.Core/CoreSettings.cs @@ -8,6 +8,7 @@ using System; using System.Threading.Tasks; +using Emgu.CV.Cuda; namespace UVtools.Core { @@ -37,6 +38,16 @@ namespace UVtools.Core /// public static ParallelOptions ParallelOptions => new() {MaxDegreeOfParallelism = _maxDegreeOfParallelism}; + /// + /// Gets or sets if operations run via cuda when possible + /// + public static bool EnableCuda { get; set; } + + /// + /// Gets if we can use cuda on operations + /// + public static bool CanUseCuda => EnableCuda && CudaInvoke.HasCuda; + #endregion } } diff --git a/UVtools.Core/EmguCV/Contour.cs b/UVtools.Core/EmguCV/EmguContour.cs similarity index 94% rename from UVtools.Core/EmguCV/Contour.cs rename to UVtools.Core/EmguCV/EmguContour.cs index 291cad8..9b43995 100644 --- a/UVtools.Core/EmguCV/Contour.cs +++ b/UVtools.Core/EmguCV/EmguContour.cs @@ -21,8 +21,17 @@ namespace UVtools.Core.EmguCV /// /// A contour cache for OpenCV /// - public class Contour : IReadOnlyCollection, IDisposable + public class EmguContour : IReadOnlyCollection, IDisposable { + #region Constants + + public const byte HierarchyNextSameLevel = 0; + public const byte HierarchyPreviousSameLevel = 1; + public const byte HierarchyFirstChild = 2; + public const byte HierarchyParent = 3; + + #endregion + #region Members private VectorOfPoint _points; @@ -116,10 +125,10 @@ namespace UVtools.Core.EmguCV #endregion #region Constructor - public Contour(VectorOfPoint points) : this(points.ToArray()) + public EmguContour(VectorOfPoint points) : this(points.ToArray()) { } - public Contour(Point[] points) + public EmguContour(Point[] points) { Points = new VectorOfPoint(points); } diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 5968786..a21ca9c 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -10,6 +10,7 @@ using System; using System.Drawing; using System.Runtime.InteropServices; using Emgu.CV; +using Emgu.CV.Cuda; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; @@ -125,6 +126,18 @@ namespace UVtools.Core.Extensions return layers; } + + /// + /// Create a new from + /// + /// + /// + public static GpuMat ToGpuMat(this Mat mat) + { + var gpuMat = new GpuMat(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels); + gpuMat.Upload(mat); + return gpuMat; + } #endregion #region Memory accessors @@ -871,6 +884,71 @@ namespace UVtools.Core.Extensions #endregion #region Utilities methods + + /// + /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,]. + /// The function modifies the source image content + /// + /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content + /// Retrieval mode + /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation). + /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context + /// The contour hierarchy + public static VectorOfVectorOfPoint FindContours(this Mat mat, RetrType mode = RetrType.List, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default) + { + using var hierarchy = new Mat(); + var contours = new VectorOfVectorOfPoint(); + CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset); + return contours; + } + + /* + /// + /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,]. + /// The function modifies the source image content + /// + /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content + /// Detected contours. Each contour is stored as a vector of points. + /// Retrieval mode + /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation). + /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context + /// The contour hierarchy + public static int[,] FindContours(this Mat mat, IOutputArray contours, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default) + { + using var hierarchy = new Mat(); + CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset); + var numArray = new int[hierarchy.Cols, 4]; + var gcHandle = GCHandle.Alloc(numArray, GCHandleType.Pinned); + using (var mat2 = new Mat(hierarchy.Rows, hierarchy.Cols, hierarchy.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchy.Step)) + hierarchy.CopyTo(mat2); + gcHandle.Free(); + return numArray; + }*/ + + /// + /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,]. + /// The function modifies the source image content + /// + /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content + /// The contour hierarchy + /// Retrieval mode + /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation). + /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context + /// Detected contours. Each contour is stored as a vector of points. + public static VectorOfVectorOfPoint FindContours(this Mat mat, out int[,] hierarchy, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default) + { + var contours = new VectorOfVectorOfPoint(); + using var hierarchyMat = new Mat(); + CvInvoke.FindContours(mat, contours, hierarchyMat, mode, method, offset); + hierarchy = new int[hierarchyMat.Cols, 4]; + var gcHandle = GCHandle.Alloc(hierarchy, GCHandleType.Pinned); + using (var mat2 = new Mat(hierarchyMat.Rows, hierarchyMat.Cols, hierarchyMat.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchyMat.Step)) + hierarchyMat.CopyTo(mat2); + gcHandle.Free(); + return contours; + } + + /// /// Determine the area (i.e. total number of pixels in the image), /// initialize the output skeletonized image, and construct the diff --git a/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs b/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs index c153037..cfcb1da 100644 --- a/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs +++ b/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs @@ -21,6 +21,7 @@ using BinarySerialization; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Util; +using UVtools.Core.EmguCV; using UVtools.Core.Extensions; using UVtools.Core.Operations; @@ -514,10 +515,7 @@ namespace UVtools.Core.FileFormats using var mat = this[layerIndex].LayerMat; //CvInvoke.Threshold(mat, mat, 127, 255, ThresholdType.Binary); // no AA - using var contours = new VectorOfVectorOfPoint(); - using var hierarchy = new Mat(); - CvInvoke.FindContours(mat, contours, hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxSimple); - var hierarchyJagged = hierarchy.GetData(); + using var contours = mat.FindContours(out var hierarchy, RetrType.Tree); float minx = SVGDocument.PrintParameters.PrintRange.MinX; float miny = SVGDocument.PrintParameters.PrintRange.MinY; @@ -527,8 +525,7 @@ namespace UVtools.Core.FileFormats var path = new StringBuilder(); for (int i = 0; i < contours.Size; i++) { - if (contours[i].Size == 0) continue; - if ((int)hierarchyJagged.GetValue(0, i, 3) == -1) // Top hierarchy + if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy { if (path.Length > 0) { diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index ba1ed48..19e1f75 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -19,6 +19,7 @@ using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; using MoreLinq.Extensions; +using UVtools.Core.EmguCV; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Objects; @@ -737,215 +738,6 @@ namespace UVtools.Core return MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, layerIndex); } - public unsafe List GetAllIssuesBeta( - IslandDetectionConfiguration islandConfig = null, - OverhangDetectionConfiguration overhangConfig = null, - ResinTrapDetectionConfiguration resinTrapConfig = null, - TouchingBoundDetectionConfiguration touchBoundConfig = null, - PrintHeightDetectionConfiguration printHeightConfig = null, - bool emptyLayersConfig = true, - List ignoredIssues = null, - OperationProgress progress = null) - { - islandConfig ??= new IslandDetectionConfiguration(); - overhangConfig ??= new OverhangDetectionConfiguration(); - resinTrapConfig ??= new ResinTrapDetectionConfiguration(); - touchBoundConfig ??= new TouchingBoundDetectionConfiguration(); - printHeightConfig ??= new PrintHeightDetectionConfiguration(); - progress ??= new OperationProgress(); - - var result = new ConcurrentBag(); - if (!islandConfig.Enabled && !overhangConfig.Enabled && !resinTrapConfig.Enabled && !touchBoundConfig.Enabled && !emptyLayersConfig) return result.ToList(); - - ConcurrentDictionary> layerHollowAreas = new(); - List actionLayers = new(); - bool checkedEmptyLayers = false; - Mat emptyMat = new (); - Mat[] cachedLayers = new Mat[LayerCount]; - const uint cacheCount = 300; - - bool IsIgnored(LayerIssue issue) => ignoredIssues is not null && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue); - bool AddIssue(LayerIssue issue) - { - if (IsIgnored(issue)) return false; - result.Add(issue); - return true; - } - - Mat GetCachedMat(uint layerIndex) - { - if (cachedLayers[layerIndex] is not null) return cachedLayers[layerIndex]; - Parallel.For(layerIndex, Math.Min(layerIndex + cacheCount, LayerCount), CoreSettings.ParallelOptions, i => - { - if (this[i].IsEmpty) return; // empty layers - cachedLayers[i] = this[i].LayerMat; - }); - return cachedLayers[layerIndex]; - } - - - if (touchBoundConfig.Enabled || resinTrapConfig.Enabled || - (islandConfig.Enabled && islandConfig.WhiteListLayers is null) || - (overhangConfig.Enabled && overhangConfig.WhiteListLayers is null) - ) - { - checkedEmptyLayers = true; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - var layer = this[layerIndex]; - if (layer.IsEmpty) - { - cachedLayers[layerIndex] = emptyMat; - if (emptyLayersConfig) - { - AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer)); - } - continue; - } - actionLayers.Add(layerIndex); - } - } - - if (!checkedEmptyLayers && emptyLayersConfig) - { - checkedEmptyLayers = true; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - var layer = this[layerIndex]; - if (layer.IsEmpty) - { - AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer)); - } - } - } - - for (var i = 0; i < actionLayers.Count; i++) - { - var rootLayerIndex = actionLayers[i]; - GetCachedMat(rootLayerIndex); - progress.Token.ThrowIfCancellationRequested(); - uint layerAdvance = (uint) Math.Min(i + cacheCount, actionLayers.Count); - Parallel.For(i, layerAdvance, CoreSettings.ParallelOptions, l => - { - var layerIndex = actionLayers[(int) l]; - var layer = this[layerIndex]; - if (layer.IsEmpty) - { - progress.LockAndIncrement(); - - return; // Empty layer - } - - var image = cachedLayers[layerIndex]; - - int step = image.Step; - var span = image.GetBytePointer(); - - if (touchBoundConfig.Enabled) - { - // TouchingBounds Checker - List pixels = new(); - bool touchTop = layer.BoundingRectangle.Top <= touchBoundConfig.MarginTop; - bool touchBottom = layer.BoundingRectangle.Bottom >= image.Height - touchBoundConfig.MarginBottom; - bool touchLeft = layer.BoundingRectangle.Left <= touchBoundConfig.MarginLeft; - bool touchRight = layer.BoundingRectangle.Right >= image.Width - touchBoundConfig.MarginRight; - if (touchTop || touchBottom) - { - for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds - { - if (touchTop) - { - for (int y = 0; y < touchBoundConfig.MarginTop; y++) // Top - { - if (span[image.GetPixelPos(x, y)] >= - touchBoundConfig.MinimumPixelBrightness) - { - pixels.Add(new Point(x, y)); - } - } - } - - if (touchBottom) - { - for (int y = image.Height - touchBoundConfig.MarginBottom; - y < image.Height; - y++) // Bottom - { - if (span[image.GetPixelPos(x, y)] >= - touchBoundConfig.MinimumPixelBrightness) - { - pixels.Add(new Point(x, y)); - } - } - } - } - } - - if (touchLeft || touchRight) - { - for (int y = touchBoundConfig.MarginTop; - y < image.Height - touchBoundConfig.MarginBottom; - y++) // Check Left and Right bounds - { - if (touchLeft) - { - for (int x = 0; x < touchBoundConfig.MarginLeft; x++) // Left - { - if (span[image.GetPixelPos(x, y)] >= - touchBoundConfig.MinimumPixelBrightness) - { - pixels.Add(new Point(x, y)); - } - } - } - - if (touchRight) - { - for (int x = image.Width - touchBoundConfig.MarginRight; x < image.Width; x++) // Right - { - if (span[image.GetPixelPos(x, y)] >= - touchBoundConfig.MinimumPixelBrightness) - { - pixels.Add(new Point(x, y)); - } - } - } - } - } - - if (pixels.Count > 0) - { - AddIssue(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, - pixels.ToArray())); - } - } - - progress.LockAndIncrement(); - }); - - for (; i < layerAdvance - 1; i++) - { - cachedLayers[actionLayers[i]].Dispose(); - cachedLayers[actionLayers[i]] = null; - } - - - // Wait for jobs - /*foreach (var task in taskList) - { - task.Wait(); - } - - if (layerIndex > 0) - { - cachedLayers[layerIndex - 1].Dispose(); - cachedLayers[layerIndex - 1] = null; - }*/ - } - - return result.ToList(); - } - public List GetAllIssues( IslandDetectionConfiguration islandConfig = null, OverhangDetectionConfiguration overhangConfig = null, @@ -1362,16 +1154,13 @@ namespace UVtools.Core var listHollowArea = new List(); - using VectorOfVectorOfPoint contours = new(); - using Mat hierarchy = new(); - CvInvoke.FindContours(resinTrapImage, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); + using var contours = resinTrapImage.FindContours(out var hierarchy, RetrType.Tree); - if(needDispose) + if (needDispose) { resinTrapImage.Dispose(); } - var arr = hierarchy.GetData(); // //hierarchy[i][0]: the index of the next contour of the same level //hierarchy[i][1]: the index of the previous contour of the same level @@ -1381,7 +1170,7 @@ namespace UVtools.Core for (int i = 0; i < contours.Size; i++) { - if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1) + if (hierarchy[i, EmguContour.HierarchyFirstChild] != -1 || hierarchy[i, EmguContour.HierarchyParent] == -1) continue; var rect = CvInvoke.BoundingRectangle(contours[i]); @@ -1464,8 +1253,7 @@ namespace UVtools.Core var span = image.GetDataSpan(); using (var emguImage = image.NewBlank()) { - using (var vec = - new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) + using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) { CvInvoke.DrawContours(emguImage, vec, -1, EmguExtensions.WhiteColor, -1); } @@ -1641,9 +1429,7 @@ namespace UVtools.Core for (var i = 0; i < drawings.Count; i++) { var operation = drawings[i]; - VectorOfVectorOfPoint layerContours = null; - Mat layerHierarchy = null; - + if (operation.OperationType == PixelOperation.PixelOperationType.Drawing) { var operationDrawing = (PixelDrawing) operation; @@ -1681,24 +1467,27 @@ namespace UVtools.Core { var mat = modifiedLayers.GetOrAdd(operation.LayerIndex, u => this[operation.LayerIndex].LayerMat); - if (layerContours is null) - { - layerContours = new VectorOfVectorOfPoint(); - layerHierarchy = new Mat(); - - CvInvoke.FindContours(mat, layerContours, layerHierarchy, RetrType.Ccomp, - ChainApproxMethod.ChainApproxSimple); - } - + using var layerContours = mat.FindContours(out var hierarchy, RetrType.Tree); + if (mat.GetByte(operation.Location) >= 10) { - for (int contourIdx = 0; contourIdx < layerContours.Size; contourIdx++) + using var vec = new VectorOfVectorOfPoint(); + for (int index = layerContours.Size - 1; index >= 0; index--) { - if (!(CvInvoke.PointPolygonTest(layerContours[contourIdx], operation.Location, false) >= 0)) - continue; - CvInvoke.DrawContours(mat, layerContours, contourIdx, new MCvScalar(operation.PixelBrightness), -1); + if (CvInvoke.PointPolygonTest(layerContours[index], operation.Location, false) < 0) continue; + vec.Push(layerContours[index]); + for (int n = index + 1; n < layerContours.Size; n++) + { + if (hierarchy[n, EmguContour.HierarchyParent] != index) continue; + vec.Push(layerContours[n]); + } break; } + + if (vec.Size > 0) + { + CvInvoke.DrawContours(mat, vec, -1, new MCvScalar(operation.PixelBrightness), -1); + } } } else if (operation.OperationType == PixelOperation.PixelOperationType.Supports) @@ -1773,10 +1562,7 @@ namespace UVtools.Core drawnLayers++; } } - - layerContours?.Dispose(); - layerHierarchy?.Dispose(); - + progress++; } diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index 6560de9..8821364 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -11,6 +11,7 @@ using System.Drawing; using System.IO; using System.Xml.Serialization; using Emgu.CV; +using Emgu.CV.Cuda; using Emgu.CV.Util; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; diff --git a/UVtools.Core/Operations/OperationLayerExportImage.cs b/UVtools.Core/Operations/OperationLayerExportImage.cs index 5748662..405693c 100644 --- a/UVtools.Core/Operations/OperationLayerExportImage.cs +++ b/UVtools.Core/Operations/OperationLayerExportImage.cs @@ -13,6 +13,8 @@ using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Util; +using UVtools.Core.EmguCV; +using UVtools.Core.Extensions; using UVtools.Core.FileFormats; namespace UVtools.Core.Operations @@ -206,11 +208,7 @@ namespace UVtools.Core.Operations CvInvoke.Threshold(matRoi, matRoi, 127, byte.MaxValue, ThresholdType.Binary); // Remove AA - using var contours = new VectorOfVectorOfPoint(); - using var hierarchy = new Mat(); - CvInvoke.FindContours(matRoi, contours, hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxSimple); - - var hierarchyJagged = hierarchy.GetData(); + using var contours = matRoi.FindContours(out var hierarchy, RetrType.Tree); using TextWriter tw = new StreamWriter(fileFullPath); tw.WriteLine("