298 lines
7.6 KiB
C#
298 lines
7.6 KiB
C#
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
|
|
} |