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/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img
|
|
2
|
+
<img width="941" height="598" alt="Screenshot 2026-06-24 at 5 44 13 PM" src="https://github.com/user-attachments/assets/09999f02-c093-473e-aaee-519fe2e4cdd4" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# mujoco-react
|
|
@@ -1095,29 +1095,20 @@ const video = useVideoRecorder({ fps: 30, mimeType: "video/webm" });
|
|
|
1095
1095
|
// video.start(), video.stop() -> returns Blob
|
|
1096
1096
|
```
|
|
1097
1097
|
|
|
1098
|
-
###
|
|
1098
|
+
### Camera Frames, Streams, and Tensors
|
|
1099
1099
|
|
|
1100
|
-
|
|
1100
|
+
A camera pose — the viewer camera, a MuJoCo `cameraName`/`siteName`/`bodyName`, or
|
|
1101
|
+
an explicit `position`+`lookAt` — can be turned into three outputs: a **snapshot**
|
|
1102
|
+
(PNG/JPEG), a **live on-screen stream**, or a **policy tensor**.
|
|
1101
1103
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
the canvas or want to capture a custom container.
|
|
1111
|
-
|
|
1112
|
-
Use `captureCameraFrame()` / `captureCameraFrameBlob()` when dataset generation
|
|
1113
|
-
needs an offscreen camera render at a stable resolution without moving the
|
|
1114
|
-
user's interactive viewport. Pass `cameraName`, `siteName`, or `bodyName` to
|
|
1115
|
-
record true MuJoCo-mounted camera frames; the returned image includes
|
|
1116
|
-
`source.kind` so dataset pipelines can reject fallback or synthetic fixed poses.
|
|
1117
|
-
For named MuJoCo cameras, set `mujocoCameraCompatibility` when you want the
|
|
1118
|
-
Three.js offscreen camera to inherit the MJCF camera's `resolution`, `fovy`,
|
|
1119
|
-
near/far clipping, and calibrated intrinsics when the WASM model exposes
|
|
1120
|
-
`cam_intrinsic` plus `cam_sensorsize`:
|
|
1104
|
+
**Snapshots.** `captureFrame()`/`captureFrameBlob()` grab the live canvas (or use
|
|
1105
|
+
`useFrameCapture()` / the standalone `captureFrame()` when you own the canvas).
|
|
1106
|
+
`captureCameraFrame()`/`captureCameraFrameBlob()` render a chosen camera offscreen
|
|
1107
|
+
at a stable resolution without moving the viewport; pass `cameraName`/`siteName`/
|
|
1108
|
+
`bodyName` for true mounted frames (`source.kind` lets dataset pipelines reject
|
|
1109
|
+
fallback/synthetic poses). Set `mujocoCameraCompatibility` to inherit a MJCF
|
|
1110
|
+
camera's `resolution`, `fovy`, clipping, and calibrated intrinsics; use
|
|
1111
|
+
`visualOverrides`/`renderIsolation` for canonical training captures.
|
|
1121
1112
|
|
|
1122
1113
|
```tsx
|
|
1123
1114
|
const frame = await apiRef.current?.captureCameraFrame({
|
|
@@ -1128,138 +1119,84 @@ const frame = await apiRef.current?.captureCameraFrame({
|
|
|
1128
1119
|
});
|
|
1129
1120
|
```
|
|
1130
1121
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
space, and `renderIsolation` to render with an independent offscreen
|
|
1136
|
-
`WebGLRenderer` instead of inheriting viewer renderer state.
|
|
1137
|
-
|
|
1138
|
-
Use `recordCameraSequence()` / `useCameraSequenceRecorder()` to step policy
|
|
1139
|
-
rollouts and capture synchronized per-camera frames from one or more MuJoCo
|
|
1140
|
-
camera configs. Sequence recording requires mounted MuJoCo camera, site, or
|
|
1141
|
-
body selectors by default; use still capture APIs for synthetic debug poses.
|
|
1142
|
-
|
|
1143
|
-
For LeRobot-style datasets, prefer the named-camera wrapper. It resolves task
|
|
1144
|
-
camera keys to MuJoCo cameras/sites/bodies, records the sequence, and returns
|
|
1145
|
-
the plan and readiness summary alongside frame provenance:
|
|
1122
|
+
**Live streams.** `useCameraStream(canvasRef, options)` renders a camera into a
|
|
1123
|
+
DOM `<canvas>` every frame (offscreen render → blit: no PNG round-trip, no render-
|
|
1124
|
+
loop takeover, splat scenes stream safely). Call it inside `<MujocoCanvas>`; the
|
|
1125
|
+
canvas can live anywhere in your DOM:
|
|
1146
1126
|
|
|
1147
1127
|
```tsx
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
left_wrist: [{ siteName: "left_wrist_camera_optical_frame" }],
|
|
1153
|
-
right_wrist: [{ siteName: "right_wrist_camera_optical_frame" }],
|
|
1154
|
-
},
|
|
1155
|
-
defaults: {
|
|
1156
|
-
width: 640,
|
|
1157
|
-
height: 480,
|
|
1158
|
-
type: "image/png",
|
|
1159
|
-
fov: 45,
|
|
1160
|
-
near: 0.01,
|
|
1161
|
-
far: 100,
|
|
1162
|
-
},
|
|
1163
|
-
frames: 16,
|
|
1164
|
-
stepsPerFrame: 1,
|
|
1165
|
-
retainFrames: false,
|
|
1166
|
-
requireMountedSources: true,
|
|
1167
|
-
onFrame: ({ frameIndex, cameras }) => {
|
|
1168
|
-
queueLeRobotImages(frameIndex, cameras);
|
|
1169
|
-
},
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
sequence.readiness.ready; // true when every requested stream resolved
|
|
1173
|
-
sequence.plan.missingKeys; // unresolved task cameras, if requireAll is false
|
|
1174
|
-
sequence.cameraSummaries.head.source; // mounted source provenance
|
|
1175
|
-
|
|
1176
|
-
const manifest = createMountedCameraFrameSequenceManifest(sequence);
|
|
1177
|
-
manifest.streamSummaries.head.complete; // per-camera frame coverage
|
|
1178
|
-
manifest.status; // "complete", "partial", or "missing"
|
|
1128
|
+
function WristStream({ canvasRef }) {
|
|
1129
|
+
useCameraStream(canvasRef, { cameraName: "wrist_cam", width: 256, height: 192 });
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1179
1132
|
```
|
|
1180
1133
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1134
|
+
For a transparent picture-in-picture overlay, use `<CameraView>` (or
|
|
1135
|
+
`useCameraViewport` to track your own element). It scissors into the main canvas —
|
|
1136
|
+
cheaper, but it takes over the render loop while mounted (incompatible with
|
|
1137
|
+
postprocessing, occluded by opaque DOM), so prefer `useCameraStream` for panel
|
|
1138
|
+
tiles:
|
|
1185
1139
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1140
|
+
```tsx
|
|
1141
|
+
<MujocoCanvas config={config}>
|
|
1142
|
+
<CameraView cameraName="wrist_cam" style={{ right: 16, bottom: 16, width: 240, height: 180 }} />
|
|
1143
|
+
</MujocoCanvas>
|
|
1144
|
+
```
|
|
1189
1145
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
LeRobot camera streams:
|
|
1146
|
+
**Policy tensors.** For in-browser inference, capture straight into a
|
|
1147
|
+
`Float32Array` — no canvas, no PNG. `usePolicyCameraTensors` keeps one reusable
|
|
1148
|
+
session per camera and re-aims it to the live pose each step:
|
|
1194
1149
|
|
|
1195
1150
|
```tsx
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
right_wrist: { siteName: "right_wrist_camera_optical_frame" },
|
|
1203
|
-
},
|
|
1204
|
-
});
|
|
1205
|
-
|
|
1206
|
-
async function recordDatasetEpisode() {
|
|
1207
|
-
const cameraKeys = ["head", "left_wrist", "right_wrist"];
|
|
1208
|
-
const readiness = recorder.checkReadiness(cameraKeys);
|
|
1209
|
-
if (!readiness.ready) return;
|
|
1210
|
-
|
|
1211
|
-
await recorder.record({
|
|
1212
|
-
cameraKeys,
|
|
1213
|
-
frames: 16,
|
|
1214
|
-
retainFrames: false,
|
|
1215
|
-
onFrame: ({ frameIndex, cameras }) => {
|
|
1216
|
-
queueLeRobotImages(frameIndex, cameras);
|
|
1217
|
-
},
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1151
|
+
const cams = usePolicyCameraTensors({
|
|
1152
|
+
streams: [
|
|
1153
|
+
{ key: "wrist", cameraName: "wrist_cam", width: 96, height: 96, layout: "CHW" },
|
|
1154
|
+
{ key: "front", cameraName: "front", width: 96, height: 96, layout: "CHW" },
|
|
1155
|
+
],
|
|
1156
|
+
});
|
|
1220
1157
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1158
|
+
useAfterPhysicsStep(() => {
|
|
1159
|
+
const { tensors } = cams.capture();
|
|
1160
|
+
const wrist = new ort.Tensor("float32", tensors.wrist.data, [1, ...tensors.wrist.shape]);
|
|
1161
|
+
});
|
|
1227
1162
|
```
|
|
1228
1163
|
|
|
1229
|
-
`
|
|
1230
|
-
`
|
|
1231
|
-
|
|
1164
|
+
Use `usePolicyCameraTensorsFromMountedStreams` for dataset-name resolution,
|
|
1165
|
+
`captureCameraFrameTensor()` for one-offs, or `createCameraFrameCaptureSession()`
|
|
1166
|
+
+ `pixelsToPolicyImageTensor()` for lower-level control. The optional
|
|
1167
|
+
`mujoco-react/onnx` entry point wraps ONNX Runtime Web: `createOnnxPolicySession()`
|
|
1168
|
+
loads a manifest plus model, and `onnxTensorToPolicyActionChunk()` decodes the
|
|
1169
|
+
output into action chunks.
|
|
1232
1170
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
`wrist`. It also handles normalized/prefix/suffix matches such as
|
|
1239
|
-
`left_wrist` -> `left_wrist_camera_optical_frame`, and token-contained
|
|
1240
|
-
imported-model names such as `observation.images.head` ->
|
|
1241
|
-
`robot_head_camera`. It returns both the capture selector and the mounted-source
|
|
1242
|
-
provenance that should be stored beside the dataset:
|
|
1171
|
+
**Dataset recording.** `recordMountedCameraFrameSequence()` steps a rollout and
|
|
1172
|
+
captures synchronized per-camera frames, resolving LeRobot-style task camera keys
|
|
1173
|
+
to MuJoCo cameras/sites/bodies. It requires every requested `cameraKey` by default
|
|
1174
|
+
(set `requireAll: false` for exploratory tooling) and returns a plan, readiness,
|
|
1175
|
+
and per-camera source provenance:
|
|
1243
1176
|
|
|
1244
1177
|
```tsx
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
await api.recordCameraSequence({
|
|
1178
|
+
const sequence = await recordMountedCameraFrameSequence(api, {
|
|
1179
|
+
cameraKeys: ["head", "left_wrist", "right_wrist"],
|
|
1180
|
+
aliases: {
|
|
1181
|
+
head: [{ siteName: "head_camera_rgb_optical_frame" }],
|
|
1182
|
+
left_wrist: [{ siteName: "left_wrist_camera_optical_frame" }],
|
|
1183
|
+
right_wrist: [{ siteName: "right_wrist_camera_optical_frame" }],
|
|
1184
|
+
},
|
|
1185
|
+
defaults: { width: 640, height: 480, type: "image/png", fov: 45 },
|
|
1254
1186
|
frames: 16,
|
|
1255
|
-
|
|
1256
|
-
cameras: [{ key: "head", width: 640, height: 480, ...resolved.selector }],
|
|
1187
|
+
onFrame: ({ frameIndex, cameras }) => queueLeRobotImages(frameIndex, cameras),
|
|
1257
1188
|
});
|
|
1189
|
+
|
|
1190
|
+
const manifest = createMountedCameraFrameSequenceManifest(sequence); // dataset-facing manifest
|
|
1258
1191
|
```
|
|
1259
1192
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1193
|
+
Inside `<MujocoCanvas>`, `useMountedCameraSequenceRecorder()` exposes the same
|
|
1194
|
+
planning/recording with React status and a `checkReadiness()` preflight gate.
|
|
1195
|
+
`resolveMountedCameraFrameSource()` maps a single dataset feature name to a MuJoCo
|
|
1196
|
+
source (aliases first, then camera > site > body, then normalized prefix/suffix
|
|
1197
|
+
matches) when you need the selector before recording. The lower-level
|
|
1198
|
+
`recordCameraSequence()` / `useCameraSequenceRecorder()` take explicit camera
|
|
1199
|
+
configs.
|
|
1263
1200
|
|
|
1264
1201
|
### `useCtrlNoise(config)`
|
|
1265
1202
|
|
|
@@ -85,6 +85,95 @@ var SplatEnvironmentReadinessStatus = {
|
|
|
85
85
|
UnsupportedFormat: "unsupported-format",
|
|
86
86
|
Ready: "ready"
|
|
87
87
|
};
|
|
88
|
+
|
|
89
|
+
// src/policyImageTensors.ts
|
|
90
|
+
function resolveTensorOptions(options) {
|
|
91
|
+
return {
|
|
92
|
+
channels: 3,
|
|
93
|
+
layout: "CHW",
|
|
94
|
+
range: [0, 1],
|
|
95
|
+
...options
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function normalizeChannel(value, range) {
|
|
99
|
+
const [min, max] = range;
|
|
100
|
+
if (min === 0 && max === 255) return value;
|
|
101
|
+
return min + value / 255 * (max - min);
|
|
102
|
+
}
|
|
103
|
+
function pixelsToPolicyImageTensor(pixels, options) {
|
|
104
|
+
const resolved = resolveTensorOptions(options);
|
|
105
|
+
const { width, height, channels, layout, range } = resolved;
|
|
106
|
+
const expected = width * height * 4;
|
|
107
|
+
if (pixels.length < expected) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Pixel buffer of length ${pixels.length} is too small for ${width}x${height} RGBA data (${expected} bytes).`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const flipY = options.sourceOrigin === "bottom-left";
|
|
113
|
+
const flipX = options.flipX ?? false;
|
|
114
|
+
const pixelCount = width * height;
|
|
115
|
+
const data = new Float32Array(pixelCount * channels);
|
|
116
|
+
for (let y = 0; y < height; y += 1) {
|
|
117
|
+
const sourceY = flipY ? height - y - 1 : y;
|
|
118
|
+
for (let x = 0; x < width; x += 1) {
|
|
119
|
+
const sourceX = flipX ? width - x - 1 : x;
|
|
120
|
+
const source = (sourceY * width + sourceX) * 4;
|
|
121
|
+
const target = y * width + x;
|
|
122
|
+
for (let channel = 0; channel < channels; channel += 1) {
|
|
123
|
+
const value = normalizeChannel(pixels[source + channel], range);
|
|
124
|
+
if (layout === "CHW") {
|
|
125
|
+
data[channel * pixelCount + target] = value;
|
|
126
|
+
} else {
|
|
127
|
+
data[target * channels + channel] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
data,
|
|
134
|
+
shape: layout === "CHW" ? [channels, height, width] : [height, width, channels],
|
|
135
|
+
width,
|
|
136
|
+
height,
|
|
137
|
+
channels,
|
|
138
|
+
layout,
|
|
139
|
+
range
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function imageDataToPolicyImageTensor(imageData, options) {
|
|
143
|
+
const resolved = resolveTensorOptions(options);
|
|
144
|
+
if (imageData.width !== resolved.width || imageData.height !== resolved.height) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`ImageData size ${imageData.width}x${imageData.height} does not match tensor size ${resolved.width}x${resolved.height}.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return pixelsToPolicyImageTensor(imageData.data, {
|
|
150
|
+
...resolved,
|
|
151
|
+
sourceOrigin: "top-left"
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function decodeImageSource(dataUrl) {
|
|
155
|
+
const image = new Image();
|
|
156
|
+
image.decoding = "async";
|
|
157
|
+
image.src = dataUrl;
|
|
158
|
+
await image.decode();
|
|
159
|
+
return image;
|
|
160
|
+
}
|
|
161
|
+
async function dataUrlToPolicyImageTensor(dataUrl, options) {
|
|
162
|
+
const resolved = resolveTensorOptions(options);
|
|
163
|
+
const image = await decodeImageSource(dataUrl);
|
|
164
|
+
const canvas = document.createElement("canvas");
|
|
165
|
+
canvas.width = resolved.width;
|
|
166
|
+
canvas.height = resolved.height;
|
|
167
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
168
|
+
if (!context) {
|
|
169
|
+
throw new Error("Unable to create a 2D canvas context for policy image tensor conversion.");
|
|
170
|
+
}
|
|
171
|
+
context.drawImage(image, 0, 0, resolved.width, resolved.height);
|
|
172
|
+
return imageDataToPolicyImageTensor(
|
|
173
|
+
context.getImageData(0, 0, resolved.width, resolved.height),
|
|
174
|
+
resolved
|
|
175
|
+
);
|
|
176
|
+
}
|
|
88
177
|
var CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCaptureRender";
|
|
89
178
|
var CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCapturePreRender";
|
|
90
179
|
var CAPTURE_EXCLUDE_KEY = "mujoco.capture.exclude";
|
|
@@ -465,7 +554,7 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
465
554
|
);
|
|
466
555
|
return captureOptions;
|
|
467
556
|
}
|
|
468
|
-
function
|
|
557
|
+
function renderCaptureToTarget(captureOptions, readback) {
|
|
469
558
|
const previousState = saveRendererState(sessionRenderer);
|
|
470
559
|
const previousSceneState = applyCaptureVisualOverrides(
|
|
471
560
|
sessionRenderer,
|
|
@@ -494,6 +583,15 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
494
583
|
}
|
|
495
584
|
sessionRenderer.clear();
|
|
496
585
|
sessionRenderer.render(scene, camera);
|
|
586
|
+
readback();
|
|
587
|
+
} finally {
|
|
588
|
+
restoreObjectVisibility(hidden);
|
|
589
|
+
if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
|
|
590
|
+
restoreRendererState(sessionRenderer, previousState);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function renderPreparedCapture(captureOptions) {
|
|
594
|
+
renderCaptureToTarget(captureOptions, () => {
|
|
497
595
|
readRenderTargetToCanvas(
|
|
498
596
|
sessionRenderer,
|
|
499
597
|
target,
|
|
@@ -506,22 +604,44 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
506
604
|
sessionRenderer.outputColorSpace,
|
|
507
605
|
captureOptions.flipX ?? false
|
|
508
606
|
);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
restoreObjectVisibility(hidden);
|
|
518
|
-
if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
|
|
519
|
-
restoreRendererState(sessionRenderer, previousState);
|
|
520
|
-
}
|
|
607
|
+
});
|
|
608
|
+
return {
|
|
609
|
+
canvas,
|
|
610
|
+
camera,
|
|
611
|
+
width,
|
|
612
|
+
height,
|
|
613
|
+
source: getCameraFrameCaptureSource(captureOptions)
|
|
614
|
+
};
|
|
521
615
|
}
|
|
522
616
|
function capture(nextOptions = {}) {
|
|
523
617
|
return renderPreparedCapture(resolveCaptureOptions(nextOptions));
|
|
524
618
|
}
|
|
619
|
+
function capturePixels(nextOptions = {}) {
|
|
620
|
+
const captureOptions = resolveCaptureOptions(nextOptions);
|
|
621
|
+
renderCaptureToTarget(captureOptions, () => {
|
|
622
|
+
sessionRenderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
pixels,
|
|
626
|
+
camera,
|
|
627
|
+
width,
|
|
628
|
+
height,
|
|
629
|
+
source: getCameraFrameCaptureSource(captureOptions)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function captureTensor(nextOptions = {}) {
|
|
633
|
+
const result = capturePixels(nextOptions);
|
|
634
|
+
const tensor = pixelsToPolicyImageTensor(pixels, {
|
|
635
|
+
width,
|
|
636
|
+
height,
|
|
637
|
+
channels: nextOptions.channels,
|
|
638
|
+
layout: nextOptions.layout,
|
|
639
|
+
range: nextOptions.range,
|
|
640
|
+
sourceOrigin: "bottom-left",
|
|
641
|
+
flipX: nextOptions.flipX
|
|
642
|
+
});
|
|
643
|
+
return { ...tensor, camera, source: result.source };
|
|
644
|
+
}
|
|
525
645
|
async function captureAsync(nextOptions = {}) {
|
|
526
646
|
const captureOptions = resolveCaptureOptions(nextOptions);
|
|
527
647
|
runCapturePreRenderHooks(scene);
|
|
@@ -594,6 +714,8 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
|
|
|
594
714
|
height,
|
|
595
715
|
capture,
|
|
596
716
|
captureAsync,
|
|
717
|
+
capturePixels,
|
|
718
|
+
captureTensor,
|
|
597
719
|
captureDataUrl(nextOptions = {}) {
|
|
598
720
|
const type = nextOptions.type ?? options.type ?? "image/png";
|
|
599
721
|
const result = capture(nextOptions);
|
|
@@ -686,6 +808,19 @@ async function captureCameraFrameBlob(renderer, scene, fallbackCamera, options =
|
|
|
686
808
|
session.dispose();
|
|
687
809
|
}
|
|
688
810
|
}
|
|
811
|
+
function captureCameraFrameTensor(renderer, scene, fallbackCamera, options = {}) {
|
|
812
|
+
const session = createCameraFrameCaptureSession(
|
|
813
|
+
renderer,
|
|
814
|
+
scene,
|
|
815
|
+
fallbackCamera,
|
|
816
|
+
options
|
|
817
|
+
);
|
|
818
|
+
try {
|
|
819
|
+
return session.captureTensor(options);
|
|
820
|
+
} finally {
|
|
821
|
+
session.dispose();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
689
824
|
var DEFAULT_BACKGROUND = "#181a1f";
|
|
690
825
|
function ScenarioLighting({
|
|
691
826
|
preset = "studio",
|
|
@@ -1284,6 +1419,12 @@ function clamp01(value) {
|
|
|
1284
1419
|
* @license
|
|
1285
1420
|
* SPDX-License-Identifier: Apache-2.0
|
|
1286
1421
|
*/
|
|
1422
|
+
/**
|
|
1423
|
+
* @license
|
|
1424
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1425
|
+
*
|
|
1426
|
+
* Helpers for turning browser camera captures into policy image tensors.
|
|
1427
|
+
*/
|
|
1287
1428
|
/**
|
|
1288
1429
|
* @license
|
|
1289
1430
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -1291,6 +1432,6 @@ function clamp01(value) {
|
|
|
1291
1432
|
* Offscreen camera-frame capture for R3F/MuJoCo scenes.
|
|
1292
1433
|
*/
|
|
1293
1434
|
|
|
1294
|
-
export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
1295
|
-
//# sourceMappingURL=chunk-
|
|
1296
|
-
//# sourceMappingURL=chunk-
|
|
1435
|
+
export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createCaptureCamera, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, prepareCaptureCamera, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
1436
|
+
//# sourceMappingURL=chunk-KHZ5U36J.js.map
|
|
1437
|
+
//# sourceMappingURL=chunk-KHZ5U36J.js.map
|