Add UI for repair suction cups

This commit is contained in:
Tiago Conceição
2021-09-17 23:28:39 +01:00
parent f66687cabc
commit c6a7489359
8 changed files with 117 additions and 69 deletions
+6 -6
View File
@@ -210,7 +210,7 @@ namespace UVtools.Core.EmguCV
return result;
}
public static bool CheckContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2)
public static bool ContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2)
{
var contour1Rect = CvInvoke.BoundingRectangle(contour1[0]);
var contour2Rect = CvInvoke.BoundingRectangle(contour2[0]);
@@ -226,17 +226,17 @@ namespace UVtools.Core.EmguCV
var totalRect = new Rectangle(minX, minY, maxX - minX, maxY - minY);
using Mat contour1Mat = EmguExtensions.InitMat(totalRect.Size);
using Mat contour2Mat = EmguExtensions.InitMat(totalRect.Size);
using Mat overlapCheck = EmguExtensions.InitMat(totalRect.Size);
using var contour1Mat = EmguExtensions.InitMat(totalRect.Size);
using var contour2Mat = EmguExtensions.InitMat(totalRect.Size);
using var overlapCheck = EmguExtensions.InitMat(totalRect.Size);
var inverseOffset = new Point(minX * -1, minY * -1);
CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, overlapCheck);
return (CvInvoke.CountNonZero(overlapCheck) > 0);
return CvInvoke.CountNonZero(overlapCheck) > 0;
}
}
@@ -33,12 +33,12 @@ namespace UVtools.Core.Operations
private bool _repairIslands = true;
private bool _repairResinTraps = true;
private bool _removeEmptyLayers = true;
/* Suction Cup repair disabled by default, needs UI component to toggle */
private bool _repairSuctionCups = false;
private bool _repairSuctionCups;
private ushort _removeIslandsBelowEqualPixelCount = 5;
private ushort _removeIslandsRecursiveIterations = 4;
private ushort _attachIslandsBelowLayers = 2;
private byte _resinTrapsOverlapBy = 5;
private byte _suctionCupsVentHole = 16;
private uint _gapClosingIterations = 1;
private uint _noiseRemovalIterations;
@@ -62,7 +62,7 @@ namespace UVtools.Core.Operations
{
var sb = new StringBuilder();
if (!RepairIslands && !RemoveEmptyLayers && !RepairResinTraps)
if (!_repairIslands && !_repairResinTraps && !_repairSuctionCups && !_removeEmptyLayers)
{
sb.AppendLine("You must select at least one repair operation.");
}
@@ -92,6 +92,12 @@ namespace UVtools.Core.Operations
set => RaiseAndSetIfChanged(ref _repairResinTraps, value);
}
public bool RepairSuctionCups
{
get => _repairSuctionCups;
set => RaiseAndSetIfChanged(ref _repairSuctionCups, value);
}
public bool RemoveEmptyLayers
{
get => _removeEmptyLayers;
@@ -122,6 +128,12 @@ namespace UVtools.Core.Operations
set => RaiseAndSetIfChanged(ref _resinTrapsOverlapBy, value);
}
public byte SuctionCupsVentHole
{
get => _suctionCupsVentHole;
set => RaiseAndSetIfChanged(ref _suctionCupsVentHole, value);
}
public uint GapClosingIterations
{
get => _gapClosingIterations;
@@ -193,9 +205,9 @@ namespace UVtools.Core.Operations
Parallel.ForEach(issuesGroup, CoreSettings.ParallelOptions, group =>
{
if (progress.Token.IsCancellationRequested) return;
Layer layer = SlicerFile[group.Key];
Mat image = layer.LayerMat;
Span<byte> bytes = image.GetDataSpan<byte>();
var layer = SlicerFile[group.Key];
var image = layer.LayerMat;
var bytes = image.GetDataByteSpan();
foreach (var issue in group)
{
foreach (var issuePixel in issue.Pixels)
@@ -382,7 +394,7 @@ namespace UVtools.Core.Operations
if (_repairIslands && (_gapClosingIterations > 0 || _noiseRemovalIterations > 0))
{
initImage();
using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
new Point(-1, -1));
if (_gapClosingIterations > 0)
{
@@ -408,13 +420,12 @@ namespace UVtools.Core.Operations
}
if (_repairSuctionCups)
if (_repairSuctionCups && Issues is not null)
{
/* build out parent/child relationships between all detected suction cups */
ConcurrentBag<LayerIssue> bottomSuctionIssues = new();
var bottomSuctionIssues = new ConcurrentBag<LayerIssue>();
var layerCount = SlicerFile.LayerCount;
Parallel.ForEach(Issues.Where(issue => issue.Type == LayerIssue.IssueType.SuctionCup), issue => {
if (issue.LayerIndex == 0)
@@ -422,11 +433,12 @@ namespace UVtools.Core.Operations
bottomSuctionIssues.Add(issue);
return;
}
bool overlapFound = false;
foreach(var candidate in Issues.Where(candidate => candidate.LayerIndex == issue.LayerIndex - 1 && candidate.Type == LayerIssue.IssueType.SuctionCup))
{
if (EmguContours.CheckContoursIntersect(new VectorOfVectorOfPoint(issue.Contours), new VectorOfVectorOfPoint(candidate.Contours))) {
if (EmguContours.ContoursIntersect(new VectorOfVectorOfPoint(issue.Contours), new VectorOfVectorOfPoint(candidate.Contours))) {
overlapFound = true;
break;
}
@@ -439,52 +451,42 @@ namespace UVtools.Core.Operations
(bool canDrill, Point location) GetDrillLocation(LayerIssue issue, int diameter)
{
var centroid = EmguCV.EmguContour.GetCentroid(new VectorOfPoint(issue.Contours[0]));
using Mat circleCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
using Mat contourMat = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
using Mat overlapCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
using var vecCentroid = new VectorOfPoint(issue.Contours[0]);
var centroid = EmguContour.GetCentroid(vecCentroid);
using var circleCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
using var contourMat = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
using var overlapCheck = EmguExtensions.InitMat(issue.BoundingRectangle.Size);
var inverseOffset = new Point(issue.BoundingRectangle.X * -1, issue.BoundingRectangle.Y * -1);
CvInvoke.DrawContours(contourMat, new VectorOfVectorOfPoint(issue.Contours), -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
using var vec = new VectorOfVectorOfPoint(issue.Contours);
CvInvoke.DrawContours(contourMat, vec, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset);
CvInvoke.Circle(circleCheck, new(centroid.X + inverseOffset.X, centroid.Y + inverseOffset.Y), diameter, EmguExtensions.WhiteColor, -1);
CvInvoke.BitwiseAnd(circleCheck, contourMat, overlapCheck);
if (CvInvoke.CountNonZero(overlapCheck) > 0)
{
/* 5px centroid is inside layer! drill baby drill */
return (true,centroid);
} else
{
/* centroid is not inside the actual contour, no drill */
return (false, new Point());
}
return CvInvoke.CountNonZero(overlapCheck) > 0
? (true,centroid) /* 5px centroid is inside layer! drill baby drill */
: (false, new Point()); /* centroid is not inside the actual contour, no drill */
}
List<PixelOperation> drillOps = new List<PixelOperation>();
var suctionReliefSize = (int)Math.Max(SlicerFile.PpmmMax * .5, 15);
var drillOps = new List<PixelOperation>();
//var suctionReliefSize = (ushort)Math.Max(SlicerFile.PpmmMax * 0.8, 17);
/* for each suction cup issue that is an initial layer */
foreach (var issue in bottomSuctionIssues)
{
var drillPoint = GetDrillLocation(issue, suctionReliefSize);
if (drillPoint.canDrill)
{
drillOps.Add(new PixelDrainHole(issue.LayerIndex, drillPoint.location, (byte)suctionReliefSize));
}
var drillPoint = GetDrillLocation(issue, _suctionCupsVentHole);
if (!drillPoint.canDrill) continue;
drillOps.Add(new PixelDrainHole(issue.LayerIndex, drillPoint.location, _suctionCupsVentHole));
}
SlicerFile.LayerManager.DrawModifications(drillOps, progress);
}
if (_removeEmptyLayers)
{
List<uint> removeLayers = new();
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
var removeLayers = new List<uint>();
for (var layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
{
if (SlicerFile[layerIndex].NonZeroPixelCount == 0)
{
+3 -3
View File
@@ -11,10 +11,10 @@ namespace UVtools.Core.PixelEditor
{
public class PixelDrainHole : PixelOperation
{
private byte _diameter = 50;
private ushort _diameter = 50;
public override PixelOperationType OperationType => PixelOperationType.DrainHole;
public byte Diameter
public ushort Diameter
{
get => _diameter;
set => RaiseAndSetIfChanged(ref _diameter, value);
@@ -22,7 +22,7 @@ namespace UVtools.Core.PixelEditor
public PixelDrainHole(){ _pixelBrightness = 0; }
public PixelDrainHole(uint layerIndex, Point location, byte diameter) : base(layerIndex, location)
public PixelDrainHole(uint layerIndex, Point location, ushort diameter) : base(layerIndex, location)
{
Diameter = diameter;
Size = new Size(diameter, diameter);
@@ -12,8 +12,7 @@
<CheckBox
IsChecked="{Binding Operation.RepairIslands}"
ToolTip.Tip="If enabled, repair will first attempt to eliminate islands smaller than the pixel area removal threshold, and then runs the “gap closure” technique."
Content="Repair islands"
/>
Content="Repair islands"/>
<!-- Repair resin traps -->
@@ -21,8 +20,13 @@
IsChecked="{Binding Operation.RepairResinTraps}"
ToolTip.Tip="If enabled, repair will fill black pixels found within a resin trap with white pixels.
&#x0a;Hollow areas will become solid."
Content="Repair resin traps"
/>
Content="Repair resin traps"/>
<!-- Repair resin traps -->
<CheckBox
IsChecked="{Binding Operation.RepairSuctionCups}"
ToolTip.Tip="If enabled, repair will drill suction cup layers with a vertical hole at the specified size."
Content="Repair suction cups"/>
<!-- Remove empty layers -->
@@ -30,12 +34,10 @@
IsChecked="{Binding Operation.RemoveEmptyLayers}"
ToolTip.Tip="If enabled, repair will remove all layers with no white pixels.
&#x0a;The model will be recalculated to ensure the correct Z height is maintained."
Content="Remove empty layers"
/>
Content="Remove empty layers"/>
</StackPanel>
<Grid ColumnDefinitions="Auto,10,190,5,Auto"
RowDefinitions="Auto,10,Auto,10,Auto,10,Auto">
<Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,10,190,5,Auto">
<!-- Remove islands equal or smaller than -->
<TextBlock Grid.Row="0" Grid.Column="0"
@@ -137,6 +139,22 @@
&#x0a;Use this field when you have aggressive blur on model and a visible margin between white pixels and the resin trap highlight area.
&#x0a;(0 = Fill the found area without overlap nearby pixels)"
Value="{Binding Operation.ResinTrapsOverlapBy}"/>
<!-- Suction cup vent hole -->
<TextBlock Grid.Row="8" Grid.Column="0"
VerticalAlignment="Center"
IsEnabled="{Binding Operation.RepairSuctionCups}"
ToolTip.Tip="Hole diameter to drill the layers with"
Text="Suction cup vent hole diameter:"/>
<NumericUpDown Grid.Row="8" Grid.Column="2"
Classes="ValueLabel ValueLabel_px"
Increment="1"
Minimum="4"
Maximum="255"
IsEnabled="{Binding Operation.RepairSuctionCups}"
ToolTip.Tip="Hole diameter to drill the layers with"
Value="{Binding Operation.SuctionCupsVentHole}"/>
</Grid>
@@ -24,11 +24,13 @@ namespace UVtools.WPF.Controls.Tools
{
RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands,
RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps,
RepairSuctionCups = UserSettings.Instance.LayerRepair.RepairSuctionCups,
RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers,
RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels,
RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations,
AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers,
ResinTrapsOverlapBy = UserSettings.Instance.LayerRepair.ResinTrapsOverlapBy,
SuctionCupsVentHole = UserSettings.Instance.LayerRepair.SuctionCupsVentHole,
GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations,
NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations,
};
@@ -37,11 +39,13 @@ namespace UVtools.WPF.Controls.Tools
{
Operation.RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands;
Operation.RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps;
Operation.RepairSuctionCups = UserSettings.Instance.LayerRepair.RepairSuctionCups;
Operation.RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers;
Operation.RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels;
Operation.RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations;
Operation.AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers;
Operation.ResinTrapsOverlapBy = UserSettings.Instance.LayerRepair.ResinTrapsOverlapBy;
Operation.SuctionCupsVentHole = UserSettings.Instance.LayerRepair.SuctionCupsVentHole;
Operation.GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations;
Operation.NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations;
}
+8 -9
View File
@@ -23,7 +23,6 @@ using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
using UVtools.WPF.Extensions;
using static UVtools.Core.LayerIssue;
using Brushes = Avalonia.Media.Brushes;
namespace UVtools.WPF
@@ -87,18 +86,18 @@ namespace UVtools.WPF
public List<LayerIssue> GetOverlappingIssues(LayerIssue targetIssue, int indexOffset)
{
List<LayerIssue> retValue = new();
var retValue = new List<LayerIssue>();
var targetLayerIndex = targetIssue.LayerIndex + indexOffset;
if (targetLayerIndex > SlicerFile.LayerCount - 1 || targetLayerIndex < 0) return retValue;
foreach (var candidate in Issues.Where(candidate => candidate.LayerIndex == targetLayerIndex && candidate.Type == LayerIssue.IssueType.SuctionCup))
{
if (EmguContours.CheckContoursIntersect(new VectorOfVectorOfPoint(targetIssue.Contours), new VectorOfVectorOfPoint(candidate.Contours)))
{
retValue.Add(candidate);
break;
}
using var vec1 = new VectorOfVectorOfPoint(targetIssue.Contours);
using var vec2 = new VectorOfVectorOfPoint(candidate.Contours);
if (!EmguContours.ContoursIntersect(vec1, vec2)) continue;
retValue.Add(candidate);
break;
}
return retValue;
@@ -242,8 +241,8 @@ namespace UVtools.WPF
{
if (issueRemoveList.Contains(issue)) continue;
Stack<LayerIssue> upDirection = new Stack<LayerIssue>();
Stack<LayerIssue> downDirection = new Stack<LayerIssue>();
var upDirection = new Stack<LayerIssue>();
var downDirection = new Stack<LayerIssue>();
upDirection.Push(issue);
downDirection.Push(issue);
+15 -1
View File
@@ -788,7 +788,7 @@ namespace UVtools.WPF
private byte _resinTrapRequiredAreaToProcessCheck = 17;
private byte _resinTrapRequiredBlackPixelsToDrain = 10;
private byte _resinTrapMaximumPixelBrightnessToDrain = 30;
private uint _suctionCupRequiredAreaToConsider = 5000;
private uint _suctionCupRequiredAreaToConsider = 10000;
private byte _touchingBoundMinimumPixelBrightness = 127;
private byte _touchingBoundMarginLeft = 5;
private byte _touchingBoundMarginTop = 5;
@@ -1219,11 +1219,13 @@ namespace UVtools.WPF
{
private bool _repairIslands = true;
private bool _repairResinTraps = true;
private bool _repairSuctionCups;
private bool _removeEmptyLayers = true;
private ushort _removeIslandsBelowEqualPixels = 5;
private ushort _removeIslandsRecursiveIterations = 4;
private ushort _attachIslandsBelowLayers = 2;
private byte _resinTrapsOverlapBy = 0;
private byte _suctionCupsVentHole = 16;
private byte _closingIterations = 2;
private byte _openingIterations = 0;
@@ -1239,6 +1241,12 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _repairResinTraps, value);
}
public bool RepairSuctionCups
{
get => _repairSuctionCups;
set => RaiseAndSetIfChanged(ref _repairSuctionCups, value);
}
public bool RemoveEmptyLayers
{
get => _removeEmptyLayers;
@@ -1269,6 +1277,12 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _resinTrapsOverlapBy, value);
}
public byte SuctionCupsVentHole
{
get => _suctionCupsVentHole;
set => RaiseAndSetIfChanged(ref _suctionCupsVentHole, value);
}
public byte ClosingIterations
{
get => _closingIterations;
+14 -3
View File
@@ -1596,11 +1596,13 @@
<TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="Default values"/>
<CheckBox IsChecked="{Binding Settings.LayerRepair.RepairIslands}"
Margin="10,0" Content="Attempt to repair islands by default"/>
Margin="10,0" Content="Attempt to repair islands by default"/>
<CheckBox IsChecked="{Binding Settings.LayerRepair.RepairResinTraps}"
Margin="10,0" Content="Attempt to repair resin traps by default"/>
Margin="10,0" Content="Attempt to repair resin traps by default"/>
<CheckBox IsChecked="{Binding Settings.LayerRepair.RepairSuctionCups}"
Margin="10,0" Content="Attempt to repair suction cups by default"/>
<CheckBox IsChecked="{Binding Settings.LayerRepair.RemoveEmptyLayers}"
Margin="10,0" Content="Remove empty layers by default"/>
Margin="10,0" Content="Remove empty layers by default"/>
<StackPanel Orientation="Horizontal" Margin="10,0" Spacing="10">
<NumericUpDown
@@ -1640,6 +1642,15 @@
<TextBlock VerticalAlignment="Center" Text="Resin traps: Overlap and expand resin trap area by this value in pixels (0 = Disabled)"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,0" Spacing="10">
<NumericUpDown
Value="{Binding Settings.LayerRepair.SuctionCupsVentHole}"
Width="70"
Minimum="4"
Maximum="255"/>
<TextBlock VerticalAlignment="Center" Text="Suction cups: Hole diameter to drill the layers with"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,0" Spacing="10">
<NumericUpDown
Value="{Binding Settings.LayerRepair.ClosingIterations}"