adjusted namespaces and made separate data dir
@@ -6,7 +6,8 @@
|
||||
},
|
||||
"Test all img": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img/*.png\""
|
||||
"commandLineArgs": "\"img/*.png\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
},
|
||||
"Test single img": {
|
||||
"commandName": "Project",
|
||||
|
||||
@@ -3,49 +3,48 @@ using Lookup.Memory;
|
||||
using Ocr.Tesseract.Models;
|
||||
using Process.Interface;
|
||||
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The screenshot processor
|
||||
/// </summary>
|
||||
protected IProcessor<MagickImage, ScanResult> Processor { get; }
|
||||
Processor = processor;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
/// <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))
|
||||
{
|
||||
Processor = processor;
|
||||
}
|
||||
|
||||
/// <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();
|
||||
Lookup.Add(kv.Word, kv.Image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
Lookup.Clear();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using ReportGenerator.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator;
|
||||
|
||||
@@ -15,19 +14,15 @@ internal static class Program
|
||||
|
||||
// Parse
|
||||
|
||||
Console.WriteLine("Evaluating");
|
||||
Console.WriteLine("Generating report");
|
||||
var scans = Scan(tagFileInfos, scanFileInfos);
|
||||
var report = Table.ReportGenerator
|
||||
var report = ReportGenerator
|
||||
.FromData(scans)
|
||||
.WithTitle("OCR Report")
|
||||
.WithBestOf("Best of")
|
||||
.WithFullStatistic("Statistic")
|
||||
.ToString();
|
||||
.WithFullStatistic("Statistic");
|
||||
|
||||
// Generate output file
|
||||
|
||||
Console.WriteLine("Generating report");
|
||||
File.WriteAllText("Report.md", report, Encoding.UTF8);
|
||||
report.ToFile("Report.md");
|
||||
|
||||
Console.WriteLine("Completed");
|
||||
}
|
||||
@@ -48,7 +43,6 @@ internal static class Program
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<TagFileInfo> GetTagFileInfos(string dir)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"ReportGenerator": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img\" \"results\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\CLI\\bin\\Debug\\net6.0"
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
}
|
||||
}
|
||||
}
|
||||
174
Examples/ReportGenerator/ReportGenerator.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using Common.Extensions;
|
||||
using ReportGenerator.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator;
|
||||
|
||||
public class ReportGenerator
|
||||
{
|
||||
private ICollection<ImageStats> Images { get; }
|
||||
|
||||
private readonly StringBuilder _sb = new();
|
||||
|
||||
private ReportGenerator(IEnumerable<ImageStats> stats) => Images = stats.ToArray();
|
||||
|
||||
public void ToFile(string path) =>
|
||||
File.WriteAllText(path, ToString(), Encoding.UTF8);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() =>
|
||||
_sb.ToString();
|
||||
|
||||
#region Fluent definition
|
||||
|
||||
public ReportGenerator WithTitle(string text)
|
||||
{
|
||||
_sb.AppendHeading(1, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReportGenerator WithFullStatistic(string title)
|
||||
{
|
||||
_sb.AppendHeading(2, title);
|
||||
|
||||
foreach (var stat in Images)
|
||||
{
|
||||
_sb.AppendHeading(3, stat.ImageName);
|
||||
_sb.AppendParagraph(HtmlImage(Path.Combine("img", stat.ImageName), 350, 350));
|
||||
|
||||
AppendRow(stat
|
||||
.Reference
|
||||
.Prepend("Image")
|
||||
.Prepend("CER (avg)")
|
||||
.Prepend("WER")
|
||||
.Prepend("Elapsed")
|
||||
.Prepend("Processor")
|
||||
);
|
||||
|
||||
AppendRowSeparator(stat.Reference.Count + 5);
|
||||
|
||||
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");
|
||||
|
||||
AppendRow(processor.Words
|
||||
.Select(s => s.ToString() ?? string.Empty)
|
||||
.Prepend(HtmlImage(imgPath, 150, 150))
|
||||
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
||||
.Prepend($"{processor.Distance * 100:F1}%")
|
||||
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
||||
.Prepend(processor.Name)
|
||||
);
|
||||
}
|
||||
|
||||
_sb.AppendLine();
|
||||
_sb.AppendParagraph(
|
||||
$"*Comparison data generated based on {stat.Reference.Count} tagged words.*"
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ReportGenerator WithBestOf(string title, int context = 5)
|
||||
{
|
||||
_sb.AppendHeading(2, title);
|
||||
|
||||
var lookup = Images
|
||||
.SelectMany(s => s.Processors)
|
||||
.ToLookup(p => p.Name);
|
||||
|
||||
// Compare time across all images
|
||||
var byTime = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.ProcessingTime) * 1000))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Compare WER across all images
|
||||
var byWer = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.Distance) * 100))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Compare CER across all images
|
||||
var byCer = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.Words.Average(w => w.Distance))))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Print
|
||||
AppendComparison(3, "Time", byTime, " ms");
|
||||
AppendComparison(3, "WER", byWer, " %");
|
||||
AppendComparison(3, "CER", byCer, " changes");
|
||||
|
||||
return this;
|
||||
|
||||
void AppendComparison(
|
||||
int level,
|
||||
string tableTitle,
|
||||
IEnumerable<(string, double)> values,
|
||||
string valueUnit = ""
|
||||
)
|
||||
{
|
||||
var tValues = values.ToArray();
|
||||
var tContext = Math.Min(tValues.Length / 2, context);
|
||||
|
||||
_sb.AppendHeading(level, tableTitle);
|
||||
|
||||
AppendRow(new[] { "Processor", "Average" });
|
||||
AppendRowSeparator(2);
|
||||
AppendRows(tValues.Take(tContext).Select(v => new[]
|
||||
{
|
||||
v.Item1,
|
||||
v.Item2.ToString("F2") + valueUnit
|
||||
}));
|
||||
AppendRowSeparator(2, "...");
|
||||
AppendRows(tValues.TakeLast(tContext).Select(v => new[]
|
||||
{
|
||||
v.Item1,
|
||||
v.Item2.ToString("F2") + valueUnit
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void AppendRow(IEnumerable<string> row)
|
||||
{
|
||||
const string separator = " | ";
|
||||
_sb.AppendLine(separator + string.Join(" | ", row) + separator);
|
||||
}
|
||||
|
||||
private void AppendRows(IEnumerable<IEnumerable<string>> rows)
|
||||
{
|
||||
foreach (var row in rows)
|
||||
{
|
||||
AppendRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
private static string HtmlImage(string path, int maxWidth, int maxHeight)
|
||||
{
|
||||
if (!path.EndsWith(".png"))
|
||||
{
|
||||
path += ".png";
|
||||
}
|
||||
|
||||
return $"<img src=\"{path}\" style=\"max-width:{maxWidth}px;max-height:{maxHeight}px;\" />";
|
||||
}
|
||||
|
||||
private void AppendRowSeparator(int columns, string content = "---") =>
|
||||
AppendRow(Enumerable.Range(0, columns).Select(_ => content));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
public static ReportGenerator FromData(IEnumerable<ImageStats> stats) => new(stats);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
using Common.Extensions;
|
||||
using ReportGenerator.Models;
|
||||
using System.Text;
|
||||
|
||||
namespace ReportGenerator.Table
|
||||
{
|
||||
public class ReportGenerator
|
||||
{
|
||||
private ICollection<ImageStats> Images { get; }
|
||||
|
||||
private readonly StringBuilder _sb = new();
|
||||
|
||||
private ReportGenerator(IEnumerable<ImageStats> stats) => Images = stats.ToArray();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => _sb.ToString();
|
||||
|
||||
#region Fluent definition
|
||||
|
||||
public ReportGenerator WithTitle(string text)
|
||||
{
|
||||
_sb.AppendHeading(1, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReportGenerator WithFullStatistic(string title)
|
||||
{
|
||||
_sb.AppendHeading(2, title);
|
||||
|
||||
foreach (var stat in Images)
|
||||
{
|
||||
_sb.AppendHeading(3, stat.ImageName);
|
||||
_sb.AppendParagraph(HtmlImage(Path.Combine("img", stat.ImageName), 350, 350));
|
||||
|
||||
AppendRow(stat
|
||||
.Reference
|
||||
.Prepend("Image")
|
||||
.Prepend("CER (avg)")
|
||||
.Prepend("WER")
|
||||
.Prepend("Elapsed")
|
||||
.Prepend("Processor")
|
||||
);
|
||||
|
||||
AppendRowSeparator(stat.Reference.Count + 5);
|
||||
|
||||
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");
|
||||
|
||||
AppendRow(processor.Words
|
||||
.Select(s => s.ToString() ?? string.Empty)
|
||||
.Prepend(HtmlImage(imgPath, 150, 150))
|
||||
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
||||
.Prepend($"{processor.Distance * 100:F1}%")
|
||||
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
||||
.Prepend(processor.Name)
|
||||
);
|
||||
}
|
||||
|
||||
_sb.AppendLine();
|
||||
_sb.AppendParagraph(
|
||||
$"*Comparison data generated based on {stat.Reference.Count} tagged words.*"
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ReportGenerator WithBestOf(string title, int context = 5)
|
||||
{
|
||||
_sb.AppendHeading(2, title);
|
||||
|
||||
var lookup = Images
|
||||
.SelectMany(s => s.Processors)
|
||||
.ToLookup(p => p.Name);
|
||||
|
||||
// Compare time across all images
|
||||
var byTime = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.ProcessingTime) * 1000))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Compare WER across all images
|
||||
var byWer = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.Distance) * 100))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Compare CER across all images
|
||||
var byCer = lookup
|
||||
.Select(g => (Name: g.Key, Value: g.Average(p => p.Words.Average(w => w.Distance))))
|
||||
.OrderBy(g => g.Value);
|
||||
|
||||
// Print
|
||||
AppendComparison(3, "Time", byTime, " ms");
|
||||
AppendComparison(3, "WER", byWer, " %");
|
||||
AppendComparison(3, "CER", byCer, " changes");
|
||||
|
||||
return this;
|
||||
|
||||
void AppendComparison(
|
||||
int level,
|
||||
string tableTitle,
|
||||
IEnumerable<(string, double)> values,
|
||||
string valueUnit = ""
|
||||
)
|
||||
{
|
||||
var tValues = values.ToArray();
|
||||
var tContext = Math.Min(tValues.Length / 2, context);
|
||||
|
||||
_sb.AppendHeading(level, tableTitle);
|
||||
|
||||
AppendRow(new[] { "Processor", "Average" });
|
||||
AppendRowSeparator(2);
|
||||
AppendRows(tValues.Take(tContext).Select(v => new[]
|
||||
{
|
||||
v.Item1,
|
||||
v.Item2.ToString("F2") + valueUnit
|
||||
}));
|
||||
AppendRowSeparator(2, "...");
|
||||
AppendRows(tValues.TakeLast(tContext).Select(v => new[]
|
||||
{
|
||||
v.Item1,
|
||||
v.Item2.ToString("F2") + valueUnit
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void AppendRow(IEnumerable<string> row)
|
||||
{
|
||||
const string separator = " | ";
|
||||
_sb.AppendLine(separator + string.Join(" | ", row) + separator);
|
||||
}
|
||||
|
||||
private void AppendRows(IEnumerable<IEnumerable<string>> rows)
|
||||
{
|
||||
foreach (var row in rows)
|
||||
{
|
||||
AppendRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
private static string HtmlImage(string path, int maxWidth, int maxHeight)
|
||||
{
|
||||
if (!path.EndsWith(".png"))
|
||||
{
|
||||
path += ".png";
|
||||
}
|
||||
|
||||
return $"<img src=\"{path}\" style=\"max-width:{maxWidth}px;max-height:{maxHeight}px;\" />";
|
||||
}
|
||||
|
||||
private void AppendRowSeparator(int columns, string content = "---") =>
|
||||
AppendRow(Enumerable.Range(0, columns).Select(_ => content));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
public static ReportGenerator FromData(IEnumerable<ImageStats> stats) => new(stats);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).00.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 889 B |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Kapur).01.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).00.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(OTSU).01.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).00.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.editor_startpage_project-exist_001.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Examples/testdata/results/AutoThresholdProcessor(Triangle).01.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 28 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).00.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 28 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(05_05).01.zrs_ZAMS_filter-alarmgroup_001.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(10_10).00.editor_windows_position_006.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(10_10).00.historian_assistent_001.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(10_10).00.zrs_MetadataEditor_variables_001.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(10_10).00.zrs_REPORTS_EfficencyClass_009.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
Examples/testdata/results/ThresholdAdaptiveProcessor(10_10).00.zrs_ZAMS_3rd-connector_014.png
vendored
Normal file
|
After Width: | Height: | Size: 8.9 KiB |