From 16fdb808dfbb899b74d13a96f5cb6a4566c80a79 Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Mon, 27 May 2024 20:22:16 -0400 Subject: [PATCH] add pattern matching --- README.md | 45 +++++ .../Abstractions/IOcrService.cs | 161 +++++++++++++++++- .../OcrImplementation.android.cs | 23 ++- .../OcrImplementation.macios.cs | 22 ++- src/Plugin.Maui.OCR/OcrImplementation.net.cs | 2 +- .../OcrImplementation.windows.cs | 22 ++- .../Abstractions/IOcrService.cs | 152 ++++++++++++++++- .../Platforms/Android/OcrImplementation.cs | 19 ++- .../Platforms/Apple/OcrImplementation.cs | 23 ++- .../Platforms/UWP/OcrImplementation.cs | 16 +- 10 files changed, 455 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ad433c6..f2f89a3 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,51 @@ Install with the dotnet CLI: `dotnet add package Plugin.Maui.OCR` or `dotnet add | Android | 5.0 (API 21) | | Windows | 11 and 10 version 1809+ | +## Pattern Matching + +One of the more common things that I personally do with OCR is to recognize a pattern in the text. For example, I might want to recognize a date or a phone number or an email address. This is where the `OcrPatternConfig` class comes in. + +Let's say you want to recognize an Ontario Health Card Number in the text of your image. Numbers of those types have some specific qualities that make it easy to match. + +1. An Ontario HCN is 10 digits long. +1. The number must be [Luhn valid](https://en.wikipedia.org/wiki/Luhn_algorithm) (meaning it has a check digit and it's correct). + +To do this, you can create a `OcrPatternConfig` object like so: + +```csharp +bool IsValidLuhn(string number) +{ + // Convert the string to an array of digits + int[] digits = number.Select(d => int.Parse(d.ToString())).ToArray(); + int checkDigit = 0; + + // Luhn algorithm implementation + for (int i = digits.Length - 2; i >= 0; i--) + { + int currentDigit = digits[i]; + if ((digits.Length - 2 - i) % 2 == 0) // check if it's an even index from the right + { + currentDigit *= 2; + if (currentDigit > 9) + { + currentDigit -= 9; + } + } + checkDigit += currentDigit; + } + + return (10 - (checkDigit % 10)) % 10 == digits.Last(); +} + +var ohipPattern = new OcrPatternConfig(@"\d{10}", IsLuhnValid); + +var options = new OcrOptions(tryHard: true, patternConfig: ohipPattern); + +var result = await OcrPlugin.Default.RecognizeTextAsync(imageData, options); + +var patientHcn = result.MatchedValues.FirstOrDefault(); // This will be the HCN (and only the HCN) if it's found +``` + ## MAUI Setup and Usage For MAUI, to initialize make sure you use the MauiAppBuilder extension `AddOcr()` like so: diff --git a/src/Plugin.Maui.OCR/Abstractions/IOcrService.cs b/src/Plugin.Maui.OCR/Abstractions/IOcrService.cs index d9b5fe2..d997197 100644 --- a/src/Plugin.Maui.OCR/Abstractions/IOcrService.cs +++ b/src/Plugin.Maui.OCR/Abstractions/IOcrService.cs @@ -1,5 +1,18 @@ +using System.Text.RegularExpressions; + namespace Plugin.Maui.OCR; +/// +/// A callback that can be used to provide custom validation for the extracted text. +/// +/// +/// The extracted text to validate. +/// +/// +/// True if the text is valid, otherwise false. +/// +public delegate bool CustomOcrValidationCallback(string extractedText); + /// /// OCR API. /// @@ -56,17 +69,49 @@ public interface IOcrService } /// -/// The options for OCR. +/// A helper class to extract patterns from text and use the custom validation function (if defined). /// -/// The BCP-47 language code -/// True to try and tell the API to be more accurate, otherwise just be fast. -public record OcrOptions(string? Language = null, bool TryHard = false); +public static class OcrPatternMatcher +{ + /// + /// Extracts a pattern from the input text using the provided configuration. + /// + /// + /// The input text to extract the pattern from. + /// + /// + /// The configuration to use for pattern extraction. + /// + /// + /// The extracted pattern, or null if no pattern was found or the pattern failed validation. + /// + public static string? ExtractPattern(string input, OcrPatternConfig config) + { + var regex = new Regex(config.RegexPattern); + var match = regex.Match(input); + + if (match.Success && (config.ValidationFunction == null || config.ValidationFunction(match.Value))) + { + return match.Value; + } + return null; + } +} /// /// Provides data for the RecognitionCompleted event. /// public class OcrCompletedEventArgs : EventArgs { + /// + /// Constructor + /// + /// + /// The result of the OCR operation. + /// + /// + /// Any error message if the OCR operation failed, or empty string otherwise. + /// public OcrCompletedEventArgs(OcrResult? result, string? errorMessage = null) { Result = result; @@ -89,6 +134,109 @@ public OcrCompletedEventArgs(OcrResult? result, string? errorMessage = null) public OcrResult? Result { get; } } +/// +/// The options for OCR. +/// +public class OcrOptions +{ + /// + /// Constructor + /// + /// + /// (Optional) The BCP-47 language code of the language to recognize. + /// + /// + /// (Optional) True to try and tell the API to be more accurate, otherwise just be fast. Default is just to be fast. + /// + /// + /// (Optional) The pattern configurations for OCR. + /// + /// + /// (Optional) A callback that can be used to provide custom validation for the extracted text. + /// + public OcrOptions(string? language = null, bool tryHard = false, List? patternConfigs = null, CustomOcrValidationCallback? customCallback = null) + { + Language = language; + TryHard = tryHard; + PatternConfigs = patternConfigs; + CustomCallback = customCallback; + } + + /// + /// Constructor + /// + /// + /// (Optional) The BCP-47 language code of the language to recognize. + /// + /// + /// (Optional) True to try and tell the API to be more accurate, otherwise just be fast. Default is just to be fast. + /// + /// + /// (Optional) The pattern configuration for OCR. + /// + /// + /// (Optional) A callback that can be used to provide custom validation for the extracted text. + /// + public OcrOptions(string? language = null, bool tryHard = false, OcrPatternConfig? patternConfig = null, CustomOcrValidationCallback? customCallback = null) + { + Language = language; + TryHard = tryHard; + PatternConfigs = new List { patternConfig }; + CustomCallback = customCallback; + } + + /// + /// A callback that can be used to provide custom validation for the extracted text. + /// + public CustomOcrValidationCallback? CustomCallback { get; set; } + + /// + /// The BCP-47 language code of the language to recognize. + /// + public string? Language { get; set; } + + /// + /// The pattern configurations for OCR. + /// + public List? PatternConfigs { get; set; } + + /// + /// True to try and tell the API to be more accurate, otherwise just be fast. + /// + public bool TryHard { get; set; } +} + +/// +/// Configuration for OCR patterns. +/// +public class OcrPatternConfig +{ + /// + /// Constructor + /// + /// + /// The regex pattern to match. + /// + /// + /// If provided, the extracted text will be validated against this function. + /// + public OcrPatternConfig(string regexPattern, Func validationFunction = null) + { + RegexPattern = regexPattern; + ValidationFunction = validationFunction; + } + + /// + /// The regex pattern to match. + /// + public string RegexPattern { get; set; } + + /// + /// If provided, the extracted text will be validated against this function. + /// + public Func ValidationFunction { get; set; } +} + /// /// The result of an OCR operation. /// @@ -109,6 +257,11 @@ public class OcrResult /// public IList Lines { get; set; } = new List(); + /// + /// The matched values of the OCR result. + /// + public IList MatchedValues { get; set; } = new List(); + /// /// Was the OCR successful? /// diff --git a/src/Plugin.Maui.OCR/OcrImplementation.android.cs b/src/Plugin.Maui.OCR/OcrImplementation.android.cs index fe8376d..b09eb56 100644 --- a/src/Plugin.Maui.OCR/OcrImplementation.android.cs +++ b/src/Plugin.Maui.OCR/OcrImplementation.android.cs @@ -19,7 +19,7 @@ internal class OcrImplementation : IOcrService public IReadOnlyCollection SupportedLanguages => throw new NotImplementedException(); - public static OcrResult ProcessOcrResult(Java.Lang.Object result) + public static OcrResult ProcessOcrResult(Java.Lang.Object result, OcrOptions options) { var ocrResult = new OcrResult(); var textResult = (Text)result; @@ -45,6 +45,21 @@ public static OcrResult ProcessOcrResult(Java.Lang.Object result) } } } + + if (options.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(ocrResult.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + ocrResult.MatchedValues.Add(match); + } + } + } + + options.CustomCallback?.Invoke(ocrResult.AllText); + ocrResult.Success = true; return ocrResult; } @@ -70,7 +85,7 @@ public Task InitAsync(System.Threading.CancellationToken ct = default) /// The OCR result public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, System.Threading.CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(TryHard: tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(tryHard: tryHard, patternConfig: null), ct); } public async Task RecognizeTextAsync(byte[] imageData, OcrOptions options, System.Threading.CancellationToken ct = default) @@ -103,7 +118,7 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt // Try to perform the OCR operation. We should be installing the model necessary when this app is installed, but just in case .. var processImageTask = ToAwaitableTask(textScanner.Process(srcImage).AddOnSuccessListener(new OnSuccessListener()).AddOnFailureListener(new OnFailureListener())); var result = await processImageTask; - return ProcessOcrResult(result); + return ProcessOcrResult(result, options); } catch (MlKitException ex) when ((ex.Message ?? string.Empty).Contains("Waiting for the text optional module to be downloaded")) { @@ -158,7 +173,7 @@ public async Task StartRecognizeTextAsync(byte[] imageData, OcrOptions options, } // Try to perform the OCR operation. We should be installing the model necessary when this app is installed, but just in case .. - var result = ProcessOcrResult(await ToAwaitableTask(textScanner.Process(srcImage).AddOnSuccessListener(new OnSuccessListener()).AddOnFailureListener(new OnFailureListener()))); + var result = ProcessOcrResult(await ToAwaitableTask(textScanner.Process(srcImage).AddOnSuccessListener(new OnSuccessListener()).AddOnFailureListener(new OnFailureListener())), options); RecognitionCompleted?.Invoke(this, new OcrCompletedEventArgs(result, null)); } catch (MlKitException ex) when ((ex.Message ?? string.Empty).Contains("Waiting for the text optional module to be downloaded")) diff --git a/src/Plugin.Maui.OCR/OcrImplementation.macios.cs b/src/Plugin.Maui.OCR/OcrImplementation.macios.cs index a480e79..5c1949e 100644 --- a/src/Plugin.Maui.OCR/OcrImplementation.macios.cs +++ b/src/Plugin.Maui.OCR/OcrImplementation.macios.cs @@ -53,7 +53,7 @@ public Task InitAsync(CancellationToken ct = default) return Task.CompletedTask; } - private static OcrResult ProcessRecognitionResults(VNRequest request, CGSize imageSize) + private static OcrResult ProcessOcrResult(VNRequest request, CGSize imageSize, OcrOptions options) { var ocrResult = new OcrResult(); @@ -106,6 +106,20 @@ private static OcrResult ProcessRecognitionResults(VNRequest request, CGSize ima } } + if (options?.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(ocrResult.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + ocrResult.MatchedValues.Add(match); + } + } + } + + options?.CustomCallback?.Invoke(ocrResult.AllText); + ocrResult.Success = true; return ocrResult; } @@ -148,7 +162,7 @@ private static Point NormalizePoint(CGPoint point, CGSize imageSize) /// public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(TryHard: tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(tryHard: tryHard, patternConfig: null), ct); } /// @@ -220,7 +234,7 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt return; } - var result = ProcessRecognitionResults(request, imageSize); + var result = ProcessOcrResult(request, imageSize, options); tcs.TrySetResult(result); }); @@ -370,7 +384,7 @@ public Task StartRecognizeTextAsync(byte[] imageData, OcrOptions options, Cancel try { - var result = ProcessRecognitionResults(request, imageSize); + var result = ProcessOcrResult(request, imageSize, options); RecognitionCompleted?.Invoke(this, new OcrCompletedEventArgs(result, null)); } catch (Exception ex) diff --git a/src/Plugin.Maui.OCR/OcrImplementation.net.cs b/src/Plugin.Maui.OCR/OcrImplementation.net.cs index 2e5f7c7..700a323 100644 --- a/src/Plugin.Maui.OCR/OcrImplementation.net.cs +++ b/src/Plugin.Maui.OCR/OcrImplementation.net.cs @@ -25,7 +25,7 @@ public Task InitAsync(CancellationToken ct = default) /// The OCR result public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(null, tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(null, tryHard, patternConfig: null), ct); } public Task RecognizeTextAsync(byte[] imageData, OcrOptions options, CancellationToken ct = default) diff --git a/src/Plugin.Maui.OCR/OcrImplementation.windows.cs b/src/Plugin.Maui.OCR/OcrImplementation.windows.cs index a1f1546..51797f9 100644 --- a/src/Plugin.Maui.OCR/OcrImplementation.windows.cs +++ b/src/Plugin.Maui.OCR/OcrImplementation.windows.cs @@ -33,7 +33,7 @@ public Task InitAsync(CancellationToken ct = default) /// The OCR result public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(null, tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(null, tryHard: tryHard, patternConfig: null), ct); } public async Task RecognizeTextAsync(byte[] imageData, OcrOptions options, CancellationToken ct = default) @@ -52,8 +52,12 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt var decoder = await BitmapDecoder.CreateAsync(stream); var softwareBitmap = await decoder.GetSoftwareBitmapAsync(); - var ocrResult = await ocrEngine.RecognizeAsync(softwareBitmap); + var result = await ocrEngine.RecognizeAsync(softwareBitmap); + return ProcessOcrResult(result, options); + } + private static OcrResult ProcessOcrResult(Windows.Media.Ocr.OcrResult ocrResult, OcrOptions options) + { var result = new OcrResult { AllText = ocrResult.Text, @@ -78,6 +82,20 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt } } + if (options.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(result.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + result.MatchedValues.Add(match); + } + } + } + + options.CustomCallback?.Invoke(result.AllText); + return result; } diff --git a/src/Plugin.Xamarin.OCR/Abstractions/IOcrService.cs b/src/Plugin.Xamarin.OCR/Abstractions/IOcrService.cs index e6f3c5c..829b0bc 100644 --- a/src/Plugin.Xamarin.OCR/Abstractions/IOcrService.cs +++ b/src/Plugin.Xamarin.OCR/Abstractions/IOcrService.cs @@ -1,10 +1,22 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Plugin.Xamarin.OCR; +/// +/// A callback that can be used to provide custom validation for the extracted text. +/// +/// +/// The extracted text to validate. +/// +/// +/// True if the text is valid, otherwise false. +/// +public delegate bool CustomOcrValidationCallback(string extractedText); + /// /// OCR API. /// @@ -58,6 +70,36 @@ public interface IOcrService Task StartRecognizeTextAsync(byte[] imageData, OcrOptions options, CancellationToken ct = default); } +/// +/// A helper class to extract patterns from text and use the custom validation function (if defined). +/// +public static class OcrPatternMatcher +{ + /// + /// Extracts a pattern from the input text using the provided configuration. + /// + /// + /// The input text to extract the pattern from. + /// + /// + /// The configuration to use for pattern extraction. + /// + /// + /// The extracted pattern, or null if no pattern was found or the pattern failed validation. + /// + public static string? ExtractPattern(string input, OcrPatternConfig config) + { + var regex = new Regex(config.RegexPattern); + var match = regex.Match(input); + + if (match.Success && (config.ValidationFunction == null || config.ValidationFunction(match.Value))) + { + return match.Value; + } + return null; + } +} + /// /// Provides data for the RecognitionCompleted event. /// @@ -82,15 +124,104 @@ public class OcrCompletedEventArgs(OcrResult? result, string? errorMessage = nul /// /// The options for OCR. /// -/// -/// Constructor -/// -/// The BCP-47 language code -/// True to try and tell the API to be more accurate, otherwise just be fast. -public class OcrOptions(string? language = null, bool tryHard = false) +public class OcrOptions +{ + /// + /// Constructor + /// + /// + /// (Optional) The BCP-47 language code of the language to recognize. + /// + /// + /// (Optional) True to try and tell the API to be more accurate, otherwise just be fast. Default is just to be fast. + /// + /// + /// (Optional) The pattern configurations for OCR. + /// + /// + /// (Optional) A callback that can be used to provide custom validation for the extracted text. + /// + public OcrOptions(string? language = null, bool tryHard = false, List? patternConfigs = null, CustomOcrValidationCallback? customCallback = null) + { + Language = language; + TryHard = tryHard; + PatternConfigs = patternConfigs; + CustomCallback = customCallback; + } + + /// + /// Constructor + /// + /// + /// (Optional) The BCP-47 language code of the language to recognize. + /// + /// + /// (Optional) True to try and tell the API to be more accurate, otherwise just be fast. Default is just to be fast. + /// + /// + /// (Optional) The pattern configuration for OCR. + /// + /// + /// (Optional) A callback that can be used to provide custom validation for the extracted text. + /// + public OcrOptions(string? language = null, bool tryHard = false, OcrPatternConfig? patternConfig = null, CustomOcrValidationCallback? customCallback = null) + { + Language = language; + TryHard = tryHard; + PatternConfigs = new List { patternConfig }; + CustomCallback = customCallback; + } + + /// + /// A callback that can be used to provide custom validation for the extracted text. + /// + public CustomOcrValidationCallback? CustomCallback { get; set; } + + /// + /// The BCP-47 language code of the language to recognize. + /// + public string? Language { get; set; } + + /// + /// The pattern configurations for OCR. + /// + public List? PatternConfigs { get; set; } + + /// + /// True to try and tell the API to be more accurate, otherwise just be fast. + /// + public bool TryHard { get; set; } +} + +/// +/// Configuration for OCR patterns. +/// +public class OcrPatternConfig { - public string? Language { get; } = language; - public bool TryHard { get; } = tryHard; + /// + /// Constructor + /// + /// + /// The regex pattern to match. + /// + /// + /// If provided, the extracted text will be validated against this function. + /// + public OcrPatternConfig(string regexPattern, Func validationFunction = null) + { + RegexPattern = regexPattern; + ValidationFunction = validationFunction; + } + + /// + /// The regex pattern to match. + /// + public string RegexPattern { get; set; } + + /// + /// If provided, the extracted text will be validated against this function. + /// + public Func ValidationFunction { get; set; } } /// @@ -113,6 +244,11 @@ public class OcrResult /// public IList Lines { get; set; } = new List(); + /// + /// The matched values of the OCR result. + /// + public IList MatchedValues { get; set; } = new List(); + /// /// Was the OCR successful? /// diff --git a/src/Plugin.Xamarin.OCR/Platforms/Android/OcrImplementation.cs b/src/Plugin.Xamarin.OCR/Platforms/Android/OcrImplementation.cs index 9624a2b..48d491f 100644 --- a/src/Plugin.Xamarin.OCR/Platforms/Android/OcrImplementation.cs +++ b/src/Plugin.Xamarin.OCR/Platforms/Android/OcrImplementation.cs @@ -22,7 +22,7 @@ public class OcrImplementation : IOcrService public event EventHandler RecognitionCompleted; - public static OcrResult ProcessOcrResult(Java.Lang.Object result) + public static OcrResult ProcessOcrResult(Java.Lang.Object result, OcrOptions? options = null) { var ocrResult = new OcrResult(); var textResult = (Text)result; @@ -48,6 +48,21 @@ public static OcrResult ProcessOcrResult(Java.Lang.Object result) } } } + + if (options?.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(ocrResult.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + ocrResult.MatchedValues.Add(match); + } + } + } + + options?.CustomCallback?.Invoke(ocrResult.AllText); + ocrResult.Success = true; return ocrResult; } @@ -73,7 +88,7 @@ public Task InitAsync(System.Threading.CancellationToken ct = default) /// The OCR result public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, System.Threading.CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(tryHard: tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(tryHard: tryHard, patternConfig: null), ct); } public async Task RecognizeTextAsync(byte[] imageData, OcrOptions options, System.Threading.CancellationToken ct = default) diff --git a/src/Plugin.Xamarin.OCR/Platforms/Apple/OcrImplementation.cs b/src/Plugin.Xamarin.OCR/Platforms/Apple/OcrImplementation.cs index 46e4457..a300d06 100644 --- a/src/Plugin.Xamarin.OCR/Platforms/Apple/OcrImplementation.cs +++ b/src/Plugin.Xamarin.OCR/Platforms/Apple/OcrImplementation.cs @@ -165,7 +165,7 @@ public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = return; } - var result = ProcessRecognitionResults(request, imageSize); + var result = ProcessOcrResult(request, imageSize); tcs.TrySetResult(result); }); @@ -275,7 +275,7 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt return; } - var result = ProcessRecognitionResults(request, imageSize); + var result = ProcessOcrResult(request, imageSize, options); tcs.TrySetResult(result); }); @@ -356,7 +356,7 @@ public Task StartRecognizeTextAsync(byte[] imageData, OcrOptions options, Cancel try { - var result = ProcessRecognitionResults(request, imageSize); + var result = ProcessOcrResult(request, imageSize, options); RecognitionCompleted?.Invoke(this, new OcrCompletedEventArgs(result, null)); } catch (Exception ex) @@ -409,7 +409,7 @@ public Task StartRecognizeTextAsync(byte[] imageData, OcrOptions options, Cancel return Task.CompletedTask; } - private static OcrResult ProcessRecognitionResults(VNRequest request, CGSize imageSize) + private static OcrResult ProcessOcrResult(VNRequest request, CGSize imageSize, OcrOptions? options = null) { var ocrResult = new OcrResult(); @@ -448,7 +448,22 @@ private static OcrResult ProcessRecognitionResults(VNRequest request, CGSize ima } } + if (options?.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(ocrResult.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + ocrResult.MatchedValues.Add(match); + } + } + } + + options.CustomCallback?.Invoke(ocrResult.AllText); + ocrResult.Success = true; + return ocrResult; } diff --git a/src/Plugin.Xamarin.OCR/Platforms/UWP/OcrImplementation.cs b/src/Plugin.Xamarin.OCR/Platforms/UWP/OcrImplementation.cs index 16af3bb..3faa28e 100644 --- a/src/Plugin.Xamarin.OCR/Platforms/UWP/OcrImplementation.cs +++ b/src/Plugin.Xamarin.OCR/Platforms/UWP/OcrImplementation.cs @@ -38,7 +38,7 @@ public Task InitAsync(CancellationToken ct = default) /// The OCR result public async Task RecognizeTextAsync(byte[] imageData, bool tryHard = false, CancellationToken ct = default) { - return await RecognizeTextAsync(imageData, new OcrOptions(null, tryHard), ct); + return await RecognizeTextAsync(imageData, new OcrOptions(tryHard: tryHard, patternConfig: null), ct); } public async Task RecognizeTextAsync(byte[] imageData, OcrOptions options, CancellationToken ct = default) @@ -83,6 +83,20 @@ public async Task RecognizeTextAsync(byte[] imageData, OcrOptions opt } } + if (options.PatternConfigs != null) + { + foreach (var config in options.PatternConfigs) + { + var match = OcrPatternMatcher.ExtractPattern(result.AllText, config); + if (!string.IsNullOrEmpty(match)) + { + result.MatchedValues.Add(match); + } + } + } + + options.CustomCallback?.Invoke(result.AllText); + return result; }