diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs index 217158f..66ce8d4 100644 --- a/imagecatalog/DataModel.cs +++ b/imagecatalog/DataModel.cs @@ -450,12 +450,14 @@ namespace ImageCatalog_2 private string BuildNumberAiIdleSummary() { var workerCount = ResolveNumberAiWorkerCount(UseNumberAiGpu, NumberAiWorkloadLevel); - return $"In attesa. Carico {NumberAiWorkloadLevel}/5, {workerCount} worker, 0.00 img/s."; + var unit = UseNumberAiGpu ? "batch" : "worker"; + return $"In attesa. Carico {NumberAiWorkloadLevel}/5, {workerCount} {unit}, 0.00 img/s."; } private static string BuildNumberAiProgressSummary(AiExtractionProgressUpdate progress) { - return $"{progress.ProcessedFiles}/{progress.TotalFiles} immagini, media {progress.AverageImagesPerSecond:F2} img/s, carico {progress.WorkloadLevel}/5, {progress.WorkerCount} worker."; + var unit = progress.UseGpu ? "batch" : "worker"; + return $"{progress.ProcessedFiles}/{progress.TotalFiles} immagini, media {progress.AverageImagesPerSecond:F2} img/s, carico {progress.WorkloadLevel}/5, {progress.WorkerCount} {unit}."; } private static string BuildNumberAiCompletionSummary(AiExtractionRunSummary summary) @@ -465,7 +467,8 @@ namespace ImageCatalog_2 return "Nessuna immagine trovata per OCR."; } - return $"Completato: {summary.ProcessedFiles}/{summary.TotalFiles} immagini, media finale {summary.AverageImagesPerSecond:F2} img/s, errori {summary.FailedFiles}, carico {summary.WorkloadLevel}/5, {summary.WorkerCount} worker."; + var unit = summary.UseGpu ? "batch" : "worker"; + return $"Completato: {summary.ProcessedFiles}/{summary.TotalFiles} immagini, media finale {summary.AverageImagesPerSecond:F2} img/s, errori {summary.FailedFiles}, carico {summary.WorkloadLevel}/5, {summary.WorkerCount} {unit}."; } private List LoadAvailableFonts() @@ -2228,11 +2231,11 @@ namespace ImageCatalog_2 var requestedWorkers = useGpu ? normalized switch { - 1 => 2, - 2 => 4, - 3 => 8, - 4 => 12, - _ => 16 + 1 => 4, + 2 => 8, + 3 => 16, + 4 => 24, + _ => 32 } : normalized switch { @@ -2243,7 +2246,7 @@ namespace ImageCatalog_2 _ => 5 }; - return Math.Min(requestedWorkers, maxWorkers); + return useGpu ? requestedWorkers : Math.Min(requestedWorkers, maxWorkers); } private static int NormalizeFaceMinSize(int value) diff --git a/imagecatalog/Services/AiExtractionService.cs b/imagecatalog/Services/AiExtractionService.cs index f65bd0a..b7ea6cc 100644 --- a/imagecatalog/Services/AiExtractionService.cs +++ b/imagecatalog/Services/AiExtractionService.cs @@ -45,8 +45,8 @@ public class AiExtractionService : IAiExtractionService var total = imageFiles.Count; if (total == 0) { - var emptySummary = new AiExtractionRunSummary(0, 0, 0, 0, workloadLevel, workerCount); - await onProgress(new AiExtractionProgressUpdate(0, 0, 100, 0, workloadLevel, workerCount)).ConfigureAwait(false); + var emptySummary = new AiExtractionRunSummary(0, 0, 0, 0, workloadLevel, workerCount, request.UseGpu); + await onProgress(new AiExtractionProgressUpdate(0, 0, 100, 0, workloadLevel, workerCount, request.UseGpu)).ConfigureAwait(false); return emptySummary; } @@ -79,7 +79,7 @@ public class AiExtractionService : IAiExtractionService var currentProcessed = Interlocked.Increment(ref processed); var averageImagesPerSecond = CalculateAverageImagesPerSecond(currentProcessed, stopwatch.Elapsed); var percent = currentProcessed * 100.0 / total; - await onProgress(new AiExtractionProgressUpdate(total, currentProcessed, percent, averageImagesPerSecond, workloadLevel, workerCount)).ConfigureAwait(false); + await onProgress(new AiExtractionProgressUpdate(total, currentProcessed, percent, averageImagesPerSecond, workloadLevel, workerCount, request.UseGpu)).ConfigureAwait(false); var shouldLog = false; lock (logLock) @@ -94,54 +94,74 @@ public class AiExtractionService : IAiExtractionService if (shouldLog) { _logger.LogInformation( - "Number AI progress: {Processed}/{Total} ({Percent:F1}%), {ImagesPerSecond:F2} img/s avg, workload {WorkloadLevel} ({WorkerCount} workers)", + "Number AI progress: {Processed}/{Total} ({Percent:F1}%), {ImagesPerSecond:F2} img/s avg, workload {WorkloadLevel} ({WorkerCount} {ExecutionUnit})", currentProcessed, total, percent, averageImagesPerSecond, workloadLevel, - workerCount); + workerCount, + request.UseGpu ? "batch" : "workers"); } } }, token); - var workerTasks = Enumerable.Range(0, workerCount) - .Select(_ => Task.Run(async () => - { - using var engine = new NumberRecognitionEngine(modelConfiguration, _logger); - await foreach (var file in fileChannel.Reader.ReadAllAsync(token).ConfigureAwait(false)) - { - var extracted = string.Empty; - - try - { - extracted = engine.ProcessImage(file).Text; - } - catch (Exception ex) - { - lock (failureLock) - { - failed++; - firstFailure ??= ex; - } - - _logger.LogWarning(ex, "Error processing AI OCR for {File}", file); - } - - await resultChannel.Writer.WriteAsync(new AiResultItem { Path = file, Text = extracted }, token).ConfigureAwait(false); - } - }, token)) - .ToArray(); - try { - foreach (var file in imageFiles) + if (request.UseGpu) { - await fileChannel.Writer.WriteAsync(file, token).ConfigureAwait(false); - } + using var engine = new NumberRecognitionEngine(modelConfiguration, _logger); + var resultProgress = new SynchronousProgress(result => + { + resultChannel.Writer.TryWrite(new AiResultItem { Path = result.FilePath, Text = result.Text }); + }); - fileChannel.Writer.TryComplete(); - await Task.WhenAll(workerTasks).ConfigureAwait(false); + await engine.ProcessFilesAsync( + imageFiles, + skipTextNegative: false, + maxDegreeOfParallelism: workerCount, + progress: null, + resultProgress: resultProgress, + cancellationToken: token).ConfigureAwait(false); + } + else + { + var workerTasks = Enumerable.Range(0, workerCount) + .Select(_ => Task.Run(async () => + { + using var engine = new NumberRecognitionEngine(modelConfiguration, _logger); + await foreach (var file in fileChannel.Reader.ReadAllAsync(token).ConfigureAwait(false)) + { + var extracted = string.Empty; + + try + { + extracted = engine.ProcessImage(file).Text; + } + catch (Exception ex) + { + lock (failureLock) + { + failed++; + firstFailure ??= ex; + } + + _logger.LogWarning(ex, "Error processing AI OCR for {File}", file); + } + + await resultChannel.Writer.WriteAsync(new AiResultItem { Path = file, Text = extracted }, token).ConfigureAwait(false); + } + }, token)) + .ToArray(); + + foreach (var file in imageFiles) + { + await fileChannel.Writer.WriteAsync(file, token).ConfigureAwait(false); + } + + fileChannel.Writer.TryComplete(); + await Task.WhenAll(workerTasks).ConfigureAwait(false); + } } finally { @@ -161,16 +181,18 @@ public class AiExtractionService : IAiExtractionService failed, CalculateAverageImagesPerSecond(processed, stopwatch.Elapsed), workloadLevel, - workerCount); + workerCount, + request.UseGpu); _logger.LogInformation( - "Number AI completed: {Processed}/{Total} processed, {Failed} failures, {ImagesPerSecond:F2} img/s avg, workload {WorkloadLevel} ({WorkerCount} workers)", + "Number AI completed: {Processed}/{Total} processed, {Failed} failures, {ImagesPerSecond:F2} img/s avg, workload {WorkloadLevel} ({WorkerCount} {ExecutionUnit})", summary.ProcessedFiles, summary.TotalFiles, summary.FailedFiles, summary.AverageImagesPerSecond, summary.WorkloadLevel, - summary.WorkerCount); + summary.WorkerCount, + request.UseGpu ? "batch" : "workers"); if (!string.IsNullOrWhiteSpace(request.CsvOutputPath)) { @@ -217,11 +239,11 @@ public class AiExtractionService : IAiExtractionService var requestedWorkers = useGpu ? normalized switch { - 1 => 2, - 2 => 4, - 3 => 8, - 4 => 12, - _ => 16 + 1 => 4, + 2 => 8, + 3 => 16, + 4 => 24, + _ => 32 } : normalized switch { @@ -232,7 +254,19 @@ public class AiExtractionService : IAiExtractionService _ => 5 }; - return Math.Min(requestedWorkers, maxWorkers); + return useGpu ? requestedWorkers : Math.Min(requestedWorkers, maxWorkers); + } + + private sealed class SynchronousProgress : IProgress + { + private readonly Action _handler; + + public SynchronousProgress(Action handler) + { + _handler = handler; + } + + public void Report(T value) => _handler(value); } private static ModelConfiguration BuildModelConfiguration(string modelsFolderPath, bool useGpu) diff --git a/imagecatalog/Services/IAiExtractionService.cs b/imagecatalog/Services/IAiExtractionService.cs index 74e746c..3fd517c 100644 --- a/imagecatalog/Services/IAiExtractionService.cs +++ b/imagecatalog/Services/IAiExtractionService.cs @@ -22,7 +22,8 @@ public sealed record AiExtractionProgressUpdate( double PercentComplete, double AverageImagesPerSecond, int WorkloadLevel, - int WorkerCount); + int WorkerCount, + bool UseGpu); public sealed record AiExtractionRunSummary( int TotalFiles, @@ -30,7 +31,8 @@ public sealed record AiExtractionRunSummary( int FailedFiles, double AverageImagesPerSecond, int WorkloadLevel, - int WorkerCount); + int WorkerCount, + bool UseGpu); public interface IAiExtractionService {