Compare commits
14 Commits
| 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;
|
||||
|
||||
namespace CLI.Monitor;
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public class CliTaskMonitor : TaskMonitor
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text;
|
||||
|
||||
namespace CLI.Monitor;
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public class CompactCliTaskMonitor : TaskMonitor
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace CLI.Monitor;
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public interface ITaskMonitor
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace CLI.Monitor;
|
||||
namespace Ocr.Cli.Monitor;
|
||||
|
||||
public abstract class TaskMonitor : ITaskMonitor
|
||||
{
|
||||
@@ -10,7 +10,7 @@ using Process.Interface;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CLI.Processor;
|
||||
namespace Ocr.Cli.Processor;
|
||||
|
||||
internal class EvaluationProcessor
|
||||
{
|
||||
@@ -20,7 +20,7 @@ internal class EvaluationProcessor
|
||||
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
||||
/// </summary>
|
||||
private static readonly Regex wordRegex = new(
|
||||
@"[\w'\-]{2,}",
|
||||
@"[\w'\-äöüÄÖÜß]{2,}",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
@@ -42,6 +42,24 @@ internal class EvaluationProcessor
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(wordRegex));
|
||||
|
||||
private IProcessorChain<MagickImage, ScanResult> MakeProcessor()
|
||||
{
|
||||
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||
.Use(new CloneImageProcessor())
|
||||
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||
.Use(new NormalizeProcessor())
|
||||
.Use(_thresholdProcessor)
|
||||
.Use(new AddBorderProcessor(10))
|
||||
.Use(new BinarizeProcessor())
|
||||
.Use(new NegateCloneProcessor())
|
||||
.Complete(OnPreprocessed);
|
||||
|
||||
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||
.Use(preprocessing)
|
||||
.Use(tesseractProcessor)
|
||||
.Complete(postProcessor);
|
||||
}
|
||||
|
||||
private static readonly TesseractProcessor tesseractProcessor = new(tesseractConfig);
|
||||
|
||||
private readonly StopwatchProcessor<MagickImage, MagickImage> _thresholdProcessor;
|
||||
@@ -75,24 +93,6 @@ internal class EvaluationProcessor
|
||||
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)
|
||||
{
|
||||
var tImages = images.ToArray();
|
||||
@@ -2,7 +2,7 @@
|
||||
using Process.Interface;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CLI.Processor;
|
||||
namespace Ocr.Cli.Processor;
|
||||
|
||||
public class StopwatchProcessor<TInput, TOutput> : Processor<TInput, TOutput>
|
||||
{
|
||||
@@ -1,11 +1,11 @@
|
||||
using CLI.Monitor;
|
||||
using CLI.Processor;
|
||||
using Common.Extensions;
|
||||
using Common.Extensions;
|
||||
using ImageMagick;
|
||||
using Ocr.Cli.Monitor;
|
||||
using Ocr.Cli.Processor;
|
||||
using Ocr.Tesseract.Screenshots.Threshold;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CLI;
|
||||
namespace Ocr.Cli;
|
||||
|
||||
public class Program
|
||||
{
|
||||
@@ -20,6 +20,7 @@ public class Program
|
||||
select (Key: path, Task: processor.Process(new MagickImage(path)))
|
||||
).ToArray();
|
||||
|
||||
// return new CliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||
return new CompactCliTaskMonitor(scans) { Interval = TimeSpan.FromMilliseconds(500) }.Run();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"Test all img": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"img/*.png\"",
|
||||
"workingDirectory": "D:\\git\\BA\\Examples\\testdata"
|
||||
"workingDirectory": "D:\\git\\ba_ocr\\Implementation\\testdata"
|
||||
},
|
||||
"Test single img": {
|
||||
"commandName": "Project",
|
||||
@@ -5,7 +5,7 @@ namespace Common.Distance;
|
||||
public static class Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the levenshtein distance between
|
||||
/// Calculates the levenshtein distance between two enumerables
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="reference"></param>
|
||||
@@ -37,7 +37,9 @@ public readonly struct DistanceComparer<T> : IDistanceComparer<T>
|
||||
return str ?? string.Empty;
|
||||
}
|
||||
|
||||
return
|
||||
$"<strong style='color: orange;' title='REf: {Reference}, CER: {Distance}'>{str ?? "-"}</strong>";
|
||||
// Enable for HTML/MD only
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<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
|
||||
x:Class="GUI.Controls.ImageControl"
|
||||
x:Class="Ocr.Gui.Controls.ImageControl"
|
||||
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:converters="clr-namespace:GUI.Converters"
|
||||
xmlns:controls="clr-namespace:GUI.Controls"
|
||||
xmlns:converters="clr-namespace:Ocr.Gui.Converters"
|
||||
xmlns:controls="clr-namespace:Ocr.Gui.Controls"
|
||||
d:DataContext="{d:DesignInstance controls:ImageControl}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace GUI.Controls;
|
||||
namespace Ocr.Gui.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ImageControl.xaml
|
||||
@@ -6,12 +6,10 @@ using System.IO;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GUI.Converters;
|
||||
namespace Ocr.Gui.Converters;
|
||||
|
||||
internal class ImageConverter : IValueConverter
|
||||
{
|
||||
#region Implementation of IValueConverter
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -48,6 +46,4 @@ internal class ImageConverter : IValueConverter
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace GUI.Model;
|
||||
namespace Ocr.Gui.Model;
|
||||
|
||||
public class Command : ICommand
|
||||
{
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<Window
|
||||
x:Class="GUI.Views.ImageView"
|
||||
x:Class="Ocr.Gui.Views.ImageView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:GUI.Controls"
|
||||
xmlns:viewModels="clr-namespace:GUI.ViewModels"
|
||||
xmlns:views="clr-namespace:Ocr.Gui.Views"
|
||||
xmlns:controls="clr-namespace:Ocr.Gui.Controls"
|
||||
Title="OcrView"
|
||||
Width="800"
|
||||
Height="450"
|
||||
d:DataContext="{d:DesignInstance viewModels:ImageViewModel}"
|
||||
d:DataContext="{d:DesignInstance views:ImageViewModel}"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<CollectionViewSource
|
||||
@@ -161,52 +161,6 @@
|
||||
x:Name="EnableThreshold"
|
||||
Content="Apply Threshold"
|
||||
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
|
||||
Content="Resize"
|
||||
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
||||
@@ -229,13 +183,6 @@
|
||||
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Rectangle />
|
||||
|
||||
|
||||
<CheckBox
|
||||
Content="Filter connected components"
|
||||
IsChecked="{Binding ProcessorConfig.FilterConnectedComponents}" />
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,11 +1,10 @@
|
||||
using GUI.ViewModels;
|
||||
using ImageMagick;
|
||||
using ImageMagick;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace GUI.Views;
|
||||
namespace Ocr.Gui.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
@@ -26,19 +25,6 @@ public partial class ImageView : Window
|
||||
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;
|
||||
@@ -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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "..\Lookup\Lookup.Abstract\Lookup.Abstract.csproj", "{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}"
|
||||
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
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}"
|
||||
EndProject
|
||||
@@ -35,7 +35,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract", "..\Ocr\Ocr
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{251F9AC9-3765-498C-83FD-DB3539A19CB3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportGenerator", "ReportGenerator\ReportGenerator.csproj", "{729CB7AA-AB0D-4C39-AA17-7435E61FA0A6}"
|
||||
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
|
||||
Global
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -112,6 +144,10 @@ Global
|
||||
{E55F78E4-09F1-4D79-A9A2-460562C96DAB} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||
{D9B70035-0159-4D75-8ED6-2461F060F683} = {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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
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
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Common.Distance;
|
||||
|
||||
namespace ReportGenerator.Models;
|
||||
namespace Ocr.Report.Models;
|
||||
|
||||
public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
||||
{
|
||||
@@ -47,7 +47,6 @@ public readonly struct ProcessorStat : IDistanceComparer<IEnumerable<string>>
|
||||
) / reference.Count;
|
||||
|
||||
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)
|
||||
{
|
||||
var tResults = hypothesisCollection
|
||||
.Select(hypothesis => new DistanceComparer<string>(reference, hypothesis))
|
||||
.Cast<IDistanceComparer<string>>()
|
||||
.Select(hypothesis => (IDistanceComparer<string>)new DistanceComparer<string>(reference, hypothesis))
|
||||
.ToList();
|
||||
|
||||
results.AddRange(tResults);
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ReportGenerator.Models;
|
||||
namespace Ocr.Report.Models;
|
||||
|
||||
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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Generator\Generator\Resources\Style.css" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ReportGeneration\ReportGeneration.Generators\ReportGeneration.Generators.csproj" />
|
||||
<ProjectReference Include="..\..\ReportGeneration\ReportGeneration.Interface\ReportGeneration.Interface.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Common.Extensions;
|
||||
using ReportGenerator.Generator.Generator;
|
||||
using ReportGenerator.Models;
|
||||
using Ocr.Report.Models;
|
||||
using ReportGeneration.Generators;
|
||||
using ReportGeneration.Generators.LatexInclude;
|
||||
|
||||
namespace ReportGenerator;
|
||||
namespace Ocr.Report;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
@@ -19,22 +20,24 @@ internal static class Program
|
||||
Console.WriteLine("Generating report");
|
||||
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)
|
||||
{
|
||||
MaxDisplayRows = 8
|
||||
}
|
||||
.AddComparison("Processing summary (Average)", v =>
|
||||
{
|
||||
var result = v.Average(out var deviation);
|
||||
return (result, deviation);
|
||||
})
|
||||
// .AddComparison("Processing summary (Cumulative)", v => v.Sum())
|
||||
.AddComparison("Processing summary (Median)", v =>
|
||||
{
|
||||
var result = v.Median(out var deviation);
|
||||
return (result, deviation);
|
||||
})
|
||||
// .AddProcessorStats("Processor Stats")
|
||||
.AddProcessorStats("Processor Stats")
|
||||
.AddImageStatsFull("Scan Results");
|
||||
|
||||
Console.WriteLine($"Saved report to '{path}'");
|
||||
@@ -3,7 +3,7 @@
|
||||
"ReportGenerator": {
|
||||
"commandName": "Project",
|
||||
"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 ReportGenerator.Generator.Model;
|
||||
using ReportGenerator.Models;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Ocr.Report.Models;
|
||||
using ReportGeneration.Abstract.Model;
|
||||
using ReportGeneration.Interface;
|
||||
using System;
|
||||
|
||||
namespace ReportGenerator;
|
||||
namespace Ocr.Report;
|
||||
|
||||
public class ReportGenerator : IDisposable
|
||||
{
|
||||
public int? MaxDisplayRows { get; init; }
|
||||
|
||||
private IDocumentGenerator Document { get; }
|
||||
|
||||
private ICollection<ImageStats> Images { get; }
|
||||
@@ -57,7 +61,7 @@ public class ReportGenerator : IDisposable
|
||||
|
||||
foreach (var (processor, images) in processors)
|
||||
{
|
||||
var ordered = images
|
||||
IEnumerable<(ImageStats Stats, double Distance)> ordered = images
|
||||
.Select(i => (Stats: i, Distance: i
|
||||
.Processors
|
||||
.Where(p => p.Name.Equals(processor))
|
||||
@@ -66,10 +70,17 @@ public class ReportGenerator : IDisposable
|
||||
))
|
||||
.OrderBy(i => i.Distance)
|
||||
.ToArray();
|
||||
|
||||
if (MaxDisplayRows.HasValue)
|
||||
{
|
||||
ordered = ordered
|
||||
.Take(MaxDisplayRows.Value / 2)
|
||||
.Concat(ordered.TakeLast(MaxDisplayRows.Value / 2));
|
||||
}
|
||||
|
||||
Document
|
||||
.AppendHeading(3, processor)
|
||||
.AppendTable(2, table =>
|
||||
.AppendTable(3, table =>
|
||||
{
|
||||
table.AppendHeader(new[] { "Image", "Preview", "Distance" });
|
||||
|
||||
@@ -79,7 +90,7 @@ public class ReportGenerator : IDisposable
|
||||
|
||||
table.AppendRow(new[]
|
||||
{
|
||||
$"<a href=\"#{stats.ImageName}\">{stats.ImageName}</a>",
|
||||
Ellipsis(stats.ImageName.Replace("\\", "\\\\") + ".png", 25),
|
||||
Document.FormatImage(imgPath, new Bounds(0, 150)),
|
||||
distance.ToString("F2")
|
||||
});
|
||||
@@ -103,21 +114,30 @@ public class ReportGenerator : IDisposable
|
||||
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
|
||||
)
|
||||
.AppendTable(
|
||||
stat.Reference.Count + 5,
|
||||
stat.Reference.Count + 6,
|
||||
table =>
|
||||
{
|
||||
table.AppendHeader(stat
|
||||
.Reference
|
||||
.Prepend("Image")
|
||||
.Prepend("Perfect matches")
|
||||
.Prepend("CER (avg)")
|
||||
.Prepend("WER")
|
||||
.Prepend("Elapsed")
|
||||
.Prepend("Processor")
|
||||
);
|
||||
|
||||
var processors = stat.Processors
|
||||
IEnumerable<ProcessorStat> processors = stat.Processors
|
||||
.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)
|
||||
{
|
||||
@@ -126,10 +146,11 @@ public class ReportGenerator : IDisposable
|
||||
table.AppendRow(processor.Words
|
||||
.Select(s => s.ToString() ?? string.Empty)
|
||||
.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.Distance * 100:F1}%")
|
||||
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
||||
.Prepend(processor.Name)
|
||||
.Prepend(Ellipsis(processor.Name, 25))
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -198,7 +219,6 @@ public class ReportGenerator : IDisposable
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private void AppendComparison(
|
||||
(string name, string unit)? valueInfo,
|
||||
IEnumerable<(string, double, double)> values
|
||||
@@ -242,6 +262,20 @@ public class ReportGenerator : IDisposable
|
||||
|
||||
#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
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
@@ -260,4 +294,4 @@ public class ReportGenerator : IDisposable
|
||||
}
|
||||
|
||||
#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 |