Skip to content

Commit 1b9a7b2

Browse files
committed
Make camera tracker use multiple channels
1 parent d915a5a commit 1b9a7b2

File tree

10 files changed

+81
-122
lines changed

10 files changed

+81
-122
lines changed

examples/oxr/python/test_oak_camera.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def run_test(duration: float = 10.0, metadata_track: bool = True):
139139
with deviceio.DeviceIOSession.run(trackers, handles) as session:
140140
print(" ✓ DeviceIO session initialized")
141141

142-
# Create MCAP recorder with single CameraMetadataOak channel
142+
# Create MCAP recorder with per-stream FrameMetadataOak channels
143143
mcap_entries = [(tracker, "oak_metadata")]
144144
with mcap.McapRecorder.create(
145145
mcap_filename,
@@ -168,9 +168,8 @@ def run_test(duration: float = 10.0, metadata_track: bool = True):
168168
frame_count += 1
169169

170170
elapsed = time.time() - start_time
171-
oak_data = tracker.get_data(session)
172-
for md in oak_data.streams:
173-
name = md.stream.name
171+
for idx, name in enumerate(stream_names):
172+
md = tracker.get_stream_data(session, idx)
174173
if (
175174
md.timestamp
176175
and md.timestamp.device_time

examples/schemaio/frame_metadata_printer.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* @file frame_metadata_printer.cpp
66
* @brief Standalone application that reads and prints camera frame metadata from the OpenXR runtime.
77
*
8-
* This application demonstrates using FrameMetadataTrackerOak to read composed
9-
* CameraMetadataOak (containing per-stream FrameMetadataOak) pushed by a camera plugin.
8+
* This application demonstrates using FrameMetadataTrackerOak to read per-stream
9+
* FrameMetadataOak pushed by a camera plugin, with each stream on its own MCAP channel.
1010
*
1111
* Usage:
1212
* ./frame_metadata_printer --collection-prefix=<prefix>
@@ -26,12 +26,14 @@
2626

2727
static constexpr size_t MAX_FLATBUFFER_SIZE = 128;
2828

29-
void print_oak_metadata(const core::CameraMetadataOakT& data, size_t sample_count)
29+
void print_oak_metadata(const core::FrameMetadataTrackerOak& tracker,
30+
const core::DeviceIOSession& session,
31+
size_t sample_count)
3032
{
3133
std::cout << "Sample " << sample_count << ": ";
32-
for (size_t i = 0; i < data.streams.size(); ++i)
34+
for (size_t i = 0; i < tracker.get_stream_count(); ++i)
3335
{
34-
const auto& md = *data.streams[i];
36+
const auto& md = tracker.get_stream_data(session, i);
3537
if (i > 0)
3638
std::cout << " | ";
3739
std::cout << core::EnumNameStreamType(md.stream) << " seq=" << md.sequence_number;
@@ -48,7 +50,7 @@ void print_usage(const char* program_name)
4850
<< " --collection-prefix=PREFIX Tensor collection prefix (default: oak_camera)\n"
4951
<< " --help Show this help message\n"
5052
<< "\nDescription:\n"
51-
<< " Reads and prints CameraMetadataOak samples (all streams) pushed by a camera plugin.\n"
53+
<< " Reads and prints per-stream FrameMetadataOak samples pushed by a camera plugin.\n"
5254
<< " The collection-prefix must match the value used by the camera plugin.\n";
5355
}
5456

@@ -111,18 +113,17 @@ try
111113
break;
112114
}
113115

114-
const auto& data = tracker->get_data(*session);
115-
116116
// Detect new data by checking if any stream has a newer device_time
117117
int64_t max_dt = -1;
118-
for (const auto& md : data.streams)
118+
for (size_t i = 0; i < tracker->get_stream_count(); ++i)
119119
{
120-
if (md->timestamp)
121-
max_dt = std::max(max_dt, md->timestamp->device_time());
120+
const auto& md = tracker->get_stream_data(*session, i);
121+
if (md.timestamp)
122+
max_dt = std::max(max_dt, md.timestamp->device_time());
122123
}
123124
if (max_dt > last_device_time)
124125
{
125-
print_oak_metadata(data, ++received_count);
126+
print_oak_metadata(*tracker, *session, ++received_count);
126127
last_device_time = max_dt;
127128
}
128129

src/core/deviceio/cpp/frame_metadata_tracker_oak.cpp

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <flatbuffers/flatbuffers.h>
99
#include <schema/oak_bfbs_generated.h>
1010

11+
#include <stdexcept>
1112
#include <vector>
1213

1314
namespace core
@@ -48,36 +49,41 @@ class FrameMetadataTrackerOak::Impl : public ITrackerImpl
4849
}
4950
}
5051

51-
m_data.streams.clear();
52-
for (const auto& s : m_streams)
53-
m_data.streams.push_back(std::make_unique<FrameMetadataOakT>(s.data));
54-
5552
return true;
5653
}
5754

58-
Timestamp serialize(flatbuffers::FlatBufferBuilder& builder, size_t /*channel_index*/) const override
55+
Timestamp serialize(flatbuffers::FlatBufferBuilder& builder, size_t channel_index) const override
5956
{
60-
auto offset = CameraMetadataOak::Pack(builder, &m_data);
61-
builder.Finish(offset);
62-
63-
Timestamp latest{};
64-
for (const auto& entry : m_data.streams)
57+
if (channel_index >= m_streams.size())
6558
{
66-
if (entry->timestamp && entry->timestamp->device_time() > latest.device_time())
67-
latest = *entry->timestamp;
59+
throw std::runtime_error("FrameMetadataTrackerOak::serialize: invalid channel_index " +
60+
std::to_string(channel_index) + " (have " + std::to_string(m_streams.size()) +
61+
" streams)");
6862
}
69-
return latest;
63+
64+
const auto& data = m_streams[channel_index].data;
65+
auto offset = FrameMetadataOak::Pack(builder, &data);
66+
builder.Finish(offset);
67+
68+
if (data.timestamp)
69+
return *data.timestamp;
70+
return Timestamp{};
7071
}
7172

72-
const CameraMetadataOakT& get_data() const
73+
const FrameMetadataOakT& get_stream_data(size_t stream_index) const
7374
{
74-
return m_data;
75+
if (stream_index >= m_streams.size())
76+
{
77+
throw std::runtime_error("FrameMetadataTrackerOak::get_stream_data: invalid stream_index " +
78+
std::to_string(stream_index) + " (have " + std::to_string(m_streams.size()) +
79+
" streams)");
80+
}
81+
return m_streams[stream_index].data;
7582
}
7683

7784
private:
7885
std::vector<StreamState> m_streams;
7986
std::vector<uint8_t> m_buffer;
80-
CameraMetadataOakT m_data;
8187
};
8288

8389
// ============================================================================
@@ -88,12 +94,19 @@ FrameMetadataTrackerOak::FrameMetadataTrackerOak(const std::string& collection_p
8894
const std::vector<StreamType>& streams,
8995
size_t max_flatbuffer_size)
9096
{
97+
if (streams.empty())
98+
{
99+
throw std::runtime_error("FrameMetadataTrackerOak: at least one stream is required");
100+
}
101+
91102
for (auto type : streams)
92103
{
93-
m_configs.push_back({ .collection_id = collection_prefix + "/" + EnumNameStreamType(type),
104+
const char* name = EnumNameStreamType(type);
105+
m_configs.push_back({ .collection_id = collection_prefix + "/" + name,
94106
.max_flatbuffer_size = max_flatbuffer_size,
95107
.tensor_identifier = "frame_metadata",
96-
.localized_name = std::string("FrameMetadataTracker_") + EnumNameStreamType(type) });
108+
.localized_name = std::string("FrameMetadataTracker_") + name });
109+
m_channel_names.emplace_back(name);
97110
}
98111
}
99112

@@ -109,18 +122,18 @@ std::string_view FrameMetadataTrackerOak::get_name() const
109122

110123
std::string_view FrameMetadataTrackerOak::get_schema_name() const
111124
{
112-
return "core.CameraMetadataOak";
125+
return "core.FrameMetadataOak";
113126
}
114127

115128
std::string_view FrameMetadataTrackerOak::get_schema_text() const
116129
{
117130
return std::string_view(
118-
reinterpret_cast<const char*>(CameraMetadataOakBinarySchema::data()), CameraMetadataOakBinarySchema::size());
131+
reinterpret_cast<const char*>(FrameMetadataOakBinarySchema::data()), FrameMetadataOakBinarySchema::size());
119132
}
120133

121-
const CameraMetadataOakT& FrameMetadataTrackerOak::get_data(const DeviceIOSession& session) const
134+
const FrameMetadataOakT& FrameMetadataTrackerOak::get_stream_data(const DeviceIOSession& session, size_t stream_index) const
122135
{
123-
return static_cast<const Impl&>(session.get_tracker_impl(*this)).get_data();
136+
return static_cast<const Impl&>(session.get_tracker_impl(*this)).get_stream_data(stream_index);
124137
}
125138

126139
std::shared_ptr<ITrackerImpl> FrameMetadataTrackerOak::create_tracker(const OpenXRSessionHandles& handles) const

src/core/deviceio/cpp/inc/deviceio/frame_metadata_tracker_oak.hpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@ namespace core
1616
{
1717

1818
/*!
19-
* @brief Composite tracker for reading OAK FrameMetadataOak from multiple streams.
19+
* @brief Multi-channel tracker for reading OAK FrameMetadataOak from multiple streams.
2020
*
21-
* Maintains one SchemaTracker per stream and composes them into a single
22-
* CameraMetadataOak message for serialization / MCAP recording.
21+
* Maintains one SchemaTracker per stream and records each as a separate MCAP
22+
* channel using FrameMetadataOak as the root type.
2323
*
2424
* Usage:
2525
* @code
2626
* auto tracker = std::make_shared<FrameMetadataTrackerOak>(
2727
* "oak_camera", {StreamType_Color, StreamType_MonoLeft});
2828
* // ... create DeviceIOSession with tracker ...
2929
* session->update();
30-
* const auto& data = tracker->get_data(*session);
31-
* for (const auto& md : data.streams)
32-
* std::cout << EnumNameStreamType(md->stream) << std::endl;
30+
* const auto& color = tracker->get_stream_data(*session, 0);
31+
* std::cout << EnumNameStreamType(color.stream) << " seq=" << color.sequence_number << std::endl;
3332
* @endcode
3433
*/
3534
class FrameMetadataTrackerOak : public ITracker
@@ -57,18 +56,28 @@ class FrameMetadataTrackerOak : public ITracker
5756

5857
std::vector<std::string> get_record_channels() const override
5958
{
60-
return { "frame_metadata" };
59+
return m_channel_names;
6160
}
6261

6362
/*!
64-
* @brief Get the composed OAK metadata containing all tracked streams.
63+
* @brief Get per-stream frame metadata.
64+
* @param session Active DeviceIOSession.
65+
* @param stream_index Index into the streams vector passed at construction.
66+
* @return Reference to the FrameMetadataOakT for that stream.
6567
*/
66-
const CameraMetadataOakT& get_data(const DeviceIOSession& session) const;
68+
const FrameMetadataOakT& get_stream_data(const DeviceIOSession& session, size_t stream_index) const;
69+
70+
//! Number of streams this tracker is configured for.
71+
size_t get_stream_count() const
72+
{
73+
return m_channel_names.size();
74+
}
6775

6876
private:
6977
std::shared_ptr<ITrackerImpl> create_tracker(const OpenXRSessionHandles& handles) const override;
7078

7179
std::vector<SchemaTrackerConfig> m_configs;
80+
std::vector<std::string> m_channel_names;
7281
class Impl;
7382
};
7483

src/core/deviceio/python/deviceio_bindings.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ PYBIND11_MODULE(_deviceio, m)
6565
py::arg("max_flatbuffer_size") = core::FrameMetadataTrackerOak::DEFAULT_MAX_FLATBUFFER_SIZE,
6666
"Construct a multi-stream FrameMetadataTrackerOak")
6767
.def(
68-
"get_data",
69-
[](core::FrameMetadataTrackerOak& self, PyDeviceIOSession& session) -> const core::CameraMetadataOakT&
70-
{ return self.get_data(session.native()); },
71-
py::arg("session"), py::return_value_policy::reference_internal,
72-
"Get composed CameraMetadataOak containing all tracked streams");
68+
"get_stream_data",
69+
[](core::FrameMetadataTrackerOak& self, PyDeviceIOSession& session,
70+
size_t stream_index) -> const core::FrameMetadataOakT&
71+
{ return self.get_stream_data(session.native(), stream_index); },
72+
py::arg("session"), py::arg("stream_index"), py::return_value_policy::reference_internal,
73+
"Get FrameMetadataOak for a specific stream by index")
74+
.def_property_readonly("stream_count", &core::FrameMetadataTrackerOak::get_stream_count,
75+
"Number of streams this tracker is configured for");
7376

7477
// Generic3AxisPedalTracker class
7578
py::class_<core::Generic3AxisPedalTracker, core::ITracker, std::shared_ptr<core::Generic3AxisPedalTracker>>(

src/core/deviceio/python/deviceio_init.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Note: HeadTracker.get_head(session) returns HeadPoseT from isaacteleop.schema.
99
HandTracker.get_left_hand(session) / get_right_hand(session) return HandPoseT from isaacteleop.schema.
1010
ControllerTracker.get_left_controller(session) / get_right_controller(session) return ControllerSnapshot from isaacteleop.schema.
11-
FrameMetadataTrackerOak.get_data(session) returns CameraMetadataOak from isaacteleop.schema.
11+
FrameMetadataTrackerOak.get_stream_data(session, index) returns FrameMetadataOak from isaacteleop.schema.
1212
Import these types from isaacteleop.schema if you need to work with pose types.
1313
"""
1414

@@ -38,7 +38,6 @@
3838
ControllerSnapshot,
3939
StreamType,
4040
FrameMetadataOak,
41-
CameraMetadataOak,
4241
Generic3AxisPedalOutput,
4342
Timestamp,
4443
)
@@ -49,7 +48,6 @@
4948
"ControllerSnapshot",
5049
"StreamType",
5150
"FrameMetadataOak",
52-
"CameraMetadataOak",
5351
"Generic3AxisPedalOutput",
5452
"Timestamp",
5553
"ITracker",

src/core/schema/fbs/oak.fbs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace core;
99
enum StreamType : byte { Color = 0, MonoLeft = 1, MonoRight = 2 }
1010

1111
// Per-frame metadata pushed by the OAK camera plugin (one per stream).
12+
// Each stream is recorded as a separate MCAP channel using this as the root type.
1213
table FrameMetadataOak {
1314
// Which camera sensor this metadata belongs to.
1415
stream: StreamType (id: 0);
@@ -20,9 +21,4 @@ table FrameMetadataOak {
2021
sequence_number: uint64 (id: 2);
2122
}
2223

23-
// Composed metadata from all active OAK camera streams.
24-
table CameraMetadataOak {
25-
streams: [FrameMetadataOak] (id: 0);
26-
}
27-
28-
root_type CameraMetadataOak;
24+
root_type FrameMetadataOak;

src/core/schema/python/oak_bindings.h

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
// Python bindings for the OAK FlatBuffer schema.
5-
// Types: StreamType (enum), FrameMetadataOak (table), CameraMetadataOak (composite table).
5+
// Types: StreamType (enum), FrameMetadataOak (table).
66

77
#pragma once
88

@@ -51,22 +51,6 @@ inline void bind_oak(py::module& m)
5151
result += ", sequence_number=" + std::to_string(metadata.sequence_number) + ")";
5252
return result;
5353
});
54-
55-
py::class_<CameraMetadataOakT>(m, "CameraMetadataOak")
56-
.def(py::init<>())
57-
.def_property_readonly(
58-
"streams",
59-
[](const CameraMetadataOakT& self)
60-
{
61-
std::vector<const FrameMetadataOakT*> out;
62-
out.reserve(self.streams.size());
63-
for (const auto& entry : self.streams)
64-
out.push_back(entry.get());
65-
return out;
66-
},
67-
"List of per-stream FrameMetadataOak entries")
68-
.def("__repr__", [](const CameraMetadataOakT& self)
69-
{ return "CameraMetadataOak(streams=" + std::to_string(self.streams.size()) + ")"; });
7054
}
7155

7256
} // namespace core

src/core/schema/python/schema_init.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
# Camera-related types.
3232
StreamType,
3333
FrameMetadataOak,
34-
CameraMetadataOak,
3534
# Full body-related types.
3635
BodyJointPico,
3736
BodyJointPose,
@@ -64,7 +63,6 @@
6463
# Camera types.
6564
"StreamType",
6665
"FrameMetadataOak",
67-
"CameraMetadataOak",
6866
# Full body types.
6967
"BodyJointPose",
7068
"BodyJointsPico",

0 commit comments

Comments
 (0)