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.
- package/README.md +73 -136
- package/dist/{chunk-FBXXXPLQ.js → chunk-KHZ5U36J.js} +157 -16
- package/dist/chunk-KHZ5U36J.js.map +1 -0
- package/dist/index.d.ts +179 -48
- package/dist/index.js +470 -17
- package/dist/index.js.map +1 -1
- package/dist/onnx.d.ts +65 -0
- package/dist/onnx.js +58 -0
- package/dist/onnx.js.map +1 -0
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-CdFZCYmy.d.ts → types-CViUme8D.d.ts} +141 -1
- package/package.json +14 -3
- package/src/components/CameraView.tsx +245 -0
- package/src/core/GenericIK.ts +16 -4
- package/src/core/MujocoSimProvider.tsx +37 -1
- package/src/core/SceneLoader.ts +3 -2
- package/src/hooks/useCameraStream.ts +115 -0
- package/src/hooks/useControlGroup.ts +0 -0
- package/src/hooks/useIkController.ts +3 -0
- package/src/hooks/usePolicyCameraTensors.ts +215 -0
- package/src/index.ts +44 -0
- package/src/onnx.ts +126 -0
- package/src/policyImageTensors.ts +150 -0
- package/src/rendering/cameraFrameCapture.ts +112 -15
- package/src/types.ts +28 -0
- package/dist/chunk-FBXXXPLQ.js.map +0 -1
package/src/core/GenericIK.ts
CHANGED
|
@@ -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
|
-
|
|
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>):
|
|
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]
|
|
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
|
}
|
package/src/core/SceneLoader.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|