@@ -68,6 +68,7 @@ def run_ocr_batch(
6868 if use_openvino :
6969 params ["Det.engine_type" ] = EngineType .OPENVINO
7070 params ["Rec.engine_type" ] = EngineType .OPENVINO
71+ params ["Cls.engine_type" ] = EngineType .OPENVINO
7172
7273 rapid = RapidOCR (params = params )
7374
@@ -189,6 +190,8 @@ def run_ocr_full(
189190 "Rec.lang_type" : lang_rec ,
190191 "Rec.ocr_version" : OCRVersion .PPOCRV5 ,
191192 "Rec.rec_batch_num" : 8 ,
193+ # Classifier settings (use same engine even if disabled)
194+ "Cls.engine_type" : engine_type ,
192195 # Global settings
193196 "Global.use_cls" : False ,
194197 "Global.text_score" : text_score ,
@@ -255,10 +258,13 @@ def _create_ocr_engine(
255258 font_path : str = "" ,
256259 threads : int = 4 ,
257260) -> object :
258- """Create a RapidOCR engine with full parameters.
261+ """Create a RapidOCR engine with full parameters and fallback support .
259262
260263 This factory is shared by all OCR modes (single, batch, persistent)
261264 to ensure consistent parameter handling.
265+
266+ Includes fallback logic: if the primary engine fails to initialize,
267+ it will try the alternative engine (OpenVINO ↔ ONNX Runtime).
262268 """
263269 try :
264270 from rapidocr import EngineType , LangRec , OCRVersion , RapidOCR
@@ -281,51 +287,93 @@ def _create_ocr_engine(
281287 }
282288 lang_rec = lang_map .get (language , LangRec .LATIN )
283289
284- engine_type = EngineType .OPENVINO if use_openvino else EngineType .ONNXRUNTIME
290+ def _build_params (engine_type , use_openvino_threads : bool ):
291+ """Build parameter dict for the given engine type."""
292+ p = {
293+ "Det.engine_type" : engine_type ,
294+ "Det.box_thresh" : box_thresh ,
295+ "Det.unclip_ratio" : unclip_ratio ,
296+ "Det.score_mode" : score_mode ,
297+ "Det.limit_side_len" : limit_side_len ,
298+ "Det.limit_type" : "max" ,
299+ "Rec.engine_type" : engine_type ,
300+ "Rec.lang_type" : lang_rec ,
301+ "Rec.ocr_version" : OCRVersion .PPOCRV5 ,
302+ "Rec.rec_batch_num" : 8 ,
303+ # Classifier settings (use same engine even if disabled)
304+ "Cls.engine_type" : engine_type ,
305+ "Global.use_cls" : False ,
306+ "Global.text_score" : text_score ,
307+ "Global.max_side_len" : limit_side_len ,
308+ }
285309
286- params = {
287- "Det.engine_type" : engine_type ,
288- "Det.box_thresh" : box_thresh ,
289- "Det.unclip_ratio" : unclip_ratio ,
290- "Det.score_mode" : score_mode ,
291- "Det.limit_side_len" : limit_side_len ,
292- "Det.limit_type" : "max" ,
293- "Rec.engine_type" : engine_type ,
294- "Rec.lang_type" : lang_rec ,
295- "Rec.ocr_version" : OCRVersion .PPOCRV5 ,
296- "Rec.rec_batch_num" : 8 ,
297- "Global.use_cls" : False ,
298- "Global.text_score" : text_score ,
299- "Global.max_side_len" : limit_side_len ,
300- }
310+ if use_openvino_threads :
311+ p .update (
312+ {
313+ "Det.engine_cfg.inference_num_threads" : threads ,
314+ "Rec.engine_cfg.inference_num_threads" : threads ,
315+ }
316+ )
317+ else :
318+ p .update (
319+ {
320+ "Det.engine_cfg.intra_op_num_threads" : threads ,
321+ "Det.engine_cfg.inter_op_num_threads" : 2 ,
322+ "Rec.engine_cfg.intra_op_num_threads" : threads ,
323+ "Rec.engine_cfg.inter_op_num_threads" : 2 ,
324+ }
325+ )
301326
327+ if rec_model_path :
328+ p ["Rec.model_path" ] = rec_model_path
329+ if rec_keys_path :
330+ p ["Rec.rec_keys_path" ] = rec_keys_path
331+ if det_model_path :
332+ p ["Det.model_path" ] = det_model_path
333+ if font_path and os .path .exists (font_path ):
334+ p ["Global.font_path" ] = font_path
335+
336+ return p
337+
338+ # Determine primary and fallback engine types
302339 if use_openvino :
303- params .update (
304- {
305- "Det.engine_cfg.inference_num_threads" : threads ,
306- "Rec.engine_cfg.inference_num_threads" : threads ,
307- }
308- )
340+ primary_engine = EngineType .OPENVINO
341+ fallback_engine = EngineType .ONNXRUNTIME
342+ primary_name = "OpenVINO"
343+ fallback_name = "ONNX Runtime"
309344 else :
310- params .update (
311- {
312- "Det.engine_cfg.intra_op_num_threads" : threads ,
313- "Det.engine_cfg.inter_op_num_threads" : 2 ,
314- "Rec.engine_cfg.intra_op_num_threads" : threads ,
315- "Rec.engine_cfg.inter_op_num_threads" : 2 ,
316- }
317- )
318-
319- if rec_model_path :
320- params ["Rec.model_path" ] = rec_model_path
321- if rec_keys_path :
322- params ["Rec.rec_keys_path" ] = rec_keys_path
323- if det_model_path :
324- params ["Det.model_path" ] = det_model_path
325- if font_path and os .path .exists (font_path ):
326- params ["Global.font_path" ] = font_path
345+ primary_engine = EngineType .ONNXRUNTIME
346+ fallback_engine = EngineType .OPENVINO
347+ primary_name = "ONNX Runtime"
348+ fallback_name = "OpenVINO"
327349
328- return RapidOCR (params = params )
350+ # Try primary engine first
351+ try :
352+ params = _build_params (primary_engine , use_openvino )
353+ return RapidOCR (params = params )
354+ except Exception as primary_err :
355+ primary_msg = str (primary_err ).lower ()
356+ # Check if this is a "not installed" error that warrants fallback
357+ if "not installed" in primary_msg or "no module" in primary_msg or "import" in primary_msg :
358+ import sys
359+
360+ print (f"[OCR Worker] { primary_name } failed: { primary_err } " , file = sys .stderr )
361+ print (f"[OCR Worker] Trying fallback to { fallback_name } ..." , file = sys .stderr )
362+
363+ # Try fallback engine
364+ try :
365+ params = _build_params (fallback_engine , not use_openvino )
366+ return RapidOCR (params = params )
367+ except Exception as fallback_err :
368+ # Both engines failed
369+ raise RuntimeError (
370+ f"Both OCR engines failed.\n "
371+ f"{ primary_name } : { primary_err } \n "
372+ f"{ fallback_name } : { fallback_err } "
373+ ) from fallback_err
374+ else :
375+ # Not an installation error, re-raise
376+ raise
329377
330378
331379def _ocr_single_image (
0 commit comments