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
25 changes: 15 additions & 10 deletions docs/source/device/oak.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,34 +108,39 @@ controllers, etc.):
:doc:`TeleopSession <../getting_started/teleop_session>`'s ``PluginConfig``,
passing ``--collection-prefix`` so the plugin pushes metadata via OpenXR
``SchemaPusher``.
2. **Host tracker** — create ``FrameMetadataTrackerOak`` with the **same**
collection prefix and stream list (see :doc:`trackers`). TeleopSession's
2. **Host trackers** — create one ``FrameMetadataTrackerOak`` **per stream**,
each with collection id ``{collection_prefix}/{StreamName}`` using the
**same** prefix the plugin was given (see :doc:`trackers`). TeleopSession's
DeviceIO layer uses the live tracker implementation to read the pushed
tensors and write MCAP channels.
3. **MCAP config** — add the tracker to both ``TeleopSessionConfig.trackers``
and ``McapRecordingConfig.tracker_names``. See also
:doc:`../references/mcap_record_replay`.
3. **MCAP config** — add every tracker to both ``TeleopSessionConfig.trackers``
and ``McapRecordingConfig.tracker_names``, each under its own base name. See
also :doc:`../references/mcap_record_replay`.

.. code-block:: python

from pathlib import Path

from isaacteleop.deviceio import FrameMetadataTrackerOak, McapRecordingConfig, StreamType
from isaacteleop.deviceio import FrameMetadataTrackerOak, McapRecordingConfig
from isaacteleop.teleop_session_manager import PluginConfig, TeleopSession, TeleopSessionConfig

PLUGIN_ROOT = Path("build/src/plugins") # or your installed plugin search path
COLLECTION_PREFIX = "oak_camera"
STREAMS = [StreamType.Color, StreamType.MonoLeft]
STREAM_NAMES = ["Color", "MonoLeft"]

oak_tracker = FrameMetadataTrackerOak(COLLECTION_PREFIX, STREAMS)
# One tracker per stream, each on the collection the plugin publishes it under.
oak_trackers = {
name: FrameMetadataTrackerOak(f"{COLLECTION_PREFIX}/{name}")
for name in STREAM_NAMES
}

config = TeleopSessionConfig(
app_name="OakTeleop",
pipeline=pipeline, # your retargeting pipeline
trackers=[oak_tracker],
trackers=list(oak_trackers.values()),
mcap_config=McapRecordingConfig(
"recording.mcap",
[(oak_tracker, "oak_metadata")],
[(tracker, f"oak_metadata_{name.lower()}") for name, tracker in oak_trackers.items()],
),
plugins=[
PluginConfig(
Expand Down
10 changes: 6 additions & 4 deletions docs/source/device/trackers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ APIs (``xrLocateSpace``, ``xrSyncActions``, etc.):
reading it from OpenXR tensor collections via the
:code-file:`SchemaTracker <src/core/live_trackers/cpp/inc/live_trackers/schema_tracker.hpp>` utility.

- :code-file:`FrameMetadataTrackerOak <src/core/deviceio_trackers/cpp/inc/deviceio_trackers/frame_metadata_tracker_oak.hpp>` -- per-stream frame metadata from OAK cameras
- :code-file:`FrameMetadataTrackerOak <src/core/deviceio_trackers/cpp/inc/deviceio_trackers/frame_metadata_tracker_oak.hpp>` -- frame metadata for one OAK camera stream
- :code-file:`Generic3AxisPedalTracker <src/core/deviceio_trackers/cpp/inc/deviceio_trackers/generic_3axis_pedal_tracker.hpp>` -- foot pedal axis values
- :code-file:`JointStateTracker <src/core/deviceio_trackers/cpp/inc/deviceio_trackers/joint_state_tracker.hpp>` -- named joint-space device state (leader arms, exoskeletons, gloves, ...)
- :code-file:`Se3Tracker <src/core/deviceio_trackers/cpp/inc/deviceio_trackers/se3_tracker.hpp>` -- generic SE3 (6-DoF) pose sources (tracker pucks, mocap rigid bodies, logical trackers)
Expand Down Expand Up @@ -220,14 +220,16 @@ reads the PICO ``XR_BD_body_tracking`` extension directly.
FrameMetadataTrackerOak
~~~~~~~~~~~~~~~~~~~~~~~

Multi-channel tracker for per-frame metadata from OAK camera streams.
Uses the :code-file:`SchemaTracker <src/core/live_trackers/cpp/inc/live_trackers/schema_tracker.hpp>`
Per-frame metadata for a **single** OAK camera stream. Create one tracker per
stream, passing the tensor collection the plugin publishes that stream under --
``{collection_prefix}/{StreamName}``, e.g. ``"oak_camera/Color"``. Uses the
:code-file:`SchemaTracker <src/core/live_trackers/cpp/inc/live_trackers/schema_tracker.hpp>`
utility internally.

- Schema: :code-file:`src/core/schema/fbs/oak.fbs`
- C++ header: ``#include <deviceio/frame_metadata_tracker_oak.hpp>``
- Python import: ``from isaacteleop.deviceio import FrameMetadataTrackerOak``
- Record channels: one per configured stream (e.g. ``Color``, ``MonoLeft``) | MCAP schema: ``core.FrameMetadataOakRecord``
- Record channels: ``oak``, ``oak_tracked`` | MCAP schema: ``core.FrameMetadataOakRecord``
- Tests:

- :code-file:`src/core/schema_tests/cpp/test_oak.cpp`
Expand Down
38 changes: 21 additions & 17 deletions examples/oxr/python/test_oak_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,26 @@ def _run_recording_loop(plugin, duration: float):
def _run_schema_pusher(
plugin,
duration: float,
tracker,
stream_names: list[str],
trackers: dict,
required_extensions: list[str],
mcap_filename: str,
):
"""Read metadata via OpenXR schema tracker and record to MCAP on the host side."""
"""Read metadata via one OpenXR schema tracker per stream, recording to MCAP on the host side."""
stream_names = list(trackers)
with oxr.OpenXRSession("OakCameraTest", required_extensions) as oxr_session:
handles = oxr_session.get_handles()
print(" ✓ OpenXR session created")

# One tracker per stream, so each gets its own MCAP base name.
recording_config = deviceio.McapRecordingConfig(
mcap_filename, [(tracker, "oak_metadata")]
mcap_filename,
[
(tracker, f"oak_metadata_{name.lower()}")
for name, tracker in trackers.items()
],
)
with deviceio.DeviceIOSession.run(
[tracker], handles, recording_config
list(trackers.values()), handles, recording_config
) as session:
print(" ✓ DeviceIO session initialized (recording active during update())")
print()
Expand All @@ -88,8 +93,8 @@ def _run_schema_pusher(
frame_count += 1

elapsed = time.time() - start_time
for idx, name in enumerate(stream_names):
tracked = tracker.get_stream_data(session, idx)
for name, tracker in trackers.items():
tracked = tracker.get_data(session)
if (
tracked.data is not None
and tracked.data.sequence_number != last_seq.get(name, -1)
Expand Down Expand Up @@ -152,22 +157,22 @@ def run_test(duration: float = 10.0, mode: str = MODE_NO_METADATA):

# 3. Prepare mode-specific state
stream_names = ["Color", "MonoLeft"]
stream_types = [deviceio.StreamType.Color, deviceio.StreamType.MonoLeft]
collection_prefix = "oak_camera"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
mcap_filename = f"camera_metadata_{timestamp}.mcap"

tracker = None
trackers = {}
required_extensions = []

if mode == MODE_SCHEMA_PUSHER:
print("[Step 3] Creating composite FrameMetadataTrackerOak...")
tracker = deviceio.FrameMetadataTrackerOak(collection_prefix, stream_types)
print(
f" Created tracker (prefix: {collection_prefix}, streams: {stream_names})"
)
print("[Step 3] Creating one FrameMetadataTrackerOak per stream...")
# The plugin publishes each stream as "{collection_prefix}/{StreamName}".
for name in stream_names:
collection_id = f"{collection_prefix}/{name}"
trackers[name] = deviceio.FrameMetadataTrackerOak(collection_id)
print(f" Created tracker for {name} (collection: {collection_id})")
required_extensions = deviceio.DeviceIOSession.get_required_extensions(
[tracker]
list(trackers.values())
)
print()
print("[Step 4] Getting required OpenXR extensions...")
Expand Down Expand Up @@ -210,8 +215,7 @@ def run_test(duration: float = 10.0, mode: str = MODE_NO_METADATA):
_run_schema_pusher(
plugin,
duration,
tracker,
stream_names,
trackers,
required_extensions,
mcap_filename,
)
Expand Down
52 changes: 20 additions & 32 deletions examples/schemaio/frame_metadata_printer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* @file frame_metadata_printer.cpp
* @brief Standalone application that reads and prints camera frame metadata from the OpenXR runtime.
*
* This application demonstrates using FrameMetadataTrackerOak to read per-stream
* FrameMetadataOak pushed by a camera plugin, with each stream on its own MCAP channel.
* This application demonstrates using one FrameMetadataTrackerOak per camera stream
* to read the FrameMetadataOak pushed by a camera plugin.
*
* Usage:
* ./frame_metadata_printer --collection-prefix=<prefix>
Expand All @@ -17,6 +17,7 @@
#include <deviceio_session/deviceio_session.hpp>
#include <deviceio_trackers/frame_metadata_tracker_oak.hpp>
#include <oxr/oxr_session.hpp>
#include <schema/oak_generated.h>

#include <chrono>
#include <iostream>
Expand Down Expand Up @@ -67,15 +68,21 @@ try

std::cout << "Frame Metadata Printer (prefix: " << collection_prefix << ")" << std::endl;

// Track all three stream types; streams without a pusher simply won't receive data.
std::vector<core::StreamType> streams = { core::StreamType_Color, core::StreamType_MonoLeft,
core::StreamType_MonoRight };
// One tracker per stream; streams without a pusher simply won't receive data.
// The plugin publishes each stream as "{collection_prefix}/{StreamName}".
const std::vector<std::string> stream_names = { "Color", "MonoLeft", "MonoRight" };

std::cout << "[Step 1] Creating FrameMetadataTrackerOak..." << std::endl;
auto tracker = std::make_shared<core::FrameMetadataTrackerOak>(collection_prefix, streams, MAX_FLATBUFFER_SIZE);
std::cout << "[Step 1] Creating one FrameMetadataTrackerOak per stream..." << std::endl;
std::vector<std::shared_ptr<core::FrameMetadataTrackerOak>> stream_trackers;
for (const auto& name : stream_names)
{
const std::string collection_id = collection_prefix + "/" + name;
std::cout << " " << collection_id << std::endl;
stream_trackers.push_back(std::make_shared<core::FrameMetadataTrackerOak>(collection_id, MAX_FLATBUFFER_SIZE));
}

std::cout << "[Step 2] Creating OpenXR session with required extensions..." << std::endl;
std::vector<std::shared_ptr<core::ITracker>> trackers = { tracker };
std::vector<std::shared_ptr<core::ITracker>> trackers(stream_trackers.begin(), stream_trackers.end());
auto required_extensions = core::DeviceIOSession::get_required_extensions(trackers);
auto oxr_session = std::make_shared<core::OpenXRSession>("FrameMetadataPrinter", required_extensions);
std::cout << " OpenXR session created" << std::endl;
Expand All @@ -91,11 +98,10 @@ try
// been observed — the first sample is always printed regardless of its value.
// If data is already present at startup we seed with its sequence so we don't
// reprint it; absent data stays nullopt so sequence 0 is never skipped.
size_t stream_count = tracker->get_stream_count();
std::vector<std::optional<uint64_t>> last_sequences(stream_count);
for (size_t i = 0; i < stream_count; ++i)
std::vector<std::optional<uint64_t>> last_sequences(stream_trackers.size());
for (size_t i = 0; i < stream_trackers.size(); ++i)
{
const auto& tracked = tracker->get_stream_data(*session, i);
const auto& tracked = stream_trackers[i]->get_data(*session);
if (tracked.data)
{
last_sequences[i] = tracked.data->sequence_number;
Expand All @@ -109,28 +115,10 @@ try
{
session->update();

// Refresh stream count and extend per-stream tracking if streams were added.
stream_count = tracker->get_stream_count();
if (last_sequences.size() != stream_count)
{
size_t old_count = last_sequences.size();
last_sequences.resize(stream_count);
// Newly added streams start as nullopt; seed with current sequence if
// data is already present so we don't reprint an existing sample.
for (size_t i = old_count; i < stream_count; ++i)
{
const auto& tracked = tracker->get_stream_data(*session, i);
if (tracked.data)
{
last_sequences[i] = tracked.data->sequence_number;
}
}
}

// Print one line per stream that has a new sample.
for (size_t i = 0; i < stream_count; ++i)
for (size_t i = 0; i < stream_trackers.size(); ++i)
{
const auto& tracked = tracker->get_stream_data(*session, i);
const auto& tracked = stream_trackers[i]->get_data(*session);
if (!tracked.data ||
(last_sequences[i].has_value() && tracked.data->sequence_number == last_sequences[i].value()))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

#include "tracker.hpp"

#include <cstddef>

namespace core
{

Expand All @@ -16,7 +14,7 @@ struct FrameMetadataOakTrackedT;
class IFrameMetadataTrackerOakImpl : public ITrackerImpl
{
public:
virtual const FrameMetadataOakTrackedT& get_stream_data(size_t stream_index) const = 0;
virtual const FrameMetadataOakTrackedT& get_data() const = 0;
};

} // namespace core
30 changes: 5 additions & 25 deletions src/core/deviceio_trackers/cpp/frame_metadata_tracker_oak.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,22 @@

#include "inc/deviceio_trackers/frame_metadata_tracker_oak.hpp"

#include <stdexcept>
#include <string>

namespace core
{

// ============================================================================
// FrameMetadataTrackerOak
// ============================================================================

FrameMetadataTrackerOak::FrameMetadataTrackerOak(const std::string& collection_prefix,
const std::vector<StreamType>& streams,
size_t max_flatbuffer_size)
: collection_prefix_(collection_prefix), streams_(streams), max_flatbuffer_size_(max_flatbuffer_size)
FrameMetadataTrackerOak::FrameMetadataTrackerOak(const std::string& collection_id, size_t max_flatbuffer_size)
: collection_id_(collection_id), max_flatbuffer_size_(max_flatbuffer_size)
{
if (streams.empty())
{
throw std::runtime_error("FrameMetadataTrackerOak: at least one stream is required");
}

for (auto type : streams)
{
const char* name = EnumNameStreamType(type);
if (name == nullptr)
{
throw std::invalid_argument("FrameMetadataTrackerOak: invalid StreamType value " +
std::to_string(static_cast<int>(type)));
}
m_stream_names.emplace_back(name);
}
// No-op: members set in the initializer list.
}

const FrameMetadataOakTrackedT& FrameMetadataTrackerOak::get_stream_data(const ITrackerSession& session,
size_t stream_index) const
const FrameMetadataOakTrackedT& FrameMetadataTrackerOak::get_data(const ITrackerSession& session) const
{
return static_cast<const IFrameMetadataTrackerOakImpl&>(session.get_tracker_impl(*this)).get_stream_data(stream_index);
return static_cast<const IFrameMetadataTrackerOakImpl&>(session.get_tracker_impl(*this)).get_data();
}

} // namespace core
Loading
Loading