feature/reports #1
@@ -8,32 +8,55 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Ocr\Ocr.Processors\Ocr.Processors.csproj" />
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Text;
|
||||
|
||||
namespace 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 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 CLI.Monitor;
|
||||
|
||||
public interface ITaskMonitor
|
||||
{
|
||||
TimeSpan Interval { get; init; }
|
||||
|
||||
Task Run();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace 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,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 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 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();
|
||||
}
|
||||
@@ -1,16 +1,71 @@
|
||||
using Common;
|
||||
using CLI.Monitor;
|
||||
using CLI.Processor;
|
||||
using Common.Extensions;
|
||||
using ImageMagick;
|
||||
using Ocr.Tesseract.Screenshots.Threshold;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
var scanner = new ScreenshotScanner();
|
||||
namespace CLI;
|
||||
|
||||
Console.WriteLine($"# Scanning: {string.Join(',', args)}...");
|
||||
scanner.Process(GetImages(args));
|
||||
public class Program
|
||||
{
|
||||
public Task Run(string[] args)
|
||||
{
|
||||
Directory.Delete("results", true);
|
||||
Directory.CreateDirectory("results");
|
||||
|
||||
Console.WriteLine($"# Results ({scanner.Lookup.Keys.Count}):");
|
||||
Console.WriteLine(string.Join(' ', scanner.Lookup.Keys));
|
||||
var scans = (
|
||||
from processor in MakeThresholdVariations()
|
||||
from path in ExpandPaths(args)
|
||||
select (Key: path, Task: processor.Process(new MagickImage(path)))
|
||||
).ToArray();
|
||||
|
||||
static IEnumerable<MagickImage> GetImages(IEnumerable<string> paths) => paths
|
||||
.SelectMany(p => p.ExpandPath())
|
||||
.Select(p => new MagickImage(p))
|
||||
.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
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
},
|
||||
"Test all img": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img/*\""
|
||||
"commandLineArgs": "\"img/*.png\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
},
|
||||
"Test single img": {
|
||||
"commandName": "Project",
|
||||
|
||||
@@ -12,22 +12,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Lookup\Lookup.Memory\Lookup.Memory.csproj" />
|
||||
<ProjectReference Include="..\..\Ocr\Ocr.Processors\Ocr.Processors.csproj" />
|
||||
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj" />
|
||||
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract\Ocr.Tesseract.csproj" />
|
||||
<ProjectReference Include="..\..\Process\Process.Interface\Process.Interface.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="tessdata\deu.traineddata">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="tessdata\eng.traineddata">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="tessdata\osd.traineddata">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Common.Distance;
|
||||
|
||||
public static class Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the levenshtein distance between two enumerables
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="reference"></param>
|
||||
/// <param name="hypothesis"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetDistance<T>(T reference, T? hypothesis)
|
||||
where T : IEnumerable
|
||||
{
|
||||
// Setup
|
||||
var refArr = reference.Cast<object>().ToArray();
|
||||
var hypArr = hypothesis?.Cast<object>().ToArray() ?? Array.Empty<object>();
|
||||
|
||||
var distance = new int[refArr.Length + 1, hypArr.Length + 1];
|
||||
|
||||
// Fill matrix
|
||||
for (var x = 0; x <= refArr.Length; x++)
|
||||
{
|
||||
// Reference on X axis
|
||||
distance[x, 0] = x;
|
||||
}
|
||||
|
||||
for (var y = 0; y <= hypArr.Length; y++)
|
||||
{
|
||||
// Hypothesis on Y axis
|
||||
distance[0, y] = y;
|
||||
}
|
||||
|
||||
// Calculate distance
|
||||
for (var x = 0; x < refArr.Length; x++)
|
||||
{
|
||||
for (var y = 0; y < hypArr.Length; y++)
|
||||
{
|
||||
// BL Cost depends on whether the two elements are equal
|
||||
var cost = Equals(refArr[x], hypArr[y]) ? 0 : 1;
|
||||
|
||||
// Apply distance mask
|
||||
var c1 = distance[x, y] + cost; // Bottom left
|
||||
|
||||
var c2 = distance[x, y + 1] + 1; // Top left
|
||||
var c3 = distance[x + 1, y] + 1; // Bottom right
|
||||
|
||||
distance[x + 1, y + 1] = Min(c1, c2, c3); // Top right
|
||||
}
|
||||
}
|
||||
|
||||
return distance[refArr.Length, hypArr.Length];
|
||||
}
|
||||
|
||||
private static T Min<T>(params T[] values)
|
||||
{
|
||||
if (!values.Any())
|
||||
{
|
||||
throw new ArgumentException("Array cannot be empty", nameof(values));
|
||||
}
|
||||
|
||||
return values.Min()!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Common.Distance;
|
||||
|
||||
public readonly struct DistanceComparer<T> : IDistanceComparer<T>
|
||||
where T : IEnumerable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public T Reference { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public T? Hypothesis { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public double Distance { get; }
|
||||
|
||||
public DistanceComparer(T reference) : this(reference, default)
|
||||
{
|
||||
}
|
||||
|
||||
public DistanceComparer(T reference, T? hypothesis)
|
||||
{
|
||||
Reference = reference;
|
||||
Hypothesis = hypothesis;
|
||||
|
||||
Distance = Calculator.GetDistance(Reference, Hypothesis);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var str = Hypothesis?.ToString();
|
||||
|
||||
if (Hypothesis is var hyp && Equals(hyp, Reference))
|
||||
{
|
||||
return str ?? string.Empty;
|
||||
}
|
||||
|
||||
return
|
||||
$"<strong style='color: orange;' title='REf: {Reference}, CER: {Distance}'>{str ?? "-"}</strong>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Common.Distance;
|
||||
|
||||
public interface IDistanceComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// The calculated absolute distance between
|
||||
/// <see cref="IDistanceComparer{T}.Reference"/> and
|
||||
/// <see cref="IDistanceComparer{T}.Hypothesis"/>
|
||||
/// </summary>
|
||||
public double Distance { get; }
|
||||
}
|
||||
|
||||
public interface IDistanceComparer<out T> : IDistanceComparer
|
||||
where T : IEnumerable
|
||||
{
|
||||
/// <summary>
|
||||
/// The comparison reference, meaning the "known to be correct" value
|
||||
/// </summary>
|
||||
public T Reference { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The value hypothesis, whose correctness is checked against <see cref="Reference"/>
|
||||
/// </summary>
|
||||
public T? Hypothesis { get; }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Common.Extensions;
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static double Median(this IEnumerable<double> values)
|
||||
{
|
||||
var tValues = values.OrderBy(v => v).ToArray();
|
||||
if (!tValues.Any())
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The list must contain at least one value for calculating the median");
|
||||
}
|
||||
|
||||
if (tValues.Length % 2 != 0)
|
||||
{
|
||||
return tValues[tValues.Length / 2];
|
||||
}
|
||||
|
||||
var center = tValues.Length / 2;
|
||||
return (tValues[center - 1] + tValues[center]) / 2.0;
|
||||
}
|
||||
|
||||
public static double Median(this IEnumerable<double> values, out double deviation)
|
||||
{
|
||||
var tValues = values.ToArray();
|
||||
var median = tValues.Median();
|
||||
deviation = tValues.Select(value => Math.Abs(value - median)).Median();
|
||||
return median;
|
||||
}
|
||||
|
||||
public static double Average(this IEnumerable<double> values, out double deviation)
|
||||
{
|
||||
var tValues = values?.ToArray();
|
||||
if (tValues is null || tValues.Length < 2)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The list must contain at least two values for calculating standard deviation");
|
||||
}
|
||||
|
||||
var average = tValues.Average();
|
||||
|
||||
var diffSquaredSum = tValues.Sum(value => Math.Pow(value - average, 2));
|
||||
deviation = Math.Sqrt(diffSquaredSum / (tValues.Length - 1));
|
||||
|
||||
return average;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Extensions;
|
||||
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
public static StringBuilder AppendHeading(this StringBuilder sb, int level, string text) => sb
|
||||
.Append(new string('#', level))
|
||||
.Append(' ')
|
||||
.AppendParagraph(text);
|
||||
|
||||
public static StringBuilder AppendParagraph(this StringBuilder sb, string text) => sb
|
||||
.AppendLine(text)
|
||||
.AppendLine();
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Common.Extensions;
|
||||
namespace Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for the string object type
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly Regex patternRegex = new Regex(@"^\*$");
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this string contains the specified string. Not case sensitive.
|
||||
/// </summary>
|
||||
@@ -24,18 +20,20 @@ public static class StringExtensions
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <returns></returns>
|
||||
public static ICollection<string> ExpandPath(this string self)
|
||||
public static IEnumerable<string> ExpandPath(this string self)
|
||||
{
|
||||
string pattern = Path.GetFileName(self);
|
||||
if (patternRegex.IsMatch(pattern))
|
||||
var parts = self.Split(Path.DirectorySeparatorChar);
|
||||
|
||||
var fileName = parts.Last();
|
||||
if (fileName.Contains('*') || fileName.Contains('?'))
|
||||
{
|
||||
return Directory.GetFiles(
|
||||
self.Substring(0, self.Length - pattern.Length),
|
||||
pattern,
|
||||
SearchOption.TopDirectoryOnly
|
||||
);
|
||||
// Path contains file pattern
|
||||
|
||||
var path = Path.Combine(parts.SkipLast(1).ToArray());
|
||||
return Directory.EnumerateFiles(path, fileName);
|
||||
}
|
||||
|
||||
// Path contains no pattern
|
||||
return new[] { self };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +1,50 @@
|
||||
using ImageMagick;
|
||||
using Lookup.Memory;
|
||||
using Ocr.Tesseract;
|
||||
using Ocr.Tesseract.Configuration;
|
||||
using Ocr.Tesseract.Models;
|
||||
using Ocr.Tesseract.Screenshots;
|
||||
using Ocr.Tesseract.Screenshots.Configuration;
|
||||
using Process.Abstract.Configuration;
|
||||
using Process.Interface;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Common
|
||||
namespace Common;
|
||||
|
||||
/// <summary>
|
||||
/// Scanner class, scanning <see cref="MagickImage"/>s for <see cref="Word"/>s
|
||||
/// via optical character recognition. Optimized for digital Screenshots.
|
||||
/// </summary>
|
||||
public class ScreenshotScanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Scanner class, scanning <see cref="MagickImage"/>s for <see cref="Word"/>s
|
||||
/// via optical character recognition. Optimized for digital Screenshots.
|
||||
/// The screenshot processor
|
||||
/// </summary>
|
||||
public class ScreenshotScanner
|
||||
protected IProcessor<MagickImage, ScanResult> Processor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Data storage
|
||||
/// </summary>
|
||||
public Lookup.Interface.ILookup<Word, MagickImage> Lookup { get; } =
|
||||
new MemoryLookup<Word, MagickImage>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ScreenshotScanner(IProcessor<MagickImage, ScanResult> processor)
|
||||
{
|
||||
private readonly IProcessor<MagickImage, ScanResult> _processor;
|
||||
Processor = processor;
|
||||
}
|
||||
|
||||
/// <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
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Data storage
|
||||
/// </summary>
|
||||
public Lookup.Interface.ILookup<Word, MagickImage> Lookup { get; } =
|
||||
new MemoryLookup<Word, MagickImage>();
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the <see cref="ImageProcessor"/>
|
||||
/// </summary>
|
||||
public ScreenshotProcessorConfiguration ImageProcessorConfiguration { get; }
|
||||
|
||||
public ITesseractConfiguration TesseractConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ScreenshotScanner(
|
||||
ScreenshotProcessorConfiguration imageProcessorConfig,
|
||||
ITesseractConfiguration tesseractConfig
|
||||
)
|
||||
{
|
||||
ImageProcessorConfiguration = imageProcessorConfig;
|
||||
TesseractConfiguration = tesseractConfig;
|
||||
|
||||
_processor = MakeProcessor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the provided <paramref name="images"/> and add the results to
|
||||
/// the <see cref="Lookup"/>
|
||||
/// </summary>
|
||||
/// <param name="images">The <see cref="MagickImage"/>s to process</param>
|
||||
public void Process(IEnumerable<MagickImage> images)
|
||||
{
|
||||
foreach (var kv in _processor.Process(images))
|
||||
{
|
||||
Lookup.Add(kv.Word, kv.Image);
|
||||
}
|
||||
}
|
||||
|
||||
private IProcessor<MagickImage, ScanResult> MakeProcessor()
|
||||
{
|
||||
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||
.Use(new ScreenshotProcessor(ImageProcessorConfiguration)) // Preprocess input data
|
||||
.Use(new ProcessingEvent<MagickImage>(OnProcessing)) // Scan
|
||||
.Use(new TesseractProcessor(TesseractConfiguration)) // Scan
|
||||
.Use(new ProcessingEvent<ScanResult>(OnProcessed)) // Scan
|
||||
.Use(new ConfidenceFilter(50)) // Process output data
|
||||
.Use(new DuplicateFilter())
|
||||
.Use(new ToLowerProcessor())
|
||||
.Complete(new RegexFilter(wordRegex));
|
||||
}
|
||||
|
||||
protected virtual void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||
/// <summary>
|
||||
/// Process the provided <paramref name="images"/> and add the results to
|
||||
/// the <see cref="Lookup"/>
|
||||
/// </summary>
|
||||
/// <param name="images">The <see cref="MagickImage"/>s to process</param>
|
||||
public void Process(IEnumerable<MagickImage> images)
|
||||
{
|
||||
foreach (var kv in Processor.Process(images))
|
||||
{
|
||||
Lookup.Add(kv.Word, kv.Image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
Lookup.Clear();
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract", "..\Ocr\Ocr
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{251F9AC9-3765-498C-83FD-DB3539A19CB3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportGenerator", "ReportGenerator\ReportGenerator.csproj", "{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -89,6 +91,10 @@ Global
|
||||
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tesseract/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -3,25 +3,24 @@ using GUI.Views;
|
||||
using Serilog;
|
||||
using System.Windows;
|
||||
|
||||
namespace GUI
|
||||
namespace GUI;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
/// <inheritdoc />
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
base.OnStartup(e);
|
||||
|
||||
var loggingCollection = new LoggingCollection(100);
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Sink(loggingCollection)
|
||||
.CreateLogger();
|
||||
var loggingCollection = new LoggingCollection(100);
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Sink(loggingCollection)
|
||||
.CreateLogger();
|
||||
|
||||
new LogView(loggingCollection).Show();
|
||||
new ImageView().Show();
|
||||
}
|
||||
new LogView(loggingCollection).Show();
|
||||
new ImageView().Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,43 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace GUI.Controls
|
||||
namespace GUI.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ImageControl.xaml
|
||||
/// </summary>
|
||||
public partial class ImageControl : UserControl
|
||||
{
|
||||
/// <inheritdoc cref="Image"/>
|
||||
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
|
||||
nameof(Image), typeof(MagickImage), typeof(ImageControl), new PropertyMetadata(default(MagickImage)));
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ImageControl.xaml
|
||||
/// The <see cref="MagickImage"/> displayed in this <see cref="ImageControl"/>
|
||||
/// </summary>
|
||||
public partial class ImageControl : UserControl
|
||||
public MagickImage Image
|
||||
{
|
||||
/// <inheritdoc cref="Image"/>
|
||||
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
|
||||
nameof(Image), typeof(MagickImage), typeof(ImageControl), new PropertyMetadata(default(MagickImage)));
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MagickImage"/> displayed in this <see cref="ImageControl"/>
|
||||
/// </summary>
|
||||
public MagickImage Image
|
||||
{
|
||||
get => (MagickImage)GetValue(ImageProperty);
|
||||
set => SetValue(ImageProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ImageName"/>
|
||||
public static readonly DependencyProperty ImageNameProperty = DependencyProperty.Register(
|
||||
nameof(ImageName), typeof(object), typeof(ImageControl),
|
||||
new PropertyMetadata(default(object)));
|
||||
|
||||
/// <summary>
|
||||
/// The name of the loaded image
|
||||
/// </summary>
|
||||
public object ImageName
|
||||
{
|
||||
get => (object)GetValue(ImageNameProperty);
|
||||
set => SetValue(ImageNameProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
get => (MagickImage)GetValue(ImageProperty);
|
||||
set => SetValue(ImageProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ImageName"/>
|
||||
public static readonly DependencyProperty ImageNameProperty = DependencyProperty.Register(
|
||||
nameof(ImageName), typeof(object), typeof(ImageControl),
|
||||
new PropertyMetadata(default(object)));
|
||||
|
||||
/// <summary>
|
||||
/// The name of the loaded image
|
||||
/// </summary>
|
||||
public object ImageName
|
||||
{
|
||||
get => (object)GetValue(ImageNameProperty);
|
||||
set => SetValue(ImageNameProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -6,49 +6,48 @@ using System.IO;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GUI.Converters
|
||||
namespace GUI.Converters;
|
||||
|
||||
internal class ImageConverter : IValueConverter
|
||||
{
|
||||
internal class ImageConverter : IValueConverter
|
||||
#region Implementation of IValueConverter
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
#region Implementation of IValueConverter
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
if (value is not MagickImage image)
|
||||
{
|
||||
if (value is not MagickImage image)
|
||||
{
|
||||
return Binding.DoNothing;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
// Save image to stream
|
||||
image.Write(stream, MagickFormat.Png);
|
||||
|
||||
// Build Bitmap from stream
|
||||
var imageSource = new BitmapImage();
|
||||
imageSource.BeginInit();
|
||||
imageSource.StreamSource = stream;
|
||||
imageSource.CacheOption = BitmapCacheOption.OnLoad;
|
||||
imageSource.EndInit();
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{e.Message}");
|
||||
return Binding.DoNothing;
|
||||
}
|
||||
return Binding.DoNothing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
try
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
#endregion
|
||||
// Save image to stream
|
||||
image.Write(stream, MagickFormat.Png);
|
||||
|
||||
// Build Bitmap from stream
|
||||
var imageSource = new BitmapImage();
|
||||
imageSource.BeginInit();
|
||||
imageSource.StreamSource = stream;
|
||||
imageSource.CacheOption = BitmapCacheOption.OnLoad;
|
||||
imageSource.EndInit();
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{e.Message}");
|
||||
return Binding.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -15,31 +15,4 @@
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="img\command-processing_screentypes_controlgroup_005.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_startpage_project-exist_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\editor_windows_position_006.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\historian_assistent_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_MetadataEditor_variables_001.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="img\zrs_REPORTS_EfficencyClass_009.png">
|
||||
<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.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
using GUI.Model;
|
||||
using ImageMagick;
|
||||
using Microsoft.Win32;
|
||||
using Ocr.Tesseract;
|
||||
using Ocr.Tesseract.Configuration;
|
||||
using Ocr.Tesseract.Models;
|
||||
using Ocr.Tesseract.Screenshots;
|
||||
using Ocr.Tesseract.Screenshots.Configuration;
|
||||
using Ocr.Tesseract.Screenshots.Threshold;
|
||||
using Process.Abstract.Configuration;
|
||||
using Process.Interface;
|
||||
using Serilog;
|
||||
using System;
|
||||
@@ -14,33 +18,91 @@ using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace GUI.ViewModels;
|
||||
|
||||
internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||
internal class ImageViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private static ITesseractConfiguration CreateTesseractConfiguration() =>
|
||||
new TesseractScreenshotConfiguration()
|
||||
/// <summary>
|
||||
/// The internally used <see cref="ScreenshotScanner"/>
|
||||
/// </summary>
|
||||
public ScreenshotScanner Scanner { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tesseract engine configuration
|
||||
/// </summary>
|
||||
public static readonly ITesseractConfiguration TesseractConfig =
|
||||
new TesseractScreenshotConfiguration
|
||||
{
|
||||
DataPath = "tessdata",
|
||||
Languages = new[] { "eng", "deu" }
|
||||
};
|
||||
|
||||
public ImageViewModel() : base(new(), CreateTesseractConfiguration())
|
||||
public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
||||
/// </summary>
|
||||
public static readonly Regex WordRegex = new(
|
||||
@"[\w'\-]{2,}",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
public ImageViewModel()
|
||||
{
|
||||
ImageProcessorConfiguration.PropertyChanged += (sender, args) => Task.Run(UpdateImage);
|
||||
Scanner = new ScreenshotScanner(MakeProcessor());
|
||||
ProcessorConfig.PropertyChanged += (sender, args) => Task.Run(UpdateImage);
|
||||
|
||||
OpenFileCommand = new Command(OpenFile);
|
||||
SaveEditedImageCommand = new Command(SaveEditedImage);
|
||||
}
|
||||
|
||||
public ImageViewModel(MagickImage image) : this()
|
||||
{
|
||||
Image = image;
|
||||
}
|
||||
|
||||
private IProcessorChain<MagickImage, ScanResult> MakeProcessor()
|
||||
{
|
||||
var threshold =
|
||||
new ThresholdAdaptiveProcessor(
|
||||
ProcessorConfig.ThresholdWidth,
|
||||
ProcessorConfig.ThresholdHeight
|
||||
);
|
||||
|
||||
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||
.Use(new CloneImageProcessor())
|
||||
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||
.Use(new NormalizeProcessor())
|
||||
.Use(threshold)
|
||||
.Use(new AddBorderProcessor(ProcessorConfig.Border))
|
||||
.Use(new BinarizeProcessor())
|
||||
.Complete(new NegateCloneProcessor());
|
||||
|
||||
var postprocessing = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(WordRegex));
|
||||
|
||||
var scan = new TesseractProcessor(TesseractConfig);
|
||||
|
||||
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||
.Use(preprocessing)
|
||||
.Use(new ProcessingEvent<MagickImage>(OnProcessing))
|
||||
.Use(scan)
|
||||
.Use(new ProcessingEvent<ScanResult>(OnProcessed))
|
||||
.Complete(postprocessing);
|
||||
}
|
||||
|
||||
#region Overrides of Scanner
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||
protected void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
@@ -52,30 +114,28 @@ internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||
protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||
{
|
||||
ScannedText = $"[{inputs.Count} words] " + string.Join(' ', inputs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public ImageViewModel(MagickImage image) : this()
|
||||
{
|
||||
Image = image;
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
Scanner.Clear();
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
ScannedText = string.Empty;
|
||||
Words.Clear();
|
||||
Lookup.Clear();
|
||||
Edited.Clear();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Handling
|
||||
|
||||
private void OpenFile()
|
||||
{
|
||||
var dialog = new OpenFileDialog()
|
||||
@@ -105,10 +165,14 @@ internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Updating data
|
||||
|
||||
private void UpdateConfidence()
|
||||
{
|
||||
Confidence = Lookup.Keys.Any()
|
||||
? Lookup.Keys.Sum(key => key.Confidence) / Lookup.Keys.Count
|
||||
Confidence = Scanner.Lookup.Keys.Any()
|
||||
? Scanner.Lookup.Keys.Sum(key => key.Confidence) / Scanner.Lookup.Keys.Count
|
||||
: 0;
|
||||
}
|
||||
|
||||
@@ -121,7 +185,7 @@ internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||
Clear();
|
||||
if (Image != null)
|
||||
{
|
||||
Process(new[] { Image });
|
||||
Scanner.Process(new[] { Image });
|
||||
}
|
||||
|
||||
UpdateWords();
|
||||
@@ -135,13 +199,15 @@ internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
foreach (var word in Lookup.Keys)
|
||||
foreach (var word in Scanner.Lookup.Keys)
|
||||
{
|
||||
Words.Add(word);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private float _confidence;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
namespace GUI.ViewModels
|
||||
{
|
||||
public class LogViewModel
|
||||
{
|
||||
public LoggingCollection LoggingCollection { get; }
|
||||
namespace GUI.ViewModels;
|
||||
|
||||
public LogViewModel(LoggingCollection loggingCollection)
|
||||
{
|
||||
LoggingCollection = loggingCollection;
|
||||
}
|
||||
public class LogViewModel
|
||||
{
|
||||
public LoggingCollection LoggingCollection { get; }
|
||||
|
||||
public LogViewModel(LoggingCollection loggingCollection)
|
||||
{
|
||||
LoggingCollection = loggingCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@
|
||||
<CheckBox
|
||||
x:Name="EnableThreshold"
|
||||
Content="Apply Threshold"
|
||||
IsChecked="{Binding ImageProcessorConfiguration.EnableThresholding}" />
|
||||
IsChecked="{Binding ProcessorConfig.EnableThresholding}" />
|
||||
|
||||
<Grid
|
||||
Margin="4"
|
||||
@@ -209,7 +209,7 @@
|
||||
|
||||
<CheckBox
|
||||
Content="Resize"
|
||||
IsChecked="{Binding ImageProcessorConfiguration.EnableResizing}" />
|
||||
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
||||
<Grid Margin="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -235,7 +235,7 @@
|
||||
|
||||
<CheckBox
|
||||
Content="Filter connected components"
|
||||
IsChecked="{Binding ImageProcessorConfiguration.FilterConnectedComponents}" />
|
||||
IsChecked="{Binding ProcessorConfig.FilterConnectedComponents}" />
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -5,44 +5,43 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace GUI.Views
|
||||
namespace GUI.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class ImageView : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class ImageView : Window
|
||||
private ImageViewModel ViewModel => (ImageViewModel)DataContext;
|
||||
|
||||
public ImageView()
|
||||
{
|
||||
private ImageViewModel ViewModel => (ImageViewModel)DataContext;
|
||||
|
||||
public ImageView()
|
||||
{
|
||||
DataContext = new ImageViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ImageView(MagickImage image)
|
||||
{
|
||||
DataContext = new ImageViewModel(image);
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SldThreshold1_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ImageProcessorConfiguration.ThresholdWidth = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
|
||||
private void SldThreshold2_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ImageProcessorConfiguration.ThresholdHeight = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
|
||||
|
||||
private void SldBorder_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ImageProcessorConfiguration.Border = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
DataContext = new ImageViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public ImageView(MagickImage image)
|
||||
{
|
||||
DataContext = new ImageViewModel(image);
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SldThreshold1_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ProcessorConfig.ThresholdWidth = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
|
||||
private void SldThreshold2_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ProcessorConfig.ThresholdHeight = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
|
||||
|
||||
private void SldBorder_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
vm.ProcessorConfig.Border = (int)Math.Round(((Slider)sender).Value);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using GUI.ViewModels;
|
||||
using System.Windows;
|
||||
|
||||
namespace GUI.Views
|
||||
namespace GUI.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for LogView.xaml
|
||||
/// </summary>
|
||||
public partial class LogView : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LogView.xaml
|
||||
/// </summary>
|
||||
public partial class LogView : Window
|
||||
public LogView(LoggingCollection loggingCollection)
|
||||
{
|
||||
public LogView(LoggingCollection loggingCollection)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new LogViewModel(loggingCollection);
|
||||
}
|
||||
InitializeComponent();
|
||||
DataContext = new LogViewModel(loggingCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,57 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Abstract;
|
||||
|
||||
public abstract class DocumentGeneratorBase : StreamWriterBase, IDocumentGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected DocumentGeneratorBase() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected DocumentGeneratorBase(Stream stream) : base(stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected DocumentGeneratorBase(string filePath) : base(File.Open(filePath, FileMode.Create,
|
||||
FileAccess.Write))
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected DocumentGeneratorBase(Stream stream, Encoding encoding) : base(stream, encoding) { }
|
||||
|
||||
#region Writing
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IDocumentGenerator Append(string? text = default)
|
||||
{
|
||||
Write(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IDocumentGenerator AppendLine(string? text = default)
|
||||
{
|
||||
WriteLine(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IDocumentGenerator AppendHeading(int level, string text);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IDocumentGenerator AppendParagraph(string? text = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table)
|
||||
{
|
||||
Write(() => MakeTable(columns, new MemoryStream()), table);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract ITableGenerator MakeTable(int columns, Stream stream);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string FormatImage(string path, IBounds? bounds = default);
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Abstract;
|
||||
|
||||
public abstract class StreamWriterBase : IStreamWriter
|
||||
{
|
||||
private bool _isOpen;
|
||||
private bool _isClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Underlying <see cref="Stream"/>
|
||||
/// </summary>
|
||||
private Stream Stream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal <see cref="StreamWriter"/> for generating the output <see cref="string"/>
|
||||
/// </summary>
|
||||
protected TextWriter Writer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor; Configures the
|
||||
/// <see cref="StreamWriterBase"/> to write to the memory
|
||||
/// </summary>
|
||||
protected StreamWriterBase() : this(new MemoryStream()) { }
|
||||
|
||||
/// <inheritdoc cref="StreamWriterBase(System.IO.Stream, Encoding)"/>
|
||||
protected StreamWriterBase(Stream stream) : this(stream, Encoding.UTF8) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor; Configures the <see cref="StreamWriterBase"/>
|
||||
/// to write to the specified <paramref name="stream"/>
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/> to write to</param>
|
||||
/// <param name="encoding">Text <see cref="Encoding"/> of the written data</param>
|
||||
protected StreamWriterBase(Stream stream, Encoding encoding)
|
||||
{
|
||||
Stream = stream;
|
||||
Writer = new StreamWriter(stream, encoding);
|
||||
}
|
||||
|
||||
#region Control
|
||||
|
||||
public void Open()
|
||||
{
|
||||
if (_isOpen)
|
||||
{
|
||||
throw new InvalidOperationException($"{GetType()} has already been opened");
|
||||
}
|
||||
|
||||
if (_isClosed)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot call open on a closed {GetType()}");
|
||||
}
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
OnOpen();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_isClosed)
|
||||
{
|
||||
throw new InvalidOperationException($"{GetType()} has already been closed");
|
||||
}
|
||||
|
||||
if (!_isOpen)
|
||||
{
|
||||
throw new InvalidOperationException($"{GetType()} has never been opened");
|
||||
}
|
||||
|
||||
_isClosed = true;
|
||||
|
||||
OnClose();
|
||||
Writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once the internal writer has been initialized
|
||||
/// and the <see cref="Stream"/> is ready for writing
|
||||
/// </summary>
|
||||
protected virtual void OnOpen() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called once the document is about to be closed
|
||||
/// </summary>
|
||||
protected virtual void OnClose() { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reading
|
||||
|
||||
/// <inheritdoc />
|
||||
public StreamReader Read()
|
||||
{
|
||||
Writer.Flush();
|
||||
Stream.Seek(0, SeekOrigin.Begin);
|
||||
return new StreamReader(Stream, Writer.Encoding);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Writing
|
||||
|
||||
public IStreamWriter Write(IStreamWriter writer)
|
||||
{
|
||||
using var reader = writer.Read();
|
||||
return Write(reader);
|
||||
}
|
||||
|
||||
public IStreamWriter Write(StreamReader reader)
|
||||
{
|
||||
int bytesRead;
|
||||
char[] buffer = new char[4096];
|
||||
|
||||
while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
Writer.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStreamWriter Write(string? text = default)
|
||||
{
|
||||
Writer.Write(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStreamWriter WriteLine(string? text = default)
|
||||
{
|
||||
Writer.WriteLine(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and configures the <typeparamref name="T"/> using the given functions
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the <see cref="IStreamWriter"/></typeparam>
|
||||
/// <param name="makeFunc">Function used to generate the <typeparamref name="T"/></param>
|
||||
/// <param name="configFunc">Function used to configure the <typeparamref name="T"/></param>
|
||||
/// <returns></returns>
|
||||
public IStreamWriter Write<T>(Func<T> makeFunc, Action<T> configFunc)
|
||||
where T : IStreamWriter
|
||||
{
|
||||
using var writer = makeFunc();
|
||||
|
||||
writer.Open();
|
||||
configFunc(writer);
|
||||
writer.Close();
|
||||
|
||||
Write(writer);
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close document
|
||||
Close();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose stream
|
||||
Stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Abstract;
|
||||
|
||||
public abstract class TableGeneratorBase : StreamWriterBase, ITableGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Columns { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected TableGeneratorBase(int columns) =>
|
||||
Columns = columns;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected TableGeneratorBase(int columns, Stream stream)
|
||||
: base(stream) => Columns = columns;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected TableGeneratorBase(int columns, Stream stream, Encoding encoding)
|
||||
: base(stream, encoding) => Columns = columns;
|
||||
|
||||
|
||||
#region Header
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ITableGenerator AppendHeader(string content) =>
|
||||
AppendHeader(Enumerable.Range(0, Columns).Select(_ => content));
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract ITableGenerator AppendHeader(IEnumerable<string> row);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ITableGenerator AppendHeader(IEnumerable<IEnumerable<string>> rows)
|
||||
{
|
||||
foreach (var row in rows)
|
||||
{
|
||||
AppendHeader(row);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Row
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ITableGenerator AppendRow(string content) =>
|
||||
AppendRow(Enumerable.Range(0, Columns).Select(_ => content));
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract ITableGenerator AppendRow(IEnumerable<string> row);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ITableGenerator AppendRows(IEnumerable<IEnumerable<string>> rows)
|
||||
{
|
||||
foreach (var row in rows)
|
||||
{
|
||||
AppendRow(row);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using ReportGenerator.Generator.Abstract;
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Generator;
|
||||
|
||||
public class HtmlDocumentGenerator : DocumentGeneratorBase
|
||||
{
|
||||
private int _sectionLevel = 0;
|
||||
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlDocumentGenerator() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlDocumentGenerator(string filePath) : base(filePath)
|
||||
{
|
||||
Title = Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlDocumentGenerator(Stream stream) : base(stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlDocumentGenerator(Stream stream, Encoding encoding) : base(stream, encoding) { }
|
||||
|
||||
#region State
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
|
||||
// Init html document
|
||||
Write("<!DOCTYPE html>");
|
||||
Write("<html>");
|
||||
|
||||
// Header
|
||||
Write("<head>");
|
||||
Write(HtmlTools.Wrap("title", Title));
|
||||
Write("<meta charset=\"utf-8\">");
|
||||
Write("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
|
||||
Write("<style>");
|
||||
|
||||
using (var stream = Resources.Get("Style.css"))
|
||||
{
|
||||
using var streamReader = new StreamReader(stream);
|
||||
Write(streamReader);
|
||||
}
|
||||
|
||||
Write("</style>");
|
||||
Write("</head>");
|
||||
|
||||
// Init body
|
||||
Write("<body>");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
base.OnClose();
|
||||
|
||||
// End document
|
||||
Write("</body>");
|
||||
Write("</html>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Writing
|
||||
|
||||
/// <inheritdoc cref="AppendParagraph(string)" />
|
||||
public IDocumentGenerator AppendParagraph(string? text, string? @class) =>
|
||||
Append(HtmlTools.Wrap("p", text, @class));
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDocumentGenerator AppendParagraph(string? text = default) =>
|
||||
AppendParagraph(text, default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDocumentGenerator AppendHeading(int level, string text) =>
|
||||
Append($"<h{level} id=\"{text}\">{text}</h{level}>");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ITableGenerator MakeTable(int columns, Stream stream) =>
|
||||
new CollapsibleHtmlTableGenerator(columns, stream);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FormatImage(string path, IBounds? bounds = default) =>
|
||||
FormatImage(path, default, bounds);
|
||||
|
||||
/// <inheritdoc cref="FormatImage(string,IBounds)" />
|
||||
public string FormatImage(string path, string? @class, IBounds? bounds = default) =>
|
||||
HtmlTools.FormatImage(path, @class, bounds);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Management
|
||||
|
||||
private static class Resources
|
||||
{
|
||||
private static readonly Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
private static readonly string basePath =
|
||||
typeof(HtmlDocumentGenerator).Namespace + ".Resources.";
|
||||
|
||||
public static Stream Get(string fileName) =>
|
||||
assembly.GetManifestResourceStream(basePath + fileName) ??
|
||||
throw new FileNotFoundException("Could not get resource", fileName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using ReportGenerator.Generator.Abstract;
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Generator;
|
||||
|
||||
internal class CollapsibleHtmlTableGenerator : HtmlTableGenerator
|
||||
{
|
||||
public string DetailsClass { get; init; }
|
||||
public string Summary { get; init; } = "Show table";
|
||||
|
||||
/// <inheritdoc />
|
||||
public CollapsibleHtmlTableGenerator(int columns)
|
||||
: base(columns) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CollapsibleHtmlTableGenerator(int columns, Stream stream)
|
||||
: base(columns, stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CollapsibleHtmlTableGenerator(int columns, Stream stream, Encoding encoding)
|
||||
: base(columns, stream, encoding) { }
|
||||
|
||||
#region Overrides of HtmlTableGenerator
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnOpen()
|
||||
{
|
||||
Writer.Write($"<details class=\"{DetailsClass}\" open>");
|
||||
Writer.Write(HtmlTools.Wrap("summary", Summary));
|
||||
base.OnOpen();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
base.OnClose();
|
||||
Writer.Write($"</details>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class HtmlTableGenerator : TableGeneratorBase
|
||||
{
|
||||
public string Class { get; init; }
|
||||
|
||||
private static readonly (string start, string end) rowFormat = ("<tr>", "</tr>");
|
||||
private static readonly (string start, string end) headerFormat = ("<th>", "</th>");
|
||||
private static readonly (string start, string end) columnFormat = ("<td>", "</td>");
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlTableGenerator(int columns) : base(columns) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlTableGenerator(int columns, Stream stream) : base(columns, stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlTableGenerator(int columns, Stream stream, Encoding encoding) : base(columns, stream,
|
||||
encoding)
|
||||
{ }
|
||||
|
||||
#region State
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
Writer.Write($"<table class={Class}>");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
base.OnClose();
|
||||
Writer.Write("</table>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Writing
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITableGenerator AppendHeader(IEnumerable<string> row) =>
|
||||
AppendRow(row, rowFormat, headerFormat);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITableGenerator AppendRow(IEnumerable<string> row) =>
|
||||
AppendRow(row, rowFormat, columnFormat);
|
||||
|
||||
private ITableGenerator AppendRow(
|
||||
IEnumerable<string> row,
|
||||
(string, string) rowFormat,
|
||||
(string, string) columnFormat
|
||||
)
|
||||
{
|
||||
var (rowStart, rowEnd) = rowFormat;
|
||||
var (colStart, colEnd) = columnFormat;
|
||||
|
||||
this
|
||||
.Write(rowStart)
|
||||
.Write(colStart)
|
||||
.Write(string.Join(colEnd + colStart, row))
|
||||
.Write(colEnd)
|
||||
.Write(rowEnd);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using ReportGenerator.Generator.Abstract;
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Generator;
|
||||
|
||||
public class MarkdownDocumentGenerator : DocumentGeneratorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public MarkdownDocumentGenerator() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownDocumentGenerator(string filePath) : base(filePath) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownDocumentGenerator(Stream stream) : base(stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownDocumentGenerator(Stream stream, Encoding encoding) : base(stream, encoding) { }
|
||||
|
||||
#region Writing
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDocumentGenerator AppendHeading(int level, string text) =>
|
||||
AppendParagraph(new string('#', level) + ' ' + text);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ITableGenerator MakeTable(int columns, Stream stream) =>
|
||||
new MarkdownTableGenerator(columns, stream);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDocumentGenerator AppendParagraph(string? text = default)
|
||||
{
|
||||
AppendLine(text);
|
||||
AppendLine();
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string FormatImage(string path, IBounds? bounds = default) =>
|
||||
HtmlTools.FormatImage(path, default, bounds);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using ReportGenerator.Generator.Abstract;
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Generator.Generator;
|
||||
|
||||
internal class MarkdownTableGenerator : TableGeneratorBase
|
||||
{
|
||||
private const string ColumnSeparator = " | ";
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownTableGenerator(int columns)
|
||||
: base(columns) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownTableGenerator(int columns, Stream stream)
|
||||
: base(columns, stream) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MarkdownTableGenerator(int columns, Stream stream, Encoding encoding)
|
||||
: base(columns, stream, encoding) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITableGenerator AppendHeader(IEnumerable<string> row) =>
|
||||
this
|
||||
.AppendRow(row)
|
||||
.AppendRow("---");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ITableGenerator AppendRow(IEnumerable<string> row)
|
||||
{
|
||||
Writer.WriteLine(ColumnSeparator + string.Join(" | ", row) + ColumnSeparator);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
td, th {
|
||||
border: 1px solid #777;
|
||||
padding: .5rem;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background: #eee
|
||||
}
|
||||
|
||||
caption {
|
||||
font-size: .8rem
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
|
||||
namespace ReportGenerator.Generator;
|
||||
|
||||
internal static class HtmlTools
|
||||
{
|
||||
public static string Wrap(string tag, string? content, string? @class = default) =>
|
||||
$"<{tag} class={@class}>{content}</{tag}>";
|
||||
|
||||
public static string FormatImage(string path, string? @class = default, IBounds? bounds = default)
|
||||
{
|
||||
var style = bounds is null
|
||||
? string.Empty
|
||||
: GetCssStyle(bounds);
|
||||
|
||||
path += path.EndsWith(".png") ? string.Empty : ".png";
|
||||
|
||||
return $"<img src=\"{path}\" style=\"{style}\" class={@class} />";
|
||||
}
|
||||
|
||||
private static string GetCssStyle(IBounds bounds)
|
||||
{
|
||||
var style = string.Empty;
|
||||
|
||||
// Width
|
||||
if (bounds.Width.HasValue)
|
||||
{
|
||||
style += $"width:{bounds.Width}{bounds.Unit};";
|
||||
}
|
||||
|
||||
if (bounds.MinWidth.HasValue)
|
||||
{
|
||||
style += $"min-width:{bounds.MinWidth}{bounds.Unit};";
|
||||
}
|
||||
|
||||
if (bounds.MaxWidth.HasValue)
|
||||
{
|
||||
style += $"max-width:{bounds.MaxWidth}{bounds.Unit};";
|
||||
}
|
||||
|
||||
// Height
|
||||
if (bounds.Height.HasValue)
|
||||
{
|
||||
style += $"height:{bounds.Height}{bounds.Unit};";
|
||||
}
|
||||
|
||||
if (bounds.MinHeight.HasValue)
|
||||
{
|
||||
style += $"min-height:{bounds.MinHeight}{bounds.Unit};";
|
||||
}
|
||||
|
||||
if (bounds.MaxHeight.HasValue)
|
||||
{
|
||||
style += $"max-height:{bounds.MaxHeight}{bounds.Unit};";
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace ReportGenerator.Generator.Interface;
|
||||
|
||||
public interface IBounds
|
||||
{
|
||||
public string Unit { get; }
|
||||
public int? MinWidth { get; }
|
||||
public int? MinHeight { get; }
|
||||
public int? MaxWidth { get; }
|
||||
public int? MaxHeight { get; }
|
||||
public int? Width { get; }
|
||||
public int? Height { get; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace ReportGenerator.Generator.Interface;
|
||||
|
||||
public interface IDocumentGenerator : IStreamWriter
|
||||
{
|
||||
IDocumentGenerator Append(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendLine(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendParagraph(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendHeading(int level, string text);
|
||||
|
||||
IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table);
|
||||
|
||||
string FormatImage(string path, IBounds? bounds = default);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace ReportGenerator.Generator.Interface;
|
||||
|
||||
public interface IStreamWriter : IDisposable
|
||||
{
|
||||
/// <inheritdoc cref="TextWriter.WriteLine(string)"/>
|
||||
IStreamWriter Write(string? text = default);
|
||||
|
||||
/// <inheritdoc cref="TextWriter.WriteLine(string)"/>
|
||||
IStreamWriter WriteLine(string? text = default);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Writes the contents of the given <paramref name="writer"/>
|
||||
/// to the internal <see cref="Stream"/></para>
|
||||
/// </summary>
|
||||
IStreamWriter Write(IStreamWriter writer);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Writes the contents of the given <paramref name="reader"/>
|
||||
/// to the internal <see cref="Stream"/></para>
|
||||
/// </summary>
|
||||
IStreamWriter Write(StreamReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the content writer
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the content writer
|
||||
/// </summary>
|
||||
void Open();
|
||||
|
||||
/// <summary>
|
||||
/// Reads the content of the underlying stream
|
||||
/// </summary>
|
||||
StreamReader Read();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace ReportGenerator.Generator.Interface;
|
||||
|
||||
public interface ITableGenerator : IStreamWriter
|
||||
{
|
||||
int Columns { get; }
|
||||
|
||||
ITableGenerator AppendHeader(string content);
|
||||
ITableGenerator AppendHeader(IEnumerable<string> row);
|
||||
ITableGenerator AppendHeader(IEnumerable<IEnumerable<string>> rows);
|
||||
|
||||
ITableGenerator AppendRow(string content);
|
||||
ITableGenerator AppendRow(IEnumerable<string> row);
|
||||
ITableGenerator AppendRows(IEnumerable<IEnumerable<string>> rows);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
|
||||
namespace ReportGenerator.Generator.Model;
|
||||
|
||||
public struct Bounds : IBounds
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Unit => "px";
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? MinWidth { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? MinHeight { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? MaxWidth { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? MaxHeight { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? Width { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? Height { get; set; } = null;
|
||||
|
||||
public Bounds() { }
|
||||
|
||||
public Bounds(int? size)
|
||||
{
|
||||
Width = size;
|
||||
Height = size;
|
||||
}
|
||||
|
||||
public Bounds(int? min, int? max, int? size = null) : this(size)
|
||||
{
|
||||
MinWidth = min;
|
||||
MinHeight = min;
|
||||
|
||||
MaxWidth = max;
|
||||
MaxHeight = max;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace ReportGenerator.Models;
|
||||
|
||||
public readonly struct ImageStats
|
||||
{
|
||||
public string ImageName { get; }
|
||||
|
||||
public ICollection<string> Reference { get; }
|
||||
|
||||
public ICollection<ProcessorStat> Processors { get; }
|
||||
|
||||
public ImageStats(
|
||||
string imageName,
|
||||
ICollection<string> taggedWords,
|
||||
IEnumerable<ScanFileInfo> scanResult
|
||||
)
|
||||
{
|
||||
Reference = taggedWords;
|
||||
ImageName = imageName;
|
||||
Processors = scanResult
|
||||
.Select(t =>
|
||||
{
|
||||
var (elapsed, words) = t.GetData();
|
||||
return new ProcessorStat(t.ProcessorName, elapsed, taggedWords, words);
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Common.Distance;
|
||||
|
||||
namespace ReportGenerator.Models;
|
||||
|
||||
public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the processor
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total milliseconds it took the processor to process the data
|
||||
/// </summary>
|
||||
public double ProcessingTime { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> Reference { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string>? Hypothesis { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public double Distance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the processed values
|
||||
/// </summary>
|
||||
public ICollection<IDistanceComparer<string>> Words { get; }
|
||||
|
||||
public ProcessorStat(
|
||||
string name,
|
||||
double processingTime,
|
||||
ICollection<string> reference,
|
||||
ICollection<string> hypothesis
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
ProcessingTime = processingTime;
|
||||
|
||||
Reference = reference;
|
||||
Hypothesis = hypothesis;
|
||||
|
||||
Distance = Calculator.GetDistance(
|
||||
reference.OrderBy(s => s).ToArray(),
|
||||
hypothesis.OrderBy(s => s).ToArray()
|
||||
) / reference.Count;
|
||||
|
||||
Words = GetDistanceInfos(reference, hypothesis).ToArray();
|
||||
// Words = reference.Select(r => GetDistanceInfo(r, hypothesis)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compares the <paramref name="referenceCollection"/> with all given values in the
|
||||
/// <paramref name="hypothesisCollection"/>, determining the <see cref="IDistanceComparer{T}"/>
|
||||
/// with the lowest error
|
||||
/// </summary>
|
||||
private static IEnumerable<IDistanceComparer<string>> GetDistanceInfos(
|
||||
ICollection<string> referenceCollection,
|
||||
ICollection<string> hypothesisCollection
|
||||
)
|
||||
{
|
||||
var results = new List<IDistanceComparer<string>>();
|
||||
|
||||
foreach (var reference in referenceCollection)
|
||||
{
|
||||
var tResults = hypothesisCollection
|
||||
.Select(hypothesis => new DistanceComparer<string>(reference, hypothesis))
|
||||
.Cast<IDistanceComparer<string>>()
|
||||
.ToList();
|
||||
|
||||
results.AddRange(tResults);
|
||||
}
|
||||
|
||||
var lookup = results
|
||||
.OrderBy(result => result.Distance)
|
||||
.ToLookup(result => result.Reference);
|
||||
|
||||
foreach (var reference in referenceCollection)
|
||||
{
|
||||
yield return lookup[reference].FirstOrDefault() ?? new DistanceComparer<string>(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ReportGenerator.Models;
|
||||
|
||||
public struct ScanFileInfo
|
||||
{
|
||||
public string Path { get; private init; }
|
||||
|
||||
private static readonly Regex parseRegex = new(
|
||||
@"(?'image'.+)\.(?'processor'.+)\..+",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
public string ProcessorName { get; set; }
|
||||
|
||||
public string ImageName { get; set; }
|
||||
|
||||
public (double Elapsed, ICollection<string> Words) GetData()
|
||||
{
|
||||
using var file = File.OpenRead(Path);
|
||||
var root = JsonDocument.Parse(file).RootElement;
|
||||
|
||||
var words = root
|
||||
.GetProperty("Words")
|
||||
.EnumerateArray()
|
||||
.Select(e => e.GetProperty("Text").GetString() ?? string.Empty)
|
||||
.ToArray();
|
||||
|
||||
var elapsed = root.GetProperty("Elapsed").GetDouble();
|
||||
|
||||
return (elapsed, words);
|
||||
}
|
||||
|
||||
public static ScanFileInfo FromPath(string path)
|
||||
{
|
||||
var match = parseRegex.Match(System.IO.Path.GetFileName(path));
|
||||
return new ScanFileInfo
|
||||
{
|
||||
Path = path,
|
||||
ProcessorName = match.Groups["processor"].Value,
|
||||
ImageName = match.Groups["image"].Value
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => ImageName;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ReportGenerator.Models;
|
||||
|
||||
internal struct TagFileInfo
|
||||
{
|
||||
public string Path { get; private init; }
|
||||
|
||||
public string ImageName { get; set; }
|
||||
|
||||
public ICollection<string> GetWords()
|
||||
{
|
||||
using var file = File.OpenRead(Path);
|
||||
return JsonDocument
|
||||
.Parse(file)
|
||||
.RootElement
|
||||
.GetProperty("words")
|
||||
.EnumerateArray()
|
||||
.Select(w => w.GetString() ?? throw new Exception("Cannot parse null words"))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static TagFileInfo FromPath(string path) => new()
|
||||
{
|
||||
Path = path,
|
||||
ImageName = System.IO.Path.GetFileNameWithoutExtension(path),
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => ImageName;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Common.Extensions;
|
||||
using ReportGenerator.Generator.Generator;
|
||||
using ReportGenerator.Models;
|
||||
|
||||
namespace ReportGenerator;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
internal static void Main(string[] args)
|
||||
{
|
||||
// Retrieve data
|
||||
|
||||
Console.WriteLine("Getting data");
|
||||
var tagFileInfos = GetTagFileInfos(args[0]);
|
||||
var scanFileInfos = GetScanFileInfos(args[1]);
|
||||
|
||||
// Parse
|
||||
|
||||
Console.WriteLine("Generating report");
|
||||
var scans = Scan(tagFileInfos, scanFileInfos);
|
||||
|
||||
var path = Path.GetFullPath("report.html");
|
||||
|
||||
using var document = new HtmlDocumentGenerator(path);
|
||||
using var report = new ReportGenerator("OCR Report", document, scans)
|
||||
.AddComparison("Processing summary (Average)", v =>
|
||||
{
|
||||
var result = v.Average(out var deviation);
|
||||
return (result, deviation);
|
||||
})
|
||||
// .AddComparison("Processing summary (Cumulative)", v => v.Sum())
|
||||
.AddComparison("Processing summary (Median)", v =>
|
||||
{
|
||||
var result = v.Median(out var deviation);
|
||||
return (result, deviation);
|
||||
})
|
||||
// .AddProcessorStats("Processor Stats")
|
||||
.AddImageStatsFull("Scan Results");
|
||||
|
||||
Console.WriteLine($"Saved report to '{path}'");
|
||||
}
|
||||
|
||||
private static IEnumerable<ImageStats> Scan(
|
||||
IEnumerable<TagFileInfo> tagFileInfos,
|
||||
IEnumerable<ScanFileInfo> scanFileInfos
|
||||
)
|
||||
{
|
||||
var scanFileLookup = scanFileInfos.ToLookup(i => i.ImageName);
|
||||
foreach (var i in tagFileInfos)
|
||||
{
|
||||
yield return new ImageStats(
|
||||
i.ImageName,
|
||||
i.GetWords().Distinct().OrderBy(w => w).ToArray(),
|
||||
scanFileLookup[i.ImageName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<TagFileInfo> GetTagFileInfos(string dir)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
throw new ArgumentException($"Invalid tagged data directory '{dir}'");
|
||||
}
|
||||
|
||||
return Directory.EnumerateFiles(dir, "*.json").Select(TagFileInfo.FromPath);
|
||||
}
|
||||
|
||||
private static IEnumerable<ScanFileInfo> GetScanFileInfos(string dir)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
throw new ArgumentException($"Invalid scan results directory '{dir}'");
|
||||
}
|
||||
|
||||
return Directory.EnumerateFiles(dir, "*.json").Select(ScanFileInfo.FromPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ReportGenerator": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img\" \"results\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using ReportGenerator.Generator.Interface;
|
||||
using ReportGenerator.Generator.Model;
|
||||
using ReportGenerator.Models;
|
||||
|
||||
namespace ReportGenerator;
|
||||
|
||||
public class ReportGenerator : IDisposable
|
||||
{
|
||||
private IDocumentGenerator Document { get; }
|
||||
|
||||
private ICollection<ImageStats> Images { get; }
|
||||
|
||||
public ReportGenerator(
|
||||
IDocumentGenerator document,
|
||||
IEnumerable<ImageStats> data
|
||||
)
|
||||
{
|
||||
Images = data.ToArray();
|
||||
|
||||
Document = document;
|
||||
document.Open();
|
||||
}
|
||||
|
||||
public ReportGenerator(
|
||||
string title,
|
||||
IDocumentGenerator document,
|
||||
IEnumerable<ImageStats> data
|
||||
) : this(document, data) => AddTitle(title);
|
||||
|
||||
#region Writing
|
||||
|
||||
public ReportGenerator AddTitle(string text)
|
||||
{
|
||||
Document.AppendHeading(1, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReportGenerator AddProcessorStats(string title)
|
||||
{
|
||||
Document.AppendHeading(2, title);
|
||||
|
||||
var processors = new Dictionary<string, ICollection<ImageStats>>();
|
||||
foreach (var image in Images)
|
||||
{
|
||||
foreach (var processor in image.Processors)
|
||||
{
|
||||
if (processors.TryGetValue(processor.Name, out var images))
|
||||
{
|
||||
images.Add(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
processors.Add(processor.Name, new List<ImageStats> { image });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (processor, images) in processors)
|
||||
{
|
||||
var ordered = images
|
||||
.Select(i => (Stats: i, Distance: i
|
||||
.Processors
|
||||
.Where(p => p.Name.Equals(processor))
|
||||
.Select(p => p.Distance)
|
||||
.Average()
|
||||
))
|
||||
.OrderBy(i => i.Distance)
|
||||
.ToArray();
|
||||
|
||||
Document
|
||||
.AppendHeading(3, processor)
|
||||
.AppendTable(2, table =>
|
||||
{
|
||||
table.AppendHeader(new[] { "Image", "Preview", "Distance" });
|
||||
|
||||
foreach (var (stats, distance) in ordered)
|
||||
{
|
||||
var imgPath = Path.Combine("results", $"{processor}.00.{stats.ImageName}.png");
|
||||
|
||||
table.AppendRow(new[]
|
||||
{
|
||||
$"<a href=\"#{stats.ImageName}\">{stats.ImageName}</a>",
|
||||
Document.FormatImage(imgPath, new Bounds(0, 150)),
|
||||
distance.ToString("F2")
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReportGenerator AddImageStatsFull(string title)
|
||||
{
|
||||
Document.AppendHeading(2, title);
|
||||
|
||||
foreach (var stat in Images)
|
||||
{
|
||||
Document
|
||||
.AppendHeading(3, stat.ImageName)
|
||||
.AppendParagraph(
|
||||
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
|
||||
)
|
||||
.AppendTable(
|
||||
stat.Reference.Count + 5,
|
||||
table =>
|
||||
{
|
||||
table.AppendHeader(stat
|
||||
.Reference
|
||||
.Prepend("Image")
|
||||
.Prepend("CER (avg)")
|
||||
.Prepend("WER")
|
||||
.Prepend("Elapsed")
|
||||
.Prepend("Processor")
|
||||
);
|
||||
|
||||
var processors = stat.Processors
|
||||
.OrderBy(s => s.Distance)
|
||||
.ThenBy(s => s.ProcessingTime);
|
||||
|
||||
foreach (var processor in processors)
|
||||
{
|
||||
var imgPath = Path.Combine("results", $"{processor.Name}.00.{stat.ImageName}.png");
|
||||
|
||||
table.AppendRow(processor.Words
|
||||
.Select(s => s.ToString() ?? string.Empty)
|
||||
.Prepend(Document.FormatImage(imgPath, new Bounds(0, 150)))
|
||||
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
||||
.Prepend($"{processor.Distance * 100:F1}%")
|
||||
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
||||
.Prepend(processor.Name)
|
||||
);
|
||||
}
|
||||
})
|
||||
.AppendParagraph(
|
||||
$"Comparison data generated based on {stat.Reference.Count} tagged words."
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReportGenerator AddComparison(
|
||||
string title,
|
||||
Func<IEnumerable<double>, (double, double)> evaluationFunc
|
||||
)
|
||||
{
|
||||
var lookup = Images
|
||||
.SelectMany(s => s.Processors)
|
||||
.ToLookup(p => p.Name);
|
||||
|
||||
Document.AppendHeading(2, title);
|
||||
|
||||
var byWer = lookup
|
||||
.Select(g =>
|
||||
{
|
||||
var (value, deviation) = evaluationFunc(g.Select(p => p.Distance * 100));
|
||||
return (
|
||||
Name: g.Key,
|
||||
Value: value,
|
||||
Deviation: deviation
|
||||
);
|
||||
})
|
||||
.OrderBy(g => g.Value);
|
||||
Document.AppendHeading(3, "WER");
|
||||
AppendComparison(("Error", "%"), byWer);
|
||||
|
||||
var byCer = lookup
|
||||
.Select(g =>
|
||||
{
|
||||
var (value, deviation) =
|
||||
evaluationFunc(g.SelectMany(p => p.Words, (_, word) => word.Distance));
|
||||
return (
|
||||
Name: g.Key,
|
||||
Value: value,
|
||||
Deviation: deviation
|
||||
);
|
||||
})
|
||||
.OrderBy(g => g.Value);
|
||||
Document.AppendHeading(3, "CER");
|
||||
AppendComparison(("Changes", string.Empty), byCer);
|
||||
|
||||
var byTime = lookup
|
||||
.Select(g =>
|
||||
{
|
||||
var (value, deviation) = evaluationFunc(g.Select(p => p.ProcessingTime * 1000));
|
||||
return (
|
||||
Name: g.Key,
|
||||
Value: value,
|
||||
Deviation: deviation
|
||||
);
|
||||
})
|
||||
.OrderBy(g => g.Value);
|
||||
Document.AppendHeading(3, "Time");
|
||||
AppendComparison(("Time", "ms"), byTime);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private void AppendComparison(
|
||||
(string name, string unit)? valueInfo,
|
||||
IEnumerable<(string, double, double)> values
|
||||
)
|
||||
{
|
||||
const int context = 5;
|
||||
|
||||
var tValues = values.ToArray();
|
||||
var tContext = Math.Min(tValues.Length / 2, context);
|
||||
|
||||
Document.AppendTable(3, table =>
|
||||
{
|
||||
table
|
||||
.AppendHeader(new[]
|
||||
{
|
||||
"Processor",
|
||||
valueInfo?.name ?? "Value",
|
||||
"Deviation"
|
||||
})
|
||||
.AppendRows(tValues
|
||||
.Take(tContext)
|
||||
.Select(MakeRow))
|
||||
.AppendRow("...")
|
||||
.AppendRows(
|
||||
tValues
|
||||
.TakeLast(tContext)
|
||||
.Select(MakeRow)
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
string[] MakeRow((string, double, double) v) =>
|
||||
new[]
|
||||
{
|
||||
v.Item1,
|
||||
v.Item2.ToString("F2") + valueInfo?.unit,
|
||||
v.Item3.ToString("F2") + valueInfo?.unit
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Document.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Properties\htmldocument-style.css" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Generator\Generator\Resources\Style.css" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"words": [
|
||||
"interlockings",
|
||||
"active",
|
||||
"interlocking",
|
||||
"text",
|
||||
"typ",
|
||||
"static",
|
||||
"id",
|
||||
"10034",
|
||||
"no"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"words": [
|
||||
"S7-300",
|
||||
"datenbausteinkonfiguration",
|
||||
"einstellungen",
|
||||
"ok",
|
||||
"abbrechen",
|
||||
"name",
|
||||
"seriell",
|
||||
"ohne",
|
||||
"id",
|
||||
"datenbaustein",
|
||||
"0",
|
||||
"offset",
|
||||
"frei",
|
||||
"archiv kennung",
|
||||
"SO",
|
||||
"produkt ID",
|
||||
"spaltenorientiert",
|
||||
"seriell",
|
||||
"schreibstatus",
|
||||
"ok",
|
||||
"abbrechen",
|
||||
"datensatz",
|
||||
"wert",
|
||||
"datentyp",
|
||||
"stelle",
|
||||
"1",
|
||||
"doppelwort",
|
||||
"neuer",
|
||||
"wert",
|
||||
"löschen",
|
||||
"neue"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"words": [
|
||||
"SEL",
|
||||
"enter",
|
||||
"an",
|
||||
"integer",
|
||||
"between",
|
||||
"100",
|
||||
"and",
|
||||
"180000",
|
||||
"ok"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"words": [
|
||||
"variablendefinition",
|
||||
"selektieren",
|
||||
"sie",
|
||||
"das",
|
||||
"gewünschte",
|
||||
"objekt",
|
||||
"verlassen",
|
||||
"hilfe",
|
||||
"neu",
|
||||
"konfiguration",
|
||||
"archive",
|
||||
"float",
|
||||
"treibervariable",
|
||||
"bit",
|
||||
"byte",
|
||||
"wort",
|
||||
"doppelwort",
|
||||
"float",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"words": [
|
||||
"new",
|
||||
"opc",
|
||||
"tag",
|
||||
"declaration",
|
||||
"a",
|
||||
"new",
|
||||
"tag",
|
||||
"file",
|
||||
"filename",
|
||||
"newOpcTag.opct",
|
||||
"description",
|
||||
"back",
|
||||
"finish",
|
||||
"cancel",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"words": [
|
||||
"select",
|
||||
"a",
|
||||
"connection",
|
||||
"connections",
|
||||
"(1)",
|
||||
"D445",
|
||||
"filter",
|
||||
"all",
|
||||
"variables",
|
||||
"only",
|
||||
"new",
|
||||
"existing",
|
||||
"variable",
|
||||
"import",
|
||||
"set",
|
||||
"resources",
|
||||
"label",
|
||||
"ok",
|
||||
"cancel"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"words": [
|
||||
"define",
|
||||
"physical",
|
||||
"monitor",
|
||||
"name",
|
||||
"MM",
|
||||
"001",
|
||||
"position",
|
||||
"alternative",
|
||||
"for",
|
||||
"undedactable",
|
||||
"M",
|
||||
"000",
|
||||
"top",
|
||||
"left",
|
||||
"right",
|
||||
"bottom",
|
||||
"0",
|
||||
"1920",
|
||||
"3840",
|
||||
"1080",
|
||||
"options",
|
||||
"display",
|
||||
"in",
|
||||
"the",
|
||||
"online",
|
||||
"menu",
|
||||
"ok",
|
||||
"cancel",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"words": [
|
||||
"connection",
|
||||
"create",
|
||||
"driver",
|
||||
"variable",
|
||||
"visualization",
|
||||
"frame",
|
||||
"screen",
|
||||
"execution",
|
||||
"function",
|
||||
"first",
|
||||
"steps",
|
||||
"welcome",
|
||||
"to",
|
||||
"the",
|
||||
"engineering",
|
||||
"studio",
|
||||
"we",
|
||||
"want",
|
||||
"make",
|
||||
"your",
|
||||
"in",
|
||||
"easy",
|
||||
"this",
|
||||
"end",
|
||||
"are",
|
||||
"introducing",
|
||||
"you",
|
||||
"basic",
|
||||
"for",
|
||||
"configuring",
|
||||
"a",
|
||||
"project",
|
||||
"following",
|
||||
"chapters",
|
||||
"how",
|
||||
"work",
|
||||
"and",
|
||||
"what",
|
||||
"need",
|
||||
"projects",
|
||||
"can",
|
||||
"incorporate",
|
||||
"data",
|
||||
"points",
|
||||
"into",
|
||||
"is",
|
||||
"tool",
|
||||
"control",
|
||||
"of",
|
||||
"processes",
|
||||
"generally",
|
||||
"do",
|
||||
"not",
|
||||
"anything",
|
||||
"because",
|
||||
"properties",
|
||||
"dialogs",
|
||||
"setting",
|
||||
"parameters",
|
||||
"them",
|
||||
"if",
|
||||
"rproject",
|
||||
"has",
|
||||
"been",
|
||||
"configured",
|
||||
"compile",
|
||||
"it",
|
||||
"start",
|
||||
"service",
|
||||
"engine",
|
||||
"then",
|
||||
"see",
|
||||
"front",
|
||||
"application",
|
||||
"back",
|
||||
"next"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"words": [
|
||||
"zenon",
|
||||
"energy",
|
||||
"edition",
|
||||
"file",
|
||||
"edit",
|
||||
"screens",
|
||||
"options",
|
||||
"window",
|
||||
"help",
|
||||
"100",
|
||||
"activatestartupscreen",
|
||||
"properties",
|
||||
"cross",
|
||||
"reference",
|
||||
"list",
|
||||
"report",
|
||||
"start",
|
||||
"variables",
|
||||
"functions",
|
||||
"language",
|
||||
"historian",
|
||||
"recipes",
|
||||
"time",
|
||||
"control",
|
||||
"windows",
|
||||
"CE",
|
||||
"filter",
|
||||
"text",
|
||||
"total",
|
||||
"filtered",
|
||||
"select",
|
||||
"project",
|
||||
"tree",
|
||||
"network",
|
||||
"topology",
|
||||
"welcome",
|
||||
"to",
|
||||
"the",
|
||||
"property",
|
||||
"shows",
|
||||
"you",
|
||||
"brief",
|
||||
"information",
|
||||
"each",
|
||||
"propery",
|
||||
"what",
|
||||
"is",
|
||||
"needed",
|
||||
"for",
|
||||
"its",
|
||||
"standard",
|
||||
"value",
|
||||
"from",
|
||||
"other",
|
||||
"depended",
|
||||
"on",
|
||||
"etc",
|
||||
"a",
|
||||
"link",
|
||||
"online",
|
||||
"also",
|
||||
"provided",
|
||||
"and",
|
||||
"name",
|
||||
"VBA",
|
||||
"VSTA",
|
||||
"displayed",
|
||||
"output",
|
||||
"load",
|
||||
"DOKU",
|
||||
"ready",
|
||||
"elemente"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"words": [
|
||||
"filter",
|
||||
"profiles",
|
||||
"savve",
|
||||
"import",
|
||||
"trend",
|
||||
"WIZ10",
|
||||
"curve",
|
||||
"name",
|
||||
"WIZ_VAR_10",
|
||||
"WIZ_VAR_11",
|
||||
"WIZ_VAR_12",
|
||||
"filter",
|
||||
"text",
|
||||
"export",
|
||||
"delete",
|
||||
"play",
|
||||
"stop",
|
||||
"continue",
|
||||
"refresh",
|
||||
"zoom",
|
||||
"rezoom",
|
||||
"cursor",
|
||||
"on",
|
||||
"off",
|
||||
"copy",
|
||||
"to",
|
||||
"clipboard",
|
||||
"diagram",
|
||||
"settings",
|
||||
"print",
|
||||
"axis",
|
||||
"active",
|
||||
"color",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"words": [
|
||||
"start",
|
||||
"page",
|
||||
"create",
|
||||
"archive",
|
||||
"archives",
|
||||
"serve",
|
||||
"for",
|
||||
"the",
|
||||
"recording",
|
||||
"of",
|
||||
"process",
|
||||
"values",
|
||||
"over",
|
||||
"a",
|
||||
"longer",
|
||||
"period",
|
||||
"time",
|
||||
"recorded",
|
||||
"data",
|
||||
"can",
|
||||
"be",
|
||||
"summarized",
|
||||
"in",
|
||||
"aggregated",
|
||||
"sum",
|
||||
"average",
|
||||
"value",
|
||||
"minimum",
|
||||
"and",
|
||||
"maximum",
|
||||
"certain",
|
||||
"used",
|
||||
"extended",
|
||||
"trend",
|
||||
"this",
|
||||
"assistant",
|
||||
"should",
|
||||
"help",
|
||||
"you",
|
||||
"with",
|
||||
"basic",
|
||||
"engineering",
|
||||
"an",
|
||||
"if",
|
||||
"want",
|
||||
"to",
|
||||
"teditor",
|
||||
"without",
|
||||
"press",
|
||||
"cancel",
|
||||
"now",
|
||||
"back",
|
||||
"next"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"words": [
|
||||
"function",
|
||||
"wizard",
|
||||
"settings",
|
||||
"syntax",
|
||||
"alarm",
|
||||
"archive",
|
||||
"archiveex",
|
||||
"archivexr",
|
||||
"archivem",
|
||||
"archivemr",
|
||||
"archivemsp",
|
||||
"archivemspr",
|
||||
"archiver",
|
||||
"archivesp",
|
||||
"parameter",
|
||||
"options",
|
||||
"ok",
|
||||
"cancel",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"words": [
|
||||
"reportassistant",
|
||||
"data",
|
||||
"report",
|
||||
"rdl",
|
||||
"tools",
|
||||
"data",
|
||||
"projects",
|
||||
"ZAD_CIP",
|
||||
"ZAD_GBL",
|
||||
"OEEFactor",
|
||||
"OEEFactor",
|
||||
"batchhistory",
|
||||
"DPA",
|
||||
"single",
|
||||
"alarms",
|
||||
"most",
|
||||
"frequent",
|
||||
"equipment",
|
||||
"related",
|
||||
"group",
|
||||
"consumption",
|
||||
"relative",
|
||||
"per",
|
||||
"medium",
|
||||
"group",
|
||||
"abs",
|
||||
"development",
|
||||
"costs",
|
||||
"absolute",
|
||||
"ZAD_MAIN",
|
||||
"filter",
|
||||
"equipmentfilter",
|
||||
"datetimefilter",
|
||||
"string",
|
||||
"variables",
|
||||
"integer",
|
||||
"report1",
|
||||
"filter()",
|
||||
"equipmentfilter",
|
||||
"element",
|
||||
"properties",
|
||||
"model",
|
||||
"mnestinglevel",
|
||||
"misc",
|
||||
"name",
|
||||
"parameterlist",
|
||||
"collection",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"words": [
|
||||
"service",
|
||||
"engine",
|
||||
"profiles",
|
||||
"action",
|
||||
"open",
|
||||
"administration",
|
||||
"dialog",
|
||||
"in",
|
||||
"the",
|
||||
"service",
|
||||
"engine",
|
||||
"load",
|
||||
"profile",
|
||||
"default",
|
||||
"last",
|
||||
"freely",
|
||||
"defined",
|
||||
"name",
|
||||
"name",
|
||||
"from",
|
||||
"variable",
|
||||
"linked",
|
||||
"no",
|
||||
"save",
|
||||
"profile",
|
||||
"ok",
|
||||
"cancel",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"words": [
|
||||
"general",
|
||||
"redundancy",
|
||||
"advanced",
|
||||
"settings",
|
||||
"communication",
|
||||
"standard",
|
||||
"port",
|
||||
"event",
|
||||
"1200",
|
||||
"9000",
|
||||
"startup",
|
||||
"no",
|
||||
"start",
|
||||
"open",
|
||||
"this",
|
||||
"box",
|
||||
"cold",
|
||||
"start",
|
||||
"load",
|
||||
"retain",
|
||||
"variables",
|
||||
"hot",
|
||||
"restart",
|
||||
"stepping",
|
||||
"mode",
|
||||
"delay",
|
||||
"s",
|
||||
"0",
|
||||
"retain",
|
||||
"data",
|
||||
"storing",
|
||||
"path",
|
||||
"for",
|
||||
"retain",
|
||||
"data",
|
||||
"users",
|
||||
"public",
|
||||
"documents",
|
||||
"zenon_projects",
|
||||
"700",
|
||||
"test",
|
||||
"rt",
|
||||
"files",
|
||||
"store",
|
||||
"variables",
|
||||
"by",
|
||||
"name",
|
||||
"cyclically",
|
||||
"data",
|
||||
"real",
|
||||
"time",
|
||||
"priority",
|
||||
"ok",
|
||||
"cancel",
|
||||
"apply",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"words": [
|
||||
"zoom",
|
||||
"steps",
|
||||
"existing",
|
||||
"zoom",
|
||||
"steps",
|
||||
"step",
|
||||
"to",
|
||||
"%",
|
||||
"0",
|
||||
"100",
|
||||
"new",
|
||||
"edit",
|
||||
"delete",
|
||||
"ok",
|
||||
"cancel",
|
||||
"help"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"words": [
|
||||
"metadata",
|
||||
"editor",
|
||||
"file",
|
||||
"edit",
|
||||
"help",
|
||||
"equipment",
|
||||
"modeling",
|
||||
"event",
|
||||
"classes",
|
||||
"groups",
|
||||
"users",
|
||||
"project",
|
||||
"variables",
|
||||
"archives",
|
||||
"efficiency",
|
||||
"class",
|
||||
"models",
|
||||
"reference",
|
||||
"curves",
|
||||
"name",
|
||||
"visual",
|
||||
"description",
|
||||
"apply",
|
||||
"clear",
|
||||
"glass",
|
||||
"bottle",
|
||||
"line",
|
||||
"WS",
|
||||
"Cur",
|
||||
"State",
|
||||
"depalletizer",
|
||||
"unpacker",
|
||||
"washer",
|
||||
"filler",
|
||||
"pasteurizer",
|
||||
"labeler",
|
||||
"conveyor",
|
||||
"grate",
|
||||
"inspector",
|
||||
"operation",
|
||||
"packer",
|
||||
"connected",
|
||||
"to",
|
||||
"reporting",
|
||||
"on",
|
||||
"testenv",
|
||||
"local",
|
||||
"total",
|
||||
"filtered",
|
||||
"selected",
|
||||
"changed",
|
||||
"ready"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"words": [
|
||||
"new",
|
||||
"report",
|
||||
"theme",
|
||||
"efficiency",
|
||||
"class",
|
||||
"analysis",
|
||||
"this",
|
||||
"contains",
|
||||
"templates",
|
||||
"that",
|
||||
"allow",
|
||||
"based",
|
||||
"on",
|
||||
"models",
|
||||
"the",
|
||||
"data",
|
||||
"can",
|
||||
"be",
|
||||
"processed",
|
||||
"archives",
|
||||
"equipment",
|
||||
"groups",
|
||||
"or",
|
||||
"formulas",
|
||||
"template",
|
||||
"formula",
|
||||
"reports",
|
||||
"perform",
|
||||
"for",
|
||||
"period",
|
||||
"1",
|
||||
"historic",
|
||||
"is",
|
||||
"normalised",
|
||||
"to",
|
||||
"a",
|
||||
"defined",
|
||||
"in",
|
||||
"model",
|
||||
"cen",
|
||||
"shown",
|
||||
"an",
|
||||
"diagram",
|
||||
"and",
|
||||
"tables",
|
||||
"preview",
|
||||
"from",
|
||||
"costs",
|
||||
"hour",
|
||||
"name",
|
||||
"lower",
|
||||
"limit",
|
||||
"higher",
|
||||
"EUR",
|
||||
"value",
|
||||
"current",
|
||||
"zenon",
|
||||
"analyzer",
|
||||
"www",
|
||||
"copadata",
|
||||
"com",
|
||||
"ok",
|
||||
"cancel",
|
||||
"page"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"words": [
|
||||
"new",
|
||||
"report",
|
||||
"theme",
|
||||
"extended",
|
||||
"historian",
|
||||
"analysis",
|
||||
"the",
|
||||
"report",
|
||||
"templates",
|
||||
"of",
|
||||
"this",
|
||||
"theme",
|
||||
"perform",
|
||||
"aggregated",
|
||||
"and",
|
||||
"historian",
|
||||
"analysis",
|
||||
"and",
|
||||
"associate",
|
||||
"the",
|
||||
"historian",
|
||||
"data",
|
||||
"with",
|
||||
"price",
|
||||
"values",
|
||||
"production",
|
||||
"counters",
|
||||
"standard",
|
||||
"values",
|
||||
"and",
|
||||
"the",
|
||||
"equipment",
|
||||
"modeling",
|
||||
"there",
|
||||
"are",
|
||||
"two",
|
||||
"types",
|
||||
"of",
|
||||
"report",
|
||||
"templates",
|
||||
"of",
|
||||
"this",
|
||||
"theme",
|
||||
"those",
|
||||
"that",
|
||||
"create",
|
||||
"aggregated",
|
||||
"trend",
|
||||
"data",
|
||||
"and",
|
||||
"thos",
|
||||
"that",
|
||||
"create",
|
||||
"distribution",
|
||||
"data",
|
||||
"report",
|
||||
"template",
|
||||
"relative",
|
||||
"historian",
|
||||
"aggregation",
|
||||
"with",
|
||||
"equipment",
|
||||
"group",
|
||||
"and",
|
||||
"variable",
|
||||
"selection",
|
||||
"reports",
|
||||
"based",
|
||||
"on",
|
||||
"this",
|
||||
"template",
|
||||
"calcculate",
|
||||
"for",
|
||||
"1",
|
||||
"period",
|
||||
"variable",
|
||||
"filtering",
|
||||
"is",
|
||||
"based",
|
||||
"on",
|
||||
"equipment",
|
||||
"groups",
|
||||
"the",
|
||||
"data",
|
||||
"is",
|
||||
"shown",
|
||||
"in",
|
||||
"charts",
|
||||
"and",
|
||||
"tables",
|
||||
"preview",
|
||||
"new",
|
||||
"report",
|
||||
"relative",
|
||||
"historian",
|
||||
"aggregation",
|
||||
"with",
|
||||
"equipment",
|
||||
"group",
|
||||
"and",
|
||||
"variable",
|
||||
"selection",
|
||||
"from",
|
||||
"filler",
|
||||
"labeller",
|
||||
"packer",
|
||||
"ZAD_GBL",
|
||||
"ENERGY",
|
||||
"MACHINE",
|
||||
"CYCLIC",
|
||||
"DATA",
|
||||
"PM",
|
||||
"kwh",
|
||||
"variable",
|
||||
"name",
|
||||
"absolute",
|
||||
"consumption",
|
||||
"relative",
|
||||
"consumption",
|
||||
"zenon",
|
||||
"analyzer",
|
||||
"www",
|
||||
"copadata",
|
||||
"com",
|
||||
"page",
|
||||
"ok",
|
||||
"cancel"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 106 KiB |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"words": [
|
||||
"metadata",
|
||||
"datasource",
|
||||
"variables",
|
||||
"project",
|
||||
"glass",
|
||||
"bottle",
|
||||
"line",
|
||||
"identification",
|
||||
"visualname",
|
||||
"description",
|
||||
"cycle",
|
||||
"time",
|
||||
"dd",
|
||||
"hh",
|
||||
"mm",
|
||||
"ss",
|
||||
"assigned",
|
||||
"equipment",
|
||||
"groups",
|
||||
"plant",
|
||||
"media",
|
||||
"total",
|
||||
"previous",
|
||||
"next",
|
||||
"ok",
|
||||
"cancel",
|
||||
"new",
|
||||
"archive",
|
||||
"for",
|
||||
"the",
|
||||
"3rd",
|
||||
"party",
|
||||
"database",
|
||||
"connector"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"words": [
|
||||
"provider",
|
||||
"options",
|
||||
"microsoft",
|
||||
"ole",
|
||||
"db",
|
||||
"provider",
|
||||
"for",
|
||||
"adbc",
|
||||
"drivers",
|
||||
"select",
|
||||
"a",
|
||||
"page",
|
||||
"general",
|
||||
"script",
|
||||
"help",
|
||||
"provider",
|
||||
"options",
|
||||
"enable",
|
||||
"name",
|
||||
"dynamic",
|
||||
"parameter",
|
||||
"nested",
|
||||
"queries",
|
||||
"level",
|
||||
"zero",
|
||||
"only",
|
||||
"allow",
|
||||
"inprocess",
|
||||
"non",
|
||||
"transacted",
|
||||
"updates",
|
||||
"index",
|
||||
"ad",
|
||||
"access",
|
||||
"path",
|
||||
"disallow",
|
||||
"adhoc",
|
||||
"access",
|
||||
"supports",
|
||||
"like",
|
||||
"operator",
|
||||
"linked",
|
||||
"server",
|
||||
"using",
|
||||
"this",
|
||||
"provider",
|
||||
"my",
|
||||
"sql",
|
||||
"server",
|
||||
"these",
|
||||
"options",
|
||||
"are",
|
||||
"applied",
|
||||
"to",
|
||||
"all",
|
||||
"linked",
|
||||
"servers",
|
||||
"that",
|
||||
"use",
|
||||
"this",
|
||||
"provider",
|
||||
"ready",
|
||||
"ok",
|
||||
"cancel",
|
||||
"view",
|
||||
"connection",
|
||||
"properties",
|
||||
"connection",
|
||||
"server",
|
||||
"CDSBG036",
|
||||
"ZA2",
|
||||
"haraldr",
|
||||
"cd",
|
||||
"ok",
|
||||
"cancel"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 93 KiB |