Skip to content

Commit 660b3e9

Browse files
committed
Update examples
Signed-off-by: stas.bucik <[email protected]>
1 parent 8622774 commit 660b3e9

File tree

2 files changed

+126
-102
lines changed

2 files changed

+126
-102
lines changed

examples/cpp/Misc/MultiDevice/multi_device.cpp

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
#include <opencv2/core/mat.hpp>
88
#include <opencv2/highgui.hpp>
99
#include <opencv2/opencv.hpp>
10+
#include <stdexcept>
1011
#include <utility>
1112
#include <vector>
1213
#include <string>
1314

1415
#include "depthai/capabilities/ImgFrameCapability.hpp"
1516
#include "depthai/common/CameraBoardSocket.hpp"
1617
#include "depthai/common/CameraExposureOffset.hpp"
18+
#include "depthai/common/M8FsyncRoles.hpp"
1719
#include "depthai/depthai.hpp"
1820
#include "depthai/pipeline/MessageQueue.hpp"
1921
#include "depthai/pipeline/Node.hpp"
@@ -58,7 +60,8 @@ int main(int argc, char** argv) {
5860
}
5961

6062
std::vector<std::shared_ptr<dai::MessageQueue>> queues;
61-
std::vector<dai::Pipeline> pipelines;
63+
std::vector<dai::Pipeline> master_pipelines;
64+
std::vector<dai::Pipeline> slave_pipelines;
6265
std::vector<std::string> device_ids;
6366

6467
for (auto deviceInfo : DEVICE_INFOS)
@@ -76,12 +79,36 @@ int main(int argc, char** argv) {
7679
auto cam = pipeline.create<dai::node::Camera>()->build(socket, std::nullopt, TARGET_FPS);
7780
auto out_q = cam->requestOutput(std::make_pair(640, 480), dai::ImgFrame::Type::NV12, dai::ImgResizeMode::STRETCH)->createOutputQueue();
7881

79-
pipeline.start();
80-
pipelines.push_back(pipeline);
82+
auto role = device->getM8FsyncRole();
83+
84+
if (role == dai::M8FsyncRole::MASTER_WITH_OUTPUT) {
85+
master_pipelines.push_back(pipeline);
86+
} else if (role == dai::M8FsyncRole::SLAVE) {
87+
slave_pipelines.push_back(pipeline);
88+
} else {
89+
throw std::runtime_error("Don't know how to handle role " + dai::toString(role));
90+
}
91+
8192
queues.push_back(out_q);
8293
device_ids.push_back(deviceInfo.getXLinkDeviceDesc().name);
8394
}
8495

96+
if (master_pipelines.size() > 1) {
97+
throw std::runtime_error("Multiple masters detected!");
98+
}
99+
if (master_pipelines.size() == 0) {
100+
throw std::runtime_error("No master detected!");
101+
}
102+
if (slave_pipelines.size() < 1) {
103+
throw std::runtime_error("No slaves detected!");
104+
}
105+
for (auto p : master_pipelines) {
106+
p.start();
107+
}
108+
for (auto p : slave_pipelines) {
109+
p.start();
110+
}
111+
85112
std::map<int, std::shared_ptr<dai::ImgFrame>> latest_frames;
86113
std::vector<bool> receivedFrames;
87114
std::vector<FPSCounter> fpsCounters(queues.size());
@@ -90,8 +117,6 @@ int main(int argc, char** argv) {
90117
receivedFrames.push_back(false);
91118
}
92119

93-
int counter = 0;
94-
95120
while (true) {
96121
for (int i = 0; i < queues.size(); i++) {
97122
auto q = queues[i];
@@ -111,59 +136,51 @@ int main(int argc, char** argv) {
111136
auto f = pair.second;
112137
ts_values.push_back(f->getTimestamp(dai::CameraExposureOffset::END));
113138
}
114-
115-
if (counter % 100000 == 0) {
116-
auto diff = *std::max_element(ts_values.begin(), ts_values.end()) - *std::min_element(ts_values.begin(), ts_values.end());
117-
std::cout << 1e-6 * float(std::chrono::duration_cast<std::chrono::microseconds>(diff).count()) << std::endl;
118-
}
119-
120-
if (true) {
121-
// std::vector<cv::Mat> imgs;
122-
cv::Mat imgs;
123-
for (int i = 0; i < queues.size(); i++) {
124-
auto msg = latest_frames[i];
125-
auto fps = fpsCounters[i].getFps();
126-
auto frame = msg->getCvFrame();
127-
128-
cv::putText(frame,
129-
device_ids[i] + " | Timestamp: " + std::to_string(ts_values[i].time_since_epoch().count()) + " | FPS: " + std::to_string(fps).substr(0, 5),
130-
{20, 40},
131-
cv::FONT_HERSHEY_SIMPLEX,
132-
0.6,
133-
{255, 0, 50},
134-
2,
135-
cv::LINE_AA);
136-
137-
if (i == 0) {
138-
imgs = frame;
139-
} else {
140-
cv::hconcat(frame, imgs, imgs);
141-
}
142-
}
143-
144-
std::string sync_status = "out of sync";
145-
if (abs(std::chrono::duration_cast<std::chrono::microseconds>(
146-
*std::max_element(ts_values.begin(), ts_values.end()) -
147-
*std::min_element(ts_values.begin(), ts_values.end())
148-
).count()) < 1e3) {
149-
sync_status = "in sync";
150-
}
151-
auto delta = *std::max_element(ts_values.begin(), ts_values.end()) - *std::min_element(ts_values.begin(), ts_values.end());
152-
cv::Scalar color = (sync_status == "in sync") ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255);
153-
154-
cv::putText(imgs,
155-
sync_status + " | delta = " + std::to_string(1e-3 * float(std::chrono::duration_cast<std::chrono::microseconds>(delta).count())).substr(0, 5) + " ms",
156-
{20, 80},
139+
140+
cv::Mat imgs;
141+
for (int i = 0; i < queues.size(); i++) {
142+
auto msg = latest_frames[i];
143+
auto fps = fpsCounters[i].getFps();
144+
auto frame = msg->getCvFrame();
145+
146+
cv::putText(frame,
147+
device_ids[i] + " | Timestamp: " + std::to_string(ts_values[i].time_since_epoch().count()) + " | FPS: " + std::to_string(fps).substr(0, 5),
148+
{20, 40},
157149
cv::FONT_HERSHEY_SIMPLEX,
158-
0.7,
159-
color,
150+
0.6,
151+
{255, 0, 50},
160152
2,
161153
cv::LINE_AA);
162154

163-
cv::imshow("synced_view", imgs);
155+
if (i == 0) {
156+
imgs = frame;
157+
} else {
158+
cv::hconcat(frame, imgs, imgs);
159+
}
160+
}
164161

165-
latest_frames.clear();
162+
std::string sync_status = "out of sync";
163+
if (abs(std::chrono::duration_cast<std::chrono::microseconds>(
164+
*std::max_element(ts_values.begin(), ts_values.end()) -
165+
*std::min_element(ts_values.begin(), ts_values.end())
166+
).count()) < 1e3) {
167+
sync_status = "in sync";
166168
}
169+
auto delta = *std::max_element(ts_values.begin(), ts_values.end()) - *std::min_element(ts_values.begin(), ts_values.end());
170+
cv::Scalar color = (sync_status == "in sync") ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255);
171+
172+
cv::putText(imgs,
173+
sync_status + " | delta = " + std::to_string(1e-3 * float(std::chrono::duration_cast<std::chrono::microseconds>(delta).count())).substr(0, 5) + " ms",
174+
{20, 80},
175+
cv::FONT_HERSHEY_SIMPLEX,
176+
0.7,
177+
color,
178+
2,
179+
cv::LINE_AA);
180+
181+
cv::imshow("synced_view", imgs);
182+
183+
latest_frames.clear();
167184
}
168185

169186
if (cv::waitKey(1) == 'q') {

examples/python/Misc/MultiDevice/multi_device.py

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def createPipeline(pipeline: dai.Pipeline, socket: dai.CameraBoardSocket = dai.C
6969

7070

7171
parser = argparse.ArgumentParser(add_help=False)
72-
parser.add_argument("-d", "--devices", default=[], nargs="+", help="Device IPs, first is master, others are slaves")
72+
parser.add_argument("-d", "--devices", default=[], nargs="+", help="Device IPs")
7373
parser.add_argument("-f", "--fps", type=float, default=30.0, help="Target FPS")
7474
args = parser.parse_args()
7575
DEVICE_INFOS = [dai.DeviceInfo(ip) for ip in args.devices] #The master camera needs to be first here
@@ -81,11 +81,10 @@ def createPipeline(pipeline: dai.Pipeline, socket: dai.CameraBoardSocket = dai.C
8181
# Main
8282
# ---------------------------------------------------------------------------
8383
with contextlib.ExitStack() as stack:
84-
# deviceInfos = dai.Device.getAllAvailableDevices()
85-
# print("=== Found devices: ", deviceInfos)
8684

8785
queues = []
88-
pipelines = []
86+
slave_pipelines = []
87+
master_pipelines = []
8988
device_ids = []
9089

9190
for idx, deviceInfo in enumerate(DEVICE_INFOS):
@@ -96,26 +95,40 @@ def createPipeline(pipeline: dai.Pipeline, socket: dai.CameraBoardSocket = dai.C
9695
print(" Device ID:", device.getDeviceId())
9796
print(" Num of cameras:", len(device.getConnectedCameras()))
9897

99-
# for c in device.getConnectedCameras():
100-
# print(f" {c}")
101-
10298
# socket = device.getConnectedCameras()[0]
10399
socket = device.getConnectedCameras()[1]
104100
pipeline, out_q = createPipeline(pipeline, socket)
105-
print(type(out_q))
106-
pipeline.start()
107-
108-
pipelines.append(pipeline)
101+
role = device.getM8FsyncRole()
102+
if (role == dai.M8FsyncRole.MASTER_WITH_OUTPUT):
103+
master_pipelines.append(pipeline)
104+
elif (role == dai.M8FsyncRole.SLAVE):
105+
slave_pipelines.append(pipeline)
106+
else:
107+
raise RuntimeError(f"Don't know how to handle role {role}")
108+
109109
queues.append(out_q)
110110
device_ids.append(deviceInfo.getXLinkDeviceDesc().name)
111-
# if (idx == 0):
112-
# time.sleep(12)
111+
112+
if (len(master_pipelines) > 1):
113+
raise RuntimeError("Multiple masters detected!")
114+
115+
if (len(master_pipelines) == 0):
116+
raise RuntimeError("No master detected!")
117+
118+
if (len(slave_pipelines) < 1):
119+
raise RuntimeError("No slaves detected!")
120+
121+
for p in master_pipelines:
122+
p.start()
123+
124+
for p in slave_pipelines:
125+
p.start()
126+
113127

114128
# Buffer for latest frames; key = queue index
115129
latest_frames = {}
116130
fpsCounters = [FPSCounter() for _ in queues]
117131
receivedFrames = [False for _ in queues]
118-
counter = 0
119132
while True:
120133
# -------------------------------------------------------------------
121134
# Collect the newest frame from each queue (non‑blocking)
@@ -134,47 +147,41 @@ def createPipeline(pipeline: dai.Pipeline, socket: dai.CameraBoardSocket = dai.C
134147
# -------------------------------------------------------------------
135148
if len(latest_frames) == len(queues):
136149
ts_values = [f.getTimestamp(dai.CameraExposureOffset.END).total_seconds() for f in latest_frames.values()]
137-
if (counter % 100000 == 0):
138-
print(max(ts_values) - min(ts_values))
139-
counter += 1
140-
# if max(ts_values) - min(ts_values) <= SYNC_THRESHOLD_SEC:
141-
# TIMESTAMPS ARE NOT ACCURATE, VERIFIED WITH PIXEL RUNNER
142-
if True:
143-
# Build composite image side‑by‑side
144-
imgs = []
145-
for i in range(len(queues)):
146-
msg = latest_frames[i]
147-
frame = msg.getCvFrame()
148-
fps = fpsCounters[i].getFps()
149-
cv2.putText(
150-
frame,
151-
f"{device_ids[i]} | Timestamp: {ts_values[i]} | FPS:{fps:.2f}",
152-
(20, 40),
153-
cv2.FONT_HERSHEY_SIMPLEX,
154-
0.6,
155-
(255, 0, 50),
156-
2,
157-
cv2.LINE_AA,
158-
)
159-
imgs.append(frame)
160-
161-
sync_status = "in sync" if abs(max(ts_values) - min(ts_values)) < 0.001 else "out of sync"
162-
delta = max(ts_values) - min(ts_values)
163-
color = (0, 255, 0) if sync_status == "in sync" else (0, 0, 255)
164-
150+
# Build composite image side‑by‑side
151+
imgs = []
152+
for i in range(len(queues)):
153+
msg = latest_frames[i]
154+
frame = msg.getCvFrame()
155+
fps = fpsCounters[i].getFps()
165156
cv2.putText(
166-
imgs[0],
167-
f"{sync_status} | delta = {delta*1e3:.3f} ms",
168-
(20, 80),
157+
frame,
158+
f"{device_ids[i]} | Timestamp: {ts_values[i]} | FPS:{fps:.2f}",
159+
(20, 40),
169160
cv2.FONT_HERSHEY_SIMPLEX,
170-
0.7,
171-
color,
161+
0.6,
162+
(255, 0, 50),
172163
2,
173164
cv2.LINE_AA,
174165
)
175-
176-
cv2.imshow("synced_view", cv2.hconcat(imgs))
177-
latest_frames.clear() # Wait for next batch
166+
imgs.append(frame)
167+
168+
sync_status = "in sync" if abs(max(ts_values) - min(ts_values)) < 0.001 else "out of sync"
169+
delta = max(ts_values) - min(ts_values)
170+
color = (0, 255, 0) if sync_status == "in sync" else (0, 0, 255)
171+
172+
cv2.putText(
173+
imgs[0],
174+
f"{sync_status} | delta = {delta*1e3:.3f} ms",
175+
(20, 80),
176+
cv2.FONT_HERSHEY_SIMPLEX,
177+
0.7,
178+
color,
179+
2,
180+
cv2.LINE_AA,
181+
)
182+
183+
cv2.imshow("synced_view", cv2.hconcat(imgs))
184+
latest_frames.clear() # Wait for next batch
178185

179186
if cv2.waitKey(1) & 0xFF == ord("q"):
180187
break

0 commit comments

Comments
 (0)