mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-11 19:12:31 +02:00
29c485458c
- (Upgrade) EmguCV from 4.5.1 to 4.5.2 - **File formats:** - (Add) Getter `IsAntiAliasingEmulated`: Gets whatever a file format uses real or emulated AntiAliasing - (Add) Getter `IsDisplayPortrait`: Gets if the display is in portrait mode - (Add) Getter `IsDisplayLandscape`: Gets if the display is in landscape mode - **Tool - Resize:** (#235) - (Fix) Division by 0 when start layer is equal to end layer - (Fix) Calculations when using the option "Increase or decrease towards 100%" - (Add) About window: OpenCV build number and a button to copy build information to clipboard - (Improvement) Exposure exposure finder: Improve the **Multiple brightness** section to auto fill with correct values for file formats that use time fractions to emulate AntiAliasing, this can be used to replace the **Multiple exposures** section - (Fix) UVJ: Error when using a null or empty layer array on manifest `config.json` file (#232) - (Fix) GCode parser: When only a G4/wait command is present on a layer it was setting the global exposure time and discard the this value per layer
422 lines
17 KiB
C#
422 lines
17 KiB
C#
/*
|
|
* GNU AFFERO GENERAL PUBLIC LICENSE
|
|
* Version 3, 19 November 2007
|
|
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
* Everyone is permitted to copy and distribute verbatim copies
|
|
* of this license document, but changing it is not allowed.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Serialization;
|
|
using Emgu.CV;
|
|
using Emgu.CV.CvEnum;
|
|
using Emgu.CV.Util;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
|
|
namespace UVtools.Core.Operations
|
|
{
|
|
[Serializable]
|
|
public class OperationRepairLayers : Operation
|
|
{
|
|
#region Members
|
|
private bool _repairIslands = true;
|
|
private bool _repairResinTraps = true;
|
|
private bool _removeEmptyLayers = true;
|
|
private ushort _removeIslandsBelowEqualPixelCount = 5;
|
|
private ushort _removeIslandsRecursiveIterations = 4;
|
|
private ushort _attachIslandsBelowLayers = 2;
|
|
private uint _gapClosingIterations = 1;
|
|
private uint _noiseRemovalIterations;
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
public override bool CanROI => false;
|
|
public override string Title => "Repair layers and issues";
|
|
public override string Description => null;
|
|
|
|
public override string ConfirmationText => "attempt this repair?";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Reparing layers {LayerIndexStart} through {LayerIndexEnd}";
|
|
|
|
public override string ProgressAction => "Repaired layers";
|
|
|
|
public override bool CanHaveProfiles => false;
|
|
|
|
public override string ValidateInternally()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (!RepairIslands && !RemoveEmptyLayers && !RepairResinTraps)
|
|
{
|
|
sb.AppendLine("You must select at least one repair operation.");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationRepairLayers() { }
|
|
|
|
public OperationRepairLayers(FileFormat slicerFile) : base(slicerFile) { }
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
public bool RepairIslands
|
|
{
|
|
get => _repairIslands;
|
|
set => RaiseAndSetIfChanged(ref _repairIslands, value);
|
|
}
|
|
|
|
public bool RepairResinTraps
|
|
{
|
|
get => _repairResinTraps;
|
|
set => RaiseAndSetIfChanged(ref _repairResinTraps, value);
|
|
}
|
|
|
|
public bool RemoveEmptyLayers
|
|
{
|
|
get => _removeEmptyLayers;
|
|
set => RaiseAndSetIfChanged(ref _removeEmptyLayers, value);
|
|
}
|
|
|
|
public ushort RemoveIslandsBelowEqualPixelCount
|
|
{
|
|
get => _removeIslandsBelowEqualPixelCount;
|
|
set => RaiseAndSetIfChanged(ref _removeIslandsBelowEqualPixelCount, value);
|
|
}
|
|
|
|
public ushort RemoveIslandsRecursiveIterations
|
|
{
|
|
get => _removeIslandsRecursiveIterations;
|
|
set => RaiseAndSetIfChanged(ref _removeIslandsRecursiveIterations, value);
|
|
}
|
|
|
|
public ushort AttachIslandsBelowLayers
|
|
{
|
|
get => _attachIslandsBelowLayers;
|
|
set => RaiseAndSetIfChanged(ref _attachIslandsBelowLayers, value);
|
|
}
|
|
|
|
public uint GapClosingIterations
|
|
{
|
|
get => _gapClosingIterations;
|
|
set => RaiseAndSetIfChanged(ref _gapClosingIterations, value);
|
|
}
|
|
|
|
public uint NoiseRemovalIterations
|
|
{
|
|
get => _noiseRemovalIterations;
|
|
set => RaiseAndSetIfChanged(ref _noiseRemovalIterations, value);
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public IslandDetectionConfiguration IslandDetectionConfig { get; set; }
|
|
|
|
[XmlIgnore]
|
|
public List<LayerIssue> Issues { get; set; }
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
// Remove islands
|
|
if (Issues is not null
|
|
&& IslandDetectionConfig is not null
|
|
&& _repairIslands
|
|
&& _removeIslandsBelowEqualPixelCount > 0
|
|
&& _removeIslandsRecursiveIterations != 1)
|
|
{
|
|
progress.Reset("Removed recursive islands");
|
|
ushort limit = _removeIslandsRecursiveIterations == 0
|
|
? ushort.MaxValue
|
|
: _removeIslandsRecursiveIterations;
|
|
|
|
var recursiveIssues = Issues;
|
|
var islandsToRecompute = new ConcurrentBag<uint>();
|
|
var islandConfig = IslandDetectionConfig.Clone();
|
|
var overhangConfig = new OverhangDetectionConfiguration(false);
|
|
var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false);
|
|
var printHeightConfig = new PrintHeightDetectionConfiguration(false);
|
|
var resinTrapsConfig = new ResinTrapDetectionConfiguration(false);
|
|
var emptyLayersConfig = false;
|
|
|
|
islandConfig.Enabled = true;
|
|
islandConfig.RequiredAreaToProcessCheck = (ushort)(_removeIslandsBelowEqualPixelCount / 2);
|
|
|
|
for (uint i = 0; i < limit; i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
/*var whiteList = islandsToRecompute.GroupBy(u => u)
|
|
.Select(grp => grp.First())
|
|
.ToList();*/
|
|
islandConfig.WhiteListLayers = islandsToRecompute.ToList();
|
|
recursiveIssues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, printHeightConfig, emptyLayersConfig);
|
|
//Debug.WriteLine(i);
|
|
}
|
|
|
|
var issuesGroup =
|
|
recursiveIssues
|
|
.Where(issue => issue.Type == LayerIssue.IssueType.Island &&
|
|
issue.Pixels.Length <= RemoveIslandsBelowEqualPixelCount)
|
|
.GroupBy(issue => issue.LayerIndex);
|
|
|
|
if (!issuesGroup.Any()) break; // Nothing to process
|
|
|
|
islandsToRecompute.Clear();
|
|
Parallel.ForEach(issuesGroup, group =>
|
|
{
|
|
if (progress.Token.IsCancellationRequested) return;
|
|
Layer layer = SlicerFile[group.Key];
|
|
Mat image = layer.LayerMat;
|
|
Span<byte> bytes = image.GetDataSpan<byte>();
|
|
foreach (var issue in group)
|
|
{
|
|
foreach (var issuePixel in issue.Pixels)
|
|
{
|
|
bytes[image.GetPixelPos(issuePixel)] = 0;
|
|
}
|
|
|
|
progress.LockAndIncrement();
|
|
}
|
|
|
|
var nextLayerIndex = group.Key + 1;
|
|
if (nextLayerIndex < SlicerFile.LayerCount)
|
|
islandsToRecompute.Add(nextLayerIndex);
|
|
|
|
layer.LayerMat = image;
|
|
});
|
|
|
|
// Remove from main list due the replicate below repair
|
|
Issues.RemoveAll(issue => issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= RemoveIslandsBelowEqualPixelCount);
|
|
|
|
if (islandsToRecompute.IsEmpty) break; // No more leftovers
|
|
}
|
|
}
|
|
|
|
if (_repairIslands && _attachIslandsBelowLayers > 0)
|
|
{
|
|
var islandsToProcess = Issues;
|
|
|
|
if (islandsToProcess is null)
|
|
{
|
|
var islandConfig = IslandDetectionConfig.Clone();
|
|
var overhangConfig = new OverhangDetectionConfiguration(false);
|
|
var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false);
|
|
var printHeightConfig = new PrintHeightDetectionConfiguration(false);
|
|
var resinTrapsConfig = new ResinTrapDetectionConfiguration(false);
|
|
var emptyLayersConfig = false;
|
|
|
|
islandConfig.Enabled = true;
|
|
|
|
islandsToProcess = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, printHeightConfig, emptyLayersConfig, null, progress);
|
|
}
|
|
|
|
var issuesGroup =
|
|
islandsToProcess
|
|
.Where(issue => issue.Type == LayerIssue.IssueType.Island)
|
|
.GroupBy(issue => issue.LayerIndex);
|
|
|
|
|
|
progress.Reset("Attempt to attach islands below", (uint) islandsToProcess.Count);
|
|
Parallel.ForEach(issuesGroup, group =>
|
|
{
|
|
using var mat = SlicerFile[group.Key].LayerMat;
|
|
var matSpan = mat.GetDataByteSpan();
|
|
var matCache = new Dictionary<uint, Mat>();
|
|
var matCacheModified = new Dictionary<uint, bool>();
|
|
var startLayer = Math.Max(0, (int)group.Key - 2);
|
|
var lowestPossibleLayer = (uint)Math.Max(0, (int)group.Key - 1 - _attachIslandsBelowLayers);
|
|
|
|
for (var layerIndex = startLayer+1; layerIndex >= lowestPossibleLayer; layerIndex--)
|
|
{
|
|
Debug.WriteLine(layerIndex);
|
|
Monitor.Enter(SlicerFile[layerIndex].Mutex);
|
|
matCache.Add((uint) layerIndex, SlicerFile[layerIndex].LayerMat);
|
|
matCacheModified.Add((uint) layerIndex, false);
|
|
}
|
|
|
|
foreach (var issue in group)
|
|
{
|
|
int foundAt = startLayer == 0 ? 0 : - 1;
|
|
var requiredSupportingPixels = Math.Max(1, issue.PixelsCount * IslandDetectionConfig.RequiredPixelsToSupportMultiplier);
|
|
|
|
for (var layerIndex = startLayer; layerIndex >= lowestPossibleLayer && foundAt < 0; layerIndex--)
|
|
{
|
|
uint pixelsSupportingIsland = 0;
|
|
|
|
unsafe
|
|
{
|
|
var span = matCache[(uint) layerIndex].GetBytePointer();
|
|
foreach (var point in issue.Pixels)
|
|
{
|
|
if (span[mat.GetPixelPos(point)] <
|
|
IslandDetectionConfig.RequiredPixelBrightnessToSupport)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pixelsSupportingIsland++;
|
|
|
|
if (pixelsSupportingIsland >= requiredSupportingPixels)
|
|
{
|
|
foundAt = layerIndex + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy pixels
|
|
if (foundAt >= 0)
|
|
{
|
|
for (var layerIndex = startLayer + 1; layerIndex >= foundAt; layerIndex--)
|
|
{
|
|
matCacheModified[(uint) layerIndex] = true;
|
|
unsafe
|
|
{
|
|
var span = matCache[(uint) layerIndex].GetBytePointer();
|
|
foreach (var point in issue.Pixels)
|
|
{
|
|
var pos = mat.GetPixelPos(point);
|
|
span[pos] = (byte) Math.Min(span[pos] + matSpan[pos], byte.MaxValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
progress.LockAndIncrement();
|
|
}
|
|
|
|
foreach (var dict in matCache)
|
|
{
|
|
if (matCacheModified[dict.Key])
|
|
{
|
|
SlicerFile[dict.Key].LayerMat = dict.Value;
|
|
}
|
|
dict.Value.Dispose();
|
|
Monitor.Exit(SlicerFile[dict.Key].Mutex);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
progress.Reset(ProgressAction, LayerRangeCount);
|
|
if (_repairIslands || _repairResinTraps)
|
|
{
|
|
Parallel.For(LayerIndexStart, LayerIndexEnd, layerIndex =>
|
|
{
|
|
if (progress.Token.IsCancellationRequested) return;
|
|
var layer = SlicerFile[layerIndex];
|
|
Mat image = null;
|
|
|
|
void initImage()
|
|
{
|
|
image ??= layer.LayerMat;
|
|
}
|
|
|
|
if (Issues is not null)
|
|
{
|
|
if (_repairIslands && _removeIslandsBelowEqualPixelCount > 0 && _removeIslandsRecursiveIterations == 1)
|
|
{
|
|
Span<byte> bytes = null;
|
|
foreach (var issue in Issues)
|
|
{
|
|
if (
|
|
issue.LayerIndex != layerIndex ||
|
|
issue.Type != LayerIssue.IssueType.Island ||
|
|
issue.Pixels.Length > _removeIslandsBelowEqualPixelCount) continue;
|
|
|
|
initImage();
|
|
if (bytes == null)
|
|
bytes = image.GetDataSpan<byte>();
|
|
|
|
foreach (var issuePixel in issue.Pixels)
|
|
{
|
|
bytes[image.GetPixelPos(issuePixel)] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_repairResinTraps)
|
|
{
|
|
foreach (var issue in Issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.ResinTrap))
|
|
{
|
|
initImage();
|
|
using var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels));
|
|
CvInvoke.DrawContours(image,
|
|
vec,
|
|
-1,
|
|
EmguExtensions.WhiteColor,
|
|
-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_repairIslands && (_gapClosingIterations > 0 || _noiseRemovalIterations > 0))
|
|
{
|
|
initImage();
|
|
using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
|
|
new Point(-1, -1));
|
|
if (_gapClosingIterations > 0)
|
|
{
|
|
CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1),
|
|
(int)_gapClosingIterations, BorderType.Default, default);
|
|
}
|
|
|
|
if (_noiseRemovalIterations > 0)
|
|
{
|
|
CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1),
|
|
(int)_noiseRemovalIterations, BorderType.Default, default);
|
|
}
|
|
}
|
|
|
|
if (image is not null)
|
|
{
|
|
layer.LayerMat = image;
|
|
image.Dispose();
|
|
}
|
|
|
|
progress.LockAndIncrement();
|
|
});
|
|
}
|
|
|
|
if (_removeEmptyLayers)
|
|
{
|
|
List<uint> removeLayers = new();
|
|
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
|
|
{
|
|
if (SlicerFile[layerIndex].NonZeroPixelCount == 0)
|
|
{
|
|
removeLayers.Add(layerIndex);
|
|
}
|
|
}
|
|
|
|
if (removeLayers.Count > 0)
|
|
{
|
|
OperationLayerRemove.RemoveLayers(SlicerFile, removeLayers, progress);
|
|
}
|
|
}
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|