Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8925144
fix: correct frame data extraction
NorbertKlockiewicz Feb 11, 2026
131d359
feat: frame extractor for zero-copy approach
NorbertKlockiewicz Feb 12, 2026
fbd25a6
chore: num minSdkVersion to 26
NorbertKlockiewicz Feb 13, 2026
15fe6a8
feat: unify frame extraction and preprocessing
NorbertKlockiewicz Feb 16, 2026
3918cd9
feat: remove unused bindJSIMethods
NorbertKlockiewicz Feb 16, 2026
a41f2e8
feat: initial version of vision model API
NorbertKlockiewicz Feb 17, 2026
0be3a2d
refactor: errors, logs, unnecessary comments, use existing TensorPtr
NorbertKlockiewicz Feb 17, 2026
6da848b
fix: change Frame import in BaseModule
NorbertKlockiewicz Feb 17, 2026
1ab7b14
feat: use TensorPtrish type for Pixel data input
NorbertKlockiewicz Feb 18, 2026
d0dcd7b
refactor: add or remove empty lines
NorbertKlockiewicz Feb 18, 2026
a44cee7
fix: errors after rebase
NorbertKlockiewicz Feb 19, 2026
2f1644d
fix: remove redundant preprocessing step
NorbertKlockiewicz Feb 20, 2026
7e44e98
refactor: changes suggested in review
NorbertKlockiewicz Feb 23, 2026
f65fb40
fix: not existing error type, add comments to JSI code
NorbertKlockiewicz Feb 23, 2026
e7df017
feat: add new PlatformNotSupported error
NorbertKlockiewicz Feb 23, 2026
9795a2e
fix: compilation JSI error
NorbertKlockiewicz Feb 23, 2026
4dcd547
feat: add tests for generateFromPixels method
NorbertKlockiewicz Feb 23, 2026
3c491ed
feat: add example screen with vision camera to computer vision app
NorbertKlockiewicz Feb 23, 2026
7e921bc
feat: suggested changes / improve comments
NorbertKlockiewicz Feb 24, 2026
8bc52b8
fix(android): object detection not working on android
NorbertKlockiewicz Feb 25, 2026
2fdb9c8
docs: add correct api references
NorbertKlockiewicz Feb 25, 2026
28a7bad
fix: requested changes
NorbertKlockiewicz Feb 26, 2026
a78f831
docs: update api reference
NorbertKlockiewicz Feb 26, 2026
c21480b
docs: update api reference
NorbertKlockiewicz Feb 26, 2026
be90629
fix: application doesn't start
NorbertKlockiewicz Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cspell-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,7 @@ antonov
rfdetr
basemodule
IMAGENET
worklet
worklets
BGRA
RGBA
16 changes: 14 additions & 2 deletions apps/computer-vision/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@
"foregroundImage": "./assets/icons/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.anonymous.computervision"
"package": "com.anonymous.computervision",
"permissions": ["android.permission.CAMERA"]
},
"web": {
"favicon": "./assets/icons/favicon.png"
},
"plugins": ["expo-font", "expo-router"]
"plugins": [
"expo-font",
"expo-router",
[
"expo-build-properties",
{
"android": {
"minSdkVersion": 26
}
}
]
]
}
}
8 changes: 8 additions & 0 deletions apps/computer-vision/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ export default function _layout() {
headerTitleStyle: { color: ColorPalette.primary },
}}
/>
<Drawer.Screen
name="object_detection_live/index"
options={{
drawerLabel: 'Object Detection (Live)',
title: 'Object Detection (Live)',
headerTitleStyle: { color: ColorPalette.primary },
}}
/>
<Drawer.Screen
name="ocr/index"
options={{
Expand Down
6 changes: 6 additions & 0 deletions apps/computer-vision/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export default function Home() {
>
<Text style={styles.buttonText}>Object Detection</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => router.navigate('object_detection_live/')}
>
<Text style={styles.buttonText}>Object Detection Live</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => router.navigate('ocr/')}
Expand Down
222 changes: 222 additions & 0 deletions apps/computer-vision/app/object_detection_live/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import {
Camera,
getCameraFormat,
Templates,
useCameraDevices,
useCameraPermission,
useFrameOutput,
} from 'react-native-vision-camera';
import { scheduleOnRN } from 'react-native-worklets';
import {
Detection,
SSDLITE_320_MOBILENET_V3_LARGE,
useObjectDetection,
} from 'react-native-executorch';
import { GeneratingContext } from '../../context';
import Spinner from '../../components/Spinner';
import ColorPalette from '../../colors';

export default function ObjectDetectionLiveScreen() {
const insets = useSafeAreaInsets();

const model = useObjectDetection({ model: SSDLITE_320_MOBILENET_V3_LARGE });
const { setGlobalGenerating } = useContext(GeneratingContext);

useEffect(() => {
setGlobalGenerating(model.isGenerating);
}, [model.isGenerating, setGlobalGenerating]);
const [detectionCount, setDetectionCount] = useState(0);
const [fps, setFps] = useState(0);
const lastFrameTimeRef = useRef(Date.now());

const cameraPermission = useCameraPermission();
const devices = useCameraDevices();
const device = devices.find((d) => d.position === 'back') ?? devices[0];

const format = useMemo(() => {
if (device == null) return undefined;
try {
return getCameraFormat(device, Templates.FrameProcessing);
} catch {
return undefined;
}
}, [device]);

const updateStats = useCallback((results: Detection[]) => {
setDetectionCount(results.length);
const now = Date.now();
const timeDiff = now - lastFrameTimeRef.current;
if (timeDiff > 0) {
setFps(Math.round(1000 / timeDiff));
}
lastFrameTimeRef.current = now;
}, []);

const frameOutput = useFrameOutput({
pixelFormat: 'rgb',
dropFramesWhileBusy: true,
onFrame(frame) {
'worklet';
if (!model.runOnFrame) {
frame.dispose();
return;
}
try {
const result = model.runOnFrame(frame, 0.5);
if (result) {
scheduleOnRN(updateStats, result);
}
} catch {
// ignore frame errors
} finally {
frame.dispose();
}
},
});

if (!model.isReady) {
return (
<Spinner
visible={!model.isReady}
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
/>
);
}

if (!cameraPermission.hasPermission) {
return (
<View style={styles.centered}>
<Text style={styles.message}>Camera access needed</Text>
<TouchableOpacity
onPress={() => cameraPermission.requestPermission()}
style={styles.button}
>
<Text style={styles.buttonText}>Grant Permission</Text>
</TouchableOpacity>
</View>
);
}

if (device == null) {
return (
<View style={styles.centered}>
<Text style={styles.message}>No camera device found</Text>
</View>
);
}

return (
<View style={styles.container}>
<StatusBar barStyle="light-content" translucent />

<Camera
style={StyleSheet.absoluteFill}
device={device}
outputs={[frameOutput]}
isActive={true}
format={format}
/>

<View
style={[styles.bottomBarWrapper, { paddingBottom: insets.bottom + 12 }]}
pointerEvents="none"
>
<View style={styles.bottomBar}>
<View style={styles.statItem}>
<Text style={styles.statValue}>{detectionCount}</Text>
<Text style={styles.statLabel}>objects</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statValue}>{fps}</Text>
<Text style={styles.statLabel}>fps</Text>
</View>
</View>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black',
},
centered: {
flex: 1,
backgroundColor: 'black',
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
message: {
color: 'white',
fontSize: 18,
},
button: {
paddingHorizontal: 24,
paddingVertical: 12,
backgroundColor: ColorPalette.primary,
borderRadius: 24,
},
buttonText: {
color: 'white',
fontSize: 15,
fontWeight: '600',
letterSpacing: 0.3,
},
bottomBarWrapper: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
alignItems: 'center',
},
bottomBar: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.55)',
borderRadius: 24,
paddingHorizontal: 28,
paddingVertical: 10,
gap: 24,
},
statItem: {
alignItems: 'center',
},
statValue: {
color: 'white',
fontSize: 22,
fontWeight: '700',
letterSpacing: -0.5,
},
statLabel: {
color: 'rgba(255,255,255,0.55)',
fontSize: 11,
fontWeight: '500',
textTransform: 'uppercase',
letterSpacing: 0.8,
},
statDivider: {
width: 1,
height: 32,
backgroundColor: 'rgba(255,255,255,0.2)',
},
});
22 changes: 0 additions & 22 deletions apps/computer-vision/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

const monorepoRoot = path.resolve(__dirname, '../..');

/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);

const { transformer, resolver } = config;

config.watchFolders = [monorepoRoot];

config.transformer = {
...transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
Expand All @@ -19,23 +14,6 @@ config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...resolver.sourceExts, 'svg'],
nodeModulesPaths: [
path.resolve(__dirname, 'node_modules'),
path.resolve(monorepoRoot, 'node_modules'),
],
// Always resolve react and react-native from the monorepo root so that
// workspace packages with their own nested node_modules (e.g.
// packages/react-native-executorch/node_modules/react) don't create a
// second React instance and trigger "Invalid hook call".
resolveRequest: (context, moduleName, platform) => {
if (moduleName === 'react' || moduleName === 'react-native') {
return {
filePath: require.resolve(moduleName, { paths: [monorepoRoot] }),
type: 'sourceFile',
};
}
return context.resolveRequest(context, moduleName, platform);
},
};

config.resolver.assetExts.push('pte');
Expand Down
6 changes: 5 additions & 1 deletion apps/computer-vision/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@react-navigation/native": "^7.1.28",
"@shopify/react-native-skia": "2.4.21",
"expo": "^54.0.27",
"expo-build-properties": "~1.0.10",
"expo-constants": "~18.0.11",
"expo-font": "~14.0.10",
"expo-linking": "~8.0.10",
Expand All @@ -30,17 +31,20 @@
"react-native-gesture-handler": "~2.28.0",
"react-native-image-picker": "^7.2.2",
"react-native-loading-spinner-overlay": "^3.0.1",
"react-native-nitro-image": "^0.12.0",
"react-native-nitro-modules": "^0.33.9",
"react-native-reanimated": "~4.2.2",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.15.3",
"react-native-svg-transformer": "^1.5.3",
"react-native-vision-camera": "5.0.0-beta.2",
"react-native-worklets": "0.7.4"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@types/pngjs": "^6.0.5",
"@types/react": "~19.1.10"
"@types/react": "~19.2.0"
},
"private": true
}
17 changes: 9 additions & 8 deletions docs/docs/05-utilities/04-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,15 @@ These errors occur when trying to perform operations on a model in an invalid st

These errors occur when invalid configuration or input is provided.

| Error Code | Description | When It Occurs | How to Handle |
| ---------------------- | ------------------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------- |
| `InvalidConfig` | Configuration parameters are invalid | Setting parameters outside valid ranges (e.g., `topp` outside [0, 1]) | Check parameter constraints and provide valid values |
| `InvalidUserInput` | Input provided to API is invalid | Passing empty arrays, null values, or malformed data to methods | Validate input before calling methods |
| `InvalidModelSource` | Model source type is invalid | Providing wrong type for model source (e.g., object when string expected) | Ensure model source matches expected type |
| `LanguageNotSupported` | Language not supported by model | Passing unsupported language to multilingual OCR or Speech-to-Text models | Use a supported language or different model |
| `WrongDimensions` | Input tensor dimensions don't match | Providing input with incorrect shape for the model | Check model's expected input dimensions |
| `UnexpectedNumInputs` | Wrong number of inputs provided | Passing more or fewer inputs than model expects | Match the number of inputs to model metadata |
| Error Code | Description | When It Occurs | How to Handle |
| ---------------------- | ------------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `InvalidConfig` | Configuration parameters are invalid | Setting parameters outside valid ranges (e.g., `topp` outside [0, 1]) | Check parameter constraints and provide valid values |
| `InvalidUserInput` | Input provided to API is invalid | Passing empty arrays, null values, or malformed data to methods | Validate input before calling methods |
| `InvalidModelSource` | Model source type is invalid | Providing wrong type for model source (e.g., object when string expected) | Ensure model source matches expected type |
| `LanguageNotSupported` | Language not supported by model | Passing unsupported language to multilingual OCR or Speech-to-Text models | Use a supported language or different model |
| `PlatformNotSupported` | Current platform is not supported | Using features (e.g., camera frame processing) on an unsupported platform or OS version | Ensure you're running on a supported platform/OS version |
| `WrongDimensions` | Input tensor dimensions don't match | Providing input with incorrect shape for the model | Check model's expected input dimensions |
| `UnexpectedNumInputs` | Wrong number of inputs provided | Passing more or fewer inputs than model expects | Match the number of inputs to model metadata |

### File Operations Errors

Expand Down
Loading