feat: Implement face encoder functionality with GPU support and recursive option
This commit is contained in:
parent
daf3b5ad2c
commit
988a3d94e1
10 changed files with 790 additions and 219 deletions
|
|
@ -5,13 +5,13 @@
|
|||
<ScrollViewer>
|
||||
<StackPanel Margin="4" Spacing="6">
|
||||
<TextBlock Text="Face Recognition Encoder" FontWeight="Bold" />
|
||||
<TextBlock Text="Esegue face_encoder.exe usando la cartella Destinazione corrente come --images e un file .pkl come --out."
|
||||
<TextBlock Text="Esegue il face encoder usando la cartella Destinazione corrente come --images e un file .pkl come --out."
|
||||
TextWrapping="Wrap" Opacity="0.8" />
|
||||
|
||||
<TextBlock Text="Eseguibile" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="face_encoder:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceExecutablePathTextBox" Text="{Binding FaceExecutablePath, Mode=TwoWay}" Watermark="C:\\tools\\face_encoder.exe" />
|
||||
<TextBox Grid.Column="1" Name="FaceExecutablePathTextBox" Text="{Binding FaceExecutablePath, Mode=TwoWay}" Watermark="C:\\tools\\face_encoder_cpu.exe" />
|
||||
<Button Grid.Column="2" Name="FaceSelectExecutableButton" Click="SelectFaceExecutable_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
|
|
@ -26,6 +26,16 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<CheckBox Content="Ricorsivo (--recursive)" IsChecked="{Binding FaceRecursive, Mode=TwoWay}" />
|
||||
<CheckBox Content="Usa GPU"
|
||||
IsChecked="{Binding UseFaceGpu, Mode=TwoWay}"
|
||||
IsEnabled="{Binding FaceGpuOptionEnabled}" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="Se l'eseguibile termina con _cpu o _gpu, il checkbox GPU cambia automaticamente il file usato. Se non c'e il suffisso, viene trattato come CPU."
|
||||
TextWrapping="Wrap"
|
||||
Opacity="0.75" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceDestinationPathTextBox" Text="{Binding DestinationPath, Mode=OneWay}" IsReadOnly="True" />
|
||||
|
|
@ -56,12 +66,14 @@
|
|||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,6,0,0">
|
||||
<Button Name="FaceRunButton" Content="Esegui Face Encoder" Click="RunFaceEncoder_Click" />
|
||||
<TextBlock Name="FaceStatusTextBlock" VerticalAlignment="Center" />
|
||||
<Button Name="FaceRunButton" Content="Esegui Face Encoder" Command="{Binding StartFaceEncoderCommand}" />
|
||||
<Button Content="Stop" Command="{Binding StopFaceEncoderCommand}" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding FaceStatusMessage}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Output comando" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<TextBox Name="FaceOutputTextBox"
|
||||
Text="{Binding FaceCommandOutput}"
|
||||
IsReadOnly="True"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ImageCatalog_2.AvaloniaViews;
|
||||
|
||||
public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
||||
{
|
||||
private readonly ILogger<FaceAiTabView> _logger;
|
||||
|
||||
public FaceAiTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_logger = Program.ServiceProvider.GetService(typeof(ILogger<FaceAiTabView>)) as ILogger<FaceAiTabView>
|
||||
?? NullLogger<FaceAiTabView>.Instance;
|
||||
}
|
||||
|
||||
private async void SelectFaceExecutable_Click(object? sender, RoutedEventArgs e)
|
||||
|
|
@ -39,7 +31,7 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
|
||||
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Seleziona face_encoder.exe",
|
||||
Title = "Seleziona face_encoder_cpu.exe o face_encoder_gpu.exe",
|
||||
FileTypeFilter =
|
||||
[
|
||||
new FilePickerFileType("Eseguibile") { Patterns = ["*.exe"] },
|
||||
|
|
@ -160,167 +152,6 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
|
||||
}
|
||||
|
||||
private async void RunFaceEncoder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var executableBox = this.FindControl<Avalonia.Controls.TextBox>("FaceExecutablePathTextBox");
|
||||
var outputFolderBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputFolderTextBox");
|
||||
var outputLogBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputTextBox");
|
||||
var statusBlock = this.FindControl<TextBlock>("FaceStatusTextBlock");
|
||||
var runButton = this.FindControl<Avalonia.Controls.Button>("FaceRunButton");
|
||||
|
||||
if (executableBox is null || outputFolderBox is null || outputLogBox is null || statusBlock is null || runButton is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DataContext is not DataModel model)
|
||||
{
|
||||
statusBlock.Text = "DataContext non valido.";
|
||||
return;
|
||||
}
|
||||
|
||||
var executablePath = executableBox.Text?.Trim().Trim('"') ?? string.Empty;
|
||||
var outputFilePath = outputFolderBox.Text?.Trim().Trim('"') ?? string.Empty;
|
||||
var imagesFolder = (model.DestinationPath ?? string.Empty).Trim().Trim('"');
|
||||
|
||||
model.FaceExecutablePath = executablePath;
|
||||
model.FaceOutputFolderPath = outputFilePath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(executablePath) || !File.Exists(executablePath))
|
||||
{
|
||||
statusBlock.Text = "Percorso eseguibile non valido.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(imagesFolder) || !Directory.Exists(imagesFolder))
|
||||
{
|
||||
statusBlock.Text = "Cartella Destinazione non valida.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(outputFilePath))
|
||||
{
|
||||
statusBlock.Text = "Inserisci il file di output .pkl.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(Path.GetExtension(outputFilePath), ".pkl", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statusBlock.Text = "Il file di output deve avere estensione .pkl.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var outputDirectory = Path.GetDirectoryName(outputFilePath);
|
||||
if (!string.IsNullOrWhiteSpace(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unable to create face output directory for file: {OutputFilePath}", outputFilePath);
|
||||
statusBlock.Text = "Impossibile creare la cartella del file di output.";
|
||||
return;
|
||||
}
|
||||
|
||||
runButton.IsEnabled = false;
|
||||
statusBlock.Text = "Esecuzione face encoder in corso...";
|
||||
outputLogBox.Text = string.Empty;
|
||||
|
||||
var outputLines = new StringBuilder();
|
||||
var errorLines = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
var imagesFolderArg = NormalizeDirectoryPathArgument(imagesFolder);
|
||||
var outputFileArg = NormalizeFilePathArgument(outputFilePath);
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
WorkingDirectory = Path.GetDirectoryName(executablePath) ?? Environment.CurrentDirectory,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
processStartInfo.ArgumentList.Add("--images");
|
||||
processStartInfo.ArgumentList.Add(imagesFolderArg);
|
||||
processStartInfo.ArgumentList.Add("--out");
|
||||
processStartInfo.ArgumentList.Add(outputFileArg);
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (outputLines)
|
||||
{
|
||||
outputLines.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (errorLines)
|
||||
{
|
||||
errorLines.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
if (!process.Start())
|
||||
{
|
||||
throw new InvalidOperationException("Avvio face_encoder.exe fallito.");
|
||||
}
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
await process.WaitForExitAsync().ConfigureAwait(true);
|
||||
|
||||
var summary = new StringBuilder();
|
||||
summary.AppendLine($"Exit code: {process.ExitCode}");
|
||||
|
||||
if (outputLines.Length > 0)
|
||||
{
|
||||
summary.AppendLine();
|
||||
summary.AppendLine("STDOUT:");
|
||||
summary.Append(outputLines);
|
||||
}
|
||||
|
||||
if (errorLines.Length > 0)
|
||||
{
|
||||
summary.AppendLine();
|
||||
summary.AppendLine("STDERR:");
|
||||
summary.Append(errorLines);
|
||||
}
|
||||
|
||||
outputLogBox.Text = summary.ToString();
|
||||
statusBlock.Text = process.ExitCode == 0
|
||||
? "Face encoder completato."
|
||||
: $"Face encoder terminato con errore (code {process.ExitCode}).";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Face encoder execution failed.");
|
||||
outputLogBox.Text = ex.ToString();
|
||||
statusBlock.Text = "Errore durante esecuzione face encoder.";
|
||||
}
|
||||
finally
|
||||
{
|
||||
runButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
|
|
@ -345,31 +176,4 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
// Ignore failures when opening Explorer.
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeDirectoryPathArgument(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var normalized = value.Trim().Trim('"');
|
||||
var root = Path.GetPathRoot(normalized);
|
||||
if (!string.IsNullOrEmpty(root) && normalized.Length > root.Length)
|
||||
{
|
||||
normalized = normalized.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string NormalizeFilePathArgument(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return value.Trim().Trim('"');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue