tryharness 0.7.23 → 0.7.26

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.
Files changed (61) hide show
  1. package/assets/opencode-plugin.js +48 -11
  2. package/assets/pi-extension.ts +105 -26
  3. package/cli.mjs +1238 -322
  4. package/lib/activity.mjs +197 -40
  5. package/lib/adapters.mjs +337 -54
  6. package/lib/agentnode.mjs +175 -35
  7. package/lib/atlas.mjs +383 -83
  8. package/lib/cards.mjs +174 -40
  9. package/lib/claudemem.mjs +74 -18
  10. package/lib/commands.mjs +13 -5
  11. package/lib/context.mjs +409 -71
  12. package/lib/control-client.mjs +5 -2
  13. package/lib/coordinator.mjs +274 -57
  14. package/lib/daemon.mjs +5268 -1024
  15. package/lib/desktop.mjs +149 -35
  16. package/lib/digest.mjs +54 -13
  17. package/lib/environment.mjs +103 -21
  18. package/lib/files.mjs +246 -13
  19. package/lib/firewall.mjs +169 -35
  20. package/lib/folders.mjs +179 -36
  21. package/lib/frames.mjs +214 -40
  22. package/lib/inference.mjs +463 -126
  23. package/lib/learn.mjs +78 -16
  24. package/lib/live.mjs +286 -61
  25. package/lib/localmodels.mjs +424 -83
  26. package/lib/mcp-stdio.mjs +868 -151
  27. package/lib/media.mjs +138 -28
  28. package/lib/members.json +1 -1
  29. package/lib/members.mjs +35 -9
  30. package/lib/models.mjs +342 -80
  31. package/lib/network.mjs +201 -42
  32. package/lib/notify.mjs +49 -12
  33. package/lib/nudge.mjs +184 -33
  34. package/lib/ocr.mjs +223 -60
  35. package/lib/onboard.mjs +321 -70
  36. package/lib/parsers.mjs +132 -41
  37. package/lib/paths.mjs +33 -10
  38. package/lib/personal.mjs +51 -13
  39. package/lib/phones.mjs +384 -60
  40. package/lib/places.mjs +555 -107
  41. package/lib/provenance.mjs +19 -4
  42. package/lib/receipts.mjs +51 -11
  43. package/lib/redact.mjs +512 -116
  44. package/lib/runner.mjs +345 -55
  45. package/lib/runtime-install.mjs +55 -14
  46. package/lib/screens.mjs +154 -38
  47. package/lib/sealed.mjs +157 -28
  48. package/lib/secrets.mjs +18 -5
  49. package/lib/service.mjs +170 -34
  50. package/lib/snippets.mjs +94 -18
  51. package/lib/speech.mjs +688 -175
  52. package/lib/store.mjs +1654 -307
  53. package/lib/swarm.mjs +596 -117
  54. package/lib/t3.mjs +400 -79
  55. package/lib/trails.mjs +285 -61
  56. package/lib/ui.mjs +95 -23
  57. package/lib/vectors.mjs +5 -2
  58. package/lib/wakes.mjs +284 -68
  59. package/lib/watches.mjs +247 -48
  60. package/lib/worktrees.mjs +186 -38
  61. package/package.json +3 -3
package/lib/ocr.mjs CHANGED
@@ -14,24 +14,54 @@ import jpeg from "jpeg-js";
14
14
  export const MAX_PIXELS = 40e6;
15
15
  // The detector's own settings (its inference.yml: thresh 0.2, box_thresh 0.45, unclip_ratio 1.4) at a long side of
16
16
  // 960 to 2,048 pixels: a retina screenshot is shrunk to 2,048 across, a small image is enlarged.
17
- const DET = { min: 960, max: 2_048, thresh: 0.2, boxThresh: 0.45, unclip: 1.4, mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225] };
17
+ const DET = {
18
+ min: 960,
19
+ max: 2_048,
20
+ thresh: 0.2,
21
+ boxThresh: 0.45,
22
+ unclip: 1.4,
23
+ mean: [0.485, 0.456, 0.406],
24
+ std: [0.229, 0.224, 0.225],
25
+ };
18
26
  const REC = { height: 48, mean: [0.5, 0.5, 0.5], std: [0.5, 0.5, 0.5] };
19
27
  // Boxes read per scheduler job, so a long image does not hold the ONNX lane from the firewall's checks.
20
28
  const REC_PER_JOB = 24;
21
29
 
22
- export const imageKind = (bytes) => bytes?.length > 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 ? "png"
23
- : bytes?.length > 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff ? "jpeg" : null;
30
+ export const imageKind = (bytes) =>
31
+ bytes?.length > 8 &&
32
+ bytes[0] === 0x89 &&
33
+ bytes[1] === 0x50 &&
34
+ bytes[2] === 0x4e &&
35
+ bytes[3] === 0x47
36
+ ? "png"
37
+ : bytes?.length > 3 &&
38
+ bytes[0] === 0xff &&
39
+ bytes[1] === 0xd8 &&
40
+ bytes[2] === 0xff
41
+ ? "jpeg"
42
+ : null;
24
43
 
25
44
  /** { width, height, data: RGBA } from PNG or JPEG bytes; null for any other format. Throws on a broken or oversized image. */
26
45
  export function decodeImage(bytes) {
27
- const buffer = Buffer.from(bytes.buffer ?? bytes, bytes.byteOffset ?? 0, bytes.byteLength ?? bytes.length);
46
+ const buffer = Buffer.from(
47
+ bytes.buffer ?? bytes,
48
+ bytes.byteOffset ?? 0,
49
+ bytes.byteLength ?? bytes.length
50
+ );
28
51
  const kind = imageKind(buffer);
29
52
  if (kind === "png") {
30
- if (buffer.readUInt32BE(16) * buffer.readUInt32BE(20) > MAX_PIXELS) throw new Error("the image is too large to check");
53
+ if (buffer.readUInt32BE(16) * buffer.readUInt32BE(20) > MAX_PIXELS)
54
+ throw new Error("the image is too large to check");
31
55
  const { width, height, data } = pngjs.PNG.sync.read(buffer);
32
56
  return { width, height, data };
33
57
  }
34
- if (kind === "jpeg") return jpeg.decode(buffer, { useTArray: true, formatAsRGBA: true, maxResolutionInMP: MAX_PIXELS / 1e6, maxMemoryUsageInMB: 1_024 });
58
+ if (kind === "jpeg")
59
+ return jpeg.decode(buffer, {
60
+ useTArray: true,
61
+ formatAsRGBA: true,
62
+ maxResolutionInMP: MAX_PIXELS / 1e6,
63
+ maxMemoryUsageInMB: 1_024,
64
+ });
35
65
  return null;
36
66
  }
37
67
 
@@ -39,13 +69,22 @@ export function decodeImage(bytes) {
39
69
  export function characterList(yml) {
40
70
  const lines = yml.split(/\r?\n/);
41
71
  const out = [];
42
- for (const line of lines.slice(lines.findIndex((each) => /^\s*character_dict:\s*$/.test(each)) + 1)) {
72
+ for (const line of lines.slice(
73
+ lines.findIndex((each) => /^\s*character_dict:\s*$/.test(each)) + 1
74
+ )) {
43
75
  const item = line.match(/^\s*- (.*)$/);
44
76
  if (!item) break;
45
77
  const value = item[1];
46
- out.push(value.startsWith("'") ? value.slice(1, -1).replace(/''/g, "'") : value.startsWith('"') ? JSON.parse(value) : value);
78
+ out.push(
79
+ value.startsWith("'")
80
+ ? value.slice(1, -1).replace(/''/g, "'")
81
+ : value.startsWith('"')
82
+ ? JSON.parse(value)
83
+ : value
84
+ );
47
85
  }
48
- if (!out.length) throw new Error("the recogniser's character list is missing");
86
+ if (!out.length)
87
+ throw new Error("the recogniser's character list is missing");
49
88
  return out;
50
89
  }
51
90
 
@@ -57,10 +96,16 @@ function taps(start, length, count, limit) {
57
96
  for (let i = 0; i < count; i += 1) {
58
97
  const list = [];
59
98
  if (scale > 1) {
60
- const a = start + i * scale, b = a + scale;
61
- for (let p = Math.floor(a); p < b; p += 1) { const w = Math.min(b, p + 1) - Math.max(a, p); if (w > 0) list.push(at(p), w / scale); }
99
+ const a = start + i * scale,
100
+ b = a + scale;
101
+ for (let p = Math.floor(a); p < b; p += 1) {
102
+ const w = Math.min(b, p + 1) - Math.max(a, p);
103
+ if (w > 0) list.push(at(p), w / scale);
104
+ }
62
105
  } else {
63
- const c = start + (i + 0.5) * scale - 0.5, p = Math.floor(c), f = c - p;
106
+ const c = start + (i + 0.5) * scale - 0.5,
107
+ p = Math.floor(c),
108
+ f = c - p;
64
109
  list.push(at(p), 1 - f, at(p + 1), f);
65
110
  }
66
111
  out[i] = list;
@@ -70,18 +115,30 @@ function taps(start, length, count, limit) {
70
115
 
71
116
  /** A region of the image resampled to width x height as a normalized BGR tensor (PaddleOCR's models read OpenCV's BGR); transparency is laid on white. */
72
117
  function tensorOf(image, [x0, y0, x1, y1], width, height, { mean, std }) {
73
- const xs = taps(x0, x1 - x0, width, image.width), ys = taps(y0, y1 - y0, height, image.height);
74
- const plane = width * height, out = new Float32Array(3 * plane), data = image.data, stride = image.width * 4;
118
+ const xs = taps(x0, x1 - x0, width, image.width),
119
+ ys = taps(y0, y1 - y0, height, image.height);
120
+ const plane = width * height,
121
+ out = new Float32Array(3 * plane),
122
+ data = image.data,
123
+ stride = image.width * 4;
75
124
  for (let j = 0; j < height; j += 1) {
76
125
  const ty = ys[j];
77
126
  for (let i = 0; i < width; i += 1) {
78
127
  const tx = xs[i];
79
- let r = 0, g = 0, b = 0;
128
+ let r = 0,
129
+ g = 0,
130
+ b = 0;
80
131
  for (let v = 0; v < ty.length; v += 2) {
81
- const row = ty[v] * stride, wy = ty[v + 1];
132
+ const row = ty[v] * stride,
133
+ wy = ty[v + 1];
82
134
  for (let u = 0; u < tx.length; u += 2) {
83
- const p = row + tx[u] * 4, w = wy * tx[u + 1], a = data[p + 3] / 255, white = 255 * (1 - a);
84
- r += (data[p] * a + white) * w; g += (data[p + 1] * a + white) * w; b += (data[p + 2] * a + white) * w;
135
+ const p = row + tx[u] * 4,
136
+ w = wy * tx[u + 1],
137
+ a = data[p + 3] / 255,
138
+ white = 255 * (1 - a);
139
+ r += (data[p] * a + white) * w;
140
+ g += (data[p + 1] * a + white) * w;
141
+ b += (data[p + 2] * a + white) * w;
85
142
  }
86
143
  }
87
144
  const k = j * width + i;
@@ -96,23 +153,43 @@ function tensorOf(image, [x0, y0, x1, y1], width, height, { mean, std }) {
96
153
  // DB post-processing: the regions above the threshold whose mean probability passes the box threshold, each grown by
97
154
  // area x unclip / perimeter on every side (the detector is trained on text shrunk by that much).
98
155
  function regions(map, width, height) {
99
- const seen = new Uint8Array(width * height), stack = new Int32Array(width * height), boxes = [];
156
+ const seen = new Uint8Array(width * height),
157
+ stack = new Int32Array(width * height),
158
+ boxes = [];
100
159
  for (let start = 0; start < map.length; start += 1) {
101
160
  if (seen[start] || map[start] <= DET.thresh) continue;
102
161
  seen[start] = 1;
103
- let top = 0, x0 = width, y0 = height, x1 = -1, y1 = -1, sum = 0, count = 0;
162
+ let top = 0,
163
+ x0 = width,
164
+ y0 = height,
165
+ x1 = -1,
166
+ y1 = -1,
167
+ sum = 0,
168
+ count = 0;
104
169
  stack[top++] = start;
105
- const visit = (q) => { if (!seen[q] && map[q] > DET.thresh) { seen[q] = 1; stack[top++] = q; } };
170
+ const visit = (q) => {
171
+ if (!seen[q] && map[q] > DET.thresh) {
172
+ seen[q] = 1;
173
+ stack[top++] = q;
174
+ }
175
+ };
106
176
  while (top) {
107
- const p = stack[--top], x = p % width, y = (p - x) / width;
108
- sum += map[p]; count += 1;
109
- if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y;
177
+ const p = stack[--top],
178
+ x = p % width,
179
+ y = (p - x) / width;
180
+ sum += map[p];
181
+ count += 1;
182
+ if (x < x0) x0 = x;
183
+ if (x > x1) x1 = x;
184
+ if (y < y0) y0 = y;
185
+ if (y > y1) y1 = y;
110
186
  if (x > 0) visit(p - 1);
111
187
  if (x < width - 1) visit(p + 1);
112
188
  if (y > 0) visit(p - width);
113
189
  if (y < height - 1) visit(p + width);
114
190
  }
115
- const w = x1 - x0 + 1, h = y1 - y0 + 1;
191
+ const w = x1 - x0 + 1,
192
+ h = y1 - y0 + 1;
116
193
  if (Math.min(w, h) < 3 || sum / count < DET.boxThresh) continue;
117
194
  const d = (w * h * DET.unclip) / (2 * (w + h));
118
195
  boxes.push([x0 - d, y0 - d, x1 + 1 + d, y1 + 1 + d]);
@@ -126,37 +203,70 @@ function regions(map, width, height) {
126
203
  // the probability map is stitched from rows at least MARGIN from a band's edge. Lines are never cut: regions are found
127
204
  // on the whole map. On the image-redaction eval's screens this found what one pass did, and the OCR models' memory
128
205
  // levelled off at 544 MB instead of growing past 2 GB (160-row bands; 256 rows: 766 MB and about 10% faster).
129
- const BAND = 160, MARGIN = 32, WINDOW = BAND + 2 * MARGIN;
206
+ const BAND = 160,
207
+ MARGIN = 32,
208
+ WINDOW = BAND + 2 * MARGIN;
130
209
 
131
210
  async function detect(image, { ort, graph }) {
132
211
  const long = Math.max(image.width, image.height);
133
212
  const ratio = Math.min(DET.max, Math.max(DET.min, long)) / long;
134
- const width = Math.max(32, Math.round((image.width * ratio) / 32) * 32), height = Math.max(32, Math.round((image.height * ratio) / 32) * 32);
213
+ const width = Math.max(32, Math.round((image.width * ratio) / 32) * 32),
214
+ height = Math.max(32, Math.round((image.height * ratio) / 32) * 32);
135
215
  const columns = Math.ceil(width / 512) * 512;
136
216
  const session = await graph("det/inference.onnx");
137
- const sx = image.width / width, sy = image.height / height;
217
+ const sx = image.width / width,
218
+ sy = image.height / height;
138
219
  const map = new Float32Array(width * height);
139
220
  for (let top = 0; top < height; top += BAND) {
140
- const a = Math.max(0, Math.min(top - MARGIN, height - WINDOW)), rows = Math.min(WINDOW, height - a);
141
- const pixels = tensorOf(image, [0, a * sy, image.width, (a + rows) * sy], width, rows, DET), input = new Float32Array(3 * WINDOW * columns);
221
+ const a = Math.max(0, Math.min(top - MARGIN, height - WINDOW)),
222
+ rows = Math.min(WINDOW, height - a);
223
+ const pixels = tensorOf(
224
+ image,
225
+ [0, a * sy, image.width, (a + rows) * sy],
226
+ width,
227
+ rows,
228
+ DET
229
+ ),
230
+ input = new Float32Array(3 * WINDOW * columns);
142
231
  // Padding repeats the edge pixels: a flat block beside the image made the detector see text in empty space.
143
- for (let c = 0; c < 3; c += 1) for (let y = 0; y < WINDOW; y += 1) {
144
- const from = (c * rows + Math.min(y, rows - 1)) * width, to = (c * WINDOW + y) * columns;
145
- input.set(pixels.subarray(from, from + width), to);
146
- input.fill(pixels[from + width - 1], to + width, to + columns);
147
- }
148
- const band = (await session.run({ [session.inputNames[0]]: new ort.Tensor("float32", input, [1, 3, WINDOW, columns]) }))[session.outputNames[0]].data;
149
- for (let y = top; y < Math.min(height, top + BAND); y += 1) map.set(band.subarray((y - a) * columns, (y - a) * columns + width), y * width);
232
+ for (let c = 0; c < 3; c += 1)
233
+ for (let y = 0; y < WINDOW; y += 1) {
234
+ const from = (c * rows + Math.min(y, rows - 1)) * width,
235
+ to = (c * WINDOW + y) * columns;
236
+ input.set(pixels.subarray(from, from + width), to);
237
+ input.fill(pixels[from + width - 1], to + width, to + columns);
238
+ }
239
+ const band = (
240
+ await session.run({
241
+ [session.inputNames[0]]: new ort.Tensor("float32", input, [
242
+ 1,
243
+ 3,
244
+ WINDOW,
245
+ columns,
246
+ ]),
247
+ })
248
+ )[session.outputNames[0]].data;
249
+ for (let y = top; y < Math.min(height, top + BAND); y += 1)
250
+ map.set(
251
+ band.subarray((y - a) * columns, (y - a) * columns + width),
252
+ y * width
253
+ );
150
254
  }
151
255
  return regions(map, width, height)
152
- .map(([x0, y0, x1, y1]) => [Math.max(0, x0 * sx), Math.max(0, y0 * sy), Math.min(image.width, x1 * sx), Math.min(image.height, y1 * sy)])
256
+ .map(([x0, y0, x1, y1]) => [
257
+ Math.max(0, x0 * sx),
258
+ Math.max(0, y0 * sy),
259
+ Math.min(image.width, x1 * sx),
260
+ Math.min(image.height, y1 * sy),
261
+ ])
153
262
  .filter(([x0, y0, x1, y1]) => x1 - x0 >= 4 && y1 - y0 >= 4);
154
263
  }
155
264
 
156
265
  // ONNX Runtime keeps memory for every input shape it has run (400 crop widths: 108 MB grew to 513 MB, October 14), so
157
266
  // crops are padded on the right to a few widths, with 0 after normalization as PaddleOCR's own batches pad. Over this
158
267
  // eval's screens that changed 16 of 845 lines, by spacing or a dash, none of a planted value.
159
- const bucket = (width) => (width <= 512 ? Math.ceil(width / 32) * 32 : Math.ceil(width / 64) * 64);
268
+ const bucket = (width) =>
269
+ width <= 512 ? Math.ceil(width / 32) * 32 : Math.ceil(width / 64) * 64;
160
270
 
161
271
  // CTC greedy decoding that keeps, for each character, the output steps it was read over (each step is a fixed slice of
162
272
  // the crop's width).
@@ -164,32 +274,66 @@ async function recognise(image, box, characters, { ort, graph }) {
164
274
  const session = await graph("rec/inference.onnx");
165
275
  const [x0, y0, x1, y1] = box;
166
276
  const width = Math.max(8, Math.round((REC.height * (x1 - x0)) / (y1 - y0)));
167
- const padded = bucket(width), crop = tensorOf(image, box, width, REC.height, REC), tensor = new Float32Array(3 * REC.height * padded);
168
- for (let row = 0; row < 3 * REC.height; row += 1) tensor.set(crop.subarray(row * width, (row + 1) * width), row * padded);
277
+ const padded = bucket(width),
278
+ crop = tensorOf(image, box, width, REC.height, REC),
279
+ tensor = new Float32Array(3 * REC.height * padded);
280
+ for (let row = 0; row < 3 * REC.height; row += 1)
281
+ tensor.set(crop.subarray(row * width, (row + 1) * width), row * padded);
169
282
  const input = new ort.Tensor("float32", tensor, [1, 3, REC.height, padded]);
170
- const out = (await session.run({ [session.inputNames[0]]: input }))[session.outputNames[0]];
171
- const [, all, classes] = out.dims, data = out.data;
283
+ const out = (await session.run({ [session.inputNames[0]]: input }))[
284
+ session.outputNames[0]
285
+ ];
286
+ const [, all, classes] = out.dims,
287
+ data = out.data;
172
288
  const steps = Math.min(all, Math.ceil((all * width) / padded));
173
289
  const space = classes === characters.length + 2 ? classes - 1 : -1;
174
290
  const pixels = ((x1 - x0) / width) * (padded / all);
175
291
  const read = [];
176
- let total = 0, previous = 0;
292
+ let total = 0,
293
+ previous = 0;
177
294
  for (let t = 0; t < steps; t += 1) {
178
- let best = 0, p = data[t * classes];
179
- for (let c = 1; c < classes; c += 1) if (data[t * classes + c] > p) { p = data[t * classes + c]; best = c; }
180
- if (best && best === previous) read[read.length - 1].to = x0 + (t + 1) * pixels;
181
- else if (best) { read.push({ ch: best === space ? " " : characters[best - 1] ?? "", from: x0 + t * pixels, to: x0 + (t + 1) * pixels }); total += p; }
295
+ let best = 0,
296
+ p = data[t * classes];
297
+ for (let c = 1; c < classes; c += 1)
298
+ if (data[t * classes + c] > p) {
299
+ p = data[t * classes + c];
300
+ best = c;
301
+ }
302
+ if (best && best === previous)
303
+ read[read.length - 1].to = x0 + (t + 1) * pixels;
304
+ else if (best) {
305
+ read.push({
306
+ ch: best === space ? " " : (characters[best - 1] ?? ""),
307
+ from: x0 + t * pixels,
308
+ to: x0 + (t + 1) * pixels,
309
+ });
310
+ total += p;
311
+ }
182
312
  previous = best;
183
313
  }
184
314
  // One entry per UTF-16 unit, so chars lines up with the text's indexes.
185
315
  const text = read.map((each) => each.ch).join("");
186
- const chars = read.flatMap(({ ch, from, to }) => Array.from({ length: ch.length }, () => [Math.floor(from), Math.ceil(to)]));
187
- const lead = text.length - text.trimStart().length, kept = text.trim().length;
188
- return { text: text.trim(), box: [Math.floor(x0), Math.floor(y0), Math.ceil(x1), Math.ceil(y1)], score: read.length ? total / read.length : 0, chars: chars.slice(lead, lead + kept) };
316
+ const chars = read.flatMap(({ ch, from, to }) =>
317
+ Array.from({ length: ch.length }, () => [Math.floor(from), Math.ceil(to)])
318
+ );
319
+ const lead = text.length - text.trimStart().length,
320
+ kept = text.trim().length;
321
+ return {
322
+ text: text.trim(),
323
+ box: [Math.floor(x0), Math.floor(y0), Math.ceil(x1), Math.ceil(y1)],
324
+ score: read.length ? total / read.length : 0,
325
+ chars: chars.slice(lead, lead + kept),
326
+ };
189
327
  }
190
328
 
191
329
  // Top to bottom, left to right within a row (rows: boxes whose tops are within a quarter of their combined height).
192
- const readingOrder = (lines) => lines.sort((a, b) => (Math.abs(a.box[1] - b.box[1]) < (a.box[3] - a.box[1] + b.box[3] - b.box[1]) / 4 ? a.box[0] - b.box[0] : a.box[1] - b.box[1]));
330
+ const readingOrder = (lines) =>
331
+ lines.sort((a, b) =>
332
+ Math.abs(a.box[1] - b.box[1]) <
333
+ (a.box[3] - a.box[1] + b.box[3] - b.box[1]) / 4
334
+ ? a.box[0] - b.box[0]
335
+ : a.box[1] - b.box[1]
336
+ );
193
337
 
194
338
  export function createOcr({ inference }) {
195
339
  let characters = null;
@@ -201,17 +345,36 @@ export function createOcr({ inference }) {
201
345
  const started = performance.now();
202
346
  const image = input?.data && input.width ? input : decodeImage(input);
203
347
  if (!image) throw new Error("only PNG and JPEG images can be read");
204
- const boxes = await inference.onnx("ocr", (model) => detect(image, model), { lane });
348
+ const boxes = await inference.onnx("ocr", (model) => detect(image, model), {
349
+ lane,
350
+ });
205
351
  const lines = [];
206
352
  for (let i = 0; i < boxes.length; i += REC_PER_JOB) {
207
- lines.push(...await inference.onnx("ocr", async (model) => {
208
- characters ||= characterList(fs.readFileSync(path.join(model.dir, "rec", "inference.yml"), "utf8"));
209
- const read = [];
210
- for (const box of boxes.slice(i, i + REC_PER_JOB)) read.push(await recognise(image, box, characters, model));
211
- return read;
212
- }, { lane }));
353
+ lines.push(
354
+ ...(await inference.onnx(
355
+ "ocr",
356
+ async (model) => {
357
+ characters ||= characterList(
358
+ fs.readFileSync(
359
+ path.join(model.dir, "rec", "inference.yml"),
360
+ "utf8"
361
+ )
362
+ );
363
+ const read = [];
364
+ for (const box of boxes.slice(i, i + REC_PER_JOB))
365
+ read.push(await recognise(image, box, characters, model));
366
+ return read;
367
+ },
368
+ { lane }
369
+ ))
370
+ );
213
371
  }
214
- return { width: image.width, height: image.height, lines: readingOrder(lines.filter((line) => line.text)), ms: Math.round(performance.now() - started) };
372
+ return {
373
+ width: image.width,
374
+ height: image.height,
375
+ lines: readingOrder(lines.filter((line) => line.text)),
376
+ ms: Math.round(performance.now() - started),
377
+ };
215
378
  };
216
379
  return { read };
217
380
  }