@qvac/ocr-ggml 0.27.1 → 0.27.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,6 +100,32 @@ console.log(response.stats) // RuntimeStats (populated when opts.stats: true)
100
100
  await ocr.unload()
101
101
  ```
102
102
 
103
+ ### Check model weight fit
104
+
105
+ `assessFit` reads the detector and recognizer GGUF tensor metadata without
106
+ loading their weights. It checks a conservative weight-load estimate against
107
+ the selected backend's currently free memory. It does **not** assess image
108
+ decoding, detection or recognition graphs, or the number of text regions in a
109
+ future image. A `fits` result therefore covers model weights only; `error`
110
+ means the check could not reach a reliable capacity verdict.
111
+
112
+ ```js
113
+ const { assessFit } = require('@qvac/ocr-ggml')
114
+
115
+ const fit = assessFit({
116
+ pathDetector: '/abs/path/craft_mlt_25k.gguf',
117
+ pathRecognizer: '/abs/path/latin_g2.gguf',
118
+ backendDevice: 'cpu',
119
+ marginBytes: 1024 * 1024 * 1024
120
+ })
121
+ console.log(fit.status, fit.reason, fit.report)
122
+ ```
123
+
124
+ Use the same `pipelineType`, `backendDevice`, `gpuDevice`, and `main-gpu`
125
+ settings intended for the eventual `OcrGgml` load. DocTR configurations that
126
+ place stages on different devices return `error` with `unsupported-config`
127
+ until a multi-device weight estimate is available.
128
+
103
129
  The `doctr` pipeline is language-agnostic, so `langList` can be omitted
104
130
  entirely:
105
131
 
package/fit.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { OcrGgmlParams } from "./index";
2
+ /** Both OCR model files and the backend placement used by load(). */
3
+ export interface OcrFitRequest extends OcrGgmlParams {
4
+ /** Bytes to leave free on the selected backend device. */
5
+ marginBytes?: number;
6
+ }
7
+ export interface OcrFitResult {
8
+ status: "fits" | "does-not-fit" | "error";
9
+ reason: string;
10
+ deviceName: string;
11
+ deviceBytes: number;
12
+ hostBytes: number;
13
+ weightsBytes: number;
14
+ deviceFreeBytes: number;
15
+ deviceTotalBytes: number;
16
+ report: string;
17
+ }
18
+ /**
19
+ * Assess loading the detector and recognizer weights from GGUF metadata.
20
+ * This does not load weights or account for image-dependent inference memory.
21
+ * `error` means no reliable capacity verdict was available.
22
+ */
23
+ export declare function assessFit(request: OcrFitRequest): OcrFitResult;
package/fit.js ADDED
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.assessFit = assessFit
4
+ /* eslint-disable @typescript-eslint/no-require-imports -- bare-path exposes a CommonJS export shape. */
5
+ const path = require('bare-path')
6
+ const backends_dir_1 = require('./lib/backends-dir')
7
+ /**
8
+ * Assess loading the detector and recognizer weights from GGUF metadata.
9
+ * This does not load weights or account for image-dependent inference memory.
10
+ * `error` means no reliable capacity verdict was available.
11
+ */
12
+ function assessFit(request) {
13
+ for (const key of ['pathDetector', 'pathRecognizer']) {
14
+ if (typeof request[key] !== 'string' || !path.isAbsolute(request[key])) {
15
+ throw new TypeError(`${key} must be an absolute path`)
16
+ }
17
+ }
18
+ if (
19
+ request.marginBytes !== undefined &&
20
+ (!Number.isSafeInteger(request.marginBytes) || request.marginBytes < 0)
21
+ ) {
22
+ throw new RangeError('marginBytes must be a non-negative safe integer')
23
+ }
24
+ // Resolve lazily so importing this package never loads the native binding.
25
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- native prebuild is resolved lazily.
26
+ const binding = require('./binding')
27
+ return binding.assessFit({
28
+ ...request,
29
+ backendsDir: request.backendsDir ?? (0, backends_dir_1.resolveBackendsDir)()
30
+ })
31
+ }
package/index.d.ts CHANGED
@@ -2,6 +2,8 @@ import QvacLogger = require("@qvac/logging");
2
2
  import { type QvacResponse } from "@qvac/infer-base";
3
3
  import { OcrGgmlInterface, type BackendInfo, type OcrGgmlRunOptions } from "./ocr-ggml";
4
4
  import { QvacErrorAddonOcrGgml, ERR_CODES } from "./lib/error";
5
+ export type { OcrFitRequest, OcrFitResult } from "./fit";
6
+ export { assessFit } from "./fit";
5
7
  /**
6
8
  * OCR pipeline backing the addon.
7
9
  * - `easyocr` (default): CRAFT detector + CRNN gen-2 recognizer.
package/index.js CHANGED
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ERR_CODES = exports.QvacErrorAddonOcrGgml = exports.OcrGgml = void 0;
3
+ exports.ERR_CODES = exports.QvacErrorAddonOcrGgml = exports.OcrGgml = exports.assessFit = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-require-imports -- Bare modules and @qvac/logging expose CommonJS export shapes. */
5
- const path = require("bare-path");
6
5
  const fs = require("bare-fs");
7
6
  const QvacLogger = require("@qvac/logging");
8
- const fabricBackends = require("@qvac/fabric/backends");
9
7
  /* eslint-enable @typescript-eslint/no-require-imports */
10
8
  const infer_base_1 = require("@qvac/infer-base");
11
9
  const ocr_ggml_1 = require("./ocr-ggml");
@@ -13,6 +11,10 @@ const error_1 = require("./lib/error");
13
11
  Object.defineProperty(exports, "QvacErrorAddonOcrGgml", { enumerable: true, get: function () { return error_1.QvacErrorAddonOcrGgml; } });
14
12
  Object.defineProperty(exports, "ERR_CODES", { enumerable: true, get: function () { return error_1.ERR_CODES; } });
15
13
  const main_gpu_1 = require("./lib/main-gpu");
14
+ const backends_dir_1 = require("./lib/backends-dir");
15
+ const fit_1 = require("./fit");
16
+ var fit_2 = require("./fit");
17
+ Object.defineProperty(exports, "assessFit", { enumerable: true, get: function () { return fit_2.assessFit; } });
16
18
  const DOCTR_INTERNAL_LANG_LIST = ["en"];
17
19
  /**
18
20
  * Native language-validation failure messages (see the EasyOCR pipeline's
@@ -21,22 +23,6 @@ const DOCTR_INTERNAL_LANG_LIST = ["en"];
21
23
  * create-time failures to ERR_CODES.UNSUPPORTED_LANGUAGE.
22
24
  */
23
25
  const NATIVE_LANGUAGE_ERROR = /unsupported languages|only compatible with english/i;
24
- // The ggml compute backends (GGML_BACKEND_DL modules) ship exactly once, next to
25
- // the @qvac/fabric runtime (<root>/<host>/qvac__fabric). We deliberately do not
26
- // copy them into this addon to avoid duplicating tens of MB per fabric
27
- // consumer. On desktop, @qvac/fabric/backends resolves that root in whichever
28
- // package holds the runtime. On mobile the package tree isn't resolvable at
29
- // runtime (the worklet runs from a packed bundle), so fall back to this addon's
30
- // own prebuilds, where the mobile packaging stages the backends. The native side
31
- // appends BACKENDS_SUBDIR ("<host>/qvac__fabric") to whichever root we return.
32
- function resolveBackendsDir() {
33
- // fabric's resolver only checks that the platform package resolves, not that
34
- // its prebuilds are on disk.
35
- const fabricRoot = fabricBackends.resolveBackendsDir();
36
- if (fabricRoot !== null && fs.existsSync(fabricRoot))
37
- return fabricRoot;
38
- return path.join(__dirname, "prebuilds");
39
- }
40
26
  /**
41
27
  * GGML-backed OCR implementation.
42
28
  *
@@ -180,7 +166,7 @@ class OcrGgml {
180
166
  configurationParams.backendsDir =
181
167
  this.params.backendsDir !== undefined
182
168
  ? this.params.backendsDir
183
- : resolveBackendsDir();
169
+ : (0, backends_dir_1.resolveBackendsDir)();
184
170
  this.logger.info("Creating ocr-ggml addon");
185
171
  try {
186
172
  this.addon = this._createAddon(configurationParams);
@@ -421,6 +407,7 @@ class OcrGgml {
421
407
  exports.OcrGgml = OcrGgml;
422
408
  module.exports = {
423
409
  OcrGgml,
410
+ assessFit: fit_1.assessFit,
424
411
  modelClass: OcrGgml,
425
412
  get modelFile() {
426
413
  return require.addon.resolve(".");
@@ -0,0 +1 @@
1
+ export declare function resolveBackendsDir(): string;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveBackendsDir = resolveBackendsDir;
4
+ /* eslint-disable @typescript-eslint/no-require-imports -- Bare modules and fabric expose CommonJS export shapes. */
5
+ const path = require("bare-path");
6
+ const fs = require("bare-fs");
7
+ const fabricBackends = require("@qvac/fabric/backends");
8
+ /* eslint-enable @typescript-eslint/no-require-imports */
9
+ // The ggml backends ship beside @qvac/fabric on desktop. Mobile bundles stage
10
+ // them beside this addon because the package tree is not resolved at runtime.
11
+ function resolveBackendsDir() {
12
+ const fabricRoot = fabricBackends.resolveBackendsDir();
13
+ if (fabricRoot !== null && fs.existsSync(fabricRoot))
14
+ return fabricRoot;
15
+ return path.join(__dirname, "..", "prebuilds");
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qvac/ocr-ggml",
3
- "version": "0.27.1",
3
+ "version": "0.27.3",
4
4
  "description": "GGML-backed OCR addon for qvac (EasyOCR and DocTR pipelines on GGUF weights)",
5
5
  "addon": true,
6
6
  "engines": {
@@ -9,9 +9,9 @@
9
9
  "scripts": {
10
10
  "build": "npm run build:ts && npm run build:native",
11
11
  "build:native": "bare-make generate && bare-make build && bare-make install",
12
- "build:ts": "tsc -p tsconfig.build.json",
12
+ "build:ts": "tsc -p tsconfig.build.json && prettier --write fit.js",
13
13
  "build:pack": "npm run build:ts && mkdir -p dist && npm pack --pack-destination dist",
14
- "check:generated": "npm run build:ts && out=$(git status --porcelain --untracked-files=all -- index.js ocr-ggml.js addonLogging.js index.d.ts ocr-ggml.d.ts addonLogging.d.ts lib) && { [ -z \"$out\" ] || { echo \"$out\"; exit 1; }; }",
14
+ "check:generated": "npm run build:ts && out=$(git status --porcelain --untracked-files=all -- index.js ocr-ggml.js addonLogging.js fit.js index.d.ts ocr-ggml.d.ts addonLogging.d.ts fit.d.ts lib) && { [ -z \"$out\" ] || { echo \"$out\"; exit 1; }; }",
15
15
  "typecheck": "tsc --noEmit -p tsconfig.json",
16
16
  "mobile:copy-prebuilds": "node ./scripts/copy-mobile-test-assets.js",
17
17
  "mobile:generate-urls": "bash ./scripts/generate-ocr-ggml-presigned-urls.sh",
@@ -45,6 +45,8 @@
45
45
  "addonLogging.d.ts",
46
46
  "binding.js",
47
47
  "index.js",
48
+ "fit.js",
49
+ "fit.d.ts",
48
50
  "ocr-ggml.js",
49
51
  "ocr-ggml.d.ts",
50
52
  "prebuilds",
@@ -80,7 +82,7 @@
80
82
  "@qvac/registry-client": "^0.4.0",
81
83
  "@types/node": "^24.2.1",
82
84
  "bare-stream": "^2.7.0",
83
- "bare-subprocess": "^5.2.1",
85
+ "bare-subprocess": "^6.2.1",
84
86
  "cmake-bare": "^1.7.5",
85
87
  "cmake-vcpkg": "^1.1.0",
86
88
  "eslint": "^9.39.4",
@@ -7,7 +7,7 @@
7
7
  ],
8
8
  "current_versions": [
9
9
  {
10
- "version": "0.27.1"
10
+ "version": "0.27.3"
11
11
  }
12
12
  ],
13
13
  "exported_symbols": [
@@ -294,10 +294,10 @@
294
294
  "__ZN2cv3MatC1ERKNSt3__16vectorIiNS1_9allocatorIiEEEEi",
295
295
  "_jpeg_mem_term",
296
296
  "_LogLuv32toXYZ",
297
- "_cvSetSeqBlockSize",
298
297
  "_TIFFFileno",
299
298
  "_cvFastArctan",
300
299
  "__ZN2cv8internal18WriteStructContextD1Ev",
300
+ "_cvSetSeqBlockSize",
301
301
  "_png_write_info_before_PLTE",
302
302
  "_jpeg_quality_scaling",
303
303
  "__ZN2cv11FileStorage4Impl19convertToCollectionEiRNS_8FileNodeE",
@@ -1011,8 +1011,8 @@
1011
1011
  "__ZN2cv6reduceERKNS_11_InputArrayERKNS_12_OutputArrayEiii",
1012
1012
  "__ZNK2cv5MatOp11elementWiseERKNS_7MatExprE",
1013
1013
  "__ZN2cv7sortIdxERKNS_11_InputArrayERKNS_12_OutputArrayEi",
1014
- "_jpeg_consume_input",
1015
1014
  "_cvSetNumThreads",
1015
+ "_jpeg_consume_input",
1016
1016
  "_inflatePrime",
1017
1017
  "_cvInRangeS",
1018
1018
  "_png_convert_from_time_t",
@@ -1467,7 +1467,7 @@
1467
1467
  "__ZNK2cv5MatOp3addERKNS_7MatExprERKNS_7Scalar_IdEERS1_",
1468
1468
  "_png_set_PLTE",
1469
1469
  "__ZN2cv15ImageCollection4Impl7advanceEv",
1470
- "_png_chunk_report",
1470
+ "__ZN23qvac_lib_infer_ocr_ggml7ocr_fit14inspectOcrGgufERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEPKc",
1471
1471
  "__ZN2cvltERKNS_3MatEd",
1472
1472
  "_png_read_png",
1473
1473
  "_png_destroy_info_struct",
@@ -1475,6 +1475,7 @@
1475
1475
  "_TIFFFieldWriteCount",
1476
1476
  "__ZN2cv7ellipseERKNS_17_InputOutputArrayENS_6Point_IiEENS_5Size_IiEEdddRKNS_7Scalar_IdEEiii",
1477
1477
  "_WebPAnimEncoderNewInternal",
1478
+ "_png_chunk_report",
1478
1479
  "_j12init_1pass_quantizer",
1479
1480
  "__ZN2cv3hal6cmp32fEPKfmS2_mPhmiiPv",
1480
1481
  "__ZN2cv6repeatERKNS_11_InputArrayEiiRKNS_12_OutputArrayE",
@@ -2167,6 +2168,7 @@
2167
2168
  "__ZN2cvngERKNS_7MatExprE",
2168
2169
  "_lzma_mf_bt2_find",
2169
2170
  "__ZNK2cv8FileNodecviEv",
2171
+ "__ZN23qvac_lib_infer_ocr_ggml7ocr_fit12assessOcrFitERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_RKNS_9OcrConfigEy",
2170
2172
  "__ZN5doctr4ggml8pipeline22StepDoctrDetectionGGMLC2ERKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEiP19ggml_backend_device",
2171
2173
  "__ZN2cv3Mat5setToERKNS_11_InputArrayES3_",
2172
2174
  "_cvGetRealND",
@@ -2320,8 +2322,8 @@
2320
2322
  "_lzma_free",
2321
2323
  "_cvCreateData",
2322
2324
  "_adler32_combine",
2323
- "_lzma_lzma_decoder_create",
2324
2325
  "__ZNK2cv5MatOp17augAssignMultiplyERKNS_7MatExprERNS_3MatE",
2326
+ "_lzma_lzma_decoder_create",
2325
2327
  "_jpeg_free_large",
2326
2328
  "_png_set_invalid",
2327
2329
  "_TIFFSwabArrayOfShort",
@@ -2370,8 +2372,8 @@
2370
2372
  "_png_calloc",
2371
2373
  "__ZN2cv8parallel14ParallelForAPID2Ev",
2372
2374
  "__ZN2cvmlERKNS_3MatES2_",
2373
- "_lzma_simple_armthumb_decoder_init",
2374
2375
  "_png_get_current_row_number",
2376
+ "_lzma_simple_armthumb_decoder_init",
2375
2377
  "__ZN2cv11RNG_MT19937C2Ev",
2376
2378
  "__ZN2cvdvEdRKNS_3MatE",
2377
2379
  "_TIFFReadRGBAImage",
@@ -2797,9 +2799,9 @@
2797
2799
  "_jinit_lossless_compressor",
2798
2800
  "_lzma_next_filter_update",
2799
2801
  "_LogLuv24fromXYZ",
2802
+ "__ZN2cv8SVDecompERKNS_11_InputArrayERKNS_12_OutputArrayES5_S5_i",
2800
2803
  "_WebPAnimEncoderAssemble",
2801
2804
  "__ZN2cv5utils5trace7details6Region4Impl14registerRegionERNS2_23TraceManagerThreadLocalE",
2802
- "__ZN2cv8SVDecompERKNS_11_InputArrayERKNS_12_OutputArrayES5_S5_i",
2803
2805
  "_cvExp",
2804
2806
  "_cvRawDataToScalar",
2805
2807
  "_png_destroy_read_struct",
@@ -3205,25 +3207,24 @@
3205
3207
  ],
3206
3208
  "weak": [
3207
3209
  "__ZZZN28qvac_lib_inference_addon_cpp16OutputCallBackJs26initializeProcessingThreadENSt3__110shared_ptrINS_11OutputQueueEEEENKUlvE_clEvENUlP11uv_handle_sE_8__invokeES7_",
3210
+ "__ZZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask5startINSt3__19monostateEEEP10js_value_sP8js_env_sNS1_12CallbackDataIT_E4WorkENSB_7MarshalEENUlP11uv_handle_sE_8__invokeESF_",
3211
+ "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask13onCloseHandleINSt3__19monostateEEEvP11uv_handle_s",
3208
3212
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface8activateEP8js_env_sP18js_callback_info_s",
3213
+ "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask13onEnvTeardownINSt3__19monostateEEEvP22js_deferred_teardown_sPv",
3209
3214
  "__ZZN28qvac_lib_inference_addon_cpp6logger8JsLogger22closeAsyncHandleLockedEvENUlP11uv_handle_sE_8__invokeES3_",
3210
3215
  "__ZN23qvac_lib_infer_ocr_ggml14createInstanceEP8js_env_sP18js_callback_info_s",
3211
- "__ZZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask3runEP8js_env_sNSt3__18functionIFvvEEEENUlP11uv_handle_sE_8__invokeES9_",
3212
3216
  "__ZN28qvac_lib_inference_addon_cpp6logger8JsLogger13asyncCallbackEP10uv_async_s",
3213
- "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask13onCloseHandleEP11uv_handle_s",
3214
3217
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface9setLoggerEP8js_env_sP18js_callback_info_s",
3218
+ "__ZN23qvac_lib_infer_ocr_ggml9assessFitEP8js_env_sP18js_callback_info_s",
3215
3219
  "__ZN28qvac_lib_inference_addon_cpp16OutputCallBackJs16jsOutputCallbackEP10uv_async_s",
3216
3220
  "__ZN23qvac_lib_infer_ocr_ggml6runJobEP8js_env_sP18js_callback_info_s",
3217
- "__ZNSt3__116__variant_detail12__visitation6__base12__dispatcherIJLm1EEE10__dispatchB8ne190102IONS1_9__variant15__value_visitorIZZN28qvac_lib_inference_addon_cpp9out_handl27JsRuntimeStatsOutputHandlerC1EvENKUlRKNS_6vectorINS_4pairINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_7variantIJdxEEEEENSG_ISL_EEEEE_clESP_EUlOT_E_EEJRKNS0_6__baseILNS0_6_TraitE0EJdxEEEEEEDcSR_DpT0_",
3218
3221
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface6cancelEP8js_env_sP18js_callback_info_s",
3219
3222
  "__ZZN28qvac_lib_inference_addon_cpp16OutputCallBackJs16drainAndTeardownEPNS0_5StateEbbENUlP11uv_handle_sE_8__invokeES4_",
3220
- "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask10onCompleteEP10uv_async_s",
3223
+ "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask10onCompleteINSt3__19monostateEEEvP10uv_async_s",
3221
3224
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface15destroyInstanceEP8js_env_sP18js_callback_info_s",
3222
- "__ZNSt3__116__variant_detail12__visitation6__base12__dispatcherIJLm0EEE10__dispatchB8ne190102IONS1_9__variant15__value_visitorIZZN28qvac_lib_inference_addon_cpp9out_handl27JsRuntimeStatsOutputHandlerC1EvENKUlRKNS_6vectorINS_4pairINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_7variantIJdxEEEEENSG_ISL_EEEEE_clESP_EUlOT_E_EEJRKNS0_6__baseILNS0_6_TraitE0EJdxEEEEEEDcSR_DpT0_",
3223
3225
  "__ZN28qvac_lib_inference_addon_cpp6logger8JsLogger13onEnvTeardownEPv",
3224
3226
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface11loadWeightsEP8js_env_sP18js_callback_info_s",
3225
3227
  "__ZN28qvac_lib_inference_addon_cpp16OutputCallBackJs13onEnvTeardownEPv",
3226
- "__ZN28qvac_lib_inference_addon_cpp2js11JsAsyncTask13onEnvTeardownEP22js_deferred_teardown_sPv",
3227
3228
  "__ZN28qvac_lib_inference_addon_cpp11JsInterface13releaseLoggerEP8js_env_sP18js_callback_info_s",
3228
3229
  "__ZN23qvac_lib_infer_ocr_ggml14getBackendInfoEP8js_env_sP18js_callback_info_s"
3229
3230
  ]