feat: Enhance AI extraction summaries and worker allocation for GPU support
This commit is contained in:
parent
48d6af13da
commit
f57dc1edba
3 changed files with 90 additions and 51 deletions
|
|
@ -450,12 +450,14 @@ namespace ImageCatalog_2
|
||||||
private string BuildNumberAiIdleSummary()
|
private string BuildNumberAiIdleSummary()
|
||||||
{
|
{
|
||||||
var workerCount = ResolveNumberAiWorkerCount(UseNumberAiGpu, NumberAiWorkloadLevel);
|
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)
|
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)
|
private static string BuildNumberAiCompletionSummary(AiExtractionRunSummary summary)
|
||||||
|
|
@ -465,7 +467,8 @@ namespace ImageCatalog_2
|
||||||
return "Nessuna immagine trovata per OCR.";
|
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<string> LoadAvailableFonts()
|
private List<string> LoadAvailableFonts()
|
||||||
|
|
@ -2228,11 +2231,11 @@ namespace ImageCatalog_2
|
||||||
var requestedWorkers = useGpu
|
var requestedWorkers = useGpu
|
||||||
? normalized switch
|
? normalized switch
|
||||||
{
|
{
|
||||||
1 => 2,
|
1 => 4,
|
||||||
2 => 4,
|
2 => 8,
|
||||||
3 => 8,
|
3 => 16,
|
||||||
4 => 12,
|
4 => 24,
|
||||||
_ => 16
|
_ => 32
|
||||||
}
|
}
|
||||||
: normalized switch
|
: normalized switch
|
||||||
{
|
{
|
||||||
|
|
@ -2243,7 +2246,7 @@ namespace ImageCatalog_2
|
||||||
_ => 5
|
_ => 5
|
||||||
};
|
};
|
||||||
|
|
||||||
return Math.Min(requestedWorkers, maxWorkers);
|
return useGpu ? requestedWorkers : Math.Min(requestedWorkers, maxWorkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int NormalizeFaceMinSize(int value)
|
private static int NormalizeFaceMinSize(int value)
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ public class AiExtractionService : IAiExtractionService
|
||||||
var total = imageFiles.Count;
|
var total = imageFiles.Count;
|
||||||
if (total == 0)
|
if (total == 0)
|
||||||
{
|
{
|
||||||
var emptySummary = new AiExtractionRunSummary(0, 0, 0, 0, workloadLevel, workerCount);
|
var emptySummary = new AiExtractionRunSummary(0, 0, 0, 0, workloadLevel, workerCount, request.UseGpu);
|
||||||
await onProgress(new AiExtractionProgressUpdate(0, 0, 100, 0, workloadLevel, workerCount)).ConfigureAwait(false);
|
await onProgress(new AiExtractionProgressUpdate(0, 0, 100, 0, workloadLevel, workerCount, request.UseGpu)).ConfigureAwait(false);
|
||||||
return emptySummary;
|
return emptySummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
var currentProcessed = Interlocked.Increment(ref processed);
|
var currentProcessed = Interlocked.Increment(ref processed);
|
||||||
var averageImagesPerSecond = CalculateAverageImagesPerSecond(currentProcessed, stopwatch.Elapsed);
|
var averageImagesPerSecond = CalculateAverageImagesPerSecond(currentProcessed, stopwatch.Elapsed);
|
||||||
var percent = currentProcessed * 100.0 / total;
|
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;
|
var shouldLog = false;
|
||||||
lock (logLock)
|
lock (logLock)
|
||||||
|
|
@ -94,54 +94,74 @@ public class AiExtractionService : IAiExtractionService
|
||||||
if (shouldLog)
|
if (shouldLog)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_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,
|
currentProcessed,
|
||||||
total,
|
total,
|
||||||
percent,
|
percent,
|
||||||
averageImagesPerSecond,
|
averageImagesPerSecond,
|
||||||
workloadLevel,
|
workloadLevel,
|
||||||
workerCount);
|
workerCount,
|
||||||
|
request.UseGpu ? "batch" : "workers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, token);
|
}, 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
|
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<ImageResult>(result =>
|
||||||
|
{
|
||||||
|
resultChannel.Writer.TryWrite(new AiResultItem { Path = result.FilePath, Text = result.Text });
|
||||||
|
});
|
||||||
|
|
||||||
fileChannel.Writer.TryComplete();
|
await engine.ProcessFilesAsync(
|
||||||
await Task.WhenAll(workerTasks).ConfigureAwait(false);
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -161,16 +181,18 @@ public class AiExtractionService : IAiExtractionService
|
||||||
failed,
|
failed,
|
||||||
CalculateAverageImagesPerSecond(processed, stopwatch.Elapsed),
|
CalculateAverageImagesPerSecond(processed, stopwatch.Elapsed),
|
||||||
workloadLevel,
|
workloadLevel,
|
||||||
workerCount);
|
workerCount,
|
||||||
|
request.UseGpu);
|
||||||
|
|
||||||
_logger.LogInformation(
|
_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.ProcessedFiles,
|
||||||
summary.TotalFiles,
|
summary.TotalFiles,
|
||||||
summary.FailedFiles,
|
summary.FailedFiles,
|
||||||
summary.AverageImagesPerSecond,
|
summary.AverageImagesPerSecond,
|
||||||
summary.WorkloadLevel,
|
summary.WorkloadLevel,
|
||||||
summary.WorkerCount);
|
summary.WorkerCount,
|
||||||
|
request.UseGpu ? "batch" : "workers");
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
||||||
{
|
{
|
||||||
|
|
@ -217,11 +239,11 @@ public class AiExtractionService : IAiExtractionService
|
||||||
var requestedWorkers = useGpu
|
var requestedWorkers = useGpu
|
||||||
? normalized switch
|
? normalized switch
|
||||||
{
|
{
|
||||||
1 => 2,
|
1 => 4,
|
||||||
2 => 4,
|
2 => 8,
|
||||||
3 => 8,
|
3 => 16,
|
||||||
4 => 12,
|
4 => 24,
|
||||||
_ => 16
|
_ => 32
|
||||||
}
|
}
|
||||||
: normalized switch
|
: normalized switch
|
||||||
{
|
{
|
||||||
|
|
@ -232,7 +254,19 @@ public class AiExtractionService : IAiExtractionService
|
||||||
_ => 5
|
_ => 5
|
||||||
};
|
};
|
||||||
|
|
||||||
return Math.Min(requestedWorkers, maxWorkers);
|
return useGpu ? requestedWorkers : Math.Min(requestedWorkers, maxWorkers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SynchronousProgress<T> : IProgress<T>
|
||||||
|
{
|
||||||
|
private readonly Action<T> _handler;
|
||||||
|
|
||||||
|
public SynchronousProgress(Action<T> handler)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Report(T value) => _handler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelConfiguration BuildModelConfiguration(string modelsFolderPath, bool useGpu)
|
private static ModelConfiguration BuildModelConfiguration(string modelsFolderPath, bool useGpu)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ public sealed record AiExtractionProgressUpdate(
|
||||||
double PercentComplete,
|
double PercentComplete,
|
||||||
double AverageImagesPerSecond,
|
double AverageImagesPerSecond,
|
||||||
int WorkloadLevel,
|
int WorkloadLevel,
|
||||||
int WorkerCount);
|
int WorkerCount,
|
||||||
|
bool UseGpu);
|
||||||
|
|
||||||
public sealed record AiExtractionRunSummary(
|
public sealed record AiExtractionRunSummary(
|
||||||
int TotalFiles,
|
int TotalFiles,
|
||||||
|
|
@ -30,7 +31,8 @@ public sealed record AiExtractionRunSummary(
|
||||||
int FailedFiles,
|
int FailedFiles,
|
||||||
double AverageImagesPerSecond,
|
double AverageImagesPerSecond,
|
||||||
int WorkloadLevel,
|
int WorkloadLevel,
|
||||||
int WorkerCount);
|
int WorkerCount,
|
||||||
|
bool UseGpu);
|
||||||
|
|
||||||
public interface IAiExtractionService
|
public interface IAiExtractionService
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue