Skip to content

Commit 9aa533a

Browse files
committed
Fix multiprocess Pico video frame streaming
1 parent 461aacc commit 9aa533a

2 files changed

Lines changed: 32 additions & 26 deletions

File tree

teleopit/inputs/pico_video.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ def __init__(
8282
def enabled(self) -> bool:
8383
return self._config.enabled
8484

85+
@property
86+
def pushed_frames(self) -> int:
87+
producer = self._producer
88+
if producer is None:
89+
return 0
90+
return int(getattr(producer, "pushed_frames", 0))
91+
8592
def start(self) -> None:
8693
if not self._config.enabled:
8794
return
@@ -152,6 +159,11 @@ def __init__(self, provider: Any, config: PicoVideoConfig) -> None:
152159
self._ready_event = threading.Event()
153160
self._thread = threading.Thread(target=self._run, name="pico_realsense_video", daemon=True)
154161
self._error: BaseException | None = None
162+
self._pushed_frames = 0
163+
164+
@property
165+
def pushed_frames(self) -> int:
166+
return int(self._pushed_frames)
155167

156168
def start(self) -> None:
157169
self._thread.start()
@@ -194,7 +206,7 @@ def _run(self) -> None:
194206
if not color_frame:
195207
continue
196208
rgb = np.ascontiguousarray(np.asanyarray(color_frame.get_data()), dtype=np.uint8)
197-
self._provider.push_video_frame(rgb)
209+
self._pushed_frames = int(self._provider.push_video_frame(rgb))
198210
finally:
199211
pipeline.stop()
200212
except BaseException as exc:
@@ -211,6 +223,11 @@ def __init__(self, provider: Any, config: PicoVideoConfig, robot: Any | None) ->
211223
self._renderer: Any | None = None
212224
self._next_frame_time = 0.0
213225
self._camera_name = "d435i_rgb"
226+
self._pushed_frames = 0
227+
228+
@property
229+
def pushed_frames(self) -> int:
230+
return int(self._pushed_frames)
214231

215232
def start(self) -> None:
216233
if self._robot is None:
@@ -236,7 +253,7 @@ def tick(self) -> None:
236253
raise RuntimeError("MuJoCo Pico video requires robot.data")
237254
self._renderer.update_scene(data, camera=self._camera_name)
238255
frame = np.ascontiguousarray(self._renderer.render(), dtype=np.uint8)
239-
self._provider.push_video_frame(frame)
256+
self._pushed_frames = int(self._provider.push_video_frame(frame))
240257
self._next_frame_time = now + 1.0 / float(self._config.fps)
241258

242259
def stop(self) -> None:

teleopit/sim2real/mp/runtime.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from teleopit.controllers.observation import VelCmdObservationBuilder, align_motion_qpos_yaw
1717
from teleopit.controllers.rl_policy import RLPolicyController
1818
from teleopit.inputs.pico4_provider import Pico4InputProvider
19-
from teleopit.inputs.pico_video import bridge_video_source, parse_pico_video_config
19+
from teleopit.inputs.pico_video import PicoVideoRuntime, bridge_video_source, parse_pico_video_config
2020
from teleopit.inputs.realtime_packet import ControlEvent, ControlEventType
2121
from teleopit.retargeting.core import RetargetingModule
2222
from teleopit.runtime.common import cfg_get, require_section
@@ -58,7 +58,7 @@
5858
SharedFrameDescriptor,
5959
SnapshotPacket,
6060
)
61-
from teleopit.sim2real.mp.shm import SharedFrameRingReader, SharedFrameRingWriter
61+
from teleopit.sim2real.mp.shm import SharedFrameRingWriter
6262
from teleopit.sim2real.reference_processor import Sim2RealReferenceProcessor
6363
from teleopit.sim2real.remote import UnitreeRemote
6464
from teleopit.sim2real.safety import Sim2RealSafetyManager
@@ -208,11 +208,8 @@ def _start_processes(self) -> None:
208208
if hand_mode != "off":
209209
specs.append(("hand_worker", _run_hand_worker))
210210
video_cfg = parse_pico_video_config(cfg_get(self.cfg, "input", {}))
211-
if video_cfg.enabled and video_cfg.source not in (None, "test-pattern"):
212-
specs.append(("video_worker", _run_video_worker))
213-
elif video_cfg.enabled and video_cfg.source == "test-pattern":
214-
# pico-bridge can generate test-pattern internally without a camera worker.
215-
logger.info("Pico video test-pattern uses pico_bridge internal source")
211+
if video_cfg.enabled:
212+
logger.info("Pico video runs inside pico_io so frames are pushed directly to PicoBridge")
216213

217214
for name, target in specs:
218215
process = self._ctx.Process(
@@ -255,12 +252,11 @@ def _main() -> None:
255252
events_pub = ZmqPublisher(endpoints.control_events_pub)
256253
health_pub = ZmqPublisher(endpoints.health_pub)
257254
command_sub = LatestSubscriber(endpoints.command_pub, COMMAND_TOPIC)
258-
video_sub = (
259-
LatestSubscriber(endpoints.video_pub, VIDEO_TOPIC)
260-
if video_cfg.enabled and video_cfg.source not in (None, "test-pattern")
261-
else None
255+
video_runtime = PicoVideoRuntime(
256+
provider=provider,
257+
config=video_cfg,
258+
mode="sim2real",
262259
)
263-
frame_reader = SharedFrameRingReader()
264260

265261
hz = float(cfg_get(_mp_cfg(cfg), "pico_io_hz", 120.0))
266262
sleep_s = 1.0 / max(hz, 1.0)
@@ -270,7 +266,9 @@ def _main() -> None:
270266
last_video_seq = -1
271267
last_health_s = 0.0
272268
try:
269+
video_runtime.start()
273270
while not stop_event.is_set():
271+
video_runtime.tick()
274272
command = command_sub.recv_latest()
275273
if isinstance(command, CommandPacket) and command.command == "shutdown":
276274
stop_event.set()
@@ -321,15 +319,8 @@ def _main() -> None:
321319
)
322320
last_hand_seq = int(hand_snapshot.seq)
323321

324-
if video_sub is not None:
325-
descriptor = video_sub.recv_latest()
326-
if isinstance(descriptor, SharedFrameDescriptor):
327-
try:
328-
frame = frame_reader.read(descriptor, copy=False)
329-
provider.push_video_frame(np.asarray(frame, dtype=np.uint8))
330-
last_video_seq = int(descriptor.seq)
331-
except Exception as exc:
332-
logger.warning("Pico video frame dropped: %s", exc)
322+
if video_cfg.enabled:
323+
last_video_seq = int(video_runtime.pushed_frames)
333324

334325
if now - last_health_s >= 1.0:
335326
health_pub.publish(
@@ -349,9 +340,7 @@ def _main() -> None:
349340
last_health_s = now
350341
time.sleep(sleep_s)
351342
finally:
352-
frame_reader.close()
353-
if video_sub is not None:
354-
video_sub.close()
343+
video_runtime.stop()
355344
command_sub.close()
356345
for publisher in (body_pub, hand_pub, controller_pub, events_pub, health_pub):
357346
publisher.close()

0 commit comments

Comments
 (0)