Renamed Examples to Implementation
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public class CliTaskMonitor : TaskMonitor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public CliTaskMonitor(IEnumerable<(string? Name, Task Task)> tasks) : base(tasks) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CliTaskMonitor(IEnumerable<Task> tasks) : base(tasks) { }
|
||||
|
||||
#region Overrides of TaskMonitor
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnUpdate(ICollection<(string? Name, Task Task)> tasks)
|
||||
{
|
||||
var sb = new StringBuilder(tasks.Count * 30);
|
||||
|
||||
foreach (var (name, task) in tasks)
|
||||
{
|
||||
sb.Append($"{StatusMap[task.Status],-5}");
|
||||
|
||||
if (name is not null)
|
||||
{
|
||||
sb.Append(": ")
|
||||
.Append(name);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
if (task.Exception is not null)
|
||||
{
|
||||
sb.Append("> EX: ")
|
||||
.AppendLine(task.Exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
Console.Clear();
|
||||
Console.Write(sb.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public class CompactCliTaskMonitor : TaskMonitor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public CompactCliTaskMonitor(IEnumerable<(string? Name, Task Task)> tasks) : base(tasks) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompactCliTaskMonitor(IEnumerable<Task> tasks) : base(tasks) { }
|
||||
|
||||
#region Overrides of TaskMonitor
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnUpdate(ICollection<(string? Name, Task Task)> tasks)
|
||||
{
|
||||
int completed = 0;
|
||||
int total = tasks.Count;
|
||||
|
||||
var sb = new StringBuilder(total * 30);
|
||||
|
||||
foreach (var (_, task) in tasks)
|
||||
{
|
||||
var status = task.Status;
|
||||
|
||||
if (status > TaskStatus.WaitingForChildrenToComplete)
|
||||
{
|
||||
completed++;
|
||||
}
|
||||
|
||||
sb.Append(task.Exception is not null ? 'X' : StatusMap[status].First());
|
||||
}
|
||||
|
||||
sb.AppendLine($" ({completed}/{total})");
|
||||
|
||||
Console.Clear();
|
||||
Console.Write(sb.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public interface ITaskMonitor
|
||||
{
|
||||
TimeSpan Interval { get; init; }
|
||||
|
||||
Task Run();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public abstract class TaskMonitor : ITaskMonitor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(1);
|
||||
|
||||
private readonly ICollection<(string? Name, Task Task)> _tasks;
|
||||
|
||||
protected static IReadOnlyDictionary<TaskStatus, string> StatusMap { get; } =
|
||||
new Dictionary<TaskStatus, string>
|
||||
{
|
||||
{ TaskStatus.RanToCompletion, "DONE" },
|
||||
{ TaskStatus.Faulted, "FAULT" },
|
||||
{ TaskStatus.Canceled, "CANCL" },
|
||||
{ TaskStatus.Created, "WAIT" },
|
||||
{ TaskStatus.WaitingToRun, "WAIT" },
|
||||
{ TaskStatus.WaitingForActivation, "WAIT" },
|
||||
{ TaskStatus.Running, "RUN" },
|
||||
{ TaskStatus.WaitingForChildrenToComplete, "RUN" },
|
||||
};
|
||||
|
||||
protected TaskMonitor(IEnumerable<(string? Name, Task Task)> tasks) => _tasks = tasks.ToArray();
|
||||
|
||||
protected TaskMonitor(IEnumerable<Task> tasks) :
|
||||
this(tasks.Select(t => (t.Id.ToString(), t))!)
|
||||
{ }
|
||||
|
||||
public Task Run()
|
||||
{
|
||||
var waitTask = Task.WhenAll(_tasks.Select(i => i.Task));
|
||||
|
||||
while (!waitTask.Wait(Interval))
|
||||
{
|
||||
OnUpdate(_tasks);
|
||||
}
|
||||
|
||||
return waitTask;
|
||||
}
|
||||
|
||||
protected abstract void OnUpdate(ICollection<(string? Name, Task Task)> tasks);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="img\command-processing_screentypes_controlgroup_005.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\command-processing_screentypes_controlgroup_005.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_startpage_project-exist_001.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_startpage_project-exist_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_windows_position_006.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_windows_position_006.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\historian_assistent_001.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\historian_assistent_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_MetadataEditor_variables_001.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_MetadataEditor_variables_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_REPORTS_EfficencyClass_009.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_REPORTS_EfficencyClass_009.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_ZAMS_3rd-connector_014.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_ZAMS_3rd-connector_014.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_ZAMS_filter-alarmgroup_001.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_ZAMS_filter-alarmgroup_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,110 @@
|
||||
using ImageMagick;
|
||||
using Ocr.Tesseract;
|
||||
using Ocr.Tesseract.Configuration;
|
||||
using Ocr.Tesseract.Extensions;
|
||||
using Ocr.Tesseract.Models;
|
||||
using Ocr.Tesseract.Screenshots;
|
||||
using Ocr.Tesseract.Screenshots.Configuration;
|
||||
using Process.Abstract.Configuration;
|
||||
using Process.Interface;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ocr.Cli.Processor;
|
||||
|
||||
internal class EvaluationProcessor
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
||||
/// </summary>
|
||||
private static readonly Regex wordRegex = new(
|
||||
@"[\w'\-äöüÄÖÜß]{2,}",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
private static readonly ITesseractConfiguration tesseractConfig =
|
||||
new TesseractScreenshotConfiguration
|
||||
{
|
||||
DataPath = "tessdata",
|
||||
Languages = new[] { "eng", "deu" }
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Processors
|
||||
|
||||
private static readonly IProcessorChain<ScanResult, ScanResult> postProcessor =
|
||||
new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(wordRegex));
|
||||
|
||||
private IProcessorChain<MagickImage, ScanResult> MakeProcessor()
|
||||
{
|
||||
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||
.Use(new CloneImageProcessor())
|
||||
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||
.Use(new NormalizeProcessor())
|
||||
.Use(_thresholdProcessor)
|
||||
.Use(new AddBorderProcessor(10))
|
||||
.Use(new BinarizeProcessor())
|
||||
.Use(new NegateCloneProcessor())
|
||||
.Complete(OnPreprocessed);
|
||||
|
||||
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||
.Use(preprocessing)
|
||||
.Use(tesseractProcessor)
|
||||
.Complete(postProcessor);
|
||||
}
|
||||
|
||||
private static readonly TesseractProcessor tesseractProcessor = new(tesseractConfig);
|
||||
|
||||
private readonly StopwatchProcessor<MagickImage, MagickImage> _thresholdProcessor;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public string OutputFolder { get; init; } = "results";
|
||||
|
||||
public EvaluationProcessor(
|
||||
IProcessor<MagickImage, MagickImage> thresholdProcessor
|
||||
) => _thresholdProcessor = new StopwatchProcessor<MagickImage, MagickImage>(thresholdProcessor);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Process(MagickImage image) => Task.Run(async () =>
|
||||
{
|
||||
var words = MakeProcessor()
|
||||
.Process(new[] { image })
|
||||
.Select(r => r.Word)
|
||||
.ToArray();
|
||||
|
||||
var result = new
|
||||
{
|
||||
Words = words.ToArray(),
|
||||
Elapsed = _thresholdProcessor.Elapsed?.TotalMilliseconds,
|
||||
};
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(image.FileName);
|
||||
var path = Path.Combine(OutputFolder, $"{name}.{_thresholdProcessor}.json");
|
||||
await using var file = File.OpenWrite(path);
|
||||
await JsonSerializer.SerializeAsync(file, result);
|
||||
});
|
||||
|
||||
private IEnumerable<MagickImage> OnPreprocessed(IEnumerable<MagickImage> images)
|
||||
{
|
||||
var tImages = images.ToArray();
|
||||
|
||||
for (var i = 0; i < tImages.Length; i++)
|
||||
{
|
||||
var image = tImages[i].CloneImage();
|
||||
var name = Path.GetFileName(image.FileName);
|
||||
var path = Path.Combine(OutputFolder, $"{_thresholdProcessor}.{i:D2}.{name}");
|
||||
image.Write(path);
|
||||
}
|
||||
|
||||
return tImages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Process.Abstract;
|
||||
using Process.Interface;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ocr.Cli.Processor;
|
||||
|
||||
public class StopwatchProcessor<TInput, TOutput> : Processor<TInput, TOutput>
|
||||
{
|
||||
private readonly IProcessor<TInput, TOutput> _processor;
|
||||
|
||||
/// <summary>
|
||||
/// Execution time of the last processing action
|
||||
/// </summary>
|
||||
public TimeSpan? Elapsed { get; private set; }
|
||||
|
||||
public StopwatchProcessor(IProcessor<TInput, TOutput> processor) => _processor = processor;
|
||||
|
||||
public override IEnumerable<TOutput> Process(IEnumerable<TInput> inputs)
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
var results = _processor.Process(inputs);
|
||||
stopWatch.Stop();
|
||||
Elapsed = stopWatch.Elapsed;
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? ToString() => _processor.ToString();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Common.Extensions;
|
||||
using ImageMagick;
|
||||
using Ocr.Cli.Monitor;
|
||||
using Ocr.Cli.Processor;
|
||||
using Ocr.Tesseract.Screenshots.Threshold;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Ocr.Cli;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public Task Run(string[] args)
|
||||
{
|
||||
Directory.Delete("results", true);
|
||||
Directory.CreateDirectory("results");
|
||||
|
||||
var scans = (
|
||||
from processor in MakeThresholdVariations()
|
||||
from path in ExpandPaths(args)
|
||||
select (Key: path, Task: processor.Process(new MagickImage(path)))
|
||||
).ToArray();
|
||||
|
||||
// return new CliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||
return new CompactCliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
||||
private static IEnumerable<EvaluationProcessor> MakeThresholdVariations()
|
||||
{
|
||||
for (int i = 4; i <= 24; i += 4)
|
||||
{
|
||||
yield return new(new ThresholdAdaptiveProcessor(i));
|
||||
}
|
||||
|
||||
for (int i = 20; i <= 80; i += 10)
|
||||
{
|
||||
yield return new(new ThresholdProcessor(i));
|
||||
}
|
||||
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Kapur));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.OTSU));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Triangle));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ExpandPaths(params string[] paths) =>
|
||||
paths.SelectMany(p => p.ExpandPath());
|
||||
|
||||
#region Main
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting up");
|
||||
|
||||
try
|
||||
{
|
||||
new Program()
|
||||
.Run(args)
|
||||
.Wait();
|
||||
|
||||
Console.WriteLine("Completed");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"profiles": {
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"distributionName": ""
|
||||
},
|
||||
"Test all img": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img/*.png\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
},
|
||||
"Test single img": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img/historian_assistent_001.png\""
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user