From ad92a0aa5cef5a9be46e2ba20ac5482fc9030015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Thu, 20 May 2021 05:12:18 +0100 Subject: v2.12.2 - (Add) Layer action - Export layers to heat map: Export a layer range to a grayscale heat map image that represents the median of the mass in the Z depth/perception. The pixel brightness/intensity shows where the most mass are concentrated. --- CHANGELOG.md | 4 + UVtools.Core/Operations/Operation.cs | 2 +- .../Operations/OperationLayerExportHeatMap.cs | 159 +++++++++++++++++++++ UVtools.Core/Operations/OperationLayerImport.cs | 2 +- UVtools.Core/UVtools.Core.csproj | 2 +- UVtools.WPF/Assets/Icons/file-gif-16x16.png | Bin 0 -> 266 bytes UVtools.WPF/Assets/Icons/gif-16x16.png | Bin 266 -> 0 bytes UVtools.WPF/Controls/Helpers.cs | 43 +++++- .../Tools/ToolLayerExportHeatMapControl.axaml | 32 +++++ .../Tools/ToolLayerExportHeatMapControl.axaml.cs | 37 +++++ UVtools.WPF/MainWindow.axaml.cs | 10 +- UVtools.WPF/UVtools.WPF.csproj | 10 +- build/CreateRelease.WPF.ps1 | 2 +- 13 files changed, 287 insertions(+), 16 deletions(-) create mode 100644 UVtools.Core/Operations/OperationLayerExportHeatMap.cs create mode 100644 UVtools.WPF/Assets/Icons/file-gif-16x16.png delete mode 100644 UVtools.WPF/Assets/Icons/gif-16x16.png create mode 100644 UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml create mode 100644 UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 923fad4..6702ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## /05/2021 - v2.12.2 + +- (Add) Layer action - Export layers to heat map: Export a layer range to a grayscale heat map image that represents the median of the mass in the Z depth/perception. The pixel brightness/intensity shows where the most mass are concentrated. + ## 19/05/2021 - v2.12.1 - (Upgrade) AvaloniaUI from 0.10.4 to 0.10.5 diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index 5cb7cad..f8b9b10 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -405,7 +405,7 @@ namespace UVtools.Core.Operations { if (!HaveMask) return null; - var mask = mat.CloneBlank(); + var mask = EmguExtensions.InitMat(mat.Size); using VectorOfVectorOfPoint vec = new(points); CvInvoke.DrawContours(mask, vec, -1, EmguExtensions.WhiteByte, -1); return GetRoiOrDefault(mask); diff --git a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs new file mode 100644 index 0000000..eb6482c --- /dev/null +++ b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs @@ -0,0 +1,159 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationLayerExportHeatMap : Operation + { + #region Members + private string _filePath; + private bool _cropByRoi = true; + + #endregion + + #region Overrides + + public override bool CanHaveProfiles => false; + public override string Title => "Export layers to heat map"; + + public override string Description => + "Export a layer range to a grayscale heat map image that represents the median of the mass in the Z depth/perception\n" + + "The pixel brightness/intensity shows where the most mass are concentrated."; + + public override string ConfirmationText => + $"generate a heatmap from layers {LayerIndexStart} through {LayerIndexEnd}?"; + + public override string ProgressTitle => + $"Generating a heatmap from layers {LayerIndexStart} through {LayerIndexEnd}"; + + public override string ProgressAction => "Packed layers"; + + public override string ValidateInternally() + { + var sb = new StringBuilder(); + + if (LayerRangeCount < 2) + { + sb.AppendLine("To generate a heat map at least two layers are required."); + } + + return sb.ToString(); + } + + public override string ToString() + { + var result = $"[Crop by ROI: {_cropByRoi}]" + + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Properties + + public string FilePath + { + get => _filePath; + set => RaiseAndSetIfChanged(ref _filePath, value); + } + + public bool CropByROI + { + get => _cropByRoi; + set => RaiseAndSetIfChanged(ref _cropByRoi, value); + } + + #endregion + + #region Constructor + + public OperationLayerExportHeatMap() + { } + + public OperationLayerExportHeatMap(FileFormat slicerFile) : base(slicerFile) + { + _filePath = SlicerFile.FileFullPath + ".heatmap.png"; + } + + #endregion + + #region Methods + + protected override bool ExecuteInternally(OperationProgress progress) + { + using var sumMat32 = EmguExtensions.InitMat(SlicerFile.Resolution, 1, DepthType.Cv32S); + var sumMat32Roi = GetRoiOrDefault(sumMat32); + using var mask = GetMask(sumMat32); + + + Parallel.For(LayerIndexStart, LayerIndexEnd+1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + using var mat = SlicerFile[layerIndex].LayerMat; + using var mat32 = new Mat(); + mat.ConvertTo(mat32, DepthType.Cv32S); + var mat32Roi = GetRoiOrDefault(mat32); + + lock (progress.Mutex) + { + CvInvoke.Add(sumMat32Roi, mat32Roi, sumMat32Roi, mask); + progress++; + } + }); + + if (!progress.Token.IsCancellationRequested) + { + using var sumMat = EmguExtensions.InitMat(sumMat32.Size); + sumMat32.ConvertTo(sumMat, DepthType.Cv8U, 1.0 / LayerRangeCount); + if (_cropByRoi && HaveROI) + { + var sumMatRoi = GetRoiOrDefault(sumMat); + sumMatRoi.Save(_filePath); + } + else + { + sumMat.Save(_filePath); + } + } + + return !progress.Token.IsCancellationRequested; + } + + #endregion + + #region Equality + + private bool Equals(OperationLayerExportHeatMap other) + { + return _filePath == other._filePath && _cropByRoi == other._cropByRoi; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationLayerExportHeatMap other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_filePath, _cropByRoi); + } + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index c1be566..5f10340 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -53,7 +53,7 @@ namespace UVtools.Core.Operations public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; public override bool CanROI => false; - public override string Title => "Import Layers"; + public override string Title => "Import layers"; public override string Description => "Import layers from local files into the model at a selected layer height.\n" + diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 9dedee7..5a6763a 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 2.12.1 + 2.12.2 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 diff --git a/UVtools.WPF/Assets/Icons/file-gif-16x16.png b/UVtools.WPF/Assets/Icons/file-gif-16x16.png new file mode 100644 index 0000000..ac06f8c Binary files /dev/null and b/UVtools.WPF/Assets/Icons/file-gif-16x16.png differ diff --git a/UVtools.WPF/Assets/Icons/gif-16x16.png b/UVtools.WPF/Assets/Icons/gif-16x16.png deleted file mode 100644 index ac06f8c..0000000 Binary files a/UVtools.WPF/Assets/Icons/gif-16x16.png and /dev/null differ diff --git a/UVtools.WPF/Controls/Helpers.cs b/UVtools.WPF/Controls/Helpers.cs index 61046f2..ed5d288 100644 --- a/UVtools.WPF/Controls/Helpers.cs +++ b/UVtools.WPF/Controls/Helpers.cs @@ -24,9 +24,48 @@ namespace UVtools.WPF.Controls "bmp", "jpeg", "jpg", - "gif" + "tif", + "tiff", } - } + }, + }; + + public static readonly List ImagesFullFileFilter = new() + { + new() + { + Name = "PNG Files", + Extensions = new List + { + "png" + } + }, + new() + { + Name = "JPG Files", + Extensions = new List + { + "jpg", + "jpeg" + } + }, + new() + { + Name = "BMP Files", + Extensions = new List + { + "bmp", + } + }, + new() + { + Name = "TIF Files", + Extensions = new List + { + "tif", + "tiff", + } + }, }; public static readonly List PngFileFilter = new() diff --git a/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml new file mode 100644 index 0000000..cb9dcc1 --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml.cs new file mode 100644 index 0000000..6c6d74e --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Controls.Tools +{ + public partial class ToolLayerExportHeatMapControl : ToolControl + { + public OperationLayerExportHeatMap Operation => BaseOperation as OperationLayerExportHeatMap; + public ToolLayerExportHeatMapControl() + { + InitializeComponent(); + BaseOperation = new OperationLayerExportHeatMap(SlicerFile); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public async void ChooseFilePath() + { + var dialog = new SaveFileDialog + { + Filters = Helpers.ImagesFullFileFilter, + InitialFileName = Path.GetFileName(SlicerFile.FileFullPath) + ".heatmap.png", + Directory = Path.GetDirectoryName(SlicerFile.FileFullPath), + }; + var file = await dialog.ShowAsync(ParentWindow); + if (string.IsNullOrWhiteSpace(file)) return; + Operation.FilePath = file; + } + } +} diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index d0ffafc..14750fc 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -338,7 +338,15 @@ namespace UVtools.WPF Tag = new OperationLayerExportGif(), Icon = new Avalonia.Controls.Image { - Source = new Bitmap(App.GetAsset("/Assets/Icons/gif-16x16.png")) + Source = new Bitmap(App.GetAsset("/Assets/Icons/file-gif-16x16.png")) + } + }, + new() + { + Tag = new OperationLayerExportHeatMap(), + Icon = new Avalonia.Controls.Image + { + Source = new Bitmap(App.GetAsset("/Assets/Icons/file-image-16x16.png")) } }, }; diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 564edcf..e54e0d5 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ LICENSE https://github.com/sn4k3/UVtools Git - 2.12.1 + 2.12.2 @@ -73,12 +73,4 @@ - - - ToolLayerArithmeticControl.axaml - - - PrusaSlicerManagerWindow.axaml - - diff --git a/build/CreateRelease.WPF.ps1 b/build/CreateRelease.WPF.ps1 index 086b533..f15382f 100644 --- a/build/CreateRelease.WPF.ps1 +++ b/build/CreateRelease.WPF.ps1 @@ -34,7 +34,7 @@ Set-Location $PSScriptRoot\.. #################################### $enableMSI = $true #$buildOnly = $null -$buildOnly = "win-x64" +#$buildOnly = "win-x64" # Profilling $stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch $deployStopWatch = New-Object -TypeName System.Diagnostics.Stopwatch -- cgit v1.2.3