Skip to content

Commit b800b72

Browse files
feat: this is the way
1 parent eed5a75 commit b800b72

8 files changed

Lines changed: 46 additions & 11 deletions

File tree

apps/computer-vision/app/vision_camera/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const TASKS: Task[] = [
9595
// Module-level consts so worklets in task components can always reference the same stable objects.
9696
// Never replaced — only mutated via setBlocking to avoid closure staleness.
9797
const frameKillSwitch = createSynchronizable(false);
98+
const cameraPositionSync = createSynchronizable<'front' | 'back'>('back');
9899

99100
export default function VisionCameraScreen() {
100101
const insets = useSafeAreaInsets();
@@ -135,6 +136,10 @@ export default function VisionCameraScreen() {
135136
return () => clearTimeout(id);
136137
}, [activeModel]);
137138

139+
useEffect(() => {
140+
cameraPositionSync.setBlocking(cameraPosition);
141+
}, [cameraPosition]);
142+
138143
const handleFpsChange = useCallback((newFps: number, newMs: number) => {
139144
setFps(newFps);
140145
setFrameMs(newMs);
@@ -177,6 +182,7 @@ export default function VisionCameraScreen() {
177182
const taskProps = {
178183
activeModel,
179184
canvasSize,
185+
cameraPositionSync,
180186
frameKillSwitch,
181187
onFrameOutputChange: setFrameOutput,
182188
onReadyChange: setIsReady,
@@ -195,7 +201,7 @@ export default function VisionCameraScreen() {
195201
outputs={frameOutput ? [frameOutput] : []}
196202
isActive={isFocused}
197203
format={format}
198-
orientationSource="interface"
204+
orientationSource="device"
199205
/>
200206

201207
{/* Layout sentinel — measures the full-screen area for bbox/canvas sizing */}

apps/computer-vision/components/vision_camera/tasks/ObjectDetectionTask.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Props = TaskProps & { activeModel: ObjModelId };
1818
export default function ObjectDetectionTask({
1919
activeModel,
2020
canvasSize,
21+
cameraPositionSync,
2122
frameKillSwitch,
2223
onFrameOutputChange,
2324
onReadyChange,
@@ -80,8 +81,8 @@ export default function ObjectDetectionTask({
8081
}
8182
try {
8283
if (!detRof) return;
83-
const result = detRof(frame, 0.5);
84-
// C++ maps coords to screen space (portrait: frameH × frameW)
84+
const isMirrored = cameraPositionSync.getDirty() === 'front';
85+
const result = detRof(frame, isMirrored, 0.5);
8586
const screenW = frame.height;
8687
const screenH = frame.width;
8788
if (result) {
@@ -97,7 +98,7 @@ export default function ObjectDetectionTask({
9798
frame.dispose();
9899
}
99100
},
100-
[detRof, frameKillSwitch, updateDetections]
101+
[cameraPositionSync, detRof, frameKillSwitch, updateDetections]
101102
),
102103
});
103104

apps/computer-vision/components/vision_camera/tasks/SegmentationTask.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Props = TaskProps & { activeModel: SegModelId };
3737
export default function SegmentationTask({
3838
activeModel,
3939
canvasSize,
40+
cameraPositionSync,
4041
frameKillSwitch,
4142
onFrameOutputChange,
4243
onReadyChange,
@@ -148,7 +149,8 @@ export default function SegmentationTask({
148149
}
149150
try {
150151
if (!segRof) return;
151-
const result = segRof(frame, [], false);
152+
const isMirrored = cameraPositionSync.getDirty() === 'front';
153+
const result = segRof(frame, isMirrored, [], false);
152154
if (result?.ARGMAX) {
153155
const argmax: Int32Array = result.ARGMAX;
154156
const side = Math.round(Math.sqrt(argmax.length));
@@ -179,7 +181,7 @@ export default function SegmentationTask({
179181
frame.dispose();
180182
}
181183
},
182-
[colors, frameKillSwitch, segRof, updateMask]
184+
[cameraPositionSync, colors, frameKillSwitch, segRof, updateMask]
183185
),
184186
});
185187

apps/computer-vision/components/vision_camera/tasks/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createSynchronizable } from 'react-native-worklets';
44
export type TaskProps = {
55
activeModel: string;
66
canvasSize: { width: number; height: number };
7+
cameraPositionSync: ReturnType<typeof createSynchronizable<'front' | 'back'>>;
78
frameKillSwitch: ReturnType<typeof createSynchronizable<boolean>>;
89
onFrameOutputChange: (frameOutput: ReturnType<typeof useFrameOutput>) => void;
910
onReadyChange: (isReady: boolean) => void;

packages/react-native-executorch/common/rnexecutorch/utils/FrameTransform.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,21 @@ void inverseRotateBbox(float &x1, float &y1, float &x2, float &y2,
9191
x2 = nx2;
9292
y2 = ny2;
9393
}
94-
// "left": no-op
94+
// "left": no-op (coords already in screen space)
95+
96+
#if defined(__APPLE__)
97+
if (orient.isMirrored) {
98+
// After CW/CCW rotation ("up"/"down") screen dims are swapped: rH × rW.
99+
// After no-op/180° ("left"/"right") screen dims are unchanged: rW × rH.
100+
bool swapped = (orient.orientation == "up" || orient.orientation == "down");
101+
float sw = swapped ? h : w;
102+
float sh = swapped ? w : h;
103+
float nx1 = sw - x2, ny1 = sh - y2;
104+
float nx2 = sw - x1, ny2 = sh - y1;
105+
x1 = nx1; y1 = ny1;
106+
x2 = nx2; y2 = ny2;
107+
}
108+
#endif
95109
}
96110

97111
cv::Mat inverseRotateMat(const cv::Mat &mat, const FrameOrientation &orient) {
@@ -103,7 +117,13 @@ cv::Mat inverseRotateMat(const cv::Mat &mat, const FrameOrientation &orient) {
103117
} else if (orient.orientation == "down") {
104118
cv::rotate(result, result, cv::ROTATE_90_COUNTERCLOCKWISE);
105119
}
106-
// "left": no-op
120+
// "left": no-op (mask already in screen space)
121+
122+
#if defined(__APPLE__)
123+
if (orient.isMirrored) {
124+
cv::rotate(result, result, cv::ROTATE_180);
125+
}
126+
#endif
107127
return result;
108128
}
109129

packages/react-native-executorch/src/modules/computer_vision/VisionModule.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export abstract class VisionModule<TOutput> extends BaseModule {
7070
const nativeGenerateFromFrame = this.nativeModule.generateFromFrame;
7171

7272
// Return worklet that captures ONLY the JSI function
73-
return (frame: any, ...args: any[]): TOutput => {
73+
return (frame: any, isMirrored: boolean, ...args: any[]): TOutput => {
7474
'worklet';
7575

7676
let nativeBuffer: any = null;
@@ -79,7 +79,7 @@ export abstract class VisionModule<TOutput> extends BaseModule {
7979
const frameData = {
8080
nativeBuffer: nativeBuffer.pointer,
8181
orientation: frame.orientation,
82-
isMirrored: frame.isMirrored,
82+
isMirrored,
8383
frameWidth: frame.width,
8484
frameHeight: frame.height,
8585
};

packages/react-native-executorch/src/types/objectDetection.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ export interface ObjectDetectionType<L extends LabelEnum> {
143143
* @returns Array of Detection objects representing detected items in the frame.
144144
*/
145145
runOnFrame:
146-
| ((frame: Frame, detectionThreshold: number) => Detection<L>[])
146+
| ((
147+
frame: Frame,
148+
isMirrored: boolean,
149+
detectionThreshold: number
150+
) => Detection<L>[])
147151
| null;
148152
}

packages/react-native-executorch/src/types/semanticSegmentation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export interface SemanticSegmentationType<L extends LabelEnum> {
184184
runOnFrame:
185185
| ((
186186
frame: Frame,
187+
isMirrored: boolean,
187188
classesOfInterest?: string[],
188189
resizeToInput?: boolean
189190
) => Record<'ARGMAX', Int32Array> & Record<string, Float32Array>)

0 commit comments

Comments
 (0)