Fixed live scanner adjustments

This commit is contained in:
Simon
2024-01-10 18:35:04 +01:00
parent 74619876c2
commit 946eea2347
4 changed files with 240 additions and 305 deletions
@@ -42,9 +42,4 @@ public class ScreenshotScanner
Lookup.Add(kv.Word, kv.Image);
}
}
public virtual void Clear()
{
Lookup.Clear();
}
}
-53
View File
@@ -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>
@@ -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;
+240 -234
View File
@@ -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
{
/// <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" }
};
/// <summary>
/// Tesseract engine configuration
/// </summary>
public static readonly ITesseractConfiguration TesseractConfig =
new TesseractScreenshotConfiguration
{
DataPath = "tessdata",
Languages = new[] { "eng", "deu" }
};
public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new();
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();
}
/// <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
);
}
#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<MagickImage, ScanResult> MakeProcessor()
{
if (value == _isIdle)
{
return;
}
var threshold =
new ThresholdAdaptiveProcessor(
ProcessorConfig.ThresholdWidth,
ProcessorConfig.ThresholdHeight
);
_isIdle = value;
OnPropertyChanged();
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);
}
}
public float Confidence
{
get => _confidence;
set
#region Overrides of Scanner
/// <inheritdoc />
protected void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
{
_confidence = value;
OnPropertyChanged();
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var image in inputs)
{
Edited.Add(image.CloneImage());
}
});
}
}
public ObservableCollection<MagickImage> Edited { get; } = new();
public MagickImage? Image
{
get => _image;
set
/// <inheritdoc />
protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
{
_image = value;
OnPropertyChanged();
var wordStr = string.Join("\", \"", inputs);
ScannedText = $"{inputs.Count} words:{Environment.NewLine}[ \"{wordStr}\" ]";
}
}
public ObservableCollection<Word> Words { get; } = new();
/// <inheritdoc />
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<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
}