This repository has been archived on 2024-06-04. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
thesis-src/Implementation/GUI/Views/ImageViewModel.cs
T
2024-01-10 18:35:04 +01:00

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
}