Fixed live scanner adjustments
This commit is contained in:
@@ -42,9 +42,4 @@ public class ScreenshotScanner
|
|||||||
Lookup.Add(kv.Word, kv.Image);
|
Lookup.Add(kv.Word, kv.Image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Clear()
|
|
||||||
{
|
|
||||||
Lookup.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -161,52 +161,6 @@
|
|||||||
x:Name="EnableThreshold"
|
x:Name="EnableThreshold"
|
||||||
Content="Apply Threshold"
|
Content="Apply Threshold"
|
||||||
IsChecked="{Binding ProcessorConfig.EnableThresholding}" />
|
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
|
<CheckBox
|
||||||
Content="Resize"
|
Content="Resize"
|
||||||
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
IsChecked="{Binding ProcessorConfig.EnableResizing}" />
|
||||||
@@ -229,13 +183,6 @@
|
|||||||
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Rectangle />
|
|
||||||
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
Content="Filter connected components"
|
|
||||||
IsChecked="{Binding ProcessorConfig.FilterConnectedComponents}" />
|
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -25,19 +25,6 @@ public partial class ImageView : Window
|
|||||||
InitializeComponent();
|
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)
|
private void SldBorder_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||||
{
|
{
|
||||||
var vm = ViewModel;
|
var vm = ViewModel;
|
||||||
|
|||||||
@@ -22,271 +22,277 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using Ocr.Tesseract.Extensions;
|
||||||
|
|
||||||
namespace Ocr.Gui.Views;
|
namespace Ocr.Gui.Views;
|
||||||
|
|
||||||
internal class ImageViewModel : INotifyPropertyChanged
|
internal class ImageViewModel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The internally used <see cref="ScreenshotScanner"/>
|
/// Tesseract engine configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ScreenshotScanner Scanner { get; private set; }
|
public static readonly ITesseractConfiguration TesseractConfig =
|
||||||
|
new TesseractScreenshotConfiguration
|
||||||
|
{
|
||||||
|
DataPath = "tessdata",
|
||||||
|
Languages = new[] { "eng", "deu" }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new();
|
||||||
/// 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>
|
/// </summary>
|
||||||
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
public static readonly Regex WordRegex = new(
|
||||||
/// </summary>
|
@"[\w'\-]{2,}",
|
||||||
public static readonly Regex WordRegex = new(
|
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||||
@"[\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
|
public ImageViewModel()
|
||||||
|
|
||||||
#region File Handling
|
|
||||||
|
|
||||||
private void OpenFile()
|
|
||||||
{
|
|
||||||
var dialog = new OpenFileDialog()
|
|
||||||
{
|
{
|
||||||
InitialDirectory = Directory.GetCurrentDirectory()
|
ProcessorConfig.PropertyChanged += (sender, args) => Task.Run(UpdateImage);
|
||||||
};
|
|
||||||
if (dialog.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
Image = new MagickImage(dialog.FileName);
|
|
||||||
UpdateImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveEditedImage()
|
OpenFileCommand = new Command(OpenFile);
|
||||||
{
|
SaveEditedImageCommand = new Command(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}'");
|
public ImageViewModel(MagickImage image) : this()
|
||||||
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;
|
Image = image;
|
||||||
|
|
||||||
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
|
private IProcessorChain<MagickImage, ScanResult> MakeProcessor()
|
||||||
{
|
|
||||||
get => _isIdle;
|
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (value == _isIdle)
|
var threshold =
|
||||||
{
|
new ThresholdAdaptiveProcessor(
|
||||||
return;
|
ProcessorConfig.ThresholdWidth,
|
||||||
}
|
ProcessorConfig.ThresholdHeight
|
||||||
|
);
|
||||||
|
|
||||||
_isIdle = value;
|
var chainConfig = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||||
OnPropertyChanged();
|
.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
|
#region Overrides of Scanner
|
||||||
{
|
|
||||||
get => _confidence;
|
/// <inheritdoc />
|
||||||
set
|
protected void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||||
{
|
{
|
||||||
_confidence = value;
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
OnPropertyChanged();
|
{
|
||||||
|
foreach (var image in inputs)
|
||||||
|
{
|
||||||
|
Edited.Add(image.CloneImage());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<MagickImage> Edited { get; } = new();
|
/// <inheritdoc />
|
||||||
|
protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||||
public MagickImage? Image
|
|
||||||
{
|
|
||||||
get => _image;
|
|
||||||
set
|
|
||||||
{
|
{
|
||||||
_image = value;
|
var wordStr = string.Join("\", \"", inputs);
|
||||||
OnPropertyChanged();
|
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; }
|
private void OpenFile()
|
||||||
public ICommand SaveEditedImageCommand { get; private set; }
|
{
|
||||||
|
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)
|
#endregion
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
#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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user