UNPKG

ppu-paddle-ocr

Version:

Lightweight, probably the fastest PaddleOCR SDK in TypeScript. Runs anywhere JavaScript runs: Node.js, Bun, Deno, mobile react-native, web browsers, and browser extensions. Docker & CLI supported. The official SDK is browser-only. Accurate text detection

5 lines 8.44 kB
import{existsSync,readFileSync,rmSync}from"fs";import*as ort from"onnxruntime-node";import*as path from"path";import{ImageProcessor}from"ppu-ocv";import{CanvasProcessor}from"ppu-ocv/canvas";import{BasePaddleOcrService}from"../core/base-paddle-ocr.service.js";import{globalImageCache,ImageCache}from"../core/image-cache.js";import{groupRecognitionResultsByLine}from"../core/recognition/result-grouping.js";import{createSessionWithFallback}from"../core/session-factory.js";import{DEFAULT_MODEL_URLS}from"../model-catalogue.js";import{parseDictionary}from"../utils.js";import{DetectionService}from"./detection.service.js";import{CACHE_DIR,fetchAndCacheResource}from"./model-cache.js";import{NodePlatformProvider}from"./platform.node.js";import{RecognitionService}from"./recognition.service.js";export class PaddleOcrService extends BasePaddleOcrService{constructor(options){super(new NodePlatformProvider,options)}async _fetchAndCache(url){return fetchAndCacheResource(url,this.options.debugging?.verbose)}async _loadResource(source,defaultUrl){if(source instanceof ArrayBuffer){this.log("Loading resource from ArrayBuffer");return source}if(typeof source==="string"){if(source.startsWith("http")){return this._fetchAndCache(source)}else{let resolvedPath=path.resolve(process.cwd(),source);this.log(`Loading resource from path: ${resolvedPath}`);let buf=readFileSync(resolvedPath);return buf.buffer.slice(buf.byteOffset,buf.byteOffset+buf.byteLength)}}return this._fetchAndCache(defaultUrl)}async initSessions(){throw new Error("Initialization is handled proactively in PaddleOcrService. Call initialize() instead.")}async initialize(){if(this.isInitialized()){this.log("PaddleOcrService is already initialized.");return}try{this.log("Initializing PaddleOcrService...");let engine=this.options.processing?.engine||"opencv";const[detModelBuffer,recModelBuffer,dictBuffer]=await Promise.all([this._loadResource(this.options.model?.detection,DEFAULT_MODEL_URLS.detection),this._loadResource(this.options.model?.recognition,DEFAULT_MODEL_URLS.recognition),this._loadResource(this.options.model?.charactersDictionary,DEFAULT_MODEL_URLS.charactersDictionary)]);const[detectionSession,recognitionSession]=await Promise.all([createSessionWithFallback(ort,new Uint8Array(detModelBuffer),this.options.session,(msg)=>this.log(msg),(next)=>this.options.session=next),createSessionWithFallback(ort,new Uint8Array(recModelBuffer),this.options.session,(msg)=>this.log(msg),(next)=>this.options.session=next),engine==="opencv"?ImageProcessor.initRuntime():Promise.resolve()]);this.detectionSession=detectionSession;this.recognitionSession=recognitionSession;if(this.options.model)this.options.model.detection=detModelBuffer;if(this.options.model)this.options.model.recognition=recModelBuffer;this.log(`Detection ONNX model loaded successfully input: ${detectionSession.inputNames} output: ${detectionSession.outputNames}`);this.log(`Recognition ONNX model loaded successfully input: ${recognitionSession.inputNames} output: ${recognitionSession.outputNames}`);let charactersDictionary=parseDictionary(dictBuffer);if(charactersDictionary.length===0){throw new Error("Character dictionary is empty or could not be loaded.")}if(this.options.model)this.options.model.charactersDictionary=dictBuffer;if(this.options.recognition)this.options.recognition.charactersDictionary=charactersDictionary;this.log(`Character dictionary loaded with ${charactersDictionary.length} entries.`);this.detector=new DetectionService(detectionSession,this.options.detection,this.options.debugging,engine);this.recognitor=new RecognitionService(recognitionSession,this.options.recognition,this.options.debugging,engine);if(this.options.model)this.options.model.detection=undefined;if(this.options.model)this.options.model.recognition=undefined}catch(error){console.error("Failed to initialize PaddleOcrService:",error);throw error}}isInitialized(){return this.detectionSession!==null&&this.recognitionSession!==null}async changeDetectionModel(model){this.log("Changing detection model...");let modelBuffer=await this._loadResource(model,DEFAULT_MODEL_URLS.detection);await this.detectionSession?.release();this.detectionSession=await createSessionWithFallback(ort,new Uint8Array(modelBuffer),this.options.session,(msg)=>this.log(msg),(next)=>this.options.session=next);this.detector=new DetectionService(this.detectionSession,this.options.detection,this.options.debugging,this.options.processing?.engine||"opencv");if(this.options.model)this.options.model.detection=modelBuffer;this.log("Detection model changed successfully.")}async changeRecognitionModel(model){this.log("Changing recognition model...");let modelBuffer=await this._loadResource(model,DEFAULT_MODEL_URLS.recognition);await this.recognitionSession?.release();this.recognitionSession=await createSessionWithFallback(ort,new Uint8Array(modelBuffer),this.options.session,(msg)=>this.log(msg),(next)=>this.options.session=next);this.recognitor=new RecognitionService(this.recognitionSession,this.options.recognition,this.options.debugging,this.options.processing?.engine||"opencv");if(this.options.model)this.options.model.recognition=modelBuffer;this.log("Recognition model changed successfully.")}async changeTextDictionary(dictionary){this.log("Changing text dictionary...");let dictBuffer=await this._loadResource(dictionary,DEFAULT_MODEL_URLS.charactersDictionary);let charactersDictionary=parseDictionary(dictBuffer);if(charactersDictionary.length===0){throw new Error("Character dictionary is empty or could not be loaded.")}if(this.options.model)this.options.model.charactersDictionary=dictBuffer;if(this.options.recognition)this.options.recognition.charactersDictionary=charactersDictionary;this.log(`Character dictionary changed successfully with ${charactersDictionary.length} entries.`)}async recognize(image,options){if(!this.isInitialized()){throw new Error("PaddleOcrService is not initialized. Call initialize() first.")}let imageBuffer;if(image instanceof ArrayBuffer){imageBuffer=image}else{if(typeof image.toBuffer==="function"){let buffer=image.toBuffer("image/png");imageBuffer=buffer.buffer.slice(buffer.byteOffset,buffer.byteOffset+buffer.byteLength)}else{let ctx=image.getContext("2d");let imageData=ctx.getImageData(0,0,image.width,image.height);let data=imageData.data;imageBuffer=data.buffer.slice(data.byteOffset,data.byteOffset+data.byteLength)}}let cacheKey=ImageCache.generateKey(imageBuffer);let cacheResult=!options?.noCache&&!options?.dictionary?globalImageCache.get(cacheKey):undefined;if(cacheResult){this.log("Using cached OCR result");if(options?.flatten){return{text:cacheResult.text,results:cacheResult.lines?cacheResult.lines.flat():cacheResult.results??[],confidence:cacheResult.confidence}}return cacheResult}let charactersDictionary;if(options?.dictionary){let dictBuffer=await this._loadResource(options.dictionary,"");charactersDictionary=parseDictionary(dictBuffer);if(charactersDictionary.length===0){throw new Error("Custom character dictionary is empty or could not be loaded.")}}let sourceCanvas=image instanceof ArrayBuffer?await CanvasProcessor.prepareCanvas(image):image;let strategy=options?.strategy??this.options.recognition?.strategy??"per-line";let detector=this.detector;let recognitor=this.recognitor;let detection=await detector.run(sourceCanvas);let recognition=await recognitor.run(sourceCanvas,detection,charactersDictionary,strategy);let processed=groupRecognitionResultsByLine(recognition);let result=options?.flatten?{text:processed.text,results:recognition,confidence:processed.confidence}:processed;if(!options?.noCache&&!options?.dictionary){globalImageCache.set(cacheKey,result)}return result}static async downloadModels(options){let verbose=options?.verbose??false;let urls=[DEFAULT_MODEL_URLS.detection,DEFAULT_MODEL_URLS.recognition,DEFAULT_MODEL_URLS.charactersDictionary];for(let url of urls){await fetchAndCacheResource(url,verbose)}}clearModelCache(){if(existsSync(CACHE_DIR)){this.log(`Clearing model cache at: ${CACHE_DIR}`);rmSync(CACHE_DIR,{recursive:true,force:true});console.log(`[PaddleOcrService] Model cache cleared: ${CACHE_DIR}`)}else{this.log("Cache directory does not exist, nothing to clear.")}}async destroy(){await this.detectionSession?.release();await this.recognitionSession?.release();this.detectionSession=null;this.recognitionSession=null;this.detector=null;this.recognitor=null}}export default PaddleOcrService;