feat: Add support for thumbnail inclusion in AI processing and enhance UI bindings
This commit is contained in:
parent
cb41c42bb5
commit
7e105e3738
9 changed files with 235 additions and 27 deletions
|
|
@ -122,6 +122,21 @@ public class DataModelCharacterizationTests
|
||||||
model.UseNumberAiGpu.ShouldBeTrue();
|
model.UseNumberAiGpu.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NumberAiThumbnails_DefaultsOffAndRaisesDataModelPropertyChanged()
|
||||||
|
{
|
||||||
|
var model = CreateModel();
|
||||||
|
model.IncludeNumberAiThumbnails.ShouldBeFalse();
|
||||||
|
|
||||||
|
string? changed = null;
|
||||||
|
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
||||||
|
|
||||||
|
model.Ai.IncludeNumberAiThumbnails = true;
|
||||||
|
|
||||||
|
changed.ShouldBe(nameof(DataModel.IncludeNumberAiThumbnails));
|
||||||
|
model.IncludeNumberAiThumbnails.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CommandLineOperationRunner_DetectsHeadlessRequest()
|
public void CommandLineOperationRunner_DetectsHeadlessRequest()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Layout;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
@ -135,6 +137,11 @@ public partial class AvaloniaMainWindow : Window
|
||||||
{
|
{
|
||||||
// Color is set by typing hex directly in the TextBox.
|
// Color is set by typing hex directly in the TextBox.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_model.ShowMessageRequested += async (_, args) =>
|
||||||
|
{
|
||||||
|
await ShowMessageDialogAsync(args.Item1, args.Item2);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isStoppingFaceEncoderForClose;
|
private bool _isStoppingFaceEncoderForClose;
|
||||||
|
|
@ -182,4 +189,48 @@ public partial class AvaloniaMainWindow : Window
|
||||||
{
|
{
|
||||||
_ = this.FindControl<Avalonia.Controls.Button>("ThemeToggleButton");
|
_ = this.FindControl<Avalonia.Controls.Button>("ThemeToggleButton");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ShowMessageDialogAsync(string title, string message)
|
||||||
|
{
|
||||||
|
var dialog = new Window
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Width = 480,
|
||||||
|
CanResize = false,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||||
|
SizeToContent = SizeToContent.Height
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Content = BuildMessageDialogContent(message, () => dialog.Close());
|
||||||
|
|
||||||
|
await dialog.ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Control BuildMessageDialogContent(string message, Action closeDialog)
|
||||||
|
{
|
||||||
|
var layout = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Thickness(16),
|
||||||
|
Spacing = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = message,
|
||||||
|
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
|
||||||
|
MaxWidth = 420
|
||||||
|
});
|
||||||
|
|
||||||
|
var closeButton = new Button
|
||||||
|
{
|
||||||
|
Content = "OK",
|
||||||
|
MinWidth = 88,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right
|
||||||
|
};
|
||||||
|
|
||||||
|
closeButton.Click += (_, _) => closeDialog();
|
||||||
|
|
||||||
|
layout.Children.Add(closeButton);
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,12 @@
|
||||||
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Margin="4">
|
<StackPanel Margin="4">
|
||||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||||
<CheckBox Content="Estrai numeri dalle immagini" IsChecked="{Binding ExtractNumbers}" Margin="0,6,0,0" />
|
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,6,0,0">
|
||||||
<CheckBox Content="Usa GPU" IsChecked="{Binding UseNumberAiGpu, Mode=TwoWay}" Margin="0,2,0,0" />
|
<CheckBox Content="Usa GPU"
|
||||||
|
IsChecked="{Binding UseNumberAiGpu, Mode=TwoWay}"
|
||||||
|
IsEnabled="{Binding NumberAiGpuOptionEnabled}" />
|
||||||
|
<CheckBox Content="Includi thumbnail" IsChecked="{Binding IncludeNumberAiThumbnails, Mode=TwoWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Margin="0,8,0,0" ColumnDefinitions="Auto,*,Auto" ColumnSpacing="6">
|
<Grid Margin="0,8,0,0" ColumnDefinitions="Auto,*,Auto" ColumnSpacing="6">
|
||||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||||
|
|
@ -47,8 +51,18 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0" Spacing="8">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0" Spacing="8">
|
||||||
<Button Content="Avvia AI" Command="{Binding StartAiCommand}" Width="120" />
|
<Button Command="{Binding StartAiCommand}" Width="132">
|
||||||
<Button Content="Annulla" Command="{Binding AsyncCancelOperationCommand}" Width="120" />
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="PlayCircle" Width="16" Height="16" Foreground="#2E7D32" />
|
||||||
|
<TextBlock Text="Avvia AI" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Command="{Binding AsyncCancelOperationCommand}" Width="132">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="Cancel" Width="16" Height="16" Foreground="#C62828" />
|
||||||
|
<TextBlock Text="Annulla" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,8,0,0" />
|
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,8,0,0" />
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,11 @@ internal static class CommandLineOperationRunner
|
||||||
model.UseFaceGpu = options.UseGpu.Value;
|
model.UseFaceGpu = options.UseGpu.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.IncludeThumbnails.HasValue)
|
||||||
|
{
|
||||||
|
model.IncludeNumberAiThumbnails = options.IncludeThumbnails.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeOperation(string operation)
|
private static string NormalizeOperation(string operation)
|
||||||
|
|
@ -159,6 +164,14 @@ internal static class CommandLineOperationRunner
|
||||||
case "--cpu":
|
case "--cpu":
|
||||||
options.UseGpu = false;
|
options.UseGpu = false;
|
||||||
break;
|
break;
|
||||||
|
case "--include-thumbnails":
|
||||||
|
case "--include-tn":
|
||||||
|
options.IncludeThumbnails = true;
|
||||||
|
break;
|
||||||
|
case "--no-thumbnails":
|
||||||
|
case "--no-tn":
|
||||||
|
options.IncludeThumbnails = false;
|
||||||
|
break;
|
||||||
case "--headless":
|
case "--headless":
|
||||||
case "--cli":
|
case "--cli":
|
||||||
break;
|
break;
|
||||||
|
|
@ -213,7 +226,7 @@ internal static class CommandLineOperationRunner
|
||||||
|
|
||||||
private static void WriteUsage()
|
private static void WriteUsage()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Usage: ImageCatalog --config <settings.xml> --operation <image-processing|number-ai|face-ai|race-upload> [--models <folder>] [--csv <path>] [--cpu|--gpu]");
|
Console.WriteLine("Usage: ImageCatalog --config <settings.xml> --operation <image-processing|number-ai|face-ai|race-upload> [--models <folder>] [--csv <path>] [--cpu|--gpu] [--include-thumbnails|--no-thumbnails]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class CommandLineOptions
|
private sealed class CommandLineOptions
|
||||||
|
|
@ -224,5 +237,6 @@ internal static class CommandLineOperationRunner
|
||||||
public string ModelsPath { get; set; } = string.Empty;
|
public string ModelsPath { get; set; } = string.Empty;
|
||||||
public string CsvPath { get; set; } = string.Empty;
|
public string CsvPath { get; set; } = string.Empty;
|
||||||
public bool? UseGpu { get; set; }
|
public bool? UseGpu { get; set; }
|
||||||
|
public bool? IncludeThumbnails { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using AIFotoONLUS.Core;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using MaddoShared;
|
using MaddoShared;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -117,6 +118,7 @@ namespace ImageCatalog_2
|
||||||
|
|
||||||
// Load available fonts
|
// Load available fonts
|
||||||
AvailableFonts = LoadAvailableFonts();
|
AvailableFonts = LoadAvailableFonts();
|
||||||
|
RefreshNumberAiGpuCapabilities();
|
||||||
RefreshFaceExecutableCapabilities();
|
RefreshFaceExecutableCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +133,16 @@ namespace ImageCatalog_2
|
||||||
{
|
{
|
||||||
// user cancelled
|
// user cancelled
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "AI extraction failed");
|
||||||
|
if (UseNumberAiGpu)
|
||||||
|
{
|
||||||
|
RefreshNumberAiGpuCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
await ShowErrorMessageAsync("Errore AI", ex.GetBaseException().Message).ConfigureAwait(false);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
MainToken = null;
|
MainToken = null;
|
||||||
|
|
@ -162,12 +174,19 @@ namespace ImageCatalog_2
|
||||||
{
|
{
|
||||||
SearchRoot = searchRoot,
|
SearchRoot = searchRoot,
|
||||||
Recursive = recursive,
|
Recursive = recursive,
|
||||||
|
IncludeThumbnails = IncludeNumberAiThumbnails,
|
||||||
ModelsFolderPath = ModelsFolderPath,
|
ModelsFolderPath = ModelsFolderPath,
|
||||||
UseGpu = UseNumberAiGpu,
|
UseGpu = UseNumberAiGpu,
|
||||||
CsvOutputPath = CsvOutputPath
|
CsvOutputPath = CsvOutputPath
|
||||||
},
|
},
|
||||||
token,
|
token,
|
||||||
result => InvokeOnUiThreadAsync(() => PreviewResults.Add(result)),
|
result => InvokeOnUiThreadAsync(() =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.Text))
|
||||||
|
{
|
||||||
|
PreviewResults.Add(result);
|
||||||
|
}
|
||||||
|
}),
|
||||||
progress => InvokeOnUiThreadAsync(() => AiProgress = progress)).ConfigureAwait(false);
|
progress => InvokeOnUiThreadAsync(() => AiProgress = progress)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,7 +219,11 @@ namespace ImageCatalog_2
|
||||||
public string ModelsFolderPath
|
public string ModelsFolderPath
|
||||||
{
|
{
|
||||||
get => _ai.ModelsFolderPath;
|
get => _ai.ModelsFolderPath;
|
||||||
set => _ai.ModelsFolderPath = value;
|
set
|
||||||
|
{
|
||||||
|
_ai.ModelsFolderPath = value;
|
||||||
|
RefreshNumberAiGpuCapabilities();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CsvOutputPath
|
public string CsvOutputPath
|
||||||
|
|
@ -212,7 +235,19 @@ namespace ImageCatalog_2
|
||||||
public bool UseNumberAiGpu
|
public bool UseNumberAiGpu
|
||||||
{
|
{
|
||||||
get => _ai.UseNumberAiGpu;
|
get => _ai.UseNumberAiGpu;
|
||||||
set => _ai.UseNumberAiGpu = value;
|
set => SetUseNumberAiGpu(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NumberAiGpuOptionEnabled
|
||||||
|
{
|
||||||
|
get => _ai.NumberAiGpuOptionEnabled;
|
||||||
|
private set => _ai.NumberAiGpuOptionEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IncludeNumberAiThumbnails
|
||||||
|
{
|
||||||
|
get => _ai.IncludeNumberAiThumbnails;
|
||||||
|
set => _ai.IncludeNumberAiThumbnails = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FaceExecutablePath
|
public string FaceExecutablePath
|
||||||
|
|
@ -1302,23 +1337,6 @@ namespace ImageCatalog_2
|
||||||
},
|
},
|
||||||
speed => { SpeedCounter = speed; }).ConfigureAwait(false);
|
speed => { SpeedCounter = speed; }).ConfigureAwait(false);
|
||||||
|
|
||||||
// AI integration: OCR runs over processed output so it matches the face AI input folder.
|
|
||||||
if (ExtractNumbers)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await RunAiExtractionCoreAsync(token, useDestination: true, recursive: true);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("AI extraction canceled");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "AI extraction failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpeedCounter = runResult.FinalSpeedCounter;
|
SpeedCounter = runResult.FinalSpeedCounter;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
|
@ -1762,6 +1780,71 @@ namespace ImageCatalog_2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshNumberAiGpuCapabilities()
|
||||||
|
{
|
||||||
|
if (!TryBuildNumberAiModelConfiguration(out var configuration))
|
||||||
|
{
|
||||||
|
NumberAiGpuOptionEnabled = false;
|
||||||
|
_ai.UseNumberAiGpu = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAiGpuOptionEnabled = NumberRecognitionEngine.TryValidateGpuRuntime(configuration, _logger, out _);
|
||||||
|
if (!NumberAiGpuOptionEnabled)
|
||||||
|
{
|
||||||
|
_ai.UseNumberAiGpu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUseNumberAiGpu(bool value)
|
||||||
|
{
|
||||||
|
if (!NumberAiGpuOptionEnabled)
|
||||||
|
{
|
||||||
|
_ai.UseNumberAiGpu = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ai.UseNumberAiGpu = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBuildNumberAiModelConfiguration(out ModelConfiguration configuration)
|
||||||
|
{
|
||||||
|
configuration = null!;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(ModelsFolderPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelsRoot = Path.GetFullPath(ModelsFolderPath.Trim().Trim('"'));
|
||||||
|
if (!Directory.Exists(modelsRoot))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration = new ModelConfiguration
|
||||||
|
{
|
||||||
|
DetectionCfg = Path.Combine(modelsRoot, "detection.cfg"),
|
||||||
|
DetectionWeights = Path.Combine(modelsRoot, "detection.weights"),
|
||||||
|
RecognitionCfg = Path.Combine(modelsRoot, "recognition.cfg"),
|
||||||
|
RecognitionWeights = Path.Combine(modelsRoot, "recognition.weights"),
|
||||||
|
UseGpu = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return File.Exists(configuration.DetectionCfg)
|
||||||
|
&& File.Exists(configuration.DetectionWeights)
|
||||||
|
&& File.Exists(configuration.RecognitionCfg)
|
||||||
|
&& File.Exists(configuration.RecognitionWeights);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ShowErrorMessageAsync(string title, string message)
|
||||||
|
{
|
||||||
|
return InvokeOnUiThreadAsync(() =>
|
||||||
|
{
|
||||||
|
ShowMessageRequested?.Invoke(this, Tuple.Create(title, message, 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void SetUseFaceGpu(bool value)
|
private void SetUseFaceGpu(bool value)
|
||||||
{
|
{
|
||||||
var currentValue = _ai.UseFaceGpu;
|
var currentValue = _ai.UseFaceGpu;
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,10 @@ namespace ImageCatalog_2.Models
|
||||||
[XmlElement("AI_UsaGpuNumeri")]
|
[XmlElement("AI_UsaGpuNumeri")]
|
||||||
public bool UseNumberAiGpu { get; set; }
|
public bool UseNumberAiGpu { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("IncludeNumberAiThumbnails")]
|
||||||
|
[XmlElement("AI_IncludiThumbnailNumeri")]
|
||||||
|
public bool IncludeNumberAiThumbnails { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("FaceExecutablePath")]
|
[JsonPropertyName("FaceExecutablePath")]
|
||||||
[XmlElement("AI_FaceExecutablePath")]
|
[XmlElement("AI_FaceExecutablePath")]
|
||||||
public string FaceExecutablePath { get; set; } = string.Empty;
|
public string FaceExecutablePath { get; set; } = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
|| f.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
|| f.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||||
|| f.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)
|
|| f.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)
|
||||||
|| f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
|
|| f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(f => request.IncludeThumbnails || !Path.GetFileName(f).StartsWith("tn_", StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var extractedResults = new List<AiResultItem>();
|
var extractedResults = new List<AiResultItem>();
|
||||||
|
|
@ -44,6 +45,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
var processed = 0;
|
var processed = 0;
|
||||||
var total = imageFiles.Count;
|
var total = imageFiles.Count;
|
||||||
var failed = 0;
|
var failed = 0;
|
||||||
|
Exception? firstFailure = null;
|
||||||
|
|
||||||
foreach (var file in imageFiles)
|
foreach (var file in imageFiles)
|
||||||
{
|
{
|
||||||
|
|
@ -58,6 +60,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
failed++;
|
failed++;
|
||||||
|
firstFailure ??= ex;
|
||||||
_logger.LogWarning(ex, "Error processing AI OCR for {File}", file);
|
_logger.LogWarning(ex, "Error processing AI OCR for {File}", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +75,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
|
|
||||||
if (imageFiles.Count > 0 && failed == imageFiles.Count)
|
if (imageFiles.Count > 0 && failed == imageFiles.Count)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"AI OCR failed for all {imageFiles.Count} image(s). See previous log entries for details.");
|
throw new InvalidOperationException($"AI OCR failed for all {imageFiles.Count} image(s). See previous log entries for details.", firstFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
||||||
|
|
@ -89,8 +92,9 @@ public class AiExtractionService : IAiExtractionService
|
||||||
sw.WriteLine("Path,Text");
|
sw.WriteLine("Path,Text");
|
||||||
foreach (var r in extractedResults)
|
foreach (var r in extractedResults)
|
||||||
{
|
{
|
||||||
|
var csvFileName = Path.GetFileName(r.Path ?? string.Empty);
|
||||||
var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\"");
|
var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\"");
|
||||||
sw.WriteLine($"\"{r.Path}\",\"{safeText}\"");
|
sw.WriteLine($"\"{csvFileName}\",\"{safeText}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public sealed class AiExtractionRequest
|
||||||
{
|
{
|
||||||
public required string SearchRoot { get; init; }
|
public required string SearchRoot { get; init; }
|
||||||
public required bool Recursive { get; init; }
|
public required bool Recursive { get; init; }
|
||||||
|
public bool IncludeThumbnails { get; init; }
|
||||||
public required string ModelsFolderPath { get; init; }
|
public required string ModelsFolderPath { get; init; }
|
||||||
public bool UseGpu { get; init; }
|
public bool UseGpu { get; init; }
|
||||||
public string CsvOutputPath { get; init; } = string.Empty;
|
public string CsvOutputPath { get; init; } = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,28 @@ public class AiSettingsViewModel : ViewModelBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _numberAiGpuOptionEnabled;
|
||||||
|
public bool NumberAiGpuOptionEnabled
|
||||||
|
{
|
||||||
|
get => _numberAiGpuOptionEnabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_numberAiGpuOptionEnabled = value;
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _includeNumberAiThumbnails;
|
||||||
|
public bool IncludeNumberAiThumbnails
|
||||||
|
{
|
||||||
|
get => _includeNumberAiThumbnails;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_includeNumberAiThumbnails = value;
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string _faceExecutablePath = string.Empty;
|
private string _faceExecutablePath = string.Empty;
|
||||||
public string FaceExecutablePath
|
public string FaceExecutablePath
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue