diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | UVtools.Core/Operations/Operation.cs | 2 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationLayerExportHeatMap.cs | 159 | ||||
-rw-r--r-- | UVtools.Core/Operations/OperationLayerImport.cs | 2 | ||||
-rw-r--r-- | UVtools.Core/UVtools.Core.csproj | 2 | ||||
-rw-r--r-- | UVtools.WPF/Assets/Icons/file-gif-16x16.png (renamed from UVtools.WPF/Assets/Icons/gif-16x16.png) | bin | 266 -> 266 bytes | |||
-rw-r--r-- | UVtools.WPF/Controls/Helpers.cs | 43 | ||||
-rw-r--r-- | UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml | 32 | ||||
-rw-r--r-- | UVtools.WPF/Controls/Tools/ToolLayerExportHeatMapControl.axaml.cs | 37 | ||||
-rw-r--r-- | UVtools.WPF/MainWindow.axaml.cs | 10 | ||||
-rw-r--r-- | UVtools.WPF/UVtools.WPF.csproj | 10 | ||||
-rw-r--r-- | build/CreateRelease.WPF.ps1 | 2 |
12 files changed, 287 insertions, 16 deletions
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. <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.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 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.12.1</Version> + <Version>2.12.2</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.WPF/Assets/Icons/gif-16x16.png b/UVtools.WPF/Assets/Icons/file-gif-16x16.png Binary files differindex ac06f8c..ac06f8c 100644 --- a/UVtools.WPF/Assets/Icons/gif-16x16.png +++ b/UVtools.WPF/Assets/Icons/file-gif-16x16.png 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<FileDialogFilter> ImagesFullFileFilter = new() + { + new() + { + Name = "PNG Files", + Extensions = new List<string> + { + "png" + } + }, + new() + { + Name = "JPG Files", + Extensions = new List<string> + { + "jpg", + "jpeg" + } + }, + new() + { + Name = "BMP Files", + Extensions = new List<string> + { + "bmp", + } + }, + new() + { + Name = "TIF Files", + Extensions = new List<string> + { + "tif", + "tiff", + } + }, }; public static readonly List<FileDialogFilter> 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 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="UVtools.WPF.Controls.Tools.ToolLayerExportHeatMapControl"> + + <StackPanel Spacing="10"> + + <StackPanel Orientation="Horizontal" Spacing="5"> + <TextBox + Watermark="Output filepath" + UseFloatingWatermark="True" + VerticalAlignment="Center" + IsReadOnly="True" + Width="500" + Text="{Binding Operation.FilePath}"/> + <Button + VerticalAlignment="Stretch" + Command="{Binding ChooseFilePath}"> + <Image Source="/Assets/Icons/open-16x16.png"/> + </Button> + </StackPanel> + + <CheckBox + Content="Crop image by selected ROI" + IsVisible="{Binding ParentWindow.IsROIVisible}" + IsChecked="{Binding Operation.CropByROI}"/> + + </StackPanel> + +</UserControl> 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 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.12.1</Version> + <Version>2.12.2</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -73,12 +73,4 @@ </AvaloniaResource> <AvaloniaResource Include="Assets\Icons\*" /> </ItemGroup> - <ItemGroup> - <Compile Update="Controls\Tools\ToolLayerArithmeticControl.axaml.cs"> - <DependentUpon>ToolLayerArithmeticControl.axaml</DependentUpon> - </Compile> - <Compile Update="Windows\PrusaSlicerManagerWindow.axaml.cs"> - <DependentUpon>PrusaSlicerManagerWindow.axaml</DependentUpon> - </Compile> - </ItemGroup> </Project> 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 |