@souscheflabs/ml-vision 0.1.0

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 (48) hide show
  1. package/README.md +274 -0
  2. package/dist/components/DetectionOverlay.d.ts +57 -0
  3. package/dist/components/DetectionOverlay.js +133 -0
  4. package/dist/components/index.d.ts +4 -0
  5. package/dist/components/index.js +9 -0
  6. package/dist/core/CacheManager.d.ts +168 -0
  7. package/dist/core/CacheManager.js +331 -0
  8. package/dist/core/MLVisionProvider.d.ts +90 -0
  9. package/dist/core/MLVisionProvider.js +188 -0
  10. package/dist/core/ServerClient.d.ts +131 -0
  11. package/dist/core/ServerClient.js +291 -0
  12. package/dist/core/index.d.ts +6 -0
  13. package/dist/core/index.js +18 -0
  14. package/dist/hooks/classLabels.d.ts +35 -0
  15. package/dist/hooks/classLabels.js +439 -0
  16. package/dist/hooks/classLabelsCoco.d.ts +43 -0
  17. package/dist/hooks/classLabelsCoco.js +103 -0
  18. package/dist/hooks/index.d.ts +8 -0
  19. package/dist/hooks/index.js +27 -0
  20. package/dist/hooks/useMultiBarcodeScanner.d.ts +34 -0
  21. package/dist/hooks/useMultiBarcodeScanner.js +290 -0
  22. package/dist/hooks/useProductDetector.d.ts +38 -0
  23. package/dist/hooks/useProductDetector.js +679 -0
  24. package/dist/hooks/useReceiptScanner.d.ts +37 -0
  25. package/dist/hooks/useReceiptScanner.js +405 -0
  26. package/dist/hooks/useVideoScanner.d.ts +118 -0
  27. package/dist/hooks/useVideoScanner.js +383 -0
  28. package/dist/index.d.ts +58 -0
  29. package/dist/index.js +130 -0
  30. package/dist/processors/detectionProcessor.d.ts +86 -0
  31. package/dist/processors/detectionProcessor.js +124 -0
  32. package/dist/processors/index.d.ts +5 -0
  33. package/dist/processors/index.js +16 -0
  34. package/dist/processors/tfliteFrameProcessor.d.ts +90 -0
  35. package/dist/processors/tfliteFrameProcessor.js +213 -0
  36. package/dist/types/barcode.d.ts +91 -0
  37. package/dist/types/barcode.js +19 -0
  38. package/dist/types/detection.d.ts +166 -0
  39. package/dist/types/detection.js +8 -0
  40. package/dist/types/index.d.ts +126 -0
  41. package/dist/types/index.js +25 -0
  42. package/dist/types/ocr.d.ts +202 -0
  43. package/dist/types/ocr.js +8 -0
  44. package/dist/utils/imagePreprocessor.d.ts +85 -0
  45. package/dist/utils/imagePreprocessor.js +304 -0
  46. package/dist/utils/yoloProcessor.d.ts +40 -0
  47. package/dist/utils/yoloProcessor.js +154 -0
  48. package/package.json +78 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * useReceiptScanner Hook
3
+ *
4
+ * React hook for scanning and parsing grocery receipts.
5
+ * Uses MLKit text recognition with intelligent parsing.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { useReceiptScanner } from '@souschef/ml-vision';
10
+ *
11
+ * function ReceiptScreen() {
12
+ * const {
13
+ * items,
14
+ * metadata,
15
+ * isProcessing,
16
+ * scanReceipt,
17
+ * } = useReceiptScanner({
18
+ * onParseComplete: (result) => console.log('Parsed:', result),
19
+ * });
20
+ *
21
+ * const handleCapture = async (photoUri: string) => {
22
+ * const result = await scanReceipt(photoUri);
23
+ * // result.items contains parsed line items
24
+ * };
25
+ *
26
+ * return (
27
+ * // Your UI
28
+ * );
29
+ * }
30
+ * ```
31
+ */
32
+ import type { UseReceiptScannerOptions, UseReceiptScannerReturn } from '../types';
33
+ /**
34
+ * Hook for receipt scanning and parsing
35
+ */
36
+ export declare function useReceiptScanner(options?: UseReceiptScannerOptions): UseReceiptScannerReturn;
37
+ export default useReceiptScanner;
@@ -0,0 +1,405 @@
1
+ "use strict";
2
+ /**
3
+ * useReceiptScanner Hook
4
+ *
5
+ * React hook for scanning and parsing grocery receipts.
6
+ * Uses MLKit text recognition with intelligent parsing.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { useReceiptScanner } from '@souschef/ml-vision';
11
+ *
12
+ * function ReceiptScreen() {
13
+ * const {
14
+ * items,
15
+ * metadata,
16
+ * isProcessing,
17
+ * scanReceipt,
18
+ * } = useReceiptScanner({
19
+ * onParseComplete: (result) => console.log('Parsed:', result),
20
+ * });
21
+ *
22
+ * const handleCapture = async (photoUri: string) => {
23
+ * const result = await scanReceipt(photoUri);
24
+ * // result.items contains parsed line items
25
+ * };
26
+ *
27
+ * return (
28
+ * // Your UI
29
+ * );
30
+ * }
31
+ * ```
32
+ */
33
+ var __importDefault = (this && this.__importDefault) || function (mod) {
34
+ return (mod && mod.__esModule) ? mod : { "default": mod };
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.useReceiptScanner = useReceiptScanner;
38
+ const react_1 = require("react");
39
+ const text_recognition_1 = __importDefault(require("@react-native-ml-kit/text-recognition"));
40
+ const MLVisionProvider_1 = require("../core/MLVisionProvider");
41
+ // ============================================================================
42
+ // Receipt Parsing Patterns
43
+ // ============================================================================
44
+ /** Price pattern: matches $X.XX, X.XX, etc. */
45
+ const PRICE_PATTERN = /\$?\d{1,4}\.\d{2}/g;
46
+ /** Quantity pattern: matches Xkg, X lb, X ct, etc. */
47
+ const QUANTITY_PATTERN = /(\d+(?:\.\d+)?)\s*(kg|lb|oz|ct|ea|pc|g|ml|l|gal)?/i;
48
+ /** Date patterns */
49
+ const DATE_PATTERNS = [
50
+ /(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})/, // MM/DD/YYYY or DD/MM/YYYY
51
+ /(\w{3})\s+(\d{1,2}),?\s*(\d{4})/i, // Jan 15, 2024
52
+ ];
53
+ /** Time pattern */
54
+ const TIME_PATTERN = /\d{1,2}:\d{2}(?::\d{2})?\s*(?:AM|PM)?/i;
55
+ // Total/Subtotal patterns (used for line classification)
56
+ // TOTAL|GRAND TOTAL|AMOUNT DUE, SUBTOTAL, TAX
57
+ /** Known store patterns for detection */
58
+ const STORE_PATTERNS = [
59
+ { pattern: /WALMART/i, store: 'walmart' },
60
+ { pattern: /TARGET/i, store: 'target' },
61
+ { pattern: /KROGER/i, store: 'kroger' },
62
+ { pattern: /SAFEWAY/i, store: 'safeway' },
63
+ { pattern: /COSTCO/i, store: 'costco' },
64
+ { pattern: /TRADER\s*JOE/i, store: 'trader_joes' },
65
+ { pattern: /WHOLE\s*FOODS/i, store: 'whole_foods' },
66
+ { pattern: /PUBLIX/i, store: 'publix' },
67
+ { pattern: /ALDI/i, store: 'aldi' },
68
+ ];
69
+ // ============================================================================
70
+ // Hook Implementation
71
+ // ============================================================================
72
+ /**
73
+ * Hook for receipt scanning and parsing
74
+ */
75
+ function useReceiptScanner(options = {}) {
76
+ const { serverFallback = true, serverUrl, minConfidence = 0.7, onItemDetected, onParseComplete, onError, } = options;
77
+ // Context
78
+ const { serverClient, serverAvailable, config } = (0, MLVisionProvider_1.useMLVisionContext)();
79
+ // State
80
+ const [isProcessing, setIsProcessing] = (0, react_1.useState)(false);
81
+ const [items, setItems] = (0, react_1.useState)([]);
82
+ const [metadata, setMetadata] = (0, react_1.useState)(null);
83
+ const [rawText, setRawText] = (0, react_1.useState)('');
84
+ const [confidence, setConfidence] = (0, react_1.useState)(0);
85
+ const [source, setSource] = (0, react_1.useState)(null);
86
+ const [error, setError] = (0, react_1.useState)(null);
87
+ // Refs
88
+ const mountedRef = (0, react_1.useRef)(true);
89
+ // Computed server URL
90
+ const effectiveServerUrl = serverUrl || config.serverUrl;
91
+ // ============================================================================
92
+ // Receipt Parsing Logic
93
+ // ============================================================================
94
+ /**
95
+ * Parse raw OCR text into receipt items
96
+ */
97
+ const parseReceiptText = (0, react_1.useCallback)((text) => {
98
+ const lines = text.split('\n').filter(line => line.trim());
99
+ const parsedLines = lines.map((line, index) => classifyLine(line, index));
100
+ // Extract metadata
101
+ const extractedMetadata = {};
102
+ // Find store name
103
+ for (const { pattern, store } of STORE_PATTERNS) {
104
+ if (pattern.test(text)) {
105
+ extractedMetadata.storeName = store.toUpperCase().replace('_', ' ');
106
+ break;
107
+ }
108
+ }
109
+ // Find date and time
110
+ for (const line of lines.slice(0, 10)) {
111
+ for (const datePattern of DATE_PATTERNS) {
112
+ const match = line.match(datePattern);
113
+ if (match) {
114
+ try {
115
+ extractedMetadata.date = new Date(match[0]);
116
+ }
117
+ catch {
118
+ // Invalid date
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ const timeMatch = line.match(TIME_PATTERN);
124
+ if (timeMatch) {
125
+ extractedMetadata.time = timeMatch[0];
126
+ }
127
+ }
128
+ // Find totals
129
+ for (const parsedLine of parsedLines) {
130
+ if (parsedLine.type === 'total' && parsedLine.values.price) {
131
+ extractedMetadata.total = parsedLine.values.price;
132
+ }
133
+ else if (parsedLine.type === 'subtotal' && parsedLine.values.price) {
134
+ extractedMetadata.subtotal = parsedLine.values.price;
135
+ }
136
+ else if (parsedLine.type === 'tax' && parsedLine.values.price) {
137
+ extractedMetadata.tax = parsedLine.values.price;
138
+ }
139
+ }
140
+ // Extract items
141
+ const extractedItems = parsedLines
142
+ .filter(line => line.type === 'item')
143
+ .map(line => {
144
+ const item = {
145
+ name: line.values.name || line.text,
146
+ normalizedName: normalizeItemName(line.values.name || line.text),
147
+ quantity: line.values.quantity,
148
+ totalPrice: line.values.price,
149
+ confidence: line.confidence,
150
+ rawText: line.text,
151
+ lineNumber: line.lineNumber,
152
+ needsReview: line.confidence < minConfidence,
153
+ };
154
+ // Notify per item
155
+ onItemDetected?.(item);
156
+ return item;
157
+ });
158
+ return { items: extractedItems, metadata: extractedMetadata };
159
+ }, [minConfidence, onItemDetected]);
160
+ /**
161
+ * Classify a single receipt line
162
+ */
163
+ function classifyLine(text, lineNumber) {
164
+ const trimmed = text.trim();
165
+ let type = 'unknown';
166
+ let lineConfidence = 0.5;
167
+ const values = {};
168
+ // Check for totals first
169
+ if (/^TOTAL/i.test(trimmed) || /GRAND\s*TOTAL/i.test(trimmed)) {
170
+ type = 'total';
171
+ lineConfidence = 0.95;
172
+ }
173
+ else if (/^SUBTOTAL/i.test(trimmed)) {
174
+ type = 'subtotal';
175
+ lineConfidence = 0.95;
176
+ }
177
+ else if (/^TAX/i.test(trimmed) || /SALES\s*TAX/i.test(trimmed)) {
178
+ type = 'tax';
179
+ lineConfidence = 0.95;
180
+ }
181
+ else if (/^(CASH|CREDIT|DEBIT|VISA|MASTERCARD|AMEX)/i.test(trimmed)) {
182
+ type = 'payment';
183
+ lineConfidence = 0.9;
184
+ }
185
+ else if (DATE_PATTERNS.some(p => p.test(trimmed))) {
186
+ type = 'date_time';
187
+ lineConfidence = 0.85;
188
+ }
189
+ else if (STORE_PATTERNS.some(({ pattern }) => pattern.test(trimmed))) {
190
+ type = 'store_info';
191
+ lineConfidence = 0.9;
192
+ }
193
+ else if (/^\d{10,20}$/.test(trimmed.replace(/[\s-]/g, ''))) {
194
+ type = 'transaction';
195
+ lineConfidence = 0.7;
196
+ }
197
+ else if (trimmed.length > 3) {
198
+ // Likely an item line
199
+ type = 'item';
200
+ // Extract price
201
+ const prices = trimmed.match(PRICE_PATTERN);
202
+ if (prices && prices.length > 0) {
203
+ const lastPrice = prices[prices.length - 1];
204
+ values.price = parseFloat(lastPrice.replace('$', ''));
205
+ lineConfidence = 0.8;
206
+ }
207
+ // Extract quantity
208
+ const qtyMatch = trimmed.match(QUANTITY_PATTERN);
209
+ if (qtyMatch) {
210
+ values.quantity = parseFloat(qtyMatch[1]);
211
+ }
212
+ // Extract name (text before price)
213
+ if (prices && prices.length > 0) {
214
+ const priceIndex = trimmed.indexOf(prices[0]);
215
+ if (priceIndex > 0) {
216
+ values.name = trimmed.substring(0, priceIndex).trim();
217
+ }
218
+ }
219
+ else {
220
+ values.name = trimmed;
221
+ lineConfidence = 0.6; // Lower confidence without price
222
+ }
223
+ }
224
+ // Extract price for non-item lines too
225
+ if (type !== 'item' && type !== 'unknown') {
226
+ const prices = trimmed.match(PRICE_PATTERN);
227
+ if (prices && prices.length > 0) {
228
+ const lastPrice = prices[prices.length - 1];
229
+ values.price = parseFloat(lastPrice.replace('$', ''));
230
+ }
231
+ }
232
+ return { text: trimmed, type, values, lineNumber, confidence: lineConfidence };
233
+ }
234
+ /**
235
+ * Normalize item name for matching
236
+ */
237
+ function normalizeItemName(name) {
238
+ return name
239
+ .toLowerCase()
240
+ .replace(/[^a-z0-9\s]/g, '')
241
+ .replace(/\s+/g, ' ')
242
+ .trim();
243
+ }
244
+ // ============================================================================
245
+ // Actions
246
+ // ============================================================================
247
+ /**
248
+ * Scan a receipt photo
249
+ */
250
+ const scanReceipt = (0, react_1.useCallback)(async (uri) => {
251
+ setIsProcessing(true);
252
+ setError(null);
253
+ const startTime = Date.now();
254
+ try {
255
+ let ocrText = '';
256
+ let processSource = 'on_device';
257
+ // Try server OCR first if available (better accuracy for receipts)
258
+ if (serverFallback &&
259
+ effectiveServerUrl &&
260
+ serverClient &&
261
+ serverAvailable) {
262
+ try {
263
+ // Note: Server OCR endpoint would return text
264
+ // For now, simulate with placeholder
265
+ console.log('[useReceiptScanner] Server OCR not yet implemented');
266
+ }
267
+ catch {
268
+ console.log('[useReceiptScanner] Server OCR failed, using on-device');
269
+ }
270
+ }
271
+ // Use on-device OCR with MLKit
272
+ if (!ocrText) {
273
+ console.log('[useReceiptScanner] Running MLKit text recognition...');
274
+ try {
275
+ const result = await text_recognition_1.default.recognize(uri);
276
+ ocrText = result.text;
277
+ console.log(`[useReceiptScanner] MLKit recognized ${result.blocks.length} blocks, ${ocrText.length} chars`);
278
+ processSource = 'on_device';
279
+ }
280
+ catch (ocrErr) {
281
+ console.error('[useReceiptScanner] MLKit OCR error:', ocrErr);
282
+ throw new Error(`OCR failed: ${ocrErr instanceof Error ? ocrErr.message : 'Unknown error'}`);
283
+ }
284
+ }
285
+ // Parse the OCR text
286
+ const { items: parsedItems, metadata: parsedMetadata } = parseReceiptText(ocrText);
287
+ const processingTimeMs = Date.now() - startTime;
288
+ const overallConfidence = parsedItems.length > 0
289
+ ? parsedItems.reduce((sum, item) => sum + item.confidence, 0) /
290
+ parsedItems.length
291
+ : 0;
292
+ // Build result
293
+ const result = {
294
+ id: `receipt_${Date.now()}_${Math.random()
295
+ .toString(36)
296
+ .substr(2, 9)}`,
297
+ type: 'receipt',
298
+ items: parsedItems,
299
+ metadata: parsedMetadata,
300
+ rawText: ocrText,
301
+ textLines: [], // Would be populated by MLKit
302
+ confidence: overallConfidence,
303
+ itemsNeedingReview: parsedItems.filter(i => i.needsReview).length,
304
+ source: processSource,
305
+ processingTimeMs,
306
+ timestamp: Date.now(),
307
+ };
308
+ // Update state
309
+ if (mountedRef.current) {
310
+ setItems(parsedItems);
311
+ setMetadata(parsedMetadata);
312
+ setRawText(ocrText);
313
+ setConfidence(overallConfidence);
314
+ setSource(processSource);
315
+ }
316
+ // Callback
317
+ onParseComplete?.(result);
318
+ return result;
319
+ }
320
+ catch (err) {
321
+ const scanError = err instanceof Error ? err : new Error('Receipt scan failed');
322
+ console.error('[useReceiptScanner] Error:', scanError);
323
+ if (mountedRef.current) {
324
+ setError(scanError);
325
+ }
326
+ onError?.(scanError);
327
+ throw scanError;
328
+ }
329
+ finally {
330
+ if (mountedRef.current) {
331
+ setIsProcessing(false);
332
+ }
333
+ }
334
+ }, [
335
+ serverFallback,
336
+ effectiveServerUrl,
337
+ serverClient,
338
+ serverAvailable,
339
+ parseReceiptText,
340
+ onParseComplete,
341
+ onError,
342
+ ]);
343
+ /**
344
+ * Scan current camera frame
345
+ */
346
+ const scanFrame = (0, react_1.useCallback)(async () => {
347
+ // This would capture current frame and scan
348
+ // Requires VisionCamera integration
349
+ throw new Error('Frame scanning not implemented - use scanReceipt with photo URI');
350
+ }, []);
351
+ /**
352
+ * Confirm an item is correct
353
+ */
354
+ const confirmItem = (0, react_1.useCallback)((item) => {
355
+ setItems(prev => prev.map(i => i.lineNumber === item.lineNumber ? { ...i, needsReview: false } : i));
356
+ }, []);
357
+ /**
358
+ * Reject/remove an item
359
+ */
360
+ const rejectItem = (0, react_1.useCallback)((item) => {
361
+ setItems(prev => prev.filter(i => i.lineNumber !== item.lineNumber));
362
+ }, []);
363
+ /**
364
+ * Edit an item
365
+ */
366
+ const editItem = (0, react_1.useCallback)((item, updates) => {
367
+ setItems(prev => prev.map(i => i.lineNumber === item.lineNumber
368
+ ? { ...i, ...updates, needsReview: false }
369
+ : i));
370
+ }, []);
371
+ /**
372
+ * Clear all results
373
+ */
374
+ const clearResults = (0, react_1.useCallback)(() => {
375
+ setItems([]);
376
+ setMetadata(null);
377
+ setRawText('');
378
+ setConfidence(0);
379
+ setSource(null);
380
+ setError(null);
381
+ }, []);
382
+ // ============================================================================
383
+ // Return
384
+ // ============================================================================
385
+ return {
386
+ // State
387
+ isProcessing,
388
+ items,
389
+ metadata,
390
+ rawText,
391
+ confidence,
392
+ source,
393
+ error,
394
+ // Actions
395
+ scanReceipt,
396
+ scanFrame,
397
+ confirmItem,
398
+ rejectItem,
399
+ editItem,
400
+ clearResults,
401
+ // Frame processor (placeholder)
402
+ frameProcessor: null,
403
+ };
404
+ }
405
+ exports.default = useReceiptScanner;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * useVideoScanner Hook
3
+ *
4
+ * React hook for real-time video scanning combining product detection,
5
+ * barcode scanning, and text recognition.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { useVideoScanner } from '@souschef/ml-vision';
10
+ *
11
+ * function LiveScanner() {
12
+ * const {
13
+ * isScanning,
14
+ * products,
15
+ * barcodes,
16
+ * startScanning,
17
+ * stopScanning,
18
+ * cameraRef,
19
+ * } = useVideoScanner({
20
+ * modes: ['products', 'barcodes'],
21
+ * onDetected: (results) => console.log('Found:', results),
22
+ * });
23
+ *
24
+ * return (
25
+ * <Camera ref={cameraRef} />
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+ import type { TensorflowModel } from 'react-native-fast-tflite';
31
+ import type { Camera, Frame } from 'react-native-vision-camera';
32
+ import type { ProductDetectionResult, BarcodeDetectionResult, ProductCategory } from '../types';
33
+ /**
34
+ * Scanning mode
35
+ */
36
+ export type ScanMode = 'products' | 'barcodes' | 'text';
37
+ /**
38
+ * Combined detection result
39
+ */
40
+ export interface VideoScanResult {
41
+ products: ProductDetectionResult[];
42
+ barcodes: BarcodeDetectionResult[];
43
+ timestamp: number;
44
+ frameNumber: number;
45
+ }
46
+ /**
47
+ * Options for useVideoScanner hook
48
+ */
49
+ export interface UseVideoScannerOptions {
50
+ /** Scanning modes to enable (default: ['products', 'barcodes']) */
51
+ modes?: ScanMode[];
52
+ /** Frame rate for processing (default: 10 fps) */
53
+ targetFps?: number;
54
+ /** Minimum confidence for product detection (default: 0.5) */
55
+ minProductConfidence?: number;
56
+ /** Product categories to detect */
57
+ productCategories?: ProductCategory[];
58
+ /** Maximum products per frame (default: 10) */
59
+ maxProducts?: number;
60
+ /** Maximum barcodes per frame (default: 5) */
61
+ maxBarcodes?: number;
62
+ /** Enable temporal smoothing (default: true) */
63
+ temporalSmoothing?: boolean;
64
+ /** Number of frames for smoothing (default: 3) */
65
+ smoothingFrames?: number;
66
+ /** Callback when items are detected */
67
+ onDetected?: (results: VideoScanResult) => void;
68
+ /** Callback on error */
69
+ onError?: (error: Error) => void;
70
+ /** Enable haptic feedback (default: true) */
71
+ hapticFeedback?: boolean;
72
+ /** Loaded TFLite model for on-device frame processing */
73
+ tfliteModel?: TensorflowModel | null;
74
+ /** Use on-device TFLite frame processor instead of server (default: true if model provided) */
75
+ useOnDeviceFrameProcessor?: boolean;
76
+ /** Only detect food classes when using COCO model (default: true) */
77
+ foodOnly?: boolean;
78
+ }
79
+ /**
80
+ * Return type for useVideoScanner hook
81
+ */
82
+ export interface UseVideoScannerReturn {
83
+ /** Whether scanning is active */
84
+ isScanning: boolean;
85
+ /** Whether all models are loaded */
86
+ isReady: boolean;
87
+ /** Detected products (smoothed) */
88
+ products: ProductDetectionResult[];
89
+ /** Detected barcodes */
90
+ barcodes: BarcodeDetectionResult[];
91
+ /** Current frame number */
92
+ frameNumber: number;
93
+ /** Average FPS */
94
+ fps: number;
95
+ /** Current error if any */
96
+ error: Error | null;
97
+ /** Last inference time in milliseconds */
98
+ inferenceTimeMs: number;
99
+ /** Start video scanning */
100
+ startScanning: () => void;
101
+ /** Stop video scanning */
102
+ stopScanning: () => void;
103
+ /** Toggle scanning */
104
+ toggleScanning: () => void;
105
+ /** Clear all detections */
106
+ clearDetections: () => void;
107
+ /** Capture current frame as photo */
108
+ captureFrame: () => Promise<string | null>;
109
+ cameraRef: React.RefObject<Camera | null>;
110
+ frameProcessorConfig: unknown;
111
+ /** Frame processor function for use with VisionCamera (if using TFLite) */
112
+ frameProcessor: ((frame: Frame) => void) | undefined;
113
+ }
114
+ /**
115
+ * Hook for real-time video scanning
116
+ */
117
+ export declare function useVideoScanner(options?: UseVideoScannerOptions): UseVideoScannerReturn;
118
+ export default useVideoScanner;