Compare commits
14 Commits
feature/reports
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b21afe6121 | |||
| 22800639d0 | |||
| 745e41f474 | |||
| 2736feb0e7 | |||
| fb9213e6c5 | |||
| 1fdedb89ad | |||
| 41aa4bf151 | |||
| 6eda25fce5 | |||
| 946eea2347 | |||
| 74619876c2 | |||
| 6c554e444f | |||
| b17044f959 | |||
| f3768348e9 | |||
| 71c7a27b36 |
@@ -1,26 +0,0 @@
|
|||||||
using GUI.ViewModels;
|
|
||||||
using GUI.Views;
|
|
||||||
using Serilog;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace GUI;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for App.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnStartup(e);
|
|
||||||
|
|
||||||
var loggingCollection = new LoggingCollection(100);
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
|
||||||
.WriteTo.Sink(loggingCollection)
|
|
||||||
.CreateLogger();
|
|
||||||
|
|
||||||
new LogView(loggingCollection).Show();
|
|
||||||
new ImageView().Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
using Common;
|
|
||||||
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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
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 : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
/// <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 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()
|
|
||||||
{
|
|
||||||
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 void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
|
||||||
{
|
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
foreach (var image in inputs)
|
|
||||||
{
|
|
||||||
Edited.Add(image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
|
||||||
{
|
|
||||||
ScannedText = $"[{inputs.Count} words] " + string.Join(' ', inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Scanner.Clear();
|
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
ScannedText = string.Empty;
|
|
||||||
Words.Clear();
|
|
||||||
Edited.Clear();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region File Handling
|
|
||||||
|
|
||||||
private void OpenFile()
|
|
||||||
{
|
|
||||||
var dialog = new OpenFileDialog()
|
|
||||||
{
|
|
||||||
InitialDirectory = Directory.GetCurrentDirectory()
|
|
||||||
};
|
|
||||||
if (dialog.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
Image = new MagickImage(dialog.FileName);
|
|
||||||
UpdateImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveEditedImage()
|
|
||||||
{
|
|
||||||
var basePath = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
|
|
||||||
for (var i = 0; i < Edited.Count; i++)
|
|
||||||
{
|
|
||||||
Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Information($"Saved image to '{basePath}'");
|
|
||||||
System.Diagnostics.Process.Start(
|
|
||||||
"explorer.exe",
|
|
||||||
Path.GetDirectoryName(basePath) ?? string.Empty
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Updating data
|
|
||||||
|
|
||||||
private void UpdateConfidence()
|
|
||||||
{
|
|
||||||
Confidence = Scanner.Lookup.Keys.Any()
|
|
||||||
? Scanner.Lookup.Keys.Sum(key => key.Confidence) / Scanner.Lookup.Keys.Count
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateImage()
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
IsIdle = false;
|
|
||||||
|
|
||||||
Clear();
|
|
||||||
if (Image != null)
|
|
||||||
{
|
|
||||||
Scanner.Process(new[] { Image });
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateWords();
|
|
||||||
UpdateConfidence();
|
|
||||||
|
|
||||||
IsIdle = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateWords()
|
|
||||||
{
|
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
foreach (var word in Scanner.Lookup.Keys)
|
|
||||||
{
|
|
||||||
Words.Add(word);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
private float _confidence;
|
|
||||||
private MagickImage? _image;
|
|
||||||
private bool _isIdle;
|
|
||||||
|
|
||||||
private string _scannedText = string.Empty;
|
|
||||||
|
|
||||||
public string ScannedText
|
|
||||||
{
|
|
||||||
get => _scannedText;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == _scannedText)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_scannedText = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsIdle
|
|
||||||
{
|
|
||||||
get => _isIdle;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == _isIdle)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isIdle = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float Confidence
|
|
||||||
{
|
|
||||||
get => _confidence;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_confidence = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<MagickImage> Edited { get; } = new();
|
|
||||||
|
|
||||||
public MagickImage? Image
|
|
||||||
{
|
|
||||||
get => _image;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_image = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<Word> Words { get; } = new();
|
|
||||||
|
|
||||||
#endregion Properties
|
|
||||||
|
|
||||||
#region Commands
|
|
||||||
|
|
||||||
public ICommand OpenFileCommand { get; private set; }
|
|
||||||
public ICommand SaveEditedImageCommand { get; private set; }
|
|
||||||
|
|
||||||
#endregion Commands
|
|
||||||
|
|
||||||
#region INotifyPropertyChanged
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion INotifyPropertyChanged
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace GUI.ViewModels;
|
|
||||||
|
|
||||||
public class LogMessage
|
|
||||||
{
|
|
||||||
public DateTime Timestamp { get; set; }
|
|
||||||
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace GUI.ViewModels;
|
|
||||||
|
|
||||||
public class LogViewModel
|
|
||||||
{
|
|
||||||
public LoggingCollection LoggingCollection { get; }
|
|
||||||
|
|
||||||
public LogViewModel(LoggingCollection loggingCollection)
|
|
||||||
{
|
|
||||||
LoggingCollection = loggingCollection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace GUI.ViewModels;
|
|
||||||
|
|
||||||
public class LoggingCollection : ILogEventSink
|
|
||||||
{
|
|
||||||
public int Capacity { get; }
|
|
||||||
|
|
||||||
public ObservableCollection<LogMessage> Items { get; }
|
|
||||||
|
|
||||||
public LoggingCollection(int capacity)
|
|
||||||
{
|
|
||||||
Capacity = capacity;
|
|
||||||
Items = new ObservableCollection<LogMessage>(new List<LogMessage>(capacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Trim(int offset = 0)
|
|
||||||
{
|
|
||||||
for (int i = Items.Count - Capacity - offset; i >= 0; i--)
|
|
||||||
{
|
|
||||||
Items.RemoveAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Implementation of ILogEventSink
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
{
|
|
||||||
Trim(1);
|
|
||||||
|
|
||||||
Items.Add(new LogMessage
|
|
||||||
{
|
|
||||||
Timestamp = logEvent.Timestamp.DateTime,
|
|
||||||
Message = logEvent.RenderMessage()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<Window
|
|
||||||
x:Class="GUI.Views.LogView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:viewModels="clr-namespace:GUI.ViewModels"
|
|
||||||
Title="LogView"
|
|
||||||
Width="800"
|
|
||||||
Height="450"
|
|
||||||
d:DataContext="{d:DesignInstance viewModels:LogViewModel}"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
<Grid>
|
|
||||||
<DataGrid
|
|
||||||
AutoGenerateColumns="False"
|
|
||||||
IsReadOnly="True"
|
|
||||||
ItemsSource="{Binding LoggingCollection.Items}"
|
|
||||||
VirtualizingPanel.ScrollUnit="Pixel">
|
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridTextColumn
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="120"
|
|
||||||
Binding="{Binding Timestamp}"
|
|
||||||
Header="Timestamp" />
|
|
||||||
<DataGridTextColumn
|
|
||||||
Width="*"
|
|
||||||
MinWidth="120"
|
|
||||||
MaxWidth="300"
|
|
||||||
Binding="{Binding Message}"
|
|
||||||
Header="Message">
|
|
||||||
<DataGridTextColumn.ElementStyle>
|
|
||||||
<Style>
|
|
||||||
<Setter Property="TextBlock.TextAlignment" Value="Left" />
|
|
||||||
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
|
|
||||||
</Style>
|
|
||||||
</DataGridTextColumn.ElementStyle>
|
|
||||||
</DataGridTextColumn>
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using GUI.ViewModels;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace GUI.Views;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for LogView.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class LogView : Window
|
|
||||||
{
|
|
||||||
public LogView(LoggingCollection loggingCollection)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
DataContext = new LogViewModel(loggingCollection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.001}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0011}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0012}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0011}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0013}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0011}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0008}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0014}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0008}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0011}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0008}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"Words":[],"Elapsed":0.0009}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/modules.xml
|
||||||
|
/.idea.Implementation.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GitToolBoxProjectSettings">
|
||||||
|
<option name="commitMessageIssueKeyValidationOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
<option name="commitMessageValidationEnabledOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CLI.Monitor;
|
namespace Ocr.Cli.Monitor;
|
||||||
|
|
||||||
public class CliTaskMonitor : TaskMonitor
|
public class CliTaskMonitor : TaskMonitor
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CLI.Monitor;
|
namespace Ocr.Cli.Monitor;
|
||||||
|
|
||||||
public class CompactCliTaskMonitor : TaskMonitor
|
public class CompactCliTaskMonitor : TaskMonitor
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CLI.Monitor;
|
namespace Ocr.Cli.Monitor;
|
||||||
|
|
||||||
public interface ITaskMonitor
|
public interface ITaskMonitor
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace CLI.Monitor;
|
namespace Ocr.Cli.Monitor;
|
||||||
|
|
||||||
public abstract class TaskMonitor : ITaskMonitor
|
public abstract class TaskMonitor : ITaskMonitor
|
||||||
{
|
{
|
||||||
@@ -10,7 +10,7 @@ using Process.Interface;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace CLI.Processor;
|
namespace Ocr.Cli.Processor;
|
||||||
|
|
||||||
internal class EvaluationProcessor
|
internal class EvaluationProcessor
|
||||||
{
|
{
|
||||||
@@ -20,7 +20,7 @@ internal class EvaluationProcessor
|
|||||||
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Regex wordRegex = new(
|
private static readonly Regex wordRegex = new(
|
||||||
@"[\w'\-]{2,}",
|
@"[\w'\-äöüÄÖÜß]{2,}",
|
||||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -42,6 +42,24 @@ internal class EvaluationProcessor
|
|||||||
.Use(new DuplicateFilter())
|
.Use(new DuplicateFilter())
|
||||||
.Complete(new RegexFilter(wordRegex));
|
.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 static readonly TesseractProcessor tesseractProcessor = new(tesseractConfig);
|
||||||
|
|
||||||
private readonly StopwatchProcessor<MagickImage, MagickImage> _thresholdProcessor;
|
private readonly StopwatchProcessor<MagickImage, MagickImage> _thresholdProcessor;
|
||||||
@@ -75,24 +93,6 @@ internal class EvaluationProcessor
|
|||||||
await JsonSerializer.SerializeAsync(file, result);
|
await JsonSerializer.SerializeAsync(file, result);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 IEnumerable<MagickImage> OnPreprocessed(IEnumerable<MagickImage> images)
|
private IEnumerable<MagickImage> OnPreprocessed(IEnumerable<MagickImage> images)
|
||||||
{
|
{
|
||||||
var tImages = images.ToArray();
|
var tImages = images.ToArray();
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using Process.Interface;
|
using Process.Interface;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace CLI.Processor;
|
namespace Ocr.Cli.Processor;
|
||||||
|
|
||||||
public class StopwatchProcessor<TInput, TOutput> : Processor<TInput, TOutput>
|
public class StopwatchProcessor<TInput, TOutput> : Processor<TInput, TOutput>
|
||||||
{
|
{
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using CLI.Monitor;
|
using Common.Extensions;
|
||||||
using CLI.Processor;
|
|
||||||
using Common.Extensions;
|
|
||||||
using ImageMagick;
|
using ImageMagick;
|
||||||
|
using Ocr.Cli.Monitor;
|
||||||
|
using Ocr.Cli.Processor;
|
||||||
using Ocr.Tesseract.Screenshots.Threshold;
|
using Ocr.Tesseract.Screenshots.Threshold;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace CLI;
|
namespace Ocr.Cli;
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
@@ -20,6 +20,7 @@ public class Program
|
|||||||
select (Key: path, Task: processor.Process(new MagickImage(path)))
|
select (Key: path, Task: processor.Process(new MagickImage(path)))
|
||||||
).ToArray();
|
).ToArray();
|
||||||
|
|
||||||
|
// return new CliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||||
return new CompactCliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
return new CompactCliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"Test all img": {
|
"Test all img": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "\"img/*.png\"",
|
"commandLineArgs": "\"img/*.png\"",
|
||||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
"workingDirectory": "D:\\git\\ba_ocr\\Implementation\\testdata"
|
||||||
},
|
},
|
||||||
"Test single img": {
|
"Test single img": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
@@ -5,7 +5,7 @@ namespace Common.Distance;
|
|||||||
public static class Calculator
|
public static class Calculator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the levenshtein distance between
|
/// Calculates the levenshtein distance between two enumerables
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="reference"></param>
|
/// <param name="reference"></param>
|
||||||
@@ -37,7 +37,9 @@ public readonly struct DistanceComparer<T> : IDistanceComparer<T>
|
|||||||
return str ?? string.Empty;
|
return str ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
// Enable for HTML/MD only
|
||||||
$"<strong style='color: orange;' title='REf: {Reference}, CER: {Distance}'>{str ?? "-"}</strong>";
|
// return $"<strong style='color: orange;' title='REf: {Reference}, CER: {Distance}'>{str ?? "-"}</strong>";
|
||||||
|
|
||||||
|
return str ?? "-";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,4 @@ public class ScreenshotScanner
|
|||||||
Lookup.Add(kv.Word, kv.Image);
|
Lookup.Add(kv.Word, kv.Image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Clear()
|
|
||||||
{
|
|
||||||
Lookup.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Application x:Class="GUI.App"
|
<Application x:Class="Ocr.Gui.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<Application.Resources/>
|
<Application.Resources/>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Ocr.Gui.Views;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ocr.Gui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
new ImageView().Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="GUI.Controls.ImageControl"
|
x:Class="Ocr.Gui.Controls.ImageControl"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:converters="clr-namespace:GUI.Converters"
|
xmlns:converters="clr-namespace:Ocr.Gui.Converters"
|
||||||
xmlns:controls="clr-namespace:GUI.Controls"
|
xmlns:controls="clr-namespace:Ocr.Gui.Controls"
|
||||||
d:DataContext="{d:DesignInstance controls:ImageControl}"
|
d:DataContext="{d:DesignInstance controls:ImageControl}"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
|
||||||
namespace GUI.Controls;
|
namespace Ocr.Gui.Controls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for ImageControl.xaml
|
/// Interaction logic for ImageControl.xaml
|
||||||
@@ -6,12 +6,10 @@ using System.IO;
|
|||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace GUI.Converters;
|
namespace Ocr.Gui.Converters;
|
||||||
|
|
||||||
internal class ImageConverter : IValueConverter
|
internal class ImageConverter : IValueConverter
|
||||||
{
|
{
|
||||||
#region Implementation of IValueConverter
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
@@ -48,6 +46,4 @@ internal class ImageConverter : IValueConverter
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace GUI.Model;
|
namespace Ocr.Gui.Model;
|
||||||
|
|
||||||
public class Command : ICommand
|
public class Command : ICommand
|
||||||
{
|
{
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<Window
|
<Window
|
||||||
x:Class="GUI.Views.ImageView"
|
x:Class="Ocr.Gui.Views.ImageView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:GUI.Controls"
|
xmlns:views="clr-namespace:Ocr.Gui.Views"
|
||||||
xmlns:viewModels="clr-namespace:GUI.ViewModels"
|
xmlns:controls="clr-namespace:Ocr.Gui.Controls"
|
||||||
Title="OcrView"
|
Title="OcrView"
|
||||||
Width="800"
|
Width="800"
|
||||||
Height="450"
|
Height="450"
|
||||||
d:DataContext="{d:DesignInstance viewModels:ImageViewModel}"
|
d:DataContext="{d:DesignInstance views:ImageViewModel}"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<CollectionViewSource
|
<CollectionViewSource
|
||||||
@@ -161,52 +161,6 @@
|
|||||||
x:Name="EnableThreshold"
|
x:Name="EnableThreshold"
|
||||||
Content="Apply Threshold"
|
Content="Apply Threshold"
|
||||||
IsChecked="{Binding ProcessorConfig.EnableThresholding}" />
|
IsChecked="{Binding ProcessorConfig.EnableThresholding}" />
|
||||||
|
|
||||||
<Grid
|
|
||||||
Margin="4"
|
|
||||||
IsEnabled="{Binding ElementName=EnableThreshold,
|
|
||||||
Path=IsChecked}">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Grid.Column="0">Block Width:</TextBlock>
|
|
||||||
<Slider
|
|
||||||
x:Name="SldThreshold1"
|
|
||||||
Grid.Column="1"
|
|
||||||
Margin="4,0"
|
|
||||||
Maximum="100"
|
|
||||||
Minimum="0"
|
|
||||||
Thumb.DragCompleted="SldThreshold1_OnDragCompleted"
|
|
||||||
Value="15" />
|
|
||||||
<TextBlock Grid.Column="2">
|
|
||||||
<Run Text="{Binding Value, ElementName=SldThreshold1, FallbackValue=0, StringFormat=0.00}" />
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
<Grid
|
|
||||||
Margin="4"
|
|
||||||
IsEnabled="{Binding ElementName=EnableThreshold,
|
|
||||||
Path=IsChecked}">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Grid.Column="0">Block Height:</TextBlock>
|
|
||||||
<Slider
|
|
||||||
x:Name="SldThreshold2"
|
|
||||||
Grid.Column="1"
|
|
||||||
Margin="4,0"
|
|
||||||
Maximum="100"
|
|
||||||
Minimum="0"
|
|
||||||
Thumb.DragCompleted="SldThreshold2_OnDragCompleted"
|
|
||||||
Value="17" />
|
|
||||||
<TextBlock Grid.Column="2">
|
|
||||||
<Run Text="{Binding Value, ElementName=SldThreshold2, FallbackValue=0, StringFormat=0.00}" />
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
Content="Resize"
|
Content="Resize"
|
||||||
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
||||||
@@ -229,13 +183,6 @@
|
|||||||
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Rectangle />
|
|
||||||
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
Content="Filter connected components"
|
|
||||||
IsChecked="{Binding ProcessorConfig.FilterConnectedComponents}" />
|
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using GUI.ViewModels;
|
using ImageMagick;
|
||||||
using ImageMagick;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
|
|
||||||
namespace GUI.Views;
|
namespace Ocr.Gui.Views;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for MainWindow.xaml
|
/// Interaction logic for MainWindow.xaml
|
||||||
@@ -26,19 +25,6 @@ public partial class ImageView : Window
|
|||||||
InitializeComponent();
|
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)
|
private void SldBorder_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||||
{
|
{
|
||||||
var vm = ViewModel;
|
var vm = ViewModel;
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
using Common;
|
||||||
|
using ImageMagick;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Ocr.Gui.Model;
|
||||||
|
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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
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;
|
||||||
|
using Ocr.Tesseract.Extensions;
|
||||||
|
|
||||||
|
namespace Ocr.Gui.Views;
|
||||||
|
|
||||||
|
internal class ImageViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tesseract engine configuration
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ITesseractConfiguration TesseractConfig =
|
||||||
|
new TesseractScreenshotConfiguration
|
||||||
|
{
|
||||||
|
DataPath = "tessdata",
|
||||||
|
Languages = new[] { "eng", "deu" }
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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 chainConfig = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||||
|
.Use(new CloneImageProcessor());
|
||||||
|
|
||||||
|
if (ProcessorConfig.EnableResizing)
|
||||||
|
{
|
||||||
|
chainConfig
|
||||||
|
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||||
|
.Use(new ProcessingEvent<MagickImage>(OnProcessing));
|
||||||
|
}
|
||||||
|
|
||||||
|
chainConfig = chainConfig
|
||||||
|
.Use(new NormalizeProcessor())
|
||||||
|
.Use(new ProcessingEvent<MagickImage>(OnProcessing));
|
||||||
|
|
||||||
|
if (ProcessorConfig.EnableThresholding)
|
||||||
|
{
|
||||||
|
chainConfig = chainConfig
|
||||||
|
.Use(threshold)
|
||||||
|
.Use(new ProcessingEvent<MagickImage>(OnProcessing));
|
||||||
|
}
|
||||||
|
|
||||||
|
var preprocessing = chainConfig
|
||||||
|
.Use(new AddBorderProcessor(ProcessorConfig.Border))
|
||||||
|
.Use(new BinarizeProcessor())
|
||||||
|
.Use(new ProcessingEvent<MagickImage>(OnProcessing))
|
||||||
|
.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(scan)
|
||||||
|
.Use(new ProcessingEvent<ScanResult>(OnProcessed))
|
||||||
|
.Complete(postprocessing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Scanner
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
foreach (var image in inputs)
|
||||||
|
{
|
||||||
|
Edited.Add(image.CloneImage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||||
|
{
|
||||||
|
var wordStr = string.Join("\", \"", inputs);
|
||||||
|
ScannedText = $"{inputs.Count} words:{Environment.NewLine}[ \"{wordStr}\" ]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ScannedText = string.Empty;
|
||||||
|
Words.Clear();
|
||||||
|
Edited.Clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region File Handling
|
||||||
|
|
||||||
|
private void OpenFile()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog()
|
||||||
|
{
|
||||||
|
InitialDirectory = Directory.GetCurrentDirectory()
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
Image = new MagickImage(dialog.FileName);
|
||||||
|
UpdateImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveEditedImage()
|
||||||
|
{
|
||||||
|
var basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
for (var i = 0; i < Edited.Count; i++)
|
||||||
|
{
|
||||||
|
Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information($"Saved image to '{basePath}'");
|
||||||
|
System.Diagnostics.Process.Start(
|
||||||
|
"explorer.exe",
|
||||||
|
Path.GetDirectoryName(basePath) ?? string.Empty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Updating data
|
||||||
|
|
||||||
|
private void UpdateImage()
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
IsIdle = false;
|
||||||
|
var scanner = new ScreenshotScanner(MakeProcessor());
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
if (Image != null)
|
||||||
|
{
|
||||||
|
scanner.Process(new[] { Image });
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var confidence = 0f;
|
||||||
|
foreach (var word in scanner.Lookup.Keys)
|
||||||
|
{
|
||||||
|
confidence += word.Confidence;
|
||||||
|
Words.Add(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
Confidence = confidence / scanner.Lookup.Keys.Count;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
IsIdle = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
private float _confidence;
|
||||||
|
private MagickImage? _image;
|
||||||
|
private bool _isIdle;
|
||||||
|
|
||||||
|
private string _scannedText = string.Empty;
|
||||||
|
|
||||||
|
public string ScannedText
|
||||||
|
{
|
||||||
|
get => _scannedText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _scannedText)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scannedText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsIdle
|
||||||
|
{
|
||||||
|
get => _isIdle;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _isIdle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isIdle = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Confidence
|
||||||
|
{
|
||||||
|
get => _confidence;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_confidence = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<MagickImage> Edited { get; } = new();
|
||||||
|
|
||||||
|
public MagickImage? Image
|
||||||
|
{
|
||||||
|
get => _image;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_image = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Word> Words { get; } = new();
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Commands
|
||||||
|
|
||||||
|
public ICommand OpenFileCommand { get; private set; }
|
||||||
|
public ICommand SaveEditedImageCommand { get; private set; }
|
||||||
|
|
||||||
|
#endregion Commands
|
||||||
|
|
||||||
|
#region INotifyPropertyChanged
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion INotifyPropertyChanged
|
||||||
|
}
|
||||||
@@ -23,9 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Database", "..\Looku
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "..\Lookup\Lookup.Abstract\Lookup.Abstract.csproj", "{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "..\Lookup\Lookup.Abstract\Lookup.Abstract.csproj", "{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CLI", "CLI\CLI.csproj", "{2856493F-EF1C-42A1-8EE5-6C0387D08F95}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Cli", "CLI\Ocr.Cli.csproj", "{2856493F-EF1C-42A1-8EE5-6C0387D08F95}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GUI", "GUI\GUI.csproj", "{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Gui", "GUI\Ocr.Gui.csproj", "{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -35,7 +35,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract", "..\Ocr\Ocr
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{251F9AC9-3765-498C-83FD-DB3539A19CB3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{251F9AC9-3765-498C-83FD-DB3539A19CB3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportGenerator", "ReportGenerator\ReportGenerator.csproj", "{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Report", "ReportGenerator\Ocr.Report.csproj", "{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReportGeneration", "ReportGeneration", "{8E08DA62-584B-4E26-AEB7-2B35742EF7A5}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportGeneration.Interface", "..\ReportGeneration\ReportGeneration.Interface\ReportGeneration.Interface.csproj", "{A0760AFE-5CB7-4603-8861-285F62BE510F}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportGeneration.Generators", "..\ReportGeneration\ReportGeneration.Generators\ReportGeneration.Generators.csproj", "{F291F2D4-5BC5-4576-A210-BE8D447276FC}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportGeneration.Abstract", "..\ReportGeneration\ReportGeneration.Abstract\ReportGeneration.Abstract.csproj", "{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocr.OneShot.Resampling", "Ocr.OneShot.Resampling\Ocr.OneShot.Resampling.csproj", "{4149E8A8-BD3C-4581-BDDD-FA70A58FBE37}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ocr.OneShot.Postprocessing", "Ocr.OneShot.Postprocessing\Ocr.OneShot.Postprocessing.csproj", "{40EA7F8E-BF46-410F-BA2B-CB05B80DCDBB}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -95,6 +107,26 @@ Global
|
|||||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A0760AFE-5CB7-4603-8861-285F62BE510F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0760AFE-5CB7-4603-8861-285F62BE510F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0760AFE-5CB7-4603-8861-285F62BE510F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A0760AFE-5CB7-4603-8861-285F62BE510F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F291F2D4-5BC5-4576-A210-BE8D447276FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F291F2D4-5BC5-4576-A210-BE8D447276FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F291F2D4-5BC5-4576-A210-BE8D447276FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F291F2D4-5BC5-4576-A210-BE8D447276FC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4149E8A8-BD3C-4581-BDDD-FA70A58FBE37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4149E8A8-BD3C-4581-BDDD-FA70A58FBE37}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4149E8A8-BD3C-4581-BDDD-FA70A58FBE37}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4149E8A8-BD3C-4581-BDDD-FA70A58FBE37}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{40EA7F8E-BF46-410F-BA2B-CB05B80DCDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{40EA7F8E-BF46-410F-BA2B-CB05B80DCDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{40EA7F8E-BF46-410F-BA2B-CB05B80DCDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{40EA7F8E-BF46-410F-BA2B-CB05B80DCDBB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -112,6 +144,10 @@ Global
|
|||||||
{E55F78E4-09F1-4D79-A9A2-460562C96DAB} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
{E55F78E4-09F1-4D79-A9A2-460562C96DAB} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||||
{D9B70035-0159-4D75-8ED6-2461F060F683} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
{D9B70035-0159-4D75-8ED6-2461F060F683} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
||||||
{251F9AC9-3765-498C-83FD-DB3539A19CB3} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
||||||
|
{8E08DA62-584B-4E26-AEB7-2B35742EF7A5} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||||
|
{A0760AFE-5CB7-4603-8861-285F62BE510F} = {8E08DA62-584B-4E26-AEB7-2B35742EF7A5}
|
||||||
|
{F291F2D4-5BC5-4576-A210-BE8D447276FC} = {8E08DA62-584B-4E26-AEB7-2B35742EF7A5}
|
||||||
|
{80BF15A5-78EB-4B87-8C1B-7F90D6D8BC74} = {8E08DA62-584B-4E26-AEB7-2B35742EF7A5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract\Ocr.Tesseract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using ImageMagick;
|
||||||
|
using Ocr.Tesseract;
|
||||||
|
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;
|
||||||
|
|
||||||
|
var wordRegex = new Regex(
|
||||||
|
@"[\w'\-]{2,}",
|
||||||
|
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
var tesseractConfig = new TesseractScreenshotConfiguration
|
||||||
|
{
|
||||||
|
DataPath = "tessdata",
|
||||||
|
Languages = new[] { "eng", "deu" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
var processor = MakeProcessor();
|
||||||
|
processor.Process(new[] { new MagickImage(args.Single()) });
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
IProcessorChain<MagickImage, ScanResult> MakeProcessor()
|
||||||
|
{
|
||||||
|
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||||
|
.Use(new CloneImageProcessor())
|
||||||
|
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||||
|
.Use(new NormalizeProcessor())
|
||||||
|
.Use(new ThresholdAdaptiveProcessor(15, 15))
|
||||||
|
.Use(new AddBorderProcessor(10))
|
||||||
|
.Use(new BinarizeProcessor())
|
||||||
|
.Complete(new NegateCloneProcessor());
|
||||||
|
|
||||||
|
var postprocessing = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||||
|
.Use(new ProcessingEvent<ScanResult>((_, data) => WriteToFile(data, "source")))
|
||||||
|
.Use(new ConfidenceFilter(50))
|
||||||
|
.Use(new ProcessingEvent<ScanResult>((_, data) => WriteToFile(data, "confidence")))
|
||||||
|
.Use(new ToLowerProcessor())
|
||||||
|
.Use(new ProcessingEvent<ScanResult>((_, data) => WriteToFile(data, "normalize")))
|
||||||
|
.Use(new DuplicateFilter())
|
||||||
|
.Use(new ProcessingEvent<ScanResult>((_, data) => WriteToFile(data, "duplicates")))
|
||||||
|
.Use(new RegexFilter(wordRegex))
|
||||||
|
.Complete(new ProcessingEvent<ScanResult>((_, data) => WriteToFile(data, "regex")));
|
||||||
|
|
||||||
|
var scan = new TesseractProcessor(tesseractConfig);
|
||||||
|
|
||||||
|
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||||
|
.Use(preprocessing)
|
||||||
|
.Use(scan)
|
||||||
|
.Complete(postprocessing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteToFile(ICollection<ScanResult> data, string name)
|
||||||
|
{
|
||||||
|
using var file1 = File.Open($"{name}.detailed.json", FileMode.Create);
|
||||||
|
JsonSerializer.Serialize(file1, data.Select(WordInfo.Create), jsonOptions);
|
||||||
|
|
||||||
|
using var file2 = File.Open($"{name}.json", FileMode.Create);
|
||||||
|
JsonSerializer.Serialize(file2, data.Select(d => d.Word.Text), jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WordInfo
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public double Confidence { get; set; }
|
||||||
|
|
||||||
|
public static WordInfo Create(ScanResult result) => new()
|
||||||
|
{
|
||||||
|
Text = result.Word.Text,
|
||||||
|
Confidence = result.Word.Confidence
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Refresh thesis results": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "source.png",
|
||||||
|
"workingDirectory": "C:\\Users\\Simon\\Documents\\Userdata\\FH\\SEM5\\BA\\bsc\\include\\postprocessing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract\Ocr.Tesseract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Ocr.Tesseract.Extensions;
|
||||||
|
|
||||||
|
const float factor = 4f;
|
||||||
|
const string downscaled = "downscaled.png";
|
||||||
|
|
||||||
|
using (var image = new MagickImage(args.Single()))
|
||||||
|
{
|
||||||
|
image.ResizeImage(0.8f, FilterType.Catrom, PixelInterpolateMethod.Undefined);
|
||||||
|
image.Write(downscaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var image = new MagickImage(downscaled))
|
||||||
|
{
|
||||||
|
image.ResizeImage(factor, FilterType.Lanczos, PixelInterpolateMethod.Catrom);
|
||||||
|
image.Write("Lanczos.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var image = new MagickImage(downscaled))
|
||||||
|
{
|
||||||
|
image.ResizeImage(factor, FilterType.Point, PixelInterpolateMethod.Integer);
|
||||||
|
image.Write("Nearest.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var image = new MagickImage(downscaled))
|
||||||
|
{
|
||||||
|
image.ResizeImage(factor, FilterType.Hermite, PixelInterpolateMethod.Mesh);
|
||||||
|
image.Write("Hermite.png");
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Test single img": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "source.png",
|
||||||
|
"workingDirectory": "D:\\git\\ba_ocr\\ocr\\Implementation\\testdata"
|
||||||
|
},
|
||||||
|
"Refresh thesis results": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "source.png",
|
||||||
|
"workingDirectory": "C:\\Users\\Simon\\Documents\\Userdata\\FH\\SEM5\\BA\\bsc\\include\\resampling"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ReportGenerator.Models;
|
namespace Ocr.Report.Models;
|
||||||
|
|
||||||
public readonly struct ImageStats
|
public readonly struct ImageStats
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Common.Distance;
|
using Common.Distance;
|
||||||
|
|
||||||
namespace ReportGenerator.Models;
|
namespace Ocr.Report.Models;
|
||||||
|
|
||||||
public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
||||||
{
|
{
|
||||||
@@ -47,7 +47,6 @@ public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
|||||||
) / reference.Count;
|
) / reference.Count;
|
||||||
|
|
||||||
Words = GetDistanceInfos(reference, hypothesis).ToArray();
|
Words = GetDistanceInfos(reference, hypothesis).ToArray();
|
||||||
// Words = reference.Select(r => GetDistanceInfo(r, hypothesis)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,8 +65,7 @@ public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
|||||||
foreach (var reference in referenceCollection)
|
foreach (var reference in referenceCollection)
|
||||||
{
|
{
|
||||||
var tResults = hypothesisCollection
|
var tResults = hypothesisCollection
|
||||||
.Select(hypothesis => new DistanceComparer<string>(reference, hypothesis))
|
.Select(hypothesis => (IDistanceComparer<string>)new DistanceComparer<string>(reference, hypothesis))
|
||||||
.Cast<IDistanceComparer<string>>()
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
results.AddRange(tResults);
|
results.AddRange(tResults);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ReportGenerator.Models;
|
namespace Ocr.Report.Models;
|
||||||
|
|
||||||
public struct ScanFileInfo
|
public struct ScanFileInfo
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Ocr.Report.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;
|
||||||
|
}
|
||||||
@@ -11,15 +11,13 @@
|
|||||||
<None Remove="Properties\htmldocument-style.css" />
|
<None Remove="Properties\htmldocument-style.css" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="Generator\Generator\Resources\Style.css" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\ReportGeneration\ReportGeneration.Generators\ReportGeneration.Generators.csproj" />
|
||||||
|
<ProjectReference Include="..\..\ReportGeneration\ReportGeneration.Interface\ReportGeneration.Interface.csproj" />
|
||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using Common.Extensions;
|
using Common.Extensions;
|
||||||
using ReportGenerator.Generator.Generator;
|
using Ocr.Report.Models;
|
||||||
using ReportGenerator.Models;
|
using ReportGeneration.Generators;
|
||||||
|
using ReportGeneration.Generators.LatexInclude;
|
||||||
|
|
||||||
namespace ReportGenerator;
|
namespace Ocr.Report;
|
||||||
|
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
@@ -19,22 +20,24 @@ internal static class Program
|
|||||||
Console.WriteLine("Generating report");
|
Console.WriteLine("Generating report");
|
||||||
var scans = Scan(tagFileInfos, scanFileInfos);
|
var scans = Scan(tagFileInfos, scanFileInfos);
|
||||||
|
|
||||||
var path = Path.GetFullPath("report.html");
|
var path = Path.GetFullPath("report");
|
||||||
|
|
||||||
using var document = new HtmlDocumentGenerator(path);
|
using var document = new LatexIncludeDocumentGenerator(path, true);
|
||||||
using var report = new ReportGenerator("OCR Report", document, scans)
|
using var report = new ReportGenerator("OCR Report", document, scans)
|
||||||
|
{
|
||||||
|
MaxDisplayRows = 8
|
||||||
|
}
|
||||||
.AddComparison("Processing summary (Average)", v =>
|
.AddComparison("Processing summary (Average)", v =>
|
||||||
{
|
{
|
||||||
var result = v.Average(out var deviation);
|
var result = v.Average(out var deviation);
|
||||||
return (result, deviation);
|
return (result, deviation);
|
||||||
})
|
})
|
||||||
// .AddComparison("Processing summary (Cumulative)", v => v.Sum())
|
|
||||||
.AddComparison("Processing summary (Median)", v =>
|
.AddComparison("Processing summary (Median)", v =>
|
||||||
{
|
{
|
||||||
var result = v.Median(out var deviation);
|
var result = v.Median(out var deviation);
|
||||||
return (result, deviation);
|
return (result, deviation);
|
||||||
})
|
})
|
||||||
// .AddProcessorStats("Processor Stats")
|
.AddProcessorStats("Processor Stats")
|
||||||
.AddImageStatsFull("Scan Results");
|
.AddImageStatsFull("Scan Results");
|
||||||
|
|
||||||
Console.WriteLine($"Saved report to '{path}'");
|
Console.WriteLine($"Saved report to '{path}'");
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"ReportGenerator": {
|
"ReportGenerator": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "\"img\" \"results\"",
|
"commandLineArgs": "\"img\" \"results\"",
|
||||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
"workingDirectory": "D:\\git\\ba_ocr\\Implementation\\testdata"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
using ReportGenerator.Generator.Interface;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||||
using ReportGenerator.Generator.Model;
|
using Ocr.Report.Models;
|
||||||
using ReportGenerator.Models;
|
using ReportGeneration.Abstract.Model;
|
||||||
|
using ReportGeneration.Interface;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace ReportGenerator;
|
namespace Ocr.Report;
|
||||||
|
|
||||||
public class ReportGenerator : IDisposable
|
public class ReportGenerator : IDisposable
|
||||||
{
|
{
|
||||||
|
public int? MaxDisplayRows { get; init; }
|
||||||
|
|
||||||
private IDocumentGenerator Document { get; }
|
private IDocumentGenerator Document { get; }
|
||||||
|
|
||||||
private ICollection<ImageStats> Images { get; }
|
private ICollection<ImageStats> Images { get; }
|
||||||
@@ -57,7 +61,7 @@ public class ReportGenerator : IDisposable
|
|||||||
|
|
||||||
foreach (var (processor, images) in processors)
|
foreach (var (processor, images) in processors)
|
||||||
{
|
{
|
||||||
var ordered = images
|
IEnumerable<(ImageStats Stats, double Distance)> ordered = images
|
||||||
.Select(i => (Stats: i, Distance: i
|
.Select(i => (Stats: i, Distance: i
|
||||||
.Processors
|
.Processors
|
||||||
.Where(p => p.Name.Equals(processor))
|
.Where(p => p.Name.Equals(processor))
|
||||||
@@ -66,10 +70,17 @@ public class ReportGenerator : IDisposable
|
|||||||
))
|
))
|
||||||
.OrderBy(i => i.Distance)
|
.OrderBy(i => i.Distance)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
if (MaxDisplayRows.HasValue)
|
||||||
|
{
|
||||||
|
ordered = ordered
|
||||||
|
.Take(MaxDisplayRows.Value / 2)
|
||||||
|
.Concat(ordered.TakeLast(MaxDisplayRows.Value / 2));
|
||||||
|
}
|
||||||
|
|
||||||
Document
|
Document
|
||||||
.AppendHeading(3, processor)
|
.AppendHeading(3, processor)
|
||||||
.AppendTable(2, table =>
|
.AppendTable(3, table =>
|
||||||
{
|
{
|
||||||
table.AppendHeader(new[] { "Image", "Preview", "Distance" });
|
table.AppendHeader(new[] { "Image", "Preview", "Distance" });
|
||||||
|
|
||||||
@@ -79,7 +90,7 @@ public class ReportGenerator : IDisposable
|
|||||||
|
|
||||||
table.AppendRow(new[]
|
table.AppendRow(new[]
|
||||||
{
|
{
|
||||||
$"<a href=\"#{stats.ImageName}\">{stats.ImageName}</a>",
|
Ellipsis(stats.ImageName.Replace("\\", "\\\\") + ".png", 25),
|
||||||
Document.FormatImage(imgPath, new Bounds(0, 150)),
|
Document.FormatImage(imgPath, new Bounds(0, 150)),
|
||||||
distance.ToString("F2")
|
distance.ToString("F2")
|
||||||
});
|
});
|
||||||
@@ -103,21 +114,30 @@ public class ReportGenerator : IDisposable
|
|||||||
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
|
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
|
||||||
)
|
)
|
||||||
.AppendTable(
|
.AppendTable(
|
||||||
stat.Reference.Count + 5,
|
stat.Reference.Count + 6,
|
||||||
table =>
|
table =>
|
||||||
{
|
{
|
||||||
table.AppendHeader(stat
|
table.AppendHeader(stat
|
||||||
.Reference
|
.Reference
|
||||||
.Prepend("Image")
|
.Prepend("Image")
|
||||||
|
.Prepend("Perfect matches")
|
||||||
.Prepend("CER (avg)")
|
.Prepend("CER (avg)")
|
||||||
.Prepend("WER")
|
.Prepend("WER")
|
||||||
.Prepend("Elapsed")
|
.Prepend("Elapsed")
|
||||||
.Prepend("Processor")
|
.Prepend("Processor")
|
||||||
);
|
);
|
||||||
|
|
||||||
var processors = stat.Processors
|
IEnumerable<ProcessorStat> processors = stat.Processors
|
||||||
.OrderBy(s => s.Distance)
|
.OrderBy(s => s.Distance)
|
||||||
.ThenBy(s => s.ProcessingTime);
|
.ThenBy(s => s.ProcessingTime)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (MaxDisplayRows.HasValue)
|
||||||
|
{
|
||||||
|
processors = processors
|
||||||
|
.Take(MaxDisplayRows.Value / 2)
|
||||||
|
.Concat(processors.TakeLast(MaxDisplayRows.Value / 2));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var processor in processors)
|
foreach (var processor in processors)
|
||||||
{
|
{
|
||||||
@@ -126,10 +146,11 @@ public class ReportGenerator : IDisposable
|
|||||||
table.AppendRow(processor.Words
|
table.AppendRow(processor.Words
|
||||||
.Select(s => s.ToString() ?? string.Empty)
|
.Select(s => s.ToString() ?? string.Empty)
|
||||||
.Prepend(Document.FormatImage(imgPath, new Bounds(0, 150)))
|
.Prepend(Document.FormatImage(imgPath, new Bounds(0, 150)))
|
||||||
|
.Prepend($"{processor.Words.Count(w => w.Distance == 0)} / {processor.Words.Count}")
|
||||||
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
||||||
.Prepend($"{processor.Distance * 100:F1}%")
|
.Prepend($"{processor.Distance * 100:F1}%")
|
||||||
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
||||||
.Prepend(processor.Name)
|
.Prepend(Ellipsis(processor.Name, 25))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -198,7 +219,6 @@ public class ReportGenerator : IDisposable
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void AppendComparison(
|
private void AppendComparison(
|
||||||
(string name, string unit)? valueInfo,
|
(string name, string unit)? valueInfo,
|
||||||
IEnumerable<(string, double, double)> values
|
IEnumerable<(string, double, double)> values
|
||||||
@@ -242,6 +262,20 @@ public class ReportGenerator : IDisposable
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private static string Ellipsis(string str, int maxLength, string ellipsis = "...")
|
||||||
|
{
|
||||||
|
if (str.Length <= maxLength)
|
||||||
|
{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int subStrLen = (maxLength - ellipsis.Length) / 2;
|
||||||
|
return string.Concat(
|
||||||
|
str.Substring(0, subStrLen),
|
||||||
|
ellipsis,
|
||||||
|
str.Substring(str.Length - subStrLen, subStrLen));
|
||||||
|
}
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
@@ -260,4 +294,4 @@ public class ReportGenerator : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |