@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.
- package/README.md +274 -0
- package/dist/components/DetectionOverlay.d.ts +57 -0
- package/dist/components/DetectionOverlay.js +133 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +9 -0
- package/dist/core/CacheManager.d.ts +168 -0
- package/dist/core/CacheManager.js +331 -0
- package/dist/core/MLVisionProvider.d.ts +90 -0
- package/dist/core/MLVisionProvider.js +188 -0
- package/dist/core/ServerClient.d.ts +131 -0
- package/dist/core/ServerClient.js +291 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +18 -0
- package/dist/hooks/classLabels.d.ts +35 -0
- package/dist/hooks/classLabels.js +439 -0
- package/dist/hooks/classLabelsCoco.d.ts +43 -0
- package/dist/hooks/classLabelsCoco.js +103 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.js +27 -0
- package/dist/hooks/useMultiBarcodeScanner.d.ts +34 -0
- package/dist/hooks/useMultiBarcodeScanner.js +290 -0
- package/dist/hooks/useProductDetector.d.ts +38 -0
- package/dist/hooks/useProductDetector.js +679 -0
- package/dist/hooks/useReceiptScanner.d.ts +37 -0
- package/dist/hooks/useReceiptScanner.js +405 -0
- package/dist/hooks/useVideoScanner.d.ts +118 -0
- package/dist/hooks/useVideoScanner.js +383 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +130 -0
- package/dist/processors/detectionProcessor.d.ts +86 -0
- package/dist/processors/detectionProcessor.js +124 -0
- package/dist/processors/index.d.ts +5 -0
- package/dist/processors/index.js +16 -0
- package/dist/processors/tfliteFrameProcessor.d.ts +90 -0
- package/dist/processors/tfliteFrameProcessor.js +213 -0
- package/dist/types/barcode.d.ts +91 -0
- package/dist/types/barcode.js +19 -0
- package/dist/types/detection.d.ts +166 -0
- package/dist/types/detection.js +8 -0
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +25 -0
- package/dist/types/ocr.d.ts +202 -0
- package/dist/types/ocr.js +8 -0
- package/dist/utils/imagePreprocessor.d.ts +85 -0
- package/dist/utils/imagePreprocessor.js +304 -0
- package/dist/utils/yoloProcessor.d.ts +40 -0
- package/dist/utils/yoloProcessor.js +154 -0
- package/package.json +78 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMultiBarcodeScanner Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for scanning multiple barcodes in a single frame or photo.
|
|
5
|
+
* Uses VisionCamera's built-in MLKit barcode scanning.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { useMultiBarcodeScanner } from '@souschef/ml-vision';
|
|
10
|
+
*
|
|
11
|
+
* function GroceryScanner() {
|
|
12
|
+
* const {
|
|
13
|
+
* isScanning,
|
|
14
|
+
* results,
|
|
15
|
+
* startScanning,
|
|
16
|
+
* stopScanning,
|
|
17
|
+
* scanPhoto,
|
|
18
|
+
* } = useMultiBarcodeScanner({
|
|
19
|
+
* formats: ['ean-13', 'upc-a'],
|
|
20
|
+
* onDetected: (barcodes) => console.log('Found:', barcodes),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* // Your camera UI
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import type { UseMultiBarcodeScannerOptions, UseMultiBarcodeScannerReturn } from '../types';
|
|
30
|
+
/**
|
|
31
|
+
* Hook for multi-barcode scanning
|
|
32
|
+
*/
|
|
33
|
+
export declare function useMultiBarcodeScanner(options?: UseMultiBarcodeScannerOptions): UseMultiBarcodeScannerReturn;
|
|
34
|
+
export default useMultiBarcodeScanner;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* useMultiBarcodeScanner Hook
|
|
4
|
+
*
|
|
5
|
+
* React hook for scanning multiple barcodes in a single frame or photo.
|
|
6
|
+
* Uses VisionCamera's built-in MLKit barcode scanning.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { useMultiBarcodeScanner } from '@souschef/ml-vision';
|
|
11
|
+
*
|
|
12
|
+
* function GroceryScanner() {
|
|
13
|
+
* const {
|
|
14
|
+
* isScanning,
|
|
15
|
+
* results,
|
|
16
|
+
* startScanning,
|
|
17
|
+
* stopScanning,
|
|
18
|
+
* scanPhoto,
|
|
19
|
+
* } = useMultiBarcodeScanner({
|
|
20
|
+
* formats: ['ean-13', 'upc-a'],
|
|
21
|
+
* onDetected: (barcodes) => console.log('Found:', barcodes),
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* // Your camera UI
|
|
26
|
+
* );
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.useMultiBarcodeScanner = useMultiBarcodeScanner;
|
|
32
|
+
const react_1 = require("react");
|
|
33
|
+
const barcode_1 = require("../types/barcode");
|
|
34
|
+
const MLVisionProvider_1 = require("../core/MLVisionProvider");
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Constants
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/** Default scan interval in milliseconds */
|
|
39
|
+
const DEFAULT_SCAN_INTERVAL = 100;
|
|
40
|
+
/** Maximum barcodes to detect per scan */
|
|
41
|
+
const DEFAULT_MAX_BARCODES = 20;
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Hook Implementation
|
|
44
|
+
// ============================================================================
|
|
45
|
+
/**
|
|
46
|
+
* Hook for multi-barcode scanning
|
|
47
|
+
*/
|
|
48
|
+
function useMultiBarcodeScanner(options = {}) {
|
|
49
|
+
const { formats = barcode_1.GROCERY_BARCODE_FORMATS, maxBarcodes = DEFAULT_MAX_BARCODES, scanInterval = DEFAULT_SCAN_INTERVAL, onDetected, onError, hapticFeedback = true, deduplicate = true, } = options;
|
|
50
|
+
// Context
|
|
51
|
+
const { cacheManager } = (0, MLVisionProvider_1.useMLVisionContext)();
|
|
52
|
+
// State
|
|
53
|
+
const [isReady, _setIsReady] = (0, react_1.useState)(true); // MLKit is always ready
|
|
54
|
+
const [isScanning, setIsScanning] = (0, react_1.useState)(false);
|
|
55
|
+
const [results, setResults] = (0, react_1.useState)([]);
|
|
56
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
57
|
+
// Refs
|
|
58
|
+
const seenBarcodesRef = (0, react_1.useRef)(new Set());
|
|
59
|
+
const lastScanTimeRef = (0, react_1.useRef)(0);
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Barcode Processing
|
|
62
|
+
// ============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Process detected barcodes from VisionCamera
|
|
65
|
+
*/
|
|
66
|
+
const processDetectedBarcodes = (0, react_1.useCallback)((codes) => {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
// Throttle processing
|
|
69
|
+
if (now - lastScanTimeRef.current < scanInterval) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
lastScanTimeRef.current = now;
|
|
73
|
+
// Convert to our format
|
|
74
|
+
const detections = codes
|
|
75
|
+
.slice(0, maxBarcodes)
|
|
76
|
+
.map((code) => {
|
|
77
|
+
const format = mapCodeType(code.type);
|
|
78
|
+
const barcodeData = {
|
|
79
|
+
format,
|
|
80
|
+
value: code.value,
|
|
81
|
+
displayValue: formatBarcodeDisplay(code.value, format),
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
id: `barcode_${now}_${Math.random().toString(36).substr(2, 9)}`,
|
|
85
|
+
type: 'barcode',
|
|
86
|
+
confidence: 1.0, // MLKit doesn't provide confidence
|
|
87
|
+
data: barcodeData,
|
|
88
|
+
source: 'on_device',
|
|
89
|
+
processingTimeMs: 0,
|
|
90
|
+
timestamp: now,
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.filter((det) => formats.includes(det.data.format));
|
|
94
|
+
// Deduplicate if enabled
|
|
95
|
+
let newDetections = detections;
|
|
96
|
+
if (deduplicate) {
|
|
97
|
+
newDetections = detections.filter((det) => {
|
|
98
|
+
const key = `${det.data.format}:${det.data.value}`;
|
|
99
|
+
if (seenBarcodesRef.current.has(key)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
seenBarcodesRef.current.add(key);
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (newDetections.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
// Update results
|
|
109
|
+
setResults((prev) => [...prev, ...newDetections]);
|
|
110
|
+
// Cache barcodes
|
|
111
|
+
if (cacheManager) {
|
|
112
|
+
for (const det of newDetections) {
|
|
113
|
+
cacheManager.setBarcode(det.data.value, {
|
|
114
|
+
barcode: det.data.value,
|
|
115
|
+
format: det.data.format,
|
|
116
|
+
found: true,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Callback
|
|
121
|
+
onDetected?.(newDetections);
|
|
122
|
+
// Haptic feedback
|
|
123
|
+
if (hapticFeedback && newDetections.length > 0) {
|
|
124
|
+
// Trigger haptic - implement based on your haptic service
|
|
125
|
+
// HapticService.light();
|
|
126
|
+
}
|
|
127
|
+
}, [
|
|
128
|
+
formats,
|
|
129
|
+
maxBarcodes,
|
|
130
|
+
scanInterval,
|
|
131
|
+
deduplicate,
|
|
132
|
+
cacheManager,
|
|
133
|
+
onDetected,
|
|
134
|
+
hapticFeedback,
|
|
135
|
+
]);
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Actions
|
|
138
|
+
// ============================================================================
|
|
139
|
+
/**
|
|
140
|
+
* Start continuous scanning
|
|
141
|
+
*/
|
|
142
|
+
const startScanning = (0, react_1.useCallback)(() => {
|
|
143
|
+
setIsScanning(true);
|
|
144
|
+
setError(null);
|
|
145
|
+
}, []);
|
|
146
|
+
/**
|
|
147
|
+
* Stop scanning
|
|
148
|
+
*/
|
|
149
|
+
const stopScanning = (0, react_1.useCallback)(() => {
|
|
150
|
+
setIsScanning(false);
|
|
151
|
+
}, []);
|
|
152
|
+
/**
|
|
153
|
+
* Scan a photo for barcodes
|
|
154
|
+
*/
|
|
155
|
+
const scanPhoto = (0, react_1.useCallback)(async (_uri) => {
|
|
156
|
+
try {
|
|
157
|
+
setError(null);
|
|
158
|
+
// Note: Photo scanning requires native module integration
|
|
159
|
+
// This is a placeholder that would use MLKit's image labeling
|
|
160
|
+
// In practice, you'd use a native module or server endpoint
|
|
161
|
+
console.log('[useMultiBarcodeScanner] Photo scanning not yet implemented natively');
|
|
162
|
+
console.log('[useMultiBarcodeScanner] Use real-time camera scanning instead');
|
|
163
|
+
// Return empty for now - implement with native module
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const scanError = err instanceof Error ? err : new Error('Scan failed');
|
|
168
|
+
setError(scanError);
|
|
169
|
+
onError?.(scanError);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}, [onError]);
|
|
173
|
+
/**
|
|
174
|
+
* Clear all results
|
|
175
|
+
*/
|
|
176
|
+
const clearResults = (0, react_1.useCallback)(() => {
|
|
177
|
+
setResults([]);
|
|
178
|
+
}, []);
|
|
179
|
+
/**
|
|
180
|
+
* Reset scanner state
|
|
181
|
+
*/
|
|
182
|
+
const reset = (0, react_1.useCallback)(() => {
|
|
183
|
+
setResults([]);
|
|
184
|
+
setError(null);
|
|
185
|
+
seenBarcodesRef.current.clear();
|
|
186
|
+
setIsScanning(false);
|
|
187
|
+
}, []);
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// Frame Processor Configuration
|
|
190
|
+
// ============================================================================
|
|
191
|
+
/**
|
|
192
|
+
* VisionCamera code scanner configuration
|
|
193
|
+
* Use this with VisionCamera's codeScanner prop
|
|
194
|
+
*/
|
|
195
|
+
const codeScannerConfig = (0, react_1.useMemo)(() => ({
|
|
196
|
+
codeTypes: formats.map(mapFormatToCodeType),
|
|
197
|
+
onCodeScanned: processDetectedBarcodes,
|
|
198
|
+
}), [formats, processDetectedBarcodes]);
|
|
199
|
+
// Frame processor is the code scanner config for VisionCamera
|
|
200
|
+
const frameProcessor = (0, react_1.useMemo)(() => {
|
|
201
|
+
if (!isScanning)
|
|
202
|
+
return null;
|
|
203
|
+
return codeScannerConfig;
|
|
204
|
+
}, [isScanning, codeScannerConfig]);
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Return
|
|
207
|
+
// ============================================================================
|
|
208
|
+
return {
|
|
209
|
+
// State
|
|
210
|
+
isReady,
|
|
211
|
+
isScanning,
|
|
212
|
+
results,
|
|
213
|
+
error,
|
|
214
|
+
// Actions
|
|
215
|
+
startScanning,
|
|
216
|
+
stopScanning,
|
|
217
|
+
scanPhoto,
|
|
218
|
+
clearResults,
|
|
219
|
+
reset,
|
|
220
|
+
// Frame processor
|
|
221
|
+
frameProcessor,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// Helper Functions
|
|
226
|
+
// ============================================================================
|
|
227
|
+
/**
|
|
228
|
+
* Map VisionCamera code type to our BarcodeFormat
|
|
229
|
+
*/
|
|
230
|
+
function mapCodeType(codeType) {
|
|
231
|
+
const mapping = {
|
|
232
|
+
'ean-13': 'ean-13',
|
|
233
|
+
'ean-8': 'ean-8',
|
|
234
|
+
'upc-a': 'upc-a',
|
|
235
|
+
'upc-e': 'upc-e',
|
|
236
|
+
'code-128': 'code-128',
|
|
237
|
+
'code-39': 'code-39',
|
|
238
|
+
'code-93': 'code-93',
|
|
239
|
+
'itf': 'itf',
|
|
240
|
+
'codabar': 'codabar',
|
|
241
|
+
'qr': 'qr',
|
|
242
|
+
'data-matrix': 'data-matrix',
|
|
243
|
+
'pdf-417': 'pdf-417',
|
|
244
|
+
'aztec': 'aztec',
|
|
245
|
+
// Handle variations
|
|
246
|
+
EAN_13: 'ean-13',
|
|
247
|
+
EAN_8: 'ean-8',
|
|
248
|
+
UPC_A: 'upc-a',
|
|
249
|
+
UPC_E: 'upc-e',
|
|
250
|
+
CODE_128: 'code-128',
|
|
251
|
+
CODE_39: 'code-39',
|
|
252
|
+
CODE_93: 'code-93',
|
|
253
|
+
ITF: 'itf',
|
|
254
|
+
CODABAR: 'codabar',
|
|
255
|
+
QR_CODE: 'qr',
|
|
256
|
+
DATA_MATRIX: 'data-matrix',
|
|
257
|
+
PDF_417: 'pdf-417',
|
|
258
|
+
AZTEC: 'aztec',
|
|
259
|
+
};
|
|
260
|
+
return mapping[codeType] || 'code-128';
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Map our BarcodeFormat to VisionCamera code type
|
|
264
|
+
*/
|
|
265
|
+
function mapFormatToCodeType(format) {
|
|
266
|
+
// VisionCamera uses the same format names
|
|
267
|
+
return format;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Format barcode value for display
|
|
271
|
+
*/
|
|
272
|
+
function formatBarcodeDisplay(value, format) {
|
|
273
|
+
switch (format) {
|
|
274
|
+
case 'ean-13':
|
|
275
|
+
// Format: X-XXXXXX-XXXXX-X
|
|
276
|
+
if (value.length === 13) {
|
|
277
|
+
return `${value[0]}-${value.slice(1, 7)}-${value.slice(7, 12)}-${value[12]}`;
|
|
278
|
+
}
|
|
279
|
+
return value;
|
|
280
|
+
case 'upc-a':
|
|
281
|
+
// Format: X-XXXXX-XXXXX-X
|
|
282
|
+
if (value.length === 12) {
|
|
283
|
+
return `${value[0]}-${value.slice(1, 6)}-${value.slice(6, 11)}-${value[11]}`;
|
|
284
|
+
}
|
|
285
|
+
return value;
|
|
286
|
+
default:
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
exports.default = useMultiBarcodeScanner;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useProductDetector Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for visual product detection using TFLite models.
|
|
5
|
+
* Detects products in photos or camera frames using YOLOv8.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { useProductDetector } from '@souschef/ml-vision';
|
|
10
|
+
*
|
|
11
|
+
* function FridgeScanner() {
|
|
12
|
+
* const {
|
|
13
|
+
* isModelLoaded,
|
|
14
|
+
* isDetecting,
|
|
15
|
+
* detections,
|
|
16
|
+
* detectProducts,
|
|
17
|
+
* } = useProductDetector({
|
|
18
|
+
* minConfidence: 0.5,
|
|
19
|
+
* onDetected: (products) => console.log('Found:', products),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const handleCapture = async (photoUri: string) => {
|
|
23
|
+
* const results = await detectProducts(photoUri);
|
|
24
|
+
* // results contain detected products with bounding boxes
|
|
25
|
+
* };
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* // Your camera UI
|
|
29
|
+
* );
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
import type { UseProductDetectorOptions, UseProductDetectorReturn } from '../types';
|
|
34
|
+
/**
|
|
35
|
+
* Hook for visual product detection using TFLite
|
|
36
|
+
*/
|
|
37
|
+
export declare function useProductDetector(options?: UseProductDetectorOptions): UseProductDetectorReturn;
|
|
38
|
+
export default useProductDetector;
|