diff --git a/Implementation/Common/ScreenshotScanner.cs b/Implementation/Common/ScreenshotScanner.cs index cb05d27..a6f43a7 100644 --- a/Implementation/Common/ScreenshotScanner.cs +++ b/Implementation/Common/ScreenshotScanner.cs @@ -42,9 +42,4 @@ public class ScreenshotScanner Lookup.Add(kv.Word, kv.Image); } } - - public virtual void Clear() - { - Lookup.Clear(); - } } \ No newline at end of file diff --git a/Implementation/GUI/Views/ImageView.xaml b/Implementation/GUI/Views/ImageView.xaml index 6fe2454..89b4cab 100644 --- a/Implementation/GUI/Views/ImageView.xaml +++ b/Implementation/GUI/Views/ImageView.xaml @@ -161,52 +161,6 @@ x:Name="EnableThreshold" Content="Apply Threshold" IsChecked="{Binding ProcessorConfig.EnableThresholding}" /> - - - - - - - - Block Width: - - - - - - - - - - - - Block Height: - - - - - - @@ -229,13 +183,6 @@ - - - - - \ No newline at end of file diff --git a/Implementation/GUI/Views/ImageView.xaml.cs b/Implementation/GUI/Views/ImageView.xaml.cs index 96a83cf..624b030 100644 --- a/Implementation/GUI/Views/ImageView.xaml.cs +++ b/Implementation/GUI/Views/ImageView.xaml.cs @@ -25,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; diff --git a/Implementation/GUI/Views/ImageViewModel.cs b/Implementation/GUI/Views/ImageViewModel.cs index e2909d1..91119cb 100644 --- a/Implementation/GUI/Views/ImageViewModel.cs +++ b/Implementation/GUI/Views/ImageViewModel.cs @@ -22,271 +22,277 @@ 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 { - /// - /// The internally used - /// - public ScreenshotScanner Scanner { get; private set; } + /// + /// Tesseract engine configuration + /// + public static readonly ITesseractConfiguration TesseractConfig = + new TesseractScreenshotConfiguration + { + DataPath = "tessdata", + Languages = new[] { "eng", "deu" } + }; - /// - /// Tesseract engine configuration - /// - public static readonly ITesseractConfiguration TesseractConfig = - new TesseractScreenshotConfiguration - { - DataPath = "tessdata", - Languages = new[] { "eng", "deu" } - }; + public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new(); - public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new(); - - /// - /// expression for extracting whole words from scan results - /// - 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 MakeProcessor() - { - var threshold = - new ThresholdAdaptiveProcessor( - ProcessorConfig.ThresholdWidth, - ProcessorConfig.ThresholdHeight - ); - - var preprocessing = new ProcessorChainConfiguration() - .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() - .Use(new ConfidenceFilter(50)) - .Use(new ToLowerProcessor()) - .Use(new DuplicateFilter()) - .Complete(new RegexFilter(WordRegex)); - - var scan = new TesseractProcessor(TesseractConfig); - - return new ProcessorChainConfiguration() - .Use(preprocessing) - .Use(new ProcessingEvent(OnProcessing)) - .Use(scan) - .Use(new ProcessingEvent(OnProcessed)) - .Complete(postprocessing); - } - - #region Overrides of Scanner - - /// - protected void OnProcessing(IProcessor sender, ICollection inputs) - { - Application.Current.Dispatcher.Invoke(() => - { - foreach (var image in inputs) - { - Edited.Add(image); - } - }); - } - - /// - protected void OnProcessed(IProcessor sender, ICollection inputs) - { - ScannedText = $"[{inputs.Count} words] " + string.Join(' ', inputs); - } - - /// - public void Clear() - { - Scanner.Clear(); - Application.Current.Dispatcher.Invoke(() => - { - ScannedText = string.Empty; - Words.Clear(); - Edited.Clear(); - } + /// + /// expression for extracting whole words from scan results + /// + public static readonly Regex WordRegex = new( + @"[\w'\-]{2,}", + RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase ); - } - #endregion - - #region File Handling - - private void OpenFile() - { - var dialog = new OpenFileDialog() + public ImageViewModel() { - InitialDirectory = Directory.GetCurrentDirectory() - }; - if (dialog.ShowDialog() == true) - { - Image = new MagickImage(dialog.FileName); - UpdateImage(); - } - } + ProcessorConfig.PropertyChanged += (sender, args) => Task.Run(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")); + OpenFileCommand = new Command(OpenFile); + SaveEditedImageCommand = new Command(SaveEditedImage); } - 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(() => + public ImageViewModel(MagickImage image) : this() { - 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(); + Image = image; } - } - public bool IsIdle - { - get => _isIdle; - set + private IProcessorChain MakeProcessor() { - if (value == _isIdle) - { - return; - } + var threshold = + new ThresholdAdaptiveProcessor( + ProcessorConfig.ThresholdWidth, + ProcessorConfig.ThresholdHeight + ); - _isIdle = value; - OnPropertyChanged(); + var chainConfig = new ProcessorChainConfiguration() + .Use(new CloneImageProcessor()); + + if (ProcessorConfig.EnableResizing) + { + chainConfig + .Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh)) + .Use(new ProcessingEvent(OnProcessing)); + } + + chainConfig = chainConfig + .Use(new NormalizeProcessor()) + .Use(new ProcessingEvent(OnProcessing)); + + if (ProcessorConfig.EnableThresholding) + { + chainConfig = chainConfig + .Use(threshold) + .Use(new ProcessingEvent(OnProcessing)); + } + + var preprocessing = chainConfig + .Use(new AddBorderProcessor(ProcessorConfig.Border)) + .Use(new BinarizeProcessor()) + .Use(new ProcessingEvent(OnProcessing)) + .Complete(new NegateCloneProcessor()); + + var postprocessing = new ProcessorChainConfiguration() + .Use(new ConfidenceFilter(50)) + .Use(new ToLowerProcessor()) + .Use(new DuplicateFilter()) + .Complete(new RegexFilter(WordRegex)); + + var scan = new TesseractProcessor(TesseractConfig); + + return new ProcessorChainConfiguration() + .Use(preprocessing) + .Use(scan) + .Use(new ProcessingEvent(OnProcessed)) + .Complete(postprocessing); } - } - public float Confidence - { - get => _confidence; - set + #region Overrides of Scanner + + /// + protected void OnProcessing(IProcessor sender, ICollection inputs) { - _confidence = value; - OnPropertyChanged(); + Application.Current.Dispatcher.Invoke(() => + { + foreach (var image in inputs) + { + Edited.Add(image.CloneImage()); + } + }); } - } - public ObservableCollection Edited { get; } = new(); - - public MagickImage? Image - { - get => _image; - set + /// + protected void OnProcessed(IProcessor sender, ICollection inputs) { - _image = value; - OnPropertyChanged(); + var wordStr = string.Join("\", \"", inputs); + ScannedText = $"{inputs.Count} words:{Environment.NewLine}[ \"{wordStr}\" ]"; } - } - public ObservableCollection Words { get; } = new(); + /// + public void Clear() + { + Application.Current.Dispatcher.Invoke(() => + { + ScannedText = string.Empty; + Words.Clear(); + Edited.Clear(); + } + ); + } - #endregion Properties + #endregion - #region Commands + #region File Handling - public ICommand OpenFileCommand { get; private set; } - public ICommand SaveEditedImageCommand { get; private set; } + private void OpenFile() + { + var dialog = new OpenFileDialog() + { + InitialDirectory = Directory.GetCurrentDirectory() + }; + if (dialog.ShowDialog() == true) + { + Image = new MagickImage(dialog.FileName); + UpdateImage(); + } + } - #endregion Commands + private void SaveEditedImage() + { + var basePath = AppDomain.CurrentDomain.BaseDirectory; - #region INotifyPropertyChanged + for (var i = 0; i < Edited.Count; i++) + { + Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png")); + } - public event PropertyChangedEventHandler? PropertyChanged; + Log.Information($"Saved image to '{basePath}'"); + System.Diagnostics.Process.Start( + "explorer.exe", + Path.GetDirectoryName(basePath) ?? string.Empty + ); + } - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + #endregion - #endregion INotifyPropertyChanged -} + #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 Edited { get; } = new(); + + public MagickImage? Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public ObservableCollection 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 +} \ No newline at end of file