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("