tapirscan 1.2.0 → 1.2.2

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 (62) hide show
  1. package/README.md +9 -10
  2. package/dist/index.d.ts +24 -29
  3. package/dist/index.js +64 -154
  4. package/dist/multiformat/format-registry.d.ts +26 -0
  5. package/dist/multiformat/format-registry.js +62 -0
  6. package/dist/multiformat/formats.d.ts +2 -24
  7. package/dist/multiformat/formats.js +3 -41
  8. package/dist/rust-session.d.ts +19 -0
  9. package/dist/rust-session.js +92 -0
  10. package/package.json +7 -9
  11. package/wasm/high-release-1.2.2-public-low-r2.wasm +0 -0
  12. package/wasm/low-release-1.2.2-public-low-r2.wasm +0 -0
  13. package/wasm/medium-release-1.2.2-public-low-r2.wasm +0 -0
  14. package/wasm/very-high-release-1.2.2-public-low-r2.wasm +0 -0
  15. package/dist/completion-host.d.mts +0 -40
  16. package/dist/completion-host.mjs +0 -188
  17. package/dist/detail-20260914/continuity.d.mts +0 -2
  18. package/dist/detail-20260914/continuity.mjs +0 -62
  19. package/dist/detail-20260914/detail-proposals-rich.d.mts +0 -13
  20. package/dist/detail-20260914/detail-proposals-rich.mjs +0 -53
  21. package/dist/detail-20260914/direct-recovery.d.mts +0 -28
  22. package/dist/detail-20260914/direct-recovery.mjs +0 -139
  23. package/dist/detail-20260914/host.d.mts +0 -40
  24. package/dist/detail-20260914/host.mjs +0 -280
  25. package/dist/detail-20260914/scanner.d.mts +0 -12
  26. package/dist/detail-20260914/scanner.mjs +0 -64
  27. package/dist/detail-20260914/source-evidence.d.mts +0 -10
  28. package/dist/detail-20260914/source-evidence.mjs +0 -94
  29. package/dist/detail-canvas.d.ts +0 -35
  30. package/dist/detail-canvas.js +0 -73
  31. package/dist/detail-runtime/direct-recovery.d.mts +0 -28
  32. package/dist/detail-runtime/direct-recovery.mjs +0 -142
  33. package/dist/detail-runtime/host.d.mts +0 -40
  34. package/dist/detail-runtime/host.mjs +0 -282
  35. package/dist/detail-runtime/scanner.d.mts +0 -12
  36. package/dist/detail-runtime/scanner.mjs +0 -70
  37. package/dist/detail.d.ts +0 -18
  38. package/dist/detail.js +0 -84
  39. package/dist/host.d.ts +0 -112
  40. package/dist/host.js +0 -184
  41. package/dist/host64.d.ts +0 -112
  42. package/dist/host64.js +0 -184
  43. package/dist/multiformat/coverage.d.ts +0 -7
  44. package/dist/multiformat/coverage.js +0 -26
  45. package/dist/multiformat/geometry.d.ts +0 -37
  46. package/dist/multiformat/geometry.js +0 -132
  47. package/dist/multiformat/linear-duplicates.d.ts +0 -16
  48. package/dist/multiformat/linear-duplicates.js +0 -163
  49. package/dist/multiformat/pixels.d.ts +0 -3
  50. package/dist/multiformat/pixels.js +0 -36
  51. package/dist/multiformat/scanner.d.ts +0 -60
  52. package/dist/multiformat/scanner.js +0 -361
  53. package/dist/multiformat-host.d.ts +0 -121
  54. package/dist/multiformat-host.js +0 -207
  55. package/dist/policy.d.ts +0 -12
  56. package/dist/policy.js +0 -12
  57. package/wasm/high-complete-release-20260916.wasm +0 -0
  58. package/wasm/low-complete-release-20260916.wasm +0 -0
  59. package/wasm/medium-complete-release-20260916.wasm +0 -0
  60. package/wasm/multiformat.json +0 -7
  61. package/wasm/multiformat.wasm +0 -0
  62. package/wasm/very-high-complete-release-20260916.wasm +0 -0
@@ -1,60 +0,0 @@
1
- import { ReleaseDetailScanner } from "../detail.js";
2
- import { IndependentScanner, type Image, type Quad } from "../completion-host.mjs";
3
- import type { Mode } from "../index.js";
4
- import { type Format } from "./formats.js";
5
- export interface Barcode {
6
- bytes?: number[];
7
- format: Format | "Unknown";
8
- text: string;
9
- polygon: Quad;
10
- support: number;
11
- localizationScore?: number;
12
- gs1?: boolean;
13
- readerInitialization?: boolean;
14
- structuredAppend?: {
15
- index: number;
16
- count: number;
17
- id?: string;
18
- parity?: number;
19
- };
20
- eanAddOn?: string;
21
- error?: number;
22
- rank?: number;
23
- }
24
- export type EanAddOnSymbol = "Ignore" | "Read" | "Require";
25
- export interface Frame {
26
- primary?: ReturnType<ReleaseDetailScanner["scanLocalized"]> | ReturnType<IndependentScanner["scanLocalized"]>;
27
- eanAddOnSymbol: EanAddOnSymbol;
28
- barcodes: Barcode[];
29
- regions: Barcode[];
30
- formats: Format[];
31
- unfinished: boolean;
32
- scanMs: number;
33
- mediumMs: number;
34
- additionalMs: number;
35
- preparationMs: number;
36
- localizationMs: number;
37
- linearStrategy: "scanlines" | "medium-localized";
38
- }
39
- /** Frozen EAN13 Medium plus project-owned opt-in readers. No reference decoder. */
40
- export declare class MediumMultiformatScanner {
41
- private medium?;
42
- private mode;
43
- private extra?;
44
- private handle;
45
- private rgba;
46
- private crop;
47
- private effort;
48
- private qrEffort;
49
- private disposed;
50
- private constructor();
51
- static create(mediumBytes: ArrayBuffer | undefined, extraBytes?: ArrayBuffer, mode?: Mode, recoveryBytes?: ArrayBuffer): Promise<MediumMultiformatScanner>;
52
- scan(image: Image, inputFormats?: readonly string[], options?: {
53
- linearStrategy?: "scanlines" | "medium-localized";
54
- eanAddOnSymbol?: EanAddOnSymbol;
55
- finishCandidates?: boolean;
56
- }): Frame;
57
- private scanExtra;
58
- private scanPrepared;
59
- dispose(): void;
60
- }
@@ -1,361 +0,0 @@
1
- import { mergeLinearDuplicates } from "./linear-duplicates.js";
2
- import { ReleaseDetailScanner, fitLimits } from "../detail.js";
3
- import { IndependentScanner } from "../completion-host.mjs";
4
- import { policy } from "../policy.js";
5
- import { rectify, rectificationPlan, project, distinctReads, polygonOverlap } from "./geometry.js";
6
- import { toGray } from "./pixels.js";
7
- import { maskFor, resolveFormats, linearFormats as supportedLinearFormats, } from "./formats.js";
8
- function scanGray(image) {
9
- return image.channels === 1 && image.stride === image.width
10
- ? image.data.subarray(0, image.width * image.height)
11
- : toGray(image);
12
- }
13
- /** Frozen EAN13 Medium plus project-owned opt-in readers. No reference decoder. */
14
- export class MediumMultiformatScanner {
15
- medium;
16
- mode = "medium";
17
- extra;
18
- handle = 0;
19
- rgba = false;
20
- crop = false;
21
- effort = 1;
22
- qrEffort = 1;
23
- disposed = false;
24
- constructor() { }
25
- static async create(mediumBytes, extraBytes, mode = "medium", recoveryBytes) {
26
- const scanner = new MediumMultiformatScanner();
27
- try {
28
- scanner.mode = mode;
29
- scanner.effort = { low: 0, medium: 1, high: 2, "very-high": 2 }[mode];
30
- scanner.qrEffort = { low: 0, medium: 1, high: 2, "very-high": 3 }[mode];
31
- if (mediumBytes)
32
- scanner.medium =
33
- mode !== "low" && recoveryBytes
34
- ? await ReleaseDetailScanner.create(mediumBytes, recoveryBytes, mode)
35
- : await IndependentScanner.create(mediumBytes);
36
- if (extraBytes) {
37
- const instance = await WebAssembly.instantiate(extraBytes, {});
38
- const e = instance.instance.exports;
39
- for (const name of [
40
- "multi_new",
41
- "multi_free",
42
- "multi_prepare",
43
- "multi_input",
44
- "multi_scan",
45
- "multi_output",
46
- "multi_output_len",
47
- ]) {
48
- if (typeof e[name] !== "function")
49
- throw Error(`Invalid multiformat WASM export: ${name}`);
50
- }
51
- if (!(e.memory instanceof WebAssembly.Memory))
52
- throw Error("Invalid multiformat WASM memory.");
53
- scanner.rgba =
54
- typeof e.multi_capabilities === "function" &&
55
- (e.multi_capabilities() & 4) !== 0 &&
56
- [e.multi_prepare_rgba, e.multi_input_rgba, e.multi_scan_rgba].every((f) => typeof f === "function");
57
- scanner.crop =
58
- typeof e.multi_capabilities === "function" &&
59
- (e.multi_capabilities() & 16) !== 0 &&
60
- [e.multi_capture_source, e.multi_crop_transform, e.multi_crop].every((f) => typeof f === "function");
61
- scanner.extra = e;
62
- scanner.handle = e.multi_new();
63
- if (!scanner.handle)
64
- throw Error("Could not create additional reader session.");
65
- }
66
- return scanner;
67
- }
68
- catch (error) {
69
- scanner.dispose();
70
- throw error;
71
- }
72
- }
73
- scan(image, inputFormats, options = {}) {
74
- if (this.disposed)
75
- throw Error("Scanner is disposed.");
76
- const medium = this.medium;
77
- const scanPolicy = { ...policy, finishCandidates: options.finishCandidates ?? false };
78
- const formats = resolveFormats(inputFormats);
79
- const eanAddOnSymbol = options.eanAddOnSymbol ?? "Ignore";
80
- if (!["Ignore", "Read", "Require"].includes(eanAddOnSymbol))
81
- throw Error("Unknown EAN add-on policy.");
82
- const requestedStrategy = options.linearStrategy ?? "scanlines";
83
- if (!["scanlines", "medium-localized"].includes(requestedStrategy))
84
- throw Error("Unknown linear strategy.");
85
- // Supplements extend beyond the frozen Medium crop, so they require the
86
- // full-frame supplemental pass. Default EAN13 remains untouched.
87
- const linearStrategy = eanAddOnSymbol === "Ignore" ? requestedStrategy : "scanlines";
88
- const localized = linearStrategy === "medium-localized" &&
89
- formats.some((f) => f !== "EAN13" && f !== "UPCA" && supportedLinearFormats.includes(f));
90
- let proposals = [];
91
- const start = performance.now();
92
- const { width, height, channels, stride, data } = image;
93
- if (!Number.isSafeInteger(width) ||
94
- !Number.isSafeInteger(height) ||
95
- width < 3 ||
96
- height < 3 ||
97
- width * height > 32 * 1024 * 1024 ||
98
- ![1, 3, 4].includes(channels) ||
99
- !Number.isSafeInteger(stride) ||
100
- stride < width * channels ||
101
- !(data instanceof Uint8Array) ||
102
- data.length < (height - 1) * stride + width * channels)
103
- throw Error("Invalid image dimensions or buffer.");
104
- const barcodes = [], regions = [];
105
- let primary;
106
- let unfinished = false, mediumMs = 0, additionalMs = 0, preparationMs = 0, localizationMs = 0;
107
- const extraFormats = formats.filter((f) => eanAddOnSymbol !== "Ignore" || (f !== "EAN13" && f !== "UPCA"));
108
- const conservative = medium instanceof ReleaseDetailScanner &&
109
- !localized &&
110
- eanAddOnSymbol === "Ignore" &&
111
- formats.some((f) => f === "EAN13" || f === "UPCA") &&
112
- extraFormats.some((f) => supportedLinearFormats.includes(f));
113
- let preparedGray;
114
- let preLinear;
115
- const coverage = [];
116
- if (conservative) {
117
- const prepareStart = performance.now();
118
- preparedGray = scanGray(image);
119
- preparationMs += performance.now() - prepareStart;
120
- const extraStart = performance.now();
121
- preLinear = this.scanExtra(preparedGray, width, height, extraFormats.filter((f) => supportedLinearFormats.includes(f)), this.effort, eanAddOnSymbol);
122
- additionalMs += performance.now() - extraStart;
123
- for (const read of preLinear.barcodes) {
124
- const error = read.error ?? Infinity;
125
- const checked = ["EAN8", "UPCE", "Code128"].includes(read.format) && read.support >= 3 && error <= 0.08;
126
- const unchecked = ["Code39", "ITF"].includes(read.format) &&
127
- read.support >= 8 &&
128
- error <= 0.035 &&
129
- read.text.length >= 8;
130
- if (checked || unchecked)
131
- coverage.push(read.polygon);
132
- }
133
- }
134
- if (formats.includes("EAN13") || formats.includes("UPCA")) {
135
- if (!medium)
136
- throw Error("EAN13 engine has not been initialized.");
137
- const begin = performance.now();
138
- const found = medium instanceof ReleaseDetailScanner
139
- ? medium.scanLocalized(image, scanPolicy, fitLimits[this.mode], true, coverage)
140
- : medium.scanLocalized(image, scanPolicy, fitLimits[this.mode], true);
141
- primary = found;
142
- const localization = found.localization;
143
- proposals = localization.proposals;
144
- localizationMs = found.localizationMs;
145
- for (const b of found.scan.barcodes) {
146
- if (formats.includes("UPCA") && b.text.startsWith("0"))
147
- barcodes.push({
148
- format: "UPCA",
149
- text: b.text.slice(1),
150
- polygon: b.polygon,
151
- support: b.support,
152
- });
153
- else if (formats.includes("EAN13"))
154
- barcodes.push({ format: "EAN13", text: b.text, polygon: b.polygon, support: b.support });
155
- }
156
- const decoded = new Set(found.scan.barcodes.flatMap((b) => b.candidate_indices));
157
- localization.proposals.forEach((p, i) => {
158
- if (!decoded.has(i))
159
- regions.push({ format: "Unknown", text: "", polygon: p.polygon, support: 0 });
160
- });
161
- if ("recovery" in found) {
162
- const recovery = found.recovery;
163
- for (const attempt of recovery.attempts) {
164
- const accepted = new Set(attempt.reads.flatMap((b) => b.candidate_indices));
165
- attempt.proposals.forEach((p, i) => {
166
- if (!accepted.has(i))
167
- regions.push({ format: "Unknown", text: "", polygon: p.polygon, support: 0 });
168
- });
169
- }
170
- }
171
- unfinished =
172
- found.scan.unfinished ||
173
- Boolean(localization.workLimited) ||
174
- (localization.omitted ?? 0) > 0;
175
- mediumMs = performance.now() - begin;
176
- }
177
- if (localized && !formats.includes("EAN13") && !formats.includes("UPCA")) {
178
- if (!medium)
179
- throw Error("EAN13 localization engine has not been initialized.");
180
- const begin = performance.now();
181
- const found = medium.scanLocalized(image, scanPolicy, fitLimits[this.mode], true)
182
- .localization;
183
- proposals = found.proposals;
184
- unfinished ||= Boolean(found.workLimited) || (found.omitted ?? 0) > 0;
185
- localizationMs = performance.now() - begin;
186
- }
187
- // UPC-A has the same optical structure as zero-prefixed EAN13, so the
188
- // frozen Medium reader handles it without a second optical search.
189
- if (extraFormats.length) {
190
- if (!this.extra || !this.handle)
191
- throw Error("Additional readers have not been loaded.");
192
- const begin = performance.now();
193
- const rgba = this.rgba &&
194
- !localized &&
195
- formats.length === 1 &&
196
- formats[0] === "QRCode" &&
197
- channels === 4 &&
198
- stride === width * 4;
199
- const gray = preparedGray ?? (rgba ? data.subarray(0, width * height * 4) : scanGray(image));
200
- preparationMs += performance.now() - begin;
201
- const matrixFormats = extraFormats.filter((f) => !supportedLinearFormats.includes(f));
202
- const linearFormats = extraFormats.filter((f) => supportedLinearFormats.includes(f));
203
- const run = (pixels, w, h, enabled, effort) => {
204
- const start = performance.now();
205
- const result = this.scanExtra(pixels, w, h, enabled, effort, eanAddOnSymbol, rgba);
206
- additionalMs += performance.now() - start;
207
- unfinished ||= result.unfinished;
208
- return result;
209
- };
210
- if (!localized || !proposals.length) {
211
- for (const [enabled, effort] of [
212
- [linearFormats, this.effort],
213
- [matrixFormats, matrixFormats.includes("QRCode") ? this.qrEffort : 1],
214
- ]) {
215
- if (!enabled.length)
216
- continue;
217
- const result = enabled === linearFormats && preLinear
218
- ? preLinear
219
- : run(gray, width, height, enabled, effort);
220
- unfinished ||= result.unfinished;
221
- barcodes.push(...result.barcodes);
222
- regions.push(...(result.regions ?? []));
223
- }
224
- }
225
- else {
226
- if (matrixFormats.length) {
227
- const result = run(gray, width, height, matrixFormats, matrixFormats.includes("QRCode") ? this.qrEffort : 1);
228
- barcodes.push(...result.barcodes);
229
- regions.push(...(result.regions ?? []));
230
- }
231
- if (this.crop) {
232
- this.extra.multi_prepare(this.handle, width, height);
233
- new Uint8Array(this.extra.memory.buffer, this.extra.multi_input(this.handle), width * height).set(gray);
234
- if (this.extra.multi_capture_source(this.handle) !== 0)
235
- throw Error("Could not retain source image.");
236
- }
237
- for (const proposal of proposals) {
238
- const start = performance.now();
239
- let crop;
240
- let pixels;
241
- try {
242
- if (this.crop) {
243
- crop = rectificationPlan(proposal.polygon);
244
- new Float64Array(this.extra.memory.buffer, this.extra.multi_crop_transform(this.handle), 8).set(crop.transform);
245
- if (this.extra.multi_crop(this.handle, crop.width, crop.height) !== 0)
246
- throw Error("Could not sample barcode crop.");
247
- }
248
- else {
249
- const sampled = rectify(gray, width, height, proposal.polygon);
250
- crop = sampled;
251
- pixels = sampled.data;
252
- }
253
- }
254
- catch {
255
- unfinished = true;
256
- regions.push({ format: "Unknown", text: "", polygon: proposal.polygon, support: 0 });
257
- continue;
258
- }
259
- preparationMs += performance.now() - start;
260
- const decodeStart = performance.now();
261
- const result = pixels
262
- ? run(pixels, crop.width, crop.height, linearFormats, 0)
263
- : this.scanPrepared(linearFormats, 0, eanAddOnSymbol);
264
- if (!pixels) {
265
- additionalMs += performance.now() - decodeStart;
266
- unfinished ||= result.unfinished;
267
- }
268
- const reads = result.barcodes;
269
- if (!reads.length)
270
- regions.push({ format: "Unknown", text: "", polygon: proposal.polygon, support: 0 });
271
- for (const read of [...reads, ...(result.regions ?? [])]) {
272
- const p = read.polygon;
273
- const mapped = (p) => project(crop.transform, p[0], p[1]);
274
- read.polygon = [mapped(p[0]), mapped(p[1]), mapped(p[2]), mapped(p[3])];
275
- if (read.text)
276
- barcodes.push(read);
277
- else
278
- regions.push(read);
279
- }
280
- }
281
- }
282
- }
283
- if (extraFormats.length) {
284
- for (const read of barcodes.filter((b) => b.eanAddOn)) {
285
- for (const base of barcodes) {
286
- if (base.format === read.format &&
287
- base.text === read.text &&
288
- !base.eanAddOn &&
289
- polygonOverlap(base.polygon, read.polygon).smaller >= 0.65)
290
- base.eanAddOn = read.eanAddOn;
291
- }
292
- }
293
- if (eanAddOnSymbol === "Require") {
294
- for (let i = barcodes.length - 1; i >= 0; i--) {
295
- const b = barcodes[i];
296
- if (["EAN13", "UPCA", "EAN8", "UPCE"].includes(b.format) && !b.eanAddOn) {
297
- regions.push({ ...b, text: "" });
298
- barcodes.splice(i, 1);
299
- }
300
- }
301
- }
302
- const distinct = distinctReads(barcodes);
303
- barcodes.splice(0, barcodes.length, ...distinct);
304
- const remaining = regions.filter((region) => !barcodes.some((b) => polygonOverlap(region.polygon, b.polygon).a >= 0.65));
305
- regions.splice(0, regions.length, ...distinctReads(remaining));
306
- }
307
- const mergedLinear = mergeLinearDuplicates(barcodes, image);
308
- barcodes.splice(0, barcodes.length, ...mergedLinear);
309
- barcodes.sort((a, b) => b.support - a.support);
310
- barcodes.forEach((b, i) => {
311
- b.rank = i + 1;
312
- });
313
- return {
314
- primary,
315
- barcodes,
316
- eanAddOnSymbol,
317
- regions: [...barcodes, ...regions],
318
- formats,
319
- unfinished,
320
- scanMs: performance.now() - start,
321
- mediumMs,
322
- additionalMs,
323
- preparationMs,
324
- localizationMs,
325
- linearStrategy,
326
- };
327
- }
328
- scanExtra(gray, width, height, formats, effort, eanAddOnSymbol, rgba = false) {
329
- const e = this.extra;
330
- if (!e || !this.handle)
331
- throw Error("Additional readers have not been loaded.");
332
- if ((rgba ? e.multi_prepare_rgba : e.multi_prepare)(this.handle, width, height) !== 0)
333
- throw Error("Additional reader rejected image size.");
334
- new Uint8Array(e.memory.buffer, (rgba ? e.multi_input_rgba : e.multi_input)(this.handle), width * height * (rgba ? 4 : 1)).set(gray);
335
- return this.scanPrepared(formats, effort, eanAddOnSymbol, rgba);
336
- }
337
- scanPrepared(formats, effort, eanAddOnSymbol, rgba = false) {
338
- const e = this.extra;
339
- if (!e || !this.handle)
340
- throw Error("Additional readers have not been loaded.");
341
- const mask = maskFor(formats) |
342
- (eanAddOnSymbol === "Read" ? 32768 : eanAddOnSymbol === "Require" ? 65536 : 0);
343
- if ((rgba ? e.multi_scan_rgba : e.multi_scan)(this.handle, mask, effort) !== 0)
344
- throw Error("Additional scanner failed.");
345
- const bytes = new Uint8Array(e.memory.buffer, e.multi_output(this.handle), e.multi_output_len(this.handle));
346
- const found = JSON.parse(new TextDecoder().decode(bytes));
347
- for (const b of [...found.barcodes, ...(found.regions ?? [])])
348
- if (!formats.includes(b.format))
349
- throw Error("Scanner returned a disabled format.");
350
- return found;
351
- }
352
- dispose() {
353
- if (this.disposed)
354
- return;
355
- this.medium?.dispose();
356
- if (this.handle)
357
- this.extra?.multi_free(this.handle);
358
- this.handle = 0;
359
- this.disposed = true;
360
- }
361
- }
@@ -1,121 +0,0 @@
1
- /** Independent EAN-13 supplied-region scanner. Experimental; no localizer or
2
- * reference decoder is imported. All result confidence remains uncalibrated. */
3
- export type Point = readonly [number, number];
4
- export type Quad = readonly [Point, Point, Point, Point];
5
- export interface Image {
6
- data: Uint8Array;
7
- width: number;
8
- height: number;
9
- channels: 1 | 3 | 4;
10
- stride: number;
11
- }
12
- export interface Policy {
13
- transitionCleanup?: boolean;
14
- sourceIdentity?: boolean;
15
- interiorNormalization?: boolean;
16
- guardBias?: boolean;
17
- /** Experimental: accept one full-quiet scanline; increases false-read risk. Default false. */
18
- allowSingleRow?: boolean;
19
- maxRetryPathsPerCandidate?: number;
20
- maxRetryPathsPerFrame?: number;
21
- maxAssociationChecks?: number;
22
- maxAssociationPixels?: number;
23
- maxResults?: number;
24
- }
25
- export interface Detection {
26
- text: string;
27
- polygon: Quad;
28
- support: number;
29
- axis: number;
30
- }
31
- export interface Barcode extends Detection {
32
- candidate_indices: number[];
33
- }
34
- export interface Observation {
35
- text: string;
36
- axis: number;
37
- fraction: number;
38
- left: number;
39
- right: number;
40
- cost: number;
41
- gap: number;
42
- }
43
- export interface Candidate {
44
- candidate_index: number;
45
- coverage: readonly (readonly [number | null, number | null])[];
46
- error: boolean;
47
- error_detail: 'invalid_geometry' | 'sampling_error' | null;
48
- unfinished: boolean;
49
- ms: number;
50
- work: Record<string, number>;
51
- detections: Detection[];
52
- observations: Observation[];
53
- }
54
- export interface ScanFrame {
55
- unfinished: boolean;
56
- candidates: Candidate[];
57
- barcodes: Barcode[];
58
- reconciliation: {
59
- comparisons: number;
60
- merged: number;
61
- ambiguous: number;
62
- conflicting: number;
63
- pending_observations: number;
64
- truncated: boolean;
65
- source_pairs: number;
66
- source_matches: number;
67
- source_pixels: number;
68
- source_capped: number;
69
- pending_coverage_checks: number;
70
- pending_quarantined: number;
71
- };
72
- }
73
- export interface ScanResult extends ScanFrame {
74
- /** This WASM build has no internal clock. Candidate ms values are unavailable zeros. */
75
- candidateTimingsAvailable: false;
76
- /** Actual host wall time, including validation, preparation/copies, decode and JSON parsing. */
77
- elapsedMs: number;
78
- }
79
- /** Explicit indices reject sparse arrays; nonfinite numeric geometry reaches Rust. */
80
- export declare function isQuadShape(value: unknown): value is Quad;
81
- export declare class ScannerError extends Error {
82
- readonly code: string;
83
- constructor(code: string, message: string);
84
- }
85
- /** Validate before asynchronous work; returned bytes are owned by the caller. */
86
- export declare function snapshotImage(image: Image): Image;
87
- export declare function snapshotPolicy(policy: Policy): Policy;
88
- /** Uncalibrated support ordering; ties preserve spatial output order. */
89
- export declare function rankBarcodes(barcodes: readonly Barcode[]): Barcode[];
90
- export declare class IndependentScanner {
91
- #private;
92
- private constructor();
93
- static create(bytes: BufferSource): Promise<IndependentScanner>;
94
- /** Localization-only entry point for opt-in independent linear decoders. */
95
- localizeOnly(image: Image, fitLimit?: number): {
96
- proposals: {
97
- polygon: Quad;
98
- score?: number;
99
- }[];
100
- workLimited?: boolean;
101
- omitted?: number;
102
- };
103
- /** All supplied regions receive the cheap pass; successful reads do not end scanning. */
104
- scan(image: Image, quads: readonly Quad[], policy?: Policy): ScanResult;
105
- /** Synchronous transaction: upload once, localize, decode the same owned pixels. */
106
- scanLocalized(image: Image, policy?: Policy, fitLimit?: number, fullFrame?: boolean): {
107
- localization: any;
108
- searchWindows: {
109
- kind: string;
110
- polygon: number[][];
111
- candidateIndex: any;
112
- }[];
113
- scan: ScanResult;
114
- localizationMs: number;
115
- decodingMs: number;
116
- scanMs: number;
117
- };
118
- /** Separate convenience; it never changes find-all work or suppresses frame evidence. */
119
- best(result: ScanFrame): Barcode | undefined;
120
- dispose(): void;
121
- }