@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,383 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* useVideoScanner Hook
|
|
4
|
+
*
|
|
5
|
+
* React hook for real-time video scanning combining product detection,
|
|
6
|
+
* barcode scanning, and text recognition.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { useVideoScanner } from '@souschef/ml-vision';
|
|
11
|
+
*
|
|
12
|
+
* function LiveScanner() {
|
|
13
|
+
* const {
|
|
14
|
+
* isScanning,
|
|
15
|
+
* products,
|
|
16
|
+
* barcodes,
|
|
17
|
+
* startScanning,
|
|
18
|
+
* stopScanning,
|
|
19
|
+
* cameraRef,
|
|
20
|
+
* } = useVideoScanner({
|
|
21
|
+
* modes: ['products', 'barcodes'],
|
|
22
|
+
* onDetected: (results) => console.log('Found:', results),
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* return (
|
|
26
|
+
* <Camera ref={cameraRef} />
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.useVideoScanner = useVideoScanner;
|
|
33
|
+
const react_1 = require("react");
|
|
34
|
+
const MLVisionProvider_1 = require("../core/MLVisionProvider");
|
|
35
|
+
const useProductDetector_1 = require("./useProductDetector");
|
|
36
|
+
const useMultiBarcodeScanner_1 = require("./useMultiBarcodeScanner");
|
|
37
|
+
const tfliteFrameProcessor_1 = require("../processors/tfliteFrameProcessor");
|
|
38
|
+
const classLabelsCoco_1 = require("./classLabelsCoco");
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Constants
|
|
41
|
+
// ============================================================================
|
|
42
|
+
const DEFAULT_TARGET_FPS = 10;
|
|
43
|
+
const DEFAULT_SMOOTHING_FRAMES = 3;
|
|
44
|
+
const MS_PER_SECOND = 1000;
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Hook Implementation
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Hook for real-time video scanning
|
|
50
|
+
*/
|
|
51
|
+
function useVideoScanner(options = {}) {
|
|
52
|
+
const { modes = ['products', 'barcodes'], targetFps = DEFAULT_TARGET_FPS, minProductConfidence = 0.5, productCategories, maxProducts = 10, maxBarcodes = 5, temporalSmoothing = true, smoothingFrames = DEFAULT_SMOOTHING_FRAMES, onDetected, onError, hapticFeedback = true,
|
|
53
|
+
// TFLite options
|
|
54
|
+
tfliteModel, useOnDeviceFrameProcessor = !!tfliteModel, foodOnly = true, } = options;
|
|
55
|
+
// Context
|
|
56
|
+
const { isInitialized } = (0, MLVisionProvider_1.useMLVisionContext)();
|
|
57
|
+
// Child hooks
|
|
58
|
+
const productDetector = (0, useProductDetector_1.useProductDetector)({
|
|
59
|
+
model: 'fast',
|
|
60
|
+
minConfidence: minProductConfidence,
|
|
61
|
+
categories: productCategories,
|
|
62
|
+
maxDetections: maxProducts,
|
|
63
|
+
hapticFeedback: false, // We'll handle haptics at video level
|
|
64
|
+
});
|
|
65
|
+
const barcodeScanner = (0, useMultiBarcodeScanner_1.useMultiBarcodeScanner)({
|
|
66
|
+
maxBarcodes,
|
|
67
|
+
hapticFeedback: false,
|
|
68
|
+
});
|
|
69
|
+
// State
|
|
70
|
+
const [isScanning, setIsScanning] = (0, react_1.useState)(false);
|
|
71
|
+
const [products, setProducts] = (0, react_1.useState)([]);
|
|
72
|
+
const [barcodes, setBarcodes] = (0, react_1.useState)([]);
|
|
73
|
+
const [frameNumber, setFrameNumber] = (0, react_1.useState)(0);
|
|
74
|
+
const [fps, setFps] = (0, react_1.useState)(0);
|
|
75
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
76
|
+
const [inferenceTimeMs, _setInferenceTimeMs] = (0, react_1.useState)(0);
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// TFLite Frame Processor (for real-time video detection)
|
|
79
|
+
// ============================================================================
|
|
80
|
+
/**
|
|
81
|
+
* Convert TFLite detection to ProductDetectionResult
|
|
82
|
+
*/
|
|
83
|
+
const tfliteToProductResult = (0, react_1.useCallback)((detection) => {
|
|
84
|
+
return {
|
|
85
|
+
id: `tflite_${Date.now()}_${detection.classIndex}`,
|
|
86
|
+
type: 'product',
|
|
87
|
+
confidence: detection.confidence,
|
|
88
|
+
boundingBox: {
|
|
89
|
+
x: detection.x,
|
|
90
|
+
y: detection.y,
|
|
91
|
+
width: detection.width,
|
|
92
|
+
height: detection.height,
|
|
93
|
+
},
|
|
94
|
+
source: 'on_device',
|
|
95
|
+
processingTimeMs: 0,
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
data: {
|
|
98
|
+
category: 'produce', // Map to closest ProductCategory
|
|
99
|
+
classLabel: detection.label,
|
|
100
|
+
classIndex: detection.classIndex,
|
|
101
|
+
name: detection.label.charAt(0).toUpperCase() + detection.label.slice(1),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}, []);
|
|
105
|
+
/**
|
|
106
|
+
* Handle TFLite detections
|
|
107
|
+
*/
|
|
108
|
+
const handleTFLiteDetections = (0, react_1.useCallback)((detections) => {
|
|
109
|
+
const productResults = detections.map(tfliteToProductResult);
|
|
110
|
+
setProducts(productResults);
|
|
111
|
+
setFrameNumber((prev) => prev + 1);
|
|
112
|
+
// Callback
|
|
113
|
+
if (productResults.length > 0 && onDetected) {
|
|
114
|
+
onDetected({
|
|
115
|
+
products: productResults,
|
|
116
|
+
barcodes,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
frameNumber,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}, [tfliteToProductResult, barcodes, frameNumber, onDetected]);
|
|
122
|
+
// TFLite frame processor hook
|
|
123
|
+
const tfliteFrameProcessor = (0, tfliteFrameProcessor_1.useTFLiteFrameProcessor)({
|
|
124
|
+
model: useOnDeviceFrameProcessor ? tfliteModel ?? null : null,
|
|
125
|
+
numClasses: 80, // COCO
|
|
126
|
+
confThreshold: minProductConfidence,
|
|
127
|
+
classLabels: [...classLabelsCoco_1.COCO_CLASSES], // Convert readonly to mutable array
|
|
128
|
+
foodOnly,
|
|
129
|
+
onDetections: handleTFLiteDetections,
|
|
130
|
+
});
|
|
131
|
+
// Refs
|
|
132
|
+
const cameraRef = (0, react_1.useRef)(null);
|
|
133
|
+
const lastFrameTimeRef = (0, react_1.useRef)(0);
|
|
134
|
+
const frameCountRef = (0, react_1.useRef)(0);
|
|
135
|
+
const fpsStartTimeRef = (0, react_1.useRef)(Date.now());
|
|
136
|
+
const productHistoryRef = (0, react_1.useRef)([]);
|
|
137
|
+
const mountedRef = (0, react_1.useRef)(true);
|
|
138
|
+
// Computed readiness
|
|
139
|
+
const isReady = (0, react_1.useMemo)(() => {
|
|
140
|
+
// For products: either TFLite frame processor is ready, or productDetector is loaded
|
|
141
|
+
const productReady = !modes.includes('products') ||
|
|
142
|
+
(useOnDeviceFrameProcessor ? tfliteFrameProcessor.isReady : productDetector.isModelLoaded);
|
|
143
|
+
const barcodeReady = !modes.includes('barcodes') || barcodeScanner.isReady;
|
|
144
|
+
return isInitialized && productReady && barcodeReady;
|
|
145
|
+
}, [
|
|
146
|
+
isInitialized,
|
|
147
|
+
modes,
|
|
148
|
+
useOnDeviceFrameProcessor,
|
|
149
|
+
tfliteFrameProcessor.isReady,
|
|
150
|
+
productDetector.isModelLoaded,
|
|
151
|
+
barcodeScanner.isReady,
|
|
152
|
+
]);
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// FPS Calculation
|
|
155
|
+
// ============================================================================
|
|
156
|
+
(0, react_1.useEffect)(() => {
|
|
157
|
+
if (!isScanning)
|
|
158
|
+
return;
|
|
159
|
+
const interval = setInterval(() => {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const elapsed = (now - fpsStartTimeRef.current) / MS_PER_SECOND;
|
|
162
|
+
if (elapsed > 0) {
|
|
163
|
+
setFps(Math.round(frameCountRef.current / elapsed));
|
|
164
|
+
}
|
|
165
|
+
frameCountRef.current = 0;
|
|
166
|
+
fpsStartTimeRef.current = now;
|
|
167
|
+
}, MS_PER_SECOND);
|
|
168
|
+
return () => clearInterval(interval);
|
|
169
|
+
}, [isScanning]);
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// Temporal Smoothing
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Apply temporal smoothing to product detections
|
|
175
|
+
*/
|
|
176
|
+
const smoothProducts = (0, react_1.useCallback)((newProducts) => {
|
|
177
|
+
if (!temporalSmoothing)
|
|
178
|
+
return newProducts;
|
|
179
|
+
// Add to history
|
|
180
|
+
productHistoryRef.current.push(newProducts);
|
|
181
|
+
if (productHistoryRef.current.length > smoothingFrames) {
|
|
182
|
+
productHistoryRef.current.shift();
|
|
183
|
+
}
|
|
184
|
+
// Count occurrences of each class
|
|
185
|
+
const classCounts = new Map();
|
|
186
|
+
const bestDetections = new Map();
|
|
187
|
+
for (const frameProducts of productHistoryRef.current) {
|
|
188
|
+
for (const product of frameProducts) {
|
|
189
|
+
const key = product.data.classLabel;
|
|
190
|
+
const count = (classCounts.get(key) || 0) + 1;
|
|
191
|
+
classCounts.set(key, count);
|
|
192
|
+
// Keep best confidence detection
|
|
193
|
+
const existing = bestDetections.get(key);
|
|
194
|
+
if (!existing || product.confidence > existing.confidence) {
|
|
195
|
+
bestDetections.set(key, product);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Keep products that appear in at least half the frames
|
|
200
|
+
const threshold = Math.ceil(smoothingFrames / 2);
|
|
201
|
+
return Array.from(bestDetections.entries())
|
|
202
|
+
.filter(([key]) => (classCounts.get(key) || 0) >= threshold)
|
|
203
|
+
.map(([, product]) => product);
|
|
204
|
+
}, [temporalSmoothing, smoothingFrames]);
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Frame Processing
|
|
207
|
+
// ============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Process a frame (called from frame processor)
|
|
210
|
+
* @deprecated Use TFLite frame processor instead
|
|
211
|
+
*/
|
|
212
|
+
const _processFrame = (0, react_1.useCallback)(async (frameUri) => {
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
const minInterval = MS_PER_SECOND / targetFps;
|
|
215
|
+
// Throttle based on target FPS
|
|
216
|
+
if (now - lastFrameTimeRef.current < minInterval) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
lastFrameTimeRef.current = now;
|
|
220
|
+
frameCountRef.current++;
|
|
221
|
+
try {
|
|
222
|
+
const results = {
|
|
223
|
+
products: [],
|
|
224
|
+
barcodes: [],
|
|
225
|
+
timestamp: now,
|
|
226
|
+
frameNumber: frameNumber,
|
|
227
|
+
};
|
|
228
|
+
// Product detection
|
|
229
|
+
if (modes.includes('products') && productDetector.isModelLoaded) {
|
|
230
|
+
const detected = await productDetector.detectProducts(frameUri);
|
|
231
|
+
results.products = smoothProducts(detected);
|
|
232
|
+
}
|
|
233
|
+
// Barcode detection is handled by VisionCamera's code scanner
|
|
234
|
+
// Results come through barcodeScanner.results
|
|
235
|
+
// Update state
|
|
236
|
+
if (mountedRef.current) {
|
|
237
|
+
setFrameNumber((prev) => prev + 1);
|
|
238
|
+
if (results.products.length > 0) {
|
|
239
|
+
setProducts(results.products);
|
|
240
|
+
}
|
|
241
|
+
// Combine with barcode results
|
|
242
|
+
results.barcodes = barcodeScanner.results;
|
|
243
|
+
// Callback
|
|
244
|
+
if (results.products.length > 0 || results.barcodes.length > 0) {
|
|
245
|
+
onDetected?.(results);
|
|
246
|
+
// Haptic feedback
|
|
247
|
+
if (hapticFeedback) {
|
|
248
|
+
// Trigger haptic
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
const frameError = err instanceof Error ? err : new Error('Frame processing failed');
|
|
255
|
+
console.error('[useVideoScanner] Frame error:', frameError);
|
|
256
|
+
setError(frameError);
|
|
257
|
+
onError?.(frameError);
|
|
258
|
+
}
|
|
259
|
+
}, [
|
|
260
|
+
targetFps,
|
|
261
|
+
frameNumber,
|
|
262
|
+
modes,
|
|
263
|
+
productDetector,
|
|
264
|
+
barcodeScanner.results,
|
|
265
|
+
smoothProducts,
|
|
266
|
+
onDetected,
|
|
267
|
+
onError,
|
|
268
|
+
hapticFeedback,
|
|
269
|
+
]);
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Actions
|
|
272
|
+
// ============================================================================
|
|
273
|
+
/**
|
|
274
|
+
* Start video scanning
|
|
275
|
+
*/
|
|
276
|
+
const startScanning = (0, react_1.useCallback)(() => {
|
|
277
|
+
if (!isReady) {
|
|
278
|
+
console.warn('[useVideoScanner] Not ready to scan');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
setIsScanning(true);
|
|
282
|
+
setError(null);
|
|
283
|
+
frameCountRef.current = 0;
|
|
284
|
+
fpsStartTimeRef.current = Date.now();
|
|
285
|
+
productHistoryRef.current = [];
|
|
286
|
+
// Start barcode scanner if enabled
|
|
287
|
+
if (modes.includes('barcodes')) {
|
|
288
|
+
barcodeScanner.startScanning();
|
|
289
|
+
}
|
|
290
|
+
}, [isReady, modes, barcodeScanner]);
|
|
291
|
+
/**
|
|
292
|
+
* Stop video scanning
|
|
293
|
+
*/
|
|
294
|
+
const stopScanning = (0, react_1.useCallback)(() => {
|
|
295
|
+
setIsScanning(false);
|
|
296
|
+
barcodeScanner.stopScanning();
|
|
297
|
+
}, [barcodeScanner]);
|
|
298
|
+
/**
|
|
299
|
+
* Toggle scanning
|
|
300
|
+
*/
|
|
301
|
+
const toggleScanning = (0, react_1.useCallback)(() => {
|
|
302
|
+
if (isScanning) {
|
|
303
|
+
stopScanning();
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
startScanning();
|
|
307
|
+
}
|
|
308
|
+
}, [isScanning, startScanning, stopScanning]);
|
|
309
|
+
/**
|
|
310
|
+
* Clear all detections
|
|
311
|
+
*/
|
|
312
|
+
const clearDetections = (0, react_1.useCallback)(() => {
|
|
313
|
+
setProducts([]);
|
|
314
|
+
setBarcodes([]);
|
|
315
|
+
barcodeScanner.clearResults();
|
|
316
|
+
productDetector.clearDetections();
|
|
317
|
+
productHistoryRef.current = [];
|
|
318
|
+
}, [barcodeScanner, productDetector]);
|
|
319
|
+
/**
|
|
320
|
+
* Capture current frame as photo
|
|
321
|
+
*/
|
|
322
|
+
const captureFrame = (0, react_1.useCallback)(async () => {
|
|
323
|
+
// This would use VisionCamera's takePhoto method
|
|
324
|
+
// Requires ref to be properly typed and connected
|
|
325
|
+
console.log('[useVideoScanner] Frame capture requires VisionCamera ref');
|
|
326
|
+
return null;
|
|
327
|
+
}, []);
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// Frame Processor Configuration
|
|
330
|
+
// ============================================================================
|
|
331
|
+
const frameProcessorConfig = (0, react_1.useMemo)(() => {
|
|
332
|
+
if (!isScanning)
|
|
333
|
+
return null;
|
|
334
|
+
return {
|
|
335
|
+
// For VisionCamera frame processor
|
|
336
|
+
// This would be used with useFrameProcessor
|
|
337
|
+
codeScanner: modes.includes('barcodes') ? barcodeScanner.frameProcessor : undefined,
|
|
338
|
+
// Product detection happens through processFrame
|
|
339
|
+
};
|
|
340
|
+
}, [isScanning, modes, barcodeScanner.frameProcessor]);
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Cleanup
|
|
343
|
+
// ============================================================================
|
|
344
|
+
(0, react_1.useEffect)(() => {
|
|
345
|
+
mountedRef.current = true;
|
|
346
|
+
return () => {
|
|
347
|
+
mountedRef.current = false;
|
|
348
|
+
};
|
|
349
|
+
}, []);
|
|
350
|
+
// Sync barcode results
|
|
351
|
+
(0, react_1.useEffect)(() => {
|
|
352
|
+
if (barcodeScanner.results.length > 0) {
|
|
353
|
+
setBarcodes(barcodeScanner.results);
|
|
354
|
+
}
|
|
355
|
+
}, [barcodeScanner.results]);
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Return
|
|
358
|
+
// ============================================================================
|
|
359
|
+
return {
|
|
360
|
+
// State
|
|
361
|
+
isScanning,
|
|
362
|
+
isReady,
|
|
363
|
+
products,
|
|
364
|
+
barcodes,
|
|
365
|
+
frameNumber,
|
|
366
|
+
fps,
|
|
367
|
+
error,
|
|
368
|
+
inferenceTimeMs,
|
|
369
|
+
// Actions
|
|
370
|
+
startScanning,
|
|
371
|
+
stopScanning,
|
|
372
|
+
toggleScanning,
|
|
373
|
+
clearDetections,
|
|
374
|
+
captureFrame,
|
|
375
|
+
// Camera ref
|
|
376
|
+
cameraRef,
|
|
377
|
+
// Frame processor config
|
|
378
|
+
frameProcessorConfig,
|
|
379
|
+
// TFLite frame processor (use this with VisionCamera)
|
|
380
|
+
frameProcessor: useOnDeviceFrameProcessor ? tfliteFrameProcessor.frameProcessor : undefined,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
exports.default = useVideoScanner;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @souschef/ml-vision
|
|
3
|
+
*
|
|
4
|
+
* ML-powered product detection for React Native
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Multi-barcode scanning (scan multiple barcodes at once)
|
|
8
|
+
* - Receipt OCR (photograph receipts to extract items)
|
|
9
|
+
* - Visual product recognition (recognize fridge/pantry contents)
|
|
10
|
+
* - Video scanning (real-time product detection)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import {
|
|
15
|
+
* MLVisionProvider,
|
|
16
|
+
* useMultiBarcodeScanner,
|
|
17
|
+
* useProductDetector,
|
|
18
|
+
* useReceiptScanner,
|
|
19
|
+
* } from '@souschef/ml-vision';
|
|
20
|
+
*
|
|
21
|
+
* // Wrap your app with the provider
|
|
22
|
+
* function App() {
|
|
23
|
+
* return (
|
|
24
|
+
* <MLVisionProvider
|
|
25
|
+
* config={{ serverUrl: 'http://192.168.1.100:8000' }}
|
|
26
|
+
* storage={mmkvInstance}
|
|
27
|
+
* >
|
|
28
|
+
* <YourApp />
|
|
29
|
+
* </MLVisionProvider>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // Use hooks in your components
|
|
34
|
+
* function ScannerScreen() {
|
|
35
|
+
* const { scanPhoto, results } = useMultiBarcodeScanner();
|
|
36
|
+
* const { detectProducts, detections } = useProductDetector();
|
|
37
|
+
*
|
|
38
|
+
* // ...
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @packageDocumentation
|
|
43
|
+
*/
|
|
44
|
+
export type { MLVisionConfig, BoundingBox, BaseDetectionResult, ModelInfo, ModelDownloadProgress, CacheEntry, CacheStats, ServerHealthResponse, ServerErrorResponse, BarcodeFormat, BarcodeData, BarcodeDetectionResult, UseMultiBarcodeScannerOptions, UseMultiBarcodeScannerReturn, BarcodeCacheEntry, ProductCategory, ProduceType, ProduceQuality, ProductData, ProductMatch, ProduceData, ProductDetectionResult, DetectionModel, UseProductDetectorOptions, UseProductDetectorReturn, TFLiteDetectionOutput, ClassLabelMap, TextLine, TextBlock, TextRecognitionResult, ReceiptItem, ReceiptMetadata, ReceiptScanResult, KnownStore, UseReceiptScannerOptions, UseReceiptScannerReturn, ReceiptLineType, ParsedReceiptLine, } from './types';
|
|
45
|
+
export { GROCERY_BARCODE_FORMATS } from './types/barcode';
|
|
46
|
+
export { MLVisionProvider, useMLVisionContext, useMLVisionReady, type MLVisionProviderProps, type MLVisionContextValue, CacheManager, createCacheManager, CACHE_TTL, type CachedBarcodeLookup, type CachedProductRecognition, ServerClient, createServerClient, ServerError, type ServerRequestOptions, type ImageUploadData, type DetectionRequestOptions, type OCRRequestOptions, } from './core';
|
|
47
|
+
export { useProductDetector } from './hooks/useProductDetector';
|
|
48
|
+
export { CLASS_LABELS, NUM_CLASSES, getClassInfo, getLabelsArray, CATEGORY_CLASSES, } from './hooks/classLabels';
|
|
49
|
+
export { useMultiBarcodeScanner } from './hooks/useMultiBarcodeScanner';
|
|
50
|
+
export { useReceiptScanner } from './hooks/useReceiptScanner';
|
|
51
|
+
export { useVideoScanner, type ScanMode, type VideoScanResult, type UseVideoScannerOptions, type UseVideoScannerReturn, } from './hooks/useVideoScanner';
|
|
52
|
+
export { DetectionOverlay, DetectionLoadingOverlay, type DetectionOverlayProps, } from './components';
|
|
53
|
+
export { createDetectionProcessor, frameDetectionToResult, getClassLabel, type FrameDetection, type DetectionProcessorOptions, } from './processors';
|
|
54
|
+
export { useTFLiteFrameProcessor, createTFLiteFrameProcessor, type TFLiteFrameDetection, type FrameProcessorResult, type UseTFLiteFrameProcessorOptions, } from './processors';
|
|
55
|
+
export { preprocessImage, isSkiaAvailable, isResizePluginAvailable, getFrameProcessorInstructions, } from './utils/imagePreprocessor';
|
|
56
|
+
export { processYoloOutput, scaleDetections, } from './utils/yoloProcessor';
|
|
57
|
+
export { COCO_CLASSES, CLASS_LABELS_COCO, NUM_CLASSES_COCO, FOOD_CLASS_INDICES, getCocoClassInfo, isFoodClass, } from './hooks/classLabelsCoco';
|
|
58
|
+
export declare const VERSION = "0.1.0";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @souschef/ml-vision
|
|
4
|
+
*
|
|
5
|
+
* ML-powered product detection for React Native
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Multi-barcode scanning (scan multiple barcodes at once)
|
|
9
|
+
* - Receipt OCR (photograph receipts to extract items)
|
|
10
|
+
* - Visual product recognition (recognize fridge/pantry contents)
|
|
11
|
+
* - Video scanning (real-time product detection)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import {
|
|
16
|
+
* MLVisionProvider,
|
|
17
|
+
* useMultiBarcodeScanner,
|
|
18
|
+
* useProductDetector,
|
|
19
|
+
* useReceiptScanner,
|
|
20
|
+
* } from '@souschef/ml-vision';
|
|
21
|
+
*
|
|
22
|
+
* // Wrap your app with the provider
|
|
23
|
+
* function App() {
|
|
24
|
+
* return (
|
|
25
|
+
* <MLVisionProvider
|
|
26
|
+
* config={{ serverUrl: 'http://192.168.1.100:8000' }}
|
|
27
|
+
* storage={mmkvInstance}
|
|
28
|
+
* >
|
|
29
|
+
* <YourApp />
|
|
30
|
+
* </MLVisionProvider>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* // Use hooks in your components
|
|
35
|
+
* function ScannerScreen() {
|
|
36
|
+
* const { scanPhoto, results } = useMultiBarcodeScanner();
|
|
37
|
+
* const { detectProducts, detections } = useProductDetector();
|
|
38
|
+
*
|
|
39
|
+
* // ...
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @packageDocumentation
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.VERSION = exports.isFoodClass = exports.getCocoClassInfo = exports.FOOD_CLASS_INDICES = exports.NUM_CLASSES_COCO = exports.CLASS_LABELS_COCO = exports.COCO_CLASSES = exports.scaleDetections = exports.processYoloOutput = exports.getFrameProcessorInstructions = exports.isResizePluginAvailable = exports.isSkiaAvailable = exports.preprocessImage = exports.createTFLiteFrameProcessor = exports.useTFLiteFrameProcessor = exports.getClassLabel = exports.frameDetectionToResult = exports.createDetectionProcessor = exports.DetectionLoadingOverlay = exports.DetectionOverlay = exports.useVideoScanner = exports.useReceiptScanner = exports.useMultiBarcodeScanner = exports.CATEGORY_CLASSES = exports.getLabelsArray = exports.getClassInfo = exports.NUM_CLASSES = exports.CLASS_LABELS = exports.useProductDetector = exports.ServerError = exports.createServerClient = exports.ServerClient = exports.CACHE_TTL = exports.createCacheManager = exports.CacheManager = exports.useMLVisionReady = exports.useMLVisionContext = exports.MLVisionProvider = exports.GROCERY_BARCODE_FORMATS = void 0;
|
|
47
|
+
// Re-export barcode format constants
|
|
48
|
+
var barcode_1 = require("./types/barcode");
|
|
49
|
+
Object.defineProperty(exports, "GROCERY_BARCODE_FORMATS", { enumerable: true, get: function () { return barcode_1.GROCERY_BARCODE_FORMATS; } });
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Core - Provider, Cache, Server Client
|
|
52
|
+
// ============================================================================
|
|
53
|
+
var core_1 = require("./core");
|
|
54
|
+
// Provider
|
|
55
|
+
Object.defineProperty(exports, "MLVisionProvider", { enumerable: true, get: function () { return core_1.MLVisionProvider; } });
|
|
56
|
+
Object.defineProperty(exports, "useMLVisionContext", { enumerable: true, get: function () { return core_1.useMLVisionContext; } });
|
|
57
|
+
Object.defineProperty(exports, "useMLVisionReady", { enumerable: true, get: function () { return core_1.useMLVisionReady; } });
|
|
58
|
+
// Cache
|
|
59
|
+
Object.defineProperty(exports, "CacheManager", { enumerable: true, get: function () { return core_1.CacheManager; } });
|
|
60
|
+
Object.defineProperty(exports, "createCacheManager", { enumerable: true, get: function () { return core_1.createCacheManager; } });
|
|
61
|
+
Object.defineProperty(exports, "CACHE_TTL", { enumerable: true, get: function () { return core_1.CACHE_TTL; } });
|
|
62
|
+
// Server Client
|
|
63
|
+
Object.defineProperty(exports, "ServerClient", { enumerable: true, get: function () { return core_1.ServerClient; } });
|
|
64
|
+
Object.defineProperty(exports, "createServerClient", { enumerable: true, get: function () { return core_1.createServerClient; } });
|
|
65
|
+
Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return core_1.ServerError; } });
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Hooks
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Phase 3: Visual product detection
|
|
70
|
+
var useProductDetector_1 = require("./hooks/useProductDetector");
|
|
71
|
+
Object.defineProperty(exports, "useProductDetector", { enumerable: true, get: function () { return useProductDetector_1.useProductDetector; } });
|
|
72
|
+
var classLabels_1 = require("./hooks/classLabels");
|
|
73
|
+
Object.defineProperty(exports, "CLASS_LABELS", { enumerable: true, get: function () { return classLabels_1.CLASS_LABELS; } });
|
|
74
|
+
Object.defineProperty(exports, "NUM_CLASSES", { enumerable: true, get: function () { return classLabels_1.NUM_CLASSES; } });
|
|
75
|
+
Object.defineProperty(exports, "getClassInfo", { enumerable: true, get: function () { return classLabels_1.getClassInfo; } });
|
|
76
|
+
Object.defineProperty(exports, "getLabelsArray", { enumerable: true, get: function () { return classLabels_1.getLabelsArray; } });
|
|
77
|
+
Object.defineProperty(exports, "CATEGORY_CLASSES", { enumerable: true, get: function () { return classLabels_1.CATEGORY_CLASSES; } });
|
|
78
|
+
// Phase 5: Multi-barcode scanning
|
|
79
|
+
var useMultiBarcodeScanner_1 = require("./hooks/useMultiBarcodeScanner");
|
|
80
|
+
Object.defineProperty(exports, "useMultiBarcodeScanner", { enumerable: true, get: function () { return useMultiBarcodeScanner_1.useMultiBarcodeScanner; } });
|
|
81
|
+
// Phase 6: Receipt OCR
|
|
82
|
+
var useReceiptScanner_1 = require("./hooks/useReceiptScanner");
|
|
83
|
+
Object.defineProperty(exports, "useReceiptScanner", { enumerable: true, get: function () { return useReceiptScanner_1.useReceiptScanner; } });
|
|
84
|
+
// Phase 7: Video scanning
|
|
85
|
+
var useVideoScanner_1 = require("./hooks/useVideoScanner");
|
|
86
|
+
Object.defineProperty(exports, "useVideoScanner", { enumerable: true, get: function () { return useVideoScanner_1.useVideoScanner; } });
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Components
|
|
89
|
+
// ============================================================================
|
|
90
|
+
var components_1 = require("./components");
|
|
91
|
+
Object.defineProperty(exports, "DetectionOverlay", { enumerable: true, get: function () { return components_1.DetectionOverlay; } });
|
|
92
|
+
Object.defineProperty(exports, "DetectionLoadingOverlay", { enumerable: true, get: function () { return components_1.DetectionLoadingOverlay; } });
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Processors
|
|
95
|
+
// ============================================================================
|
|
96
|
+
var processors_1 = require("./processors");
|
|
97
|
+
Object.defineProperty(exports, "createDetectionProcessor", { enumerable: true, get: function () { return processors_1.createDetectionProcessor; } });
|
|
98
|
+
Object.defineProperty(exports, "frameDetectionToResult", { enumerable: true, get: function () { return processors_1.frameDetectionToResult; } });
|
|
99
|
+
Object.defineProperty(exports, "getClassLabel", { enumerable: true, get: function () { return processors_1.getClassLabel; } });
|
|
100
|
+
// TFLite Frame Processor for real-time video scanning
|
|
101
|
+
var processors_2 = require("./processors");
|
|
102
|
+
Object.defineProperty(exports, "useTFLiteFrameProcessor", { enumerable: true, get: function () { return processors_2.useTFLiteFrameProcessor; } });
|
|
103
|
+
Object.defineProperty(exports, "createTFLiteFrameProcessor", { enumerable: true, get: function () { return processors_2.createTFLiteFrameProcessor; } });
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Utilities
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// export { generateImageHash } from './utils/imageUtils';
|
|
108
|
+
// export { parseReceiptText } from './utils/receiptParser';
|
|
109
|
+
// Image preprocessing for on-device TFLite inference
|
|
110
|
+
var imagePreprocessor_1 = require("./utils/imagePreprocessor");
|
|
111
|
+
Object.defineProperty(exports, "preprocessImage", { enumerable: true, get: function () { return imagePreprocessor_1.preprocessImage; } });
|
|
112
|
+
Object.defineProperty(exports, "isSkiaAvailable", { enumerable: true, get: function () { return imagePreprocessor_1.isSkiaAvailable; } });
|
|
113
|
+
Object.defineProperty(exports, "isResizePluginAvailable", { enumerable: true, get: function () { return imagePreprocessor_1.isResizePluginAvailable; } });
|
|
114
|
+
Object.defineProperty(exports, "getFrameProcessorInstructions", { enumerable: true, get: function () { return imagePreprocessor_1.getFrameProcessorInstructions; } });
|
|
115
|
+
// YOLO output processing
|
|
116
|
+
var yoloProcessor_1 = require("./utils/yoloProcessor");
|
|
117
|
+
Object.defineProperty(exports, "processYoloOutput", { enumerable: true, get: function () { return yoloProcessor_1.processYoloOutput; } });
|
|
118
|
+
Object.defineProperty(exports, "scaleDetections", { enumerable: true, get: function () { return yoloProcessor_1.scaleDetections; } });
|
|
119
|
+
// COCO class labels (80 classes for general object detection)
|
|
120
|
+
var classLabelsCoco_1 = require("./hooks/classLabelsCoco");
|
|
121
|
+
Object.defineProperty(exports, "COCO_CLASSES", { enumerable: true, get: function () { return classLabelsCoco_1.COCO_CLASSES; } });
|
|
122
|
+
Object.defineProperty(exports, "CLASS_LABELS_COCO", { enumerable: true, get: function () { return classLabelsCoco_1.CLASS_LABELS_COCO; } });
|
|
123
|
+
Object.defineProperty(exports, "NUM_CLASSES_COCO", { enumerable: true, get: function () { return classLabelsCoco_1.NUM_CLASSES_COCO; } });
|
|
124
|
+
Object.defineProperty(exports, "FOOD_CLASS_INDICES", { enumerable: true, get: function () { return classLabelsCoco_1.FOOD_CLASS_INDICES; } });
|
|
125
|
+
Object.defineProperty(exports, "getCocoClassInfo", { enumerable: true, get: function () { return classLabelsCoco_1.getCocoClassInfo; } });
|
|
126
|
+
Object.defineProperty(exports, "isFoodClass", { enumerable: true, get: function () { return classLabelsCoco_1.isFoodClass; } });
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Version
|
|
129
|
+
// ============================================================================
|
|
130
|
+
exports.VERSION = '0.1.0';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection Frame Processor
|
|
3
|
+
*
|
|
4
|
+
* VisionCamera frame processor for real-time product detection.
|
|
5
|
+
* Uses react-native-fast-tflite for on-device inference.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { useFrameProcessor } from 'react-native-vision-camera';
|
|
10
|
+
* import { detectProducts } from '@souschef/ml-vision/processors';
|
|
11
|
+
*
|
|
12
|
+
* function CameraScreen() {
|
|
13
|
+
* const frameProcessor = useFrameProcessor((frame) => {
|
|
14
|
+
* 'worklet';
|
|
15
|
+
* const detections = detectProducts(frame);
|
|
16
|
+
* // Handle detections...
|
|
17
|
+
* }, []);
|
|
18
|
+
*
|
|
19
|
+
* return <Camera frameProcessor={frameProcessor} />;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { ProductDetectionResult } from '../types';
|
|
24
|
+
import { CLASS_LABELS, NUM_CLASSES } from '../hooks/classLabels';
|
|
25
|
+
/**
|
|
26
|
+
* Frame processor detection result (simplified for worklet)
|
|
27
|
+
*/
|
|
28
|
+
export interface FrameDetection {
|
|
29
|
+
/** Class index */
|
|
30
|
+
classIndex: number;
|
|
31
|
+
/** Class label */
|
|
32
|
+
label: string;
|
|
33
|
+
/** Confidence score 0-1 */
|
|
34
|
+
confidence: number;
|
|
35
|
+
/** Bounding box (normalized 0-1) */
|
|
36
|
+
box: {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Frame processor options
|
|
45
|
+
*/
|
|
46
|
+
export interface DetectionProcessorOptions {
|
|
47
|
+
/** Minimum confidence threshold (default: 0.5) */
|
|
48
|
+
minConfidence?: number;
|
|
49
|
+
/** Maximum detections per frame (default: 10) */
|
|
50
|
+
maxDetections?: number;
|
|
51
|
+
/** Run every N frames (default: 3 for ~10fps at 30fps input) */
|
|
52
|
+
frameSkip?: number;
|
|
53
|
+
/** Class indices to detect (default: all) */
|
|
54
|
+
classFilter?: number[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a detection frame processor
|
|
58
|
+
*
|
|
59
|
+
* This returns a function that can be used with VisionCamera's useFrameProcessor.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const detectFrame = createDetectionProcessor({
|
|
64
|
+
* minConfidence: 0.5,
|
|
65
|
+
* maxDetections: 10,
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* const frameProcessor = useFrameProcessor((frame) => {
|
|
69
|
+
* 'worklet';
|
|
70
|
+
* const detections = detectFrame(frame);
|
|
71
|
+
* runOnJS(handleDetections)(detections);
|
|
72
|
+
* }, []);
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function createDetectionProcessor(options?: DetectionProcessorOptions): (_frame: unknown) => FrameDetection[];
|
|
76
|
+
/**
|
|
77
|
+
* Convert FrameDetection to ProductDetectionResult
|
|
78
|
+
*
|
|
79
|
+
* Call this on the JS thread after receiving worklet results.
|
|
80
|
+
*/
|
|
81
|
+
export declare function frameDetectionToResult(detection: FrameDetection, imageWidth: number, imageHeight: number): ProductDetectionResult;
|
|
82
|
+
/**
|
|
83
|
+
* Get label for class index (can be called from worklet via shared value)
|
|
84
|
+
*/
|
|
85
|
+
export declare function getClassLabel(classIndex: number): string;
|
|
86
|
+
export { NUM_CLASSES, CLASS_LABELS };
|