Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 16 additions & 8 deletions dimos/imitation/collection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ controls through ordinary `autoconnect()` and `remappings()`.

## Declare the recorded features

This example declares measured joint state, accepted joint targets, and one RGB
This example declares measured joint state, commanded joint targets, and one RGB
image. Constructing the profile does not connect to hardware.

```python session=profile no-result
Expand All @@ -29,13 +29,21 @@ profile = CollectionProfile(
names=["height", "width", "channels"],
),
},
actions={"action": state.model_copy(update={
"stream": "accepted", "source_kind": "joint_position_updates",
}, deep=True)},
actions={
"action": state.model_copy(
update={
"stream": "commanded",
"source_kind": "joint_position_updates",
},
deep=True,
)
},
metadata={"name": "custom-arm", "robot_type": "custom-arm-2dof"},
)
assert profile.input_types() == {
"measured": JointState, "wrist_image": Image, "accepted": JointState,
"measured": JointState,
"wrist_image": Image,
"commanded": JointState,
}
```

Expand Down Expand Up @@ -66,7 +74,7 @@ recorder = collect(profile=profile, recording=Path("recordings/session-001"))
Include `recorder`, your robot, camera producers, and an
`EpisodeMonitorModule.blueprint(instance_name="episodes")` in `autoconnect()`.
The monitor supplies `status: In[String]` containing versioned JSON episode events.
Robot-specific readiness rules belong on that monitor. The recorder must precede
The monitor only controls episode start, save, and discard. The recorder must precede
producers in the Blueprint so it stops after them.

The Python graph supports any number of coordinators. Connect each recorded stream
Expand Down Expand Up @@ -131,8 +139,8 @@ add an explicit depth feature and connect its producer.
Capture stores asynchronous streams at their native rates. The saved schema fixes
the alignment to a 30 Hz grid, anchored at the first video observation, or the first
observation when there is no video. Snapshot matching uses a 20 ms tolerance.
Accepted joint updates use causal hold: only commands at or before the target time
contribute, and omitted joints retain their last accepted target. Missing accepted
Commanded joint updates use causal hold: only commands at or before the target time
contribute, and omitted joints retain their last commanded target. Missing commanded
targets never fall back to measured state or future commands.

The collection API exposes no `sync` or `quality` options. Existing low-level
Expand Down
2 changes: 1 addition & 1 deletion dimos/imitation/dataprep/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ config = DataPrepConfig(
},
action={
"action": FeatureSpec(
stream="applied_joint_position_command",
stream="coordinator_joint_command",
field="position",
dtype="float32",
shape=(2,),
Expand Down
10 changes: 5 additions & 5 deletions dimos/imitation/dataprep/test_mcap_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def _write_collection(path: Path) -> None:
"coordinator_joint_state": _register_channel(
writer, "coordinator_joint_state", JointState
),
"applied_joint_position_command": _register_channel(
writer, "applied_joint_position_command", JointState
"coordinator_joint_command": _register_channel(
writer, "coordinator_joint_command", JointState
),
"status": _register_channel(writer, "status", String, "json"),
}
Expand Down Expand Up @@ -130,7 +130,7 @@ def _write_collection(path: Path) -> None:
)
_write_message(
writer,
channels["applied_joint_position_command"],
channels["coordinator_joint_command"],
ts,
JointState(
ts=ts,
Expand Down Expand Up @@ -179,7 +179,7 @@ def _config(source: Path, output: Path) -> DataPrepConfig:
},
action={
"action": FeatureSpec(
stream="applied_joint_position_command",
stream="coordinator_joint_command",
field="position",
dtype="float32",
shape=(2,),
Expand Down Expand Up @@ -213,7 +213,7 @@ def writer(samples: Iterator[Sample], selected_output: OutputConfig) -> Path:
dataset_path = run_dataprep(config, writer=writer)

assert info["streams"] == {
"applied_joint_position_command": 3,
"coordinator_joint_command": 3,
"color_image": 3,
"coordinator_joint_state": 3,
"status": 2,
Expand Down
1 change: 1 addition & 0 deletions dimos/robot/all_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"mid360-realsense-record-with-pcap": "dimos.robot.assembly.mid360_realsense_30:mid360_realsense_record_with_pcap",
"openarm-planner-coordinator": "dimos.robot.manipulators.openarm.blueprints.basic:openarm_planner_coordinator",
"openyam-planner-coordinator": "dimos.robot.manipulators.openyam.blueprints.basic:openyam_planner_coordinator",
"openyam-quest-collection": "dimos.robot.manipulators.openyam.blueprints.learning_quest_collection:openyam_quest_collection",
"r1pro-coordinator": "dimos.robot.galaxea.r1pro.blueprints.basic.r1pro_coordinator:r1pro_coordinator",
"r1pro-manipulation": "dimos.robot.galaxea.r1pro.blueprints.manipulation.r1pro_manipulation:r1pro_manipulation",
"r1pro-nav": "dimos.robot.galaxea.r1pro.blueprints.navigation.r1pro_nav:r1pro_nav",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Quest-guided OpenYAM collection."""

from dimos.core.coordination.blueprints import autoconnect
from dimos.hardware.sensors.camera.module import WebcamModule
from dimos.hardware.sensors.camera.webcam import WebcamConfig
from dimos.imitation.collection.episode_monitor import EpisodeMonitorModule
from dimos.imitation.collection.profile import CollectionProfile
from dimos.imitation.collection.recorder import collect
from dimos.imitation.dataprep.schema import FeatureSpec
from dimos.msgs.sensor_msgs.Image import Image
from dimos.msgs.sensor_msgs.JointState import JointState
from dimos.robot.manipulators.openyam.blueprints.teleop import teleop_webxr_openyam
from dimos.robot.manipulators.openyam.joints import OPENYAM_JOINTS

_state = FeatureSpec(
stream="coordinator_joint_state",
message_type=JointState,
field="position",
dtype="float32",
shape=(len(OPENYAM_JOINTS),),
names=list(OPENYAM_JOINTS),
)
_profile = CollectionProfile(
observations={
"observation.images.wrist": FeatureSpec(
stream="wrist_image",
message_type=Image,
field="data",
dtype="video",
shape=(480, 640, 3),
names=["height", "width", "channels"],
),
"observation.state": _state,
},
actions={
"action": _state.model_copy(
update={
"stream": "coordinator_joint_command",
"source_kind": "joint_position_updates",
},
deep=True,
)
},
metadata={"name": "openyam-quest", "robot_type": "openyam"},
)

openyam_quest_collection = autoconnect(
collect(profile=_profile),
EpisodeMonitorModule.blueprint(instance_name="episodes"),
teleop_webxr_openyam,
WebcamModule.blueprint(
instance_name="wrist",
hardware=WebcamConfig(camera_index=0, fps=30, frame_id_prefix="wrist_image"),
frame_id="wrist_camera_link",
),
).remappings(
[
("wrist", "color_image", "wrist_image"),
("wrist", "camera_info", "wrist_camera_info"),
("wrist", "tf", "wrist_tf"),
]
)
Loading
Loading