@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.
@@ -0,0 +1,108 @@
1
+ 'use strict'
2
+
3
+ const test = require('brittle')
4
+ const path = require('bare-path')
5
+ const { assessFit } = require('../..')
6
+ const { ensureModelPath, ensureDoctrModels, platform } = require('./utils')
7
+
8
+ const gpuBackend = platform === 'darwin' || platform === 'ios' ? 'metal' : 'vulkan'
9
+
10
+ test('assessFit reads both EasyOCR model descriptions without loading an image', async (t) => {
11
+ const pathDetector = path.resolve(await ensureModelPath('detector_craft'))
12
+ const pathRecognizer = path.resolve(await ensureModelPath('recognizer_latin'))
13
+ const fit = assessFit({ pathDetector, pathRecognizer, backendDevice: 'cpu' })
14
+
15
+ t.ok(['fits', 'does-not-fit', 'error'].includes(fit.status))
16
+ t.is(fit.deviceName, 'CPU')
17
+ t.ok(fit.weightsBytes > 0)
18
+ t.is(fit.hostBytes, fit.weightsBytes)
19
+ t.is(fit.deviceBytes, 0)
20
+ for (const key of [
21
+ 'weightsBytes',
22
+ 'hostBytes',
23
+ 'deviceBytes',
24
+ 'deviceFreeBytes',
25
+ 'deviceTotalBytes'
26
+ ]) {
27
+ t.ok(Number.isSafeInteger(fit[key]) && fit[key] >= 0, `${key} is a non-negative safe integer`)
28
+ }
29
+ t.ok(fit.report.includes('excludes image and inference-graph memory'))
30
+ if (fit.status === 'fits') t.is(fit.reason, 'weights-fit')
31
+ else if (fit.status === 'does-not-fit') t.is(fit.reason, 'source-weights-exceed-memory')
32
+ else t.ok(['uncertain', 'device-memory-unavailable'].includes(fit.reason))
33
+
34
+ const reserved = assessFit({
35
+ pathDetector,
36
+ pathRecognizer,
37
+ backendDevice: 'cpu',
38
+ marginBytes: Number.MAX_SAFE_INTEGER
39
+ })
40
+ t.is(reserved.status, 'error', 'an unusably large margin cannot produce a fit verdict')
41
+ t.ok(['uncertain', 'device-memory-unavailable'].includes(reserved.reason))
42
+ })
43
+
44
+ test('assessFit returns model-unreadable for a missing recognizer', async (t) => {
45
+ const pathDetector = path.resolve(await ensureModelPath('detector_craft'))
46
+ const fit = assessFit({
47
+ pathDetector,
48
+ pathRecognizer: path.join(pathDetector, 'missing-recognizer.gguf')
49
+ })
50
+ t.is(fit.status, 'error')
51
+ t.is(fit.reason, 'model-unreadable')
52
+ })
53
+
54
+ test('assessFit uses the requested gpuDevice when selecting a backend', async (t) => {
55
+ const pathDetector = path.resolve(await ensureModelPath('detector_craft'))
56
+ const pathRecognizer = path.resolve(await ensureModelPath('recognizer_latin'))
57
+ const request = { pathDetector, pathRecognizer, backendDevice: gpuBackend }
58
+ const automatic = assessFit(request)
59
+ const explicit = assessFit({ ...request, gpuDevice: 9999 })
60
+
61
+ t.is(explicit.deviceName, 'CPU', 'out-of-range GPU index falls back to CPU')
62
+ t.is(explicit.deviceBytes, 0)
63
+ if (automatic.deviceName !== 'CPU') {
64
+ t.ok(explicit.deviceName !== automatic.deviceName, 'explicit GPU index changes placement')
65
+ }
66
+ })
67
+
68
+ test('assessFit recognizes actual DocTR detector and CPU-assist placement', async (t) => {
69
+ const models = await ensureDoctrModels()
70
+ if (!models) {
71
+ t.pass('DocTR models are unavailable on this device')
72
+ return
73
+ }
74
+ const pathDetector = path.resolve(models.db_mobilenet_v3_large)
75
+ const pathRecognizer = path.resolve(models.crnn_mobilenet_v3_small)
76
+ const request = {
77
+ pathDetector,
78
+ pathRecognizer,
79
+ pipelineType: 'doctr',
80
+ backendDevice: gpuBackend,
81
+ detectionBackendDevice: gpuBackend,
82
+ recognizerCpuAssist: false
83
+ }
84
+ const singleDevice = assessFit(request)
85
+ t.ok(singleDevice.weightsBytes > 0, 'DocTR model metadata was read')
86
+ t.ok(singleDevice.deviceName, 'DocTR backend was selected')
87
+ t.ok(
88
+ singleDevice.reason !== 'unsupported-config',
89
+ 'matching detector and recognizer devices are supported'
90
+ )
91
+
92
+ if (singleDevice.deviceName === 'CPU') {
93
+ t.pass('GPU unavailable; split-device checks require a GPU')
94
+ return
95
+ }
96
+
97
+ const assisted = assessFit({ ...request, recognizerCpuAssist: true })
98
+ t.is(assisted.status, 'error')
99
+ t.is(assisted.reason, 'unsupported-config', 'GPU recognition with CPU assist is split')
100
+
101
+ const splitDetector = assessFit({
102
+ ...request,
103
+ backendDevice: 'cpu',
104
+ detectionBackendDevice: gpuBackend
105
+ })
106
+ t.is(splitDetector.status, 'error')
107
+ t.is(splitDetector.reason, 'unsupported-config', 'CPU recognition with GPU detection is split')
108
+ })
@@ -101,6 +101,11 @@ async function runErrorHandlingTest (options = {}) { // eslint-disable-line no-u
101
101
  return runIntegrationModule('../integration/error-handling.test.js', options)
102
102
  }
103
103
 
104
+ async function runFitTest (options = {}) { // eslint-disable-line no-unused-vars -- called dynamically by the mobile test runner via string lookup
105
+ if (typeof __shouldRunTest === 'function' && !__shouldRunTest('runFitTest')) return __FILTERED
106
+ return runIntegrationModule('../integration/fit.test.js', options)
107
+ }
108
+
104
109
  async function runFullCoverageTest (options = {}) { // eslint-disable-line no-unused-vars -- called dynamically by the mobile test runner via string lookup
105
110
  if (typeof __shouldRunTest === 'function' && !__shouldRunTest('runFullCoverageTest')) return __FILTERED
106
111
  return runIntegrationModule('../integration/full-coverage.test.js', options)
@@ -12,6 +12,7 @@
12
12
  "runDoctrModelsTest",
13
13
  "runDoctrParamValidationTest",
14
14
  "runErrorHandlingTest",
15
+ "runFitTest",
15
16
  "runFullCoverageTest",
16
17
  "runFullOcrSuiteTest"
17
18
  ],
@@ -48,6 +49,7 @@
48
49
  "runDoctrModelsTest",
49
50
  "runDoctrParamValidationTest",
50
51
  "runErrorHandlingTest",
52
+ "runFitTest",
51
53
  "runFullCoverageTest",
52
54
  "runFullOcrSuiteTest"
53
55
  ],