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/Examples/GUI/ViewModels/ImageViewModel.cs
T
Simon Gruber d8008e4f05 a
2023-11-20 07:43:12 +01:00

293 lines
6.4 KiB
C#

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
}