Source code for go2.modules.video.sources.virtual_source
import cv2
import threading
import numpy as np
import iceoryx2 as iox2
from typing import Optional
from typing_extensions import override
from iceoryx_interfaces.camera_data import FrameData_
from iceoryx_interfaces.qos import CameraQoS
from .camera_source import CameraSource
from ..frame_result import FrameResult
[docs]
class VirtualCameraSource(CameraSource):
def __init__(self) -> None:
self._thread: Optional[threading.Thread] = None
self._stop_event = threading.Event()
self._latest_frames: Optional[tuple[np.ndarray, np.ndarray]] = None
self._initialize_iox_services()
@override
def _start(self) -> None:
self._thread = threading.Thread(target=self._iox_thread, daemon=True)
self._thread.start()
def _initialize_iox_services(self) -> None:
iox2.set_log_level_from_env_or(iox2.LogLevel.Error)
# Kill signal handling: https://github.com/eclipse-iceoryx/iceoryx2/issues/528
self._node = iox2.NodeBuilder.new() \
.signal_handling_mode(iox2.SignalHandlingMode.Disabled) \
.create(iox2.ServiceType.Ipc)
self._cam_service = self._node.service_builder(iox2.ServiceName.new(CameraQoS.TOPIC_SIM_CAMERA)) \
.publish_subscribe(FrameData_) \
.max_publishers(CameraQoS.MAX_PUBLISHERS) \
.max_subscribers(CameraQoS.MAX_SUBSCRIBERS) \
.subscriber_max_buffer_size(CameraQoS.SUBSCRIBER_MAX_BUFFER_SIZE) \
.subscriber_max_borrowed_samples(CameraQoS.SUBSCRIBER_MAX_BORROWED_SAMPLES) \
.history_size(CameraQoS.HISTORY_SIZE) \
.open_or_create()
self._cam_sub = self._cam_service.subscriber_builder().create()
self._cycle_time = iox2.Duration.from_millis(1)
def _iox_thread(self) -> None:
while not self._stop_event.is_set():
self._node.wait(self._cycle_time)
sample = self._cam_sub.receive()
if sample is None:
continue
msg = sample.payload().contents
h, w = msg.height, msg.width
# I am more aggressive with the copy here to immediately allow a DEC_REF and the data to be freed in shared memory.
rgb = cv2.cvtColor(np.array(msg.rgb_data, copy=True, dtype=np.uint8 ).reshape((h, w, 3))[::-1], cv2.COLOR_RGB2BGR)
depth = np.array(msg.depth_data, copy=True, dtype=np.uint16).reshape((h, w))[::-1]
self._latest_frames = (rgb, depth) # atomic under GIL
@override
def _get_frames(self) -> FrameResult:
pair, self._latest_frames = self._latest_frames, None
if pair is None:
return FrameResult.pending()
return FrameResult.color_and_depth(color=pair[0], depth=pair[1])
@override
def _shutdown(self) -> None:
self._stop_event.set()
if self._thread:
self._thread.join()
self._thread = None
self._latest_frames = None