11// SPDX-License-Identifier: GPL-3.0-or-later
22
33
4+ using System . Collections . Concurrent ;
45using System . Diagnostics ;
56using System . Diagnostics . CodeAnalysis ;
67using System . Reflection ;
@@ -992,126 +993,109 @@ private static OcrResponse MapToResponse(OcrResult result, Image visualImage)
992993#endif
993994
994995 public static async Task < OcrResponse > OCRImageDetailed (
995- Image ? image = null ,
996- string ? filePath = null ,
997- TaskSettings ? taskSettings = null ,
998- string ? languageCode = null ,
999- IProgress < OCRProgress > ? progress = null ,
1000- CancellationToken cts = default
1001- )
1002-
996+ Image ? image = null ,
997+ string ? filePath = null ,
998+ TaskSettings ? taskSettings = null ,
999+ string ? languageCode = null ,
1000+ IProgress < OCRProgress > ? progress = null ,
1001+ CancellationToken cts = default
1002+ )
10031003 {
10041004#if DISABLE_OCR
1005- DebugHelper . WriteException (
1006-
1007- new Exception ( "This build of SnapX was built with DISABLE_OCR build time constant." )
1008-
1009- ) ;
1010-
1011- return new OcrResponse { FullText = "OCR Disabled in this build." } ;
1012-
1005+ DebugHelper . WriteException ( new Exception ( "This build of SnapX was built with DISABLE_OCR build time constant." ) ) ;
1006+ return new OcrResponse { FullText = "OCR Disabled in this build." } ;
10131007#else
1014-
1015- progress ? . Report (
1016- new OCRProgress (
1017- 10 ,
1018- $ "Loading model for { languageCode } ... (This may take awhile, I MAY download model files from internet)"
1019- )
1020- ) ;
1021-
1022- await Task . Yield ( ) ;
1023-
1024- progress ? . Report ( new OCRProgress ( 40 , "Running OCR inference..." ) ) ;
1025-
1026- await Task . Yield ( ) ;
1027-
1008+ progress ? . Report ( new ( 5 , "Initializing OCR engine..." ) ) ;
10281009
10291010 var imageConfig = Configuration . Default ;
1030-
10311011 imageConfig . ImageFormatsManager . SetEncoder ( AVIFFormat . Instance , AVIFEncoder . Instance ) ;
1032-
10331012 imageConfig . ImageFormatsManager . SetDecoder ( AVIFFormat . Instance , AVIFDecoder . Instance ) ;
1034-
1035- imageConfig . ImageFormatsManager . AddImageFormatDetector (
1036- new PatchedAVIFImageFormatDetector ( )
1037- ) ;
1038-
1013+ imageConfig . ImageFormatsManager . AddImageFormatDetector ( new PatchedAVIFImageFormatDetector ( ) ) ;
10391014
10401015 try
1041-
10421016 {
10431017 if ( filePath is not null && image is null )
1044-
10451018 {
1046- progress ? . Report ( new OCRProgress ( 45 , "Reading image from disk..." ) ) ;
1047-
1048- await Task . Yield ( ) ;
1049-
1019+ progress ? . Report ( new ( 10 , "Reading image from disk..." ) ) ;
10501020 image = await Image . LoadAsync ( filePath , cts ) ;
10511021 }
10521022 }
1053-
10541023 catch ( Exception ex )
1055-
10561024 {
10571025 var issue = "Failed to load image for OCR" ;
1058-
10591026 DebugHelper . Logger ? . Warning ( issue ) ;
1060-
10611027 DebugHelper . WriteException ( ex ) ;
1062-
1063- return new OcrResponse
1064- {
1065- FullText = issue + Environment . NewLine + ex . Message
1066- } ;
1028+ return new OcrResponse { FullText = $ "{ issue } { Environment . NewLine } { ex . Message } " } ;
10671029 }
10681030
1069- if ( image is null )
1070-
1071- return new OcrResponse
1072- {
1073- FullText = "SNAPX ERROR: PASSED NULL IMAGE AND NULL FILEPATH."
1074- } ;
1031+ if ( image is null ) return new OcrResponse { FullText = "SNAPX ERROR: PASSED NULL IMAGE AND NULL FILEPATH." } ;
10751032
1076- progress ? . Report ( new OCRProgress ( 55 , "Preprocessing image for Paddle..." ) ) ;
1033+ var modelDir = Path . Combine ( BaseDirectory . CacheHome , SnapX . AppName , "PaddleOCRModels" ) ;
1034+ var model = await GetModelForLanguage ( languageCode ?? "eng" ) ;
1035+ var ocrEngine = new RapidOcr ( ) ;
10771036
1078- await Task . Yield ( ) ;
1037+ var progressValue = 15 ;
1038+ var fileUrls = new ConcurrentDictionary < string , int > ( ) ;
1039+ var fileProgressValues = new ConcurrentDictionary < string , int > ( ) ;
10791040
1080- DebugHelper . WriteLine ( filePath ) ;
1041+ EventHandler < HttpProgressEventArgs > progressHandler = async ( sender , args ) =>
1042+ {
1043+ var url = args . UserState as string ;
1044+ if ( string . IsNullOrEmpty ( url ) ) return ;
1045+ if ( ! url . Contains ( "PP-OCR" , StringComparison . OrdinalIgnoreCase ) &&
1046+ ! url . Contains ( "ppocr" , StringComparison . OrdinalIgnoreCase ) ) return ;
10811047
1048+ fileUrls . TryAdd ( url , fileUrls . Count ) ;
10821049
1083- progress ? . Report ( new OCRProgress ( 65 , "Initializing Paddle Inference device..." ) ) ;
1050+ var fileIndex = fileUrls [ url ] ;
1051+ var fileCount = Math . Max ( 1 , fileUrls . Count ) ;
1052+ var perFileRange = 70 / fileCount ;
10841053
1085- await Task . Yield ( ) ;
1054+ var fileStartPct = 15 + ( fileIndex * perFileRange ) ;
1055+ var fileEndPct = fileStartPct + perFileRange ;
10861056
1087- DebugHelper . WriteLine ( $ "OCR image bytes: ??") ;
1057+ var currentFileProgress = fileStartPct + ( int ) ( ( args . ProgressPercentage / 100.0 ) * ( fileEndPct - fileStartPct ) ) ;
1058+ var lastFileProgress = fileProgressValues . GetOrAdd ( url , 0 ) ;
10881059
1089- progress ? . Report ( new OCRProgress ( 75 , "Performing Text Detection & Recognition..." ) ) ;
1060+ var uri = new Uri ( url ) ;
1061+ var query = System . Web . HttpUtility . ParseQueryString ( uri . Query ) ;
1062+ var filename = query [ "filename" ] ?? query [ "name" ] ?? query [ "response-content-disposition" ] ;
10901063
1091- await Task . Yield ( ) ;
1064+ if ( string . IsNullOrEmpty ( filename ) ) filename = Path . GetFileName ( uri . LocalPath ) ;
1065+ if ( ! string . IsNullOrEmpty ( filename ) && filename . Contains ( "filename=" ) )
1066+ filename = filename . Split ( "filename=" ) . Last ( ) . Trim ( '"' ) ;
10921067
1093- var ocrEngine = new RapidOcr ( ) ;
1094- var modelDir = Path . Combine (
1068+ filename ??= "model.onnx" ;
10951069
1096- BaseDirectory . CacheHome ,
1070+ var displayProgress = Math . Max ( progressValue , currentFileProgress ) ;
1071+ progress ? . Report ( new ( displayProgress , $ "Downloading { filename } ({ args . ProgressPercentage } %)...") ) ;
10971072
1098- SnapX . AppName ,
1073+ if ( currentFileProgress > lastFileProgress )
1074+ {
1075+ fileProgressValues [ url ] = currentFileProgress ;
1076+ if ( currentFileProgress > progressValue ) progressValue = currentFileProgress ;
1077+ }
1078+ await Task . Yield ( ) ;
1079+ } ;
10991080
1100- "PaddleOCRModels"
1081+ if ( HttpClientFactory . _ph != null ) HttpClientFactory . _ph . HttpReceiveProgress += progressHandler ;
11011082
1102- ) ;
1103- var model = await GetModelForLanguage ( languageCode ?? "eng" ) ;
1083+ try
1084+ {
1085+ progress ? . Report ( new ( 15 , "Checking and loading OCR models..." ) ) ;
1086+ await ocrEngine . LoadModelAsync ( model , modelDir , HttpClientFactory . Get ( ) , ct : cts ) ;
1087+ }
1088+ finally
1089+ {
1090+ if ( HttpClientFactory . _ph != null ) HttpClientFactory . _ph . HttpReceiveProgress -= progressHandler ;
1091+ }
11041092
1105- await ocrEngine . LoadModelAsync ( model , modelDir , HttpClientFactory . Get ( ) , ct : cts ) ;
1093+ progress ? . Report ( new ( 85 , "Preprocessing image for Paddle..." ) ) ;
11061094 var originalLongSide = Math . Max ( image . Width , image . Height ) ;
11071095 var targetLimit = 1504 ;
1096+ var finalResize = originalLongSide < targetLimit ? ( int ) ( Math . Ceiling ( originalLongSide / 32.0 ) * 32 ) : targetLimit ;
11081097
1109- // This logic ensures we don't upscale tiny images,
1110- // but we still snap the result to a multiple of 32.
1111- var finalResize = originalLongSide < targetLimit
1112- ? ( int ) ( Math . Ceiling ( originalLongSide / 32.0 ) * 32 )
1113- : targetLimit ;
1114-
1098+ progress ? . Report ( new ( 90 , "Performing Text Detection & Recognition..." ) ) ;
11151099 var ocrResult = ocrEngine . Detect ( image , new RapidOcrOptions
11161100 {
11171101 BoxScoreThresh = 0.5f ,
@@ -1120,31 +1104,17 @@ public static async Task<OcrResponse> OCRImageDetailed(
11201104 MostAngle = true ,
11211105 UnClipRatio = 1.6f ,
11221106 ImgResize = finalResize ,
1123- Padding = 10 , // Reduced to keep text from getting lost in margins
1107+ Padding = 10 ,
11241108 } ) ;
1125- var visualDebugImage = image . Clone ( _ => { } ) ;
1126-
11271109
1110+ var visualDebugImage = image . Clone ( _ => { } ) ;
11281111 foreach ( var block in ocrResult . TextBlocks )
1129-
11301112 {
1131- DebugHelper . WriteLine ( block . ToString ( ) ) ;
1132-
1133- var points = block . BoxPoints ;
1134-
1135-
1136- visualDebugImage . Mutate ( ctx => { ctx . DrawPolygon ( Color . Red , 4f , points ) ; } ) ;
1113+ visualDebugImage . Mutate ( ctx => { ctx . DrawPolygon ( Color . Red , 4f , block . BoxPoints ) ; } ) ;
11371114 }
11381115
1139-
1140- progress ? . Report ( new OCRProgress ( 95 , "Finalizing results..." ) ) ;
1141-
1142- await Task . Yield ( ) ;
1143-
1144- DebugHelper . WriteAlways ( "Detected all texts: \n " + ocrResult . StrRes ) ;
1145-
1116+ progress ? . Report ( new ( 98 , "Finalizing results..." ) ) ;
11461117 return MapToResponse ( ocrResult , visualDebugImage ) ;
1147-
11481118#endif
11491119 }
11501120
0 commit comments