mujoco-react 10.4.0 → 10.5.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.
@@ -13,9 +13,13 @@ export interface GenericIKOptions {
13
13
  epsilon: number;
14
14
  posWeight: number;
15
15
  rotWeight: number;
16
+ jointLimits?: ReadonlyArray<readonly [number, number] | null | undefined>;
16
17
  }
17
18
 
18
- const DEFAULTS: GenericIKOptions = {
19
+ type ResolvedGenericIKOptions = Required<Omit<GenericIKOptions, 'jointLimits'>> &
20
+ Pick<GenericIKOptions, 'jointLimits'>;
21
+
22
+ const DEFAULTS: Required<Omit<GenericIKOptions, 'jointLimits'>> = {
19
23
  maxIterations: 50,
20
24
  damping: 0.01,
21
25
  tolerance: 1e-3,
@@ -24,7 +28,7 @@ const DEFAULTS: GenericIKOptions = {
24
28
  rotWeight: 0.3,
25
29
  };
26
30
 
27
- function resolveOptions(opts?: Partial<GenericIKOptions>): GenericIKOptions {
31
+ function resolveOptions(opts?: Partial<GenericIKOptions>): ResolvedGenericIKOptions {
28
32
  return {
29
33
  maxIterations: opts?.maxIterations ?? DEFAULTS.maxIterations,
30
34
  damping: opts?.damping ?? DEFAULTS.damping,
@@ -32,9 +36,17 @@ function resolveOptions(opts?: Partial<GenericIKOptions>): GenericIKOptions {
32
36
  epsilon: opts?.epsilon ?? DEFAULTS.epsilon,
33
37
  posWeight: opts?.posWeight ?? DEFAULTS.posWeight,
34
38
  rotWeight: opts?.rotWeight ?? DEFAULTS.rotWeight,
39
+ jointLimits: opts?.jointLimits,
35
40
  };
36
41
  }
37
42
 
43
+ function clampJoint(value: number, limit: readonly [number, number] | null | undefined) {
44
+ if (!limit) return value;
45
+ const [min, max] = limit;
46
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min >= max) return value;
47
+ return Math.max(min, Math.min(max, value));
48
+ }
49
+
38
50
  /**
39
51
  * Generic Damped Least-Squares IK solver.
40
52
  * Uses finite-difference Jacobian via MuJoCo's mj_forward.
@@ -81,7 +93,7 @@ export class GenericIK {
81
93
 
82
94
  // Working joint angles — start from current configuration
83
95
  const q = new Float64Array(n);
84
- for (let i = 0; i < n; i++) q[i] = currentQ[i];
96
+ for (let i = 0; i < n; i++) q[i] = clampJoint(currentQ[i], o.jointLimits?.[i]);
85
97
 
86
98
  // Pre-allocate work arrays
87
99
  const J = new Float64Array(6 * n); // 6×n Jacobian (row-major)
@@ -196,7 +208,7 @@ export class GenericIK {
196
208
  }
197
209
 
198
210
  // Update joints
199
- for (let i = 0; i < n; i++) q[i] += dq[i];
211
+ for (let i = 0; i < n; i++) q[i] = clampJoint(q[i] + dq[i], o.jointLimits?.[i]);
200
212
  }
201
213
 
202
214
  // Restore original qpos
@@ -16,6 +16,7 @@ import {
16
16
  import * as THREE from 'three';
17
17
  import { MujocoData, MujocoModel, MujocoModule, getContact, withContacts } from '../types';
18
18
  import { SceneRenderer } from '../components/SceneRenderer';
19
+ import { CameraViewportProvider } from '../components/CameraView';
19
20
  import {
20
21
  ActuatedJointInfo,
21
22
  ActuatorInfo,
@@ -59,6 +60,7 @@ import {
59
60
  captureCameraFrame,
60
61
  captureCameraFrameBlob,
61
62
  createCameraFrameCaptureSession,
63
+ type CameraFrameCaptureTensorOptions,
62
64
  } from '../rendering/cameraFrameCapture';
63
65
  import {
64
66
  getCameraFrameCaptureSourceTarget,
@@ -1558,6 +1560,35 @@ export function MujocoSimProvider({
1558
1560
  [camera, gl, resolveCameraCaptureOptions, scene]
1559
1561
  );
1560
1562
 
1563
+ const createCameraFrameCaptureSessionApi = useCallback(
1564
+ (options: CameraFrameCaptureOptions = {}) =>
1565
+ createCameraFrameCaptureSession(
1566
+ gl,
1567
+ scene,
1568
+ camera,
1569
+ resolveCameraCaptureOptions(options)
1570
+ ),
1571
+ [camera, gl, resolveCameraCaptureOptions, scene]
1572
+ );
1573
+
1574
+ const captureCameraFrameTensorApi = useCallback(
1575
+ (options: CameraFrameCaptureTensorOptions = {}) => {
1576
+ const resolved: CameraFrameCaptureTensorOptions = {
1577
+ ...resolveCameraCaptureOptions(options),
1578
+ channels: options.channels,
1579
+ layout: options.layout,
1580
+ range: options.range,
1581
+ };
1582
+ const session = createCameraFrameCaptureSession(gl, scene, camera, resolved);
1583
+ try {
1584
+ return session.captureTensor(resolved);
1585
+ } finally {
1586
+ session.dispose();
1587
+ }
1588
+ },
1589
+ [camera, gl, resolveCameraCaptureOptions, scene]
1590
+ );
1591
+
1561
1592
  const recordCameraSequenceApi = useCallback(
1562
1593
  async (
1563
1594
  options: CameraFrameSequenceOptions
@@ -1882,6 +1913,9 @@ export function MujocoSimProvider({
1882
1913
  captureFrameBlob: captureFrameBlobApi,
1883
1914
  captureCameraFrame: captureCameraFrameApi,
1884
1915
  captureCameraFrameBlob: captureCameraFrameBlobApi,
1916
+ captureCameraFrameTensor: captureCameraFrameTensorApi,
1917
+ createCameraFrameCaptureSession: createCameraFrameCaptureSessionApi,
1918
+ resolveCameraCaptureOptions,
1885
1919
  recordCameraSequence: recordCameraSequenceApi,
1886
1920
  project2DTo3D,
1887
1921
  projectImagePointTo3D,
@@ -1903,6 +1937,8 @@ export function MujocoSimProvider({
1903
1937
  loadFromFilesApi, addBodyApi, removeBodyApi, recompileApi,
1904
1938
  getCanvas, getCanvasSnapshot, captureFrameApi, captureFrameBlobApi,
1905
1939
  captureCameraFrameApi, captureCameraFrameBlobApi,
1940
+ captureCameraFrameTensorApi, createCameraFrameCaptureSessionApi,
1941
+ resolveCameraCaptureOptions,
1906
1942
  recordCameraSequenceApi,
1907
1943
  project2DTo3D,
1908
1944
  projectImagePointTo3D,
@@ -1940,7 +1976,7 @@ export function MujocoSimProvider({
1940
1976
  return (
1941
1977
  <MujocoSimContext.Provider value={contextValue}>
1942
1978
  <SceneRenderer renderOptions={renderOptions} />
1943
- {children}
1979
+ <CameraViewportProvider>{children}</CameraViewportProvider>
1944
1980
  </MujocoSimContext.Provider>
1945
1981
  );
1946
1982
  }
@@ -496,9 +496,10 @@ function sceneObjectToXml(obj: SceneObject): string {
496
496
  const solref = obj.solref ? ` solref="${obj.solref}"` : '';
497
497
  const solimp = obj.solimp ? ` solimp="${obj.solimp}"` : '';
498
498
  const condim = obj.condim ? ` condim="${obj.condim}"` : '';
499
+ const contype = obj.contype ?? 1;
500
+ const conaffinity = obj.conaffinity ?? 1;
499
501
  const group = obj.group !== undefined ? ` group="${obj.group}"` : '';
500
- // Always set contype/conaffinity=1 so objects collide regardless of model defaults
501
- return `<body name="${obj.name}" pos="${pos}">${joint}<geom name="${geomName}" type="${obj.type}" size="${size}" rgba="${rgba}" contype="1" conaffinity="1"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
502
+ return `<body name="${obj.name}" pos="${pos}">${joint}<geom name="${geomName}" type="${obj.type}" size="${size}" rgba="${rgba}" contype="${contype}" conaffinity="${conaffinity}"${mass}${friction}${solref}${solimp}${condim}${group}/></body>`;
502
503
  }
503
504
 
504
505
  /** Create virtual directory structure for a file path. */
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Stream a live MuJoCo camera into a DOM `<canvas>`. Each frame the scene is
6
+ * rendered offscreen from the selected camera and blitted into the canvas, so
7
+ * it composites normally in the DOM (works inside opaque panels) and does NOT
8
+ * take over the render loop. Prefer this over `useCameraViewport` for camera
9
+ * tiles embedded in HTML UI; use `useCameraViewport` for transparent overlays
10
+ * on a full-bleed canvas.
11
+ *
12
+ * Uses the async capture path so Gaussian-splat environments render through
13
+ * their dedicated capture renderer — streaming a splat scene at full rate does
14
+ * not disturb the main view's splat sort.
15
+ */
16
+
17
+ import { useEffect, useRef } from 'react';
18
+ import type { RefObject } from 'react';
19
+ import { useFrame } from '@react-three/fiber';
20
+ import { useMujoco } from '../core/MujocoSimProvider';
21
+ import type { CameraFrameCaptureSession } from '../rendering/cameraFrameCapture';
22
+ import type { CameraFrameCaptureOptions } from '../types';
23
+
24
+ export interface CameraStreamOptions extends CameraFrameCaptureOptions {
25
+ /**
26
+ * Optional cap on updates per second. Omit to stream as fast as captures
27
+ * complete (one capture is in flight at a time regardless).
28
+ */
29
+ fps?: number;
30
+ /** Pause updates without unmounting. */
31
+ paused?: boolean;
32
+ }
33
+
34
+ function streamSignature(options: CameraStreamOptions): string {
35
+ return JSON.stringify({
36
+ cameraName: options.cameraName,
37
+ siteName: options.siteName,
38
+ bodyName: options.bodyName,
39
+ width: options.width,
40
+ height: options.height,
41
+ renderIsolation: options.renderIsolation ?? false,
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Render the live scene from a MuJoCo camera/site/body into `canvasRef`'s
47
+ * `<canvas>` every frame (throttled to `fps`). Call inside `<MujocoCanvas>`;
48
+ * the canvas itself can live anywhere in the DOM.
49
+ */
50
+ export function useCameraStream(
51
+ canvasRef: RefObject<HTMLCanvasElement | null>,
52
+ options: CameraStreamOptions
53
+ ) {
54
+ const mujoco = useMujoco();
55
+ const sessionRef = useRef<CameraFrameCaptureSession | null>(null);
56
+ const signatureRef = useRef<string>('');
57
+ const optionsRef = useRef(options);
58
+ optionsRef.current = options;
59
+ const elapsedRef = useRef(0);
60
+ const inFlightRef = useRef(false);
61
+ const mountedRef = useRef(true);
62
+
63
+ useEffect(() => {
64
+ mountedRef.current = true;
65
+ return () => {
66
+ mountedRef.current = false;
67
+ sessionRef.current?.dispose();
68
+ sessionRef.current = null;
69
+ signatureRef.current = '';
70
+ };
71
+ }, []);
72
+
73
+ useFrame((_state, delta) => {
74
+ const api = mujoco.api;
75
+ if (!api || !canvasRef.current) return;
76
+
77
+ const opts = optionsRef.current;
78
+ if (opts.paused) return;
79
+
80
+ if (opts.fps && opts.fps > 0) {
81
+ elapsedRef.current += delta;
82
+ if (elapsedRef.current < 1 / opts.fps) return;
83
+ }
84
+ // One capture in flight at a time — naturally rate-limits to capture speed.
85
+ if (inFlightRef.current) return;
86
+ elapsedRef.current = 0;
87
+
88
+ const signature = streamSignature(opts);
89
+ if (!sessionRef.current || signatureRef.current !== signature) {
90
+ sessionRef.current?.dispose();
91
+ sessionRef.current = api.createCameraFrameCaptureSession(opts);
92
+ signatureRef.current = signature;
93
+ }
94
+ const session = sessionRef.current;
95
+
96
+ inFlightRef.current = true;
97
+ session
98
+ .captureAsync(api.resolveCameraCaptureOptions(opts))
99
+ .then((frame) => {
100
+ const canvas = canvasRef.current;
101
+ if (!mountedRef.current || !canvas) return;
102
+ const ctx = canvas.getContext('2d');
103
+ if (!ctx) return;
104
+ if (canvas.width !== frame.width || canvas.height !== frame.height) {
105
+ canvas.width = frame.width;
106
+ canvas.height = frame.height;
107
+ }
108
+ ctx.drawImage(frame.canvas, 0, 0);
109
+ })
110
+ .catch(() => {})
111
+ .finally(() => {
112
+ inFlightRef.current = false;
113
+ });
114
+ });
115
+ }
Binary file
@@ -97,6 +97,9 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
97
97
  {
98
98
  damping: config.damping,
99
99
  epsilon: config.epsilon,
100
+ jointLimits: config.jointLimits ?? controlGroup.joints.map((joint) => (
101
+ joint.limited ? joint.range : joint.ctrlRange
102
+ )),
100
103
  maxIterations: config.maxIterations,
101
104
  posWeight: config.posWeight,
102
105
  rotWeight: config.rotWeight,
@@ -0,0 +1,215 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Capture policy observation tensors directly from Three/MuJoCo cameras,
6
+ * skipping the data-URL/PNG round-trip. Sessions are created once per camera
7
+ * and reused every step, so live inference and dataset recording read straight
8
+ * from the GPU into Float32 tensors.
9
+ */
10
+
11
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
+ import { useMujoco } from '../core/MujocoSimProvider';
13
+ import { createPolicyCameraFrameCapturePlanFromApi } from '../policyCameraFrames';
14
+ import type { MountedPolicyCameraFrameCaptureOptions } from './usePolicyCameraFrames';
15
+ import type {
16
+ CameraFrameCaptureSession,
17
+ CameraFrameCaptureTensorOptions,
18
+ CameraFrameTensorResult,
19
+ } from '../rendering/cameraFrameCapture';
20
+ import type { FrameCaptureStatus } from '../types';
21
+
22
+ export interface PolicyCameraTensorStream extends CameraFrameCaptureTensorOptions {
23
+ /** Payload key this stream's tensor is stored under. */
24
+ key: string;
25
+ /** Additional payload keys that should reference the same tensor. */
26
+ aliases?: readonly string[];
27
+ }
28
+
29
+ export interface PolicyCameraTensorsOptions {
30
+ streams: PolicyCameraTensorStream[];
31
+ /** Also expose tensors under `observation.images.<key>` aliases. Defaults to `false`. */
32
+ includeObservationImageAliases?: boolean;
33
+ }
34
+
35
+ export interface PolicyCameraTensorsResult {
36
+ tensors: Record<string, CameraFrameTensorResult>;
37
+ sourceSummary: string;
38
+ capturedAt: number;
39
+ }
40
+
41
+ export interface PolicyCameraTensorsAPI {
42
+ status: FrameCaptureStatus;
43
+ error: Error | null;
44
+ isCapturing: boolean;
45
+ /** Synchronously render and convert every stream into a policy image tensor. */
46
+ capture: () => PolicyCameraTensorsResult;
47
+ reset: () => void;
48
+ }
49
+
50
+ export type MountedPolicyCameraTensorOptions = MountedPolicyCameraFrameCaptureOptions & {
51
+ tensor?: Pick<
52
+ CameraFrameCaptureTensorOptions,
53
+ 'width' | 'height' | 'channels' | 'layout' | 'range'
54
+ >;
55
+ };
56
+
57
+ type SessionEntry = {
58
+ session: CameraFrameCaptureSession;
59
+ signature: string;
60
+ };
61
+
62
+ function sessionSignature(stream: PolicyCameraTensorStream): string {
63
+ return JSON.stringify({
64
+ width: stream.width,
65
+ height: stream.height,
66
+ channels: stream.channels,
67
+ renderIsolation: stream.renderIsolation ?? false,
68
+ cameraName: stream.cameraName,
69
+ siteName: stream.siteName,
70
+ bodyName: stream.bodyName,
71
+ });
72
+ }
73
+
74
+ function addTensorAliases(
75
+ tensors: Record<string, CameraFrameTensorResult>,
76
+ stream: PolicyCameraTensorStream,
77
+ tensor: CameraFrameTensorResult,
78
+ includeObservationImageAliases: boolean
79
+ ) {
80
+ const keys = new Set<string>([stream.key, ...(stream.aliases ?? [])]);
81
+ if (includeObservationImageAliases) {
82
+ for (const base of [stream.key, ...(stream.aliases ?? [])]) {
83
+ keys.add(`observation.images.${base}`);
84
+ }
85
+ }
86
+ for (const key of keys) tensors[key] = tensor;
87
+ }
88
+
89
+ export function usePolicyCameraTensors(
90
+ options: PolicyCameraTensorsOptions
91
+ ): PolicyCameraTensorsAPI {
92
+ const mujoco = useMujoco();
93
+ const [status, setStatus] = useState<FrameCaptureStatus>('idle');
94
+ const [error, setError] = useState<Error | null>(null);
95
+ const sessionsRef = useRef<Map<string, SessionEntry>>(new Map());
96
+
97
+ const disposeSessions = useCallback(() => {
98
+ for (const { session } of sessionsRef.current.values()) session.dispose();
99
+ sessionsRef.current.clear();
100
+ }, []);
101
+
102
+ useEffect(() => disposeSessions, [disposeSessions]);
103
+
104
+ const reset = useCallback(() => {
105
+ setStatus('idle');
106
+ setError(null);
107
+ }, []);
108
+
109
+ const capture = useCallback((): PolicyCameraTensorsResult => {
110
+ const api = mujoco.api;
111
+ if (!api) {
112
+ throw new Error('MuJoCo scene is not ready for policy camera tensor capture.');
113
+ }
114
+
115
+ setStatus('capturing');
116
+ setError(null);
117
+
118
+ try {
119
+ const sessions = sessionsRef.current;
120
+ const seen = new Set<string>();
121
+ const tensors: Record<string, CameraFrameTensorResult> = {};
122
+ const sourceParts: string[] = [];
123
+
124
+ for (const stream of options.streams) {
125
+ seen.add(stream.key);
126
+ const resolved: CameraFrameCaptureTensorOptions = {
127
+ ...api.resolveCameraCaptureOptions(stream),
128
+ channels: stream.channels,
129
+ layout: stream.layout,
130
+ range: stream.range,
131
+ };
132
+
133
+ const signature = sessionSignature(stream);
134
+ let entry = sessions.get(stream.key);
135
+ if (!entry || entry.signature !== signature) {
136
+ entry?.session.dispose();
137
+ entry = {
138
+ session: api.createCameraFrameCaptureSession(resolved),
139
+ signature,
140
+ };
141
+ sessions.set(stream.key, entry);
142
+ }
143
+
144
+ const tensor = entry.session.captureTensor(resolved);
145
+ addTensorAliases(
146
+ tensors,
147
+ stream,
148
+ tensor,
149
+ options.includeObservationImageAliases ?? false
150
+ );
151
+ sourceParts.push(`${stream.key}:${tensor.source.kind}`);
152
+ }
153
+
154
+ // Drop sessions for streams that are no longer requested.
155
+ for (const key of [...sessions.keys()]) {
156
+ if (!seen.has(key)) {
157
+ sessions.get(key)?.session.dispose();
158
+ sessions.delete(key);
159
+ }
160
+ }
161
+
162
+ setStatus('captured');
163
+ return {
164
+ tensors,
165
+ sourceSummary: sourceParts.join(' + ') || 'not used by policy',
166
+ capturedAt: Date.now(),
167
+ };
168
+ } catch (nextError) {
169
+ const captureError =
170
+ nextError instanceof Error
171
+ ? nextError
172
+ : new Error('Unable to capture policy camera tensors.');
173
+ setError(captureError);
174
+ setStatus('error');
175
+ throw captureError;
176
+ }
177
+ }, [mujoco.api, options.includeObservationImageAliases, options.streams]);
178
+
179
+ return {
180
+ status,
181
+ error,
182
+ isCapturing: status === 'capturing',
183
+ capture,
184
+ reset,
185
+ };
186
+ }
187
+
188
+ export function usePolicyCameraTensorsFromMountedStreams(
189
+ options: MountedPolicyCameraTensorOptions
190
+ ): PolicyCameraTensorsAPI {
191
+ const mujoco = useMujoco();
192
+ const tensorOptions = options.tensor;
193
+ const mountedOptions = useMemo<PolicyCameraTensorsOptions>(() => {
194
+ const api = mujoco.api;
195
+ if (!api) {
196
+ return {
197
+ streams: [],
198
+ includeObservationImageAliases: options.includeObservationImageAliases ?? false,
199
+ };
200
+ }
201
+
202
+ const plan = createPolicyCameraFrameCapturePlanFromApi(api, options);
203
+ return {
204
+ streams: plan.streams.map(({ key, aliases, ...stream }) => ({
205
+ ...stream,
206
+ ...tensorOptions,
207
+ key,
208
+ aliases,
209
+ })),
210
+ includeObservationImageAliases: plan.includeObservationImageAliases ?? false,
211
+ };
212
+ }, [mujoco.api, options, tensorOptions]);
213
+
214
+ return usePolicyCameraTensors(mountedOptions);
215
+ }
package/src/index.ts CHANGED
@@ -34,6 +34,8 @@ export type { ControllerOptions, ControllerComponent } from './core/createContro
34
34
 
35
35
  // IK controller hook
36
36
  export { useIkController } from './hooks/useIkController';
37
+ export { GenericIK } from './core/GenericIK';
38
+ export type { GenericIKOptions } from './core/GenericIK';
37
39
 
38
40
  // Components
39
41
  export { Body } from './components/Body';
@@ -74,6 +76,10 @@ export type {
74
76
  SplatCollisionProxyPreviewVector3,
75
77
  UseSplatCollisionProxyGeomsOptions,
76
78
  } from './components/SplatCollisionProxyPreview';
79
+ export { CameraView, useCameraViewport } from './components/CameraView';
80
+ export type { CameraViewProps, CameraViewportOptions } from './components/CameraView';
81
+ export { useCameraStream } from './hooks/useCameraStream';
82
+ export type { CameraStreamOptions } from './hooks/useCameraStream';
77
83
  export { Debug } from './components/Debug';
78
84
  export { TendonRenderer } from './components/TendonRenderer';
79
85
  export { FlexRenderer } from './components/FlexRenderer';
@@ -91,6 +97,13 @@ export { useBodyState } from './hooks/useBodyState';
91
97
  export { useBodyPose, useGeomPose, useSitePose } from './hooks/usePose';
92
98
  export type { PoseReadout, PoseResourceKind } from './hooks/usePose';
93
99
  export { useCtrl } from './hooks/useCtrl';
100
+ export { controlGroup, useControlGroup } from './hooks/useControlGroup';
101
+ export type {
102
+ ControlGroup,
103
+ ControlGroupHandle,
104
+ ControlGroupSetOptions,
105
+ UseControlGroupOptions,
106
+ } from './hooks/useControlGroup';
94
107
  export { useControlWriter } from './hooks/useControlWriter';
95
108
  export type {
96
109
  ControlWriterConflict,
@@ -130,6 +143,17 @@ export type {
130
143
  MountedPolicyCameraFrameCaptureAPI,
131
144
  MountedPolicyCameraFrameCaptureOptions,
132
145
  } from './hooks/usePolicyCameraFrames';
146
+ export {
147
+ usePolicyCameraTensors,
148
+ usePolicyCameraTensorsFromMountedStreams,
149
+ } from './hooks/usePolicyCameraTensors';
150
+ export type {
151
+ MountedPolicyCameraTensorOptions,
152
+ PolicyCameraTensorsAPI,
153
+ PolicyCameraTensorsOptions,
154
+ PolicyCameraTensorsResult,
155
+ PolicyCameraTensorStream,
156
+ } from './hooks/usePolicyCameraTensors';
133
157
  export { useCameraSequenceRecorder } from './hooks/useCameraSequenceRecorder';
134
158
  export { useMountedCameraSequenceRecorder } from './hooks/useMountedCameraSequenceRecorder';
135
159
  export type {
@@ -145,9 +169,16 @@ export {
145
169
  CAPTURE_EXCLUDE_KEY,
146
170
  captureCameraFrame,
147
171
  captureCameraFrameBlob,
172
+ captureCameraFrameTensor,
148
173
  createCameraFrameCaptureSession,
149
174
  renderCameraFrameToCanvas,
150
175
  } from './rendering/cameraFrameCapture';
176
+ export type {
177
+ CameraFrameCaptureSession,
178
+ CameraFrameCaptureTensorOptions,
179
+ CameraFramePixelsResult,
180
+ CameraFrameTensorResult,
181
+ } from './rendering/cameraFrameCapture';
151
182
  export {
152
183
  imagePointToNdc,
153
184
  projectImagePointTo3D,
@@ -172,6 +203,11 @@ export {
172
203
  readNamedObservation,
173
204
  sitePositionField,
174
205
  } from './policyObservation';
206
+ export {
207
+ dataUrlToPolicyImageTensor,
208
+ imageDataToPolicyImageTensor,
209
+ pixelsToPolicyImageTensor,
210
+ } from './policyImageTensors';
175
211
  export type {
176
212
  CreatePolicyCameraFrameCapturePlanOptions,
177
213
  PolicyCameraFrameCapturePlan,
@@ -191,6 +227,14 @@ export type {
191
227
  NamedObservationOptions,
192
228
  NamedObservationResult,
193
229
  } from './policyObservation';
230
+ export type {
231
+ PolicyImageTensorLayout,
232
+ PolicyImageTensorOptions,
233
+ PolicyImageTensorPixelOptions,
234
+ PolicyImageTensorRange,
235
+ PolicyImageTensorResult,
236
+ PolicyImageTensorSourceOrigin,
237
+ } from './policyImageTensors';
194
238
  export {
195
239
  createMountedCameraFrameSequenceManifest,
196
240
  createMountedCameraFrameSequenceReadiness,