@@ -11,6 +11,66 @@ using namespace std::chrono;
1111using namespace std ::chrono_literals;
1212
1313namespace {
14+ constexpr auto kRuntimeCalibrationFrameTimeout = 180 ;
15+ constexpr auto kRuntimeCalibrationShiftPx = 40 .0f ;
16+ constexpr auto kRuntimeCalibrationVerificationFrames = 20 ;
17+
18+ bool approxEqual (float a, float b, float absTol = 1e-4f , float relTol = 1e-4f ) {
19+ return std::abs (a - b) <= (absTol + relTol * std::max (std::abs (a), std::abs (b)));
20+ }
21+
22+ bool sameIntrinsics (const std::array<std::array<float , 3 >, 3 >& lhs, const std::array<std::array<float , 3 >, 3 >& rhs) {
23+ for (size_t i = 0 ; i < lhs.size (); ++i) {
24+ for (size_t j = 0 ; j < lhs[i].size (); ++j) {
25+ if (!approxEqual (lhs[i][j], rhs[i][j])) {
26+ return false ;
27+ }
28+ }
29+ }
30+ return true ;
31+ }
32+
33+ std::shared_ptr<dai::ImgFrame> waitForFrameAlignedTo (
34+ const std::shared_ptr<dai::MessageQueue>& queue, const dai::ImgTransformation& transformation, size_t maxFrames = kRuntimeCalibrationFrameTimeout ) {
35+ for (size_t i = 0 ; i < maxFrames; ++i) {
36+ auto frame = queue->get <dai::ImgFrame>();
37+ if (frame != nullptr && frame->transformation .isAlignedTo (transformation)) {
38+ return frame;
39+ }
40+ }
41+ return nullptr ;
42+ }
43+
44+ std::shared_ptr<dai::ImgFrame> waitForFrameWithChangedTransform (
45+ const std::shared_ptr<dai::MessageQueue>& queue, const dai::ImgTransformation& baseline, size_t maxFrames = kRuntimeCalibrationFrameTimeout ) {
46+ for (size_t i = 0 ; i < maxFrames; ++i) {
47+ auto frame = queue->get <dai::ImgFrame>();
48+ if (frame != nullptr && !frame->transformation .isAlignedTo (baseline)) {
49+ return frame;
50+ }
51+ }
52+ return nullptr ;
53+ }
54+
55+ std::shared_ptr<dai::ImgFrame> waitForNextFrame (const std::shared_ptr<dai::MessageQueue>& queue, size_t maxFrames = kRuntimeCalibrationFrameTimeout ) {
56+ for (size_t i = 0 ; i < maxFrames; ++i) {
57+ auto frame = queue->get <dai::ImgFrame>();
58+ if (frame != nullptr ) {
59+ return frame;
60+ }
61+ }
62+ return nullptr ;
63+ }
64+
65+ dai::CalibrationHandler makeShiftedCalibration (const dai::CalibrationHandler& base, std::tuple<int , int > outputSize, float shiftPx) {
66+ dai::CalibrationHandler updated = base;
67+ auto intrinsics = updated.getCameraIntrinsics (dai::CameraBoardSocket::CAM_A, outputSize);
68+ intrinsics[0 ][2 ] += shiftPx;
69+ intrinsics[1 ][2 ] += shiftPx * 0 .5f ;
70+ updated.setCameraIntrinsics (dai::CameraBoardSocket::CAM_A, intrinsics, outputSize);
71+ return updated;
72+ }
73+
1474void runImageAlignTest (bool useDepth, bool runOnHost, dai::ImgResizeMode resizeMode) {
1575 dai::Pipeline p;
1676 auto rgbCam = p.create <dai::node::Camera>()->build (dai::CameraBoardSocket::CAM_A);
@@ -57,6 +117,74 @@ void runImageAlignTest(bool useDepth, bool runOnHost, dai::ImgResizeMode resizeM
57117 }
58118 p.stop ();
59119}
120+
121+ void runImageAlignRuntimeCalibrationTest (bool useDepth) {
122+ constexpr auto rgbSize = std::pair<int , int >{1280 , 640 };
123+
124+ dai::Pipeline p;
125+ auto rgbCam = p.create <dai::node::Camera>()->build (dai::CameraBoardSocket::CAM_A);
126+ auto leftCam = p.create <dai::node::Camera>()->build (dai::CameraBoardSocket::CAM_B);
127+ auto rightCam = p.create <dai::node::Camera>()->build (dai::CameraBoardSocket::CAM_C);
128+ std::shared_ptr<dai::node::StereoDepth> stereo;
129+ auto align = p.create <dai::node::ImageAlign>();
130+
131+ auto * rgbOut = rgbCam->requestOutput (rgbSize, std::nullopt , dai::ImgResizeMode::CROP, std::nullopt , true );
132+ auto * leftOut = leftCam->requestOutput ({1280 , 800 }, std::nullopt );
133+ auto * rightOut = rightCam->requestOutput ({1280 , 800 }, std::nullopt );
134+
135+ if (useDepth) {
136+ stereo = p.create <dai::node::StereoDepth>();
137+ leftOut->link (stereo->left );
138+ rightOut->link (stereo->right );
139+ stereo->depth .link (align->input );
140+ } else {
141+ leftOut->link (align->input );
142+ rightOut->createOutputQueue (); // Keep both mono streams active on platforms that require stereo pair streaming.
143+ align->initialConfig ->staticDepthPlane = 0x5AB1 ;
144+ }
145+ rgbOut->link (align->inputAlignTo );
146+
147+ auto alignedQueue = align->outputAligned .createOutputQueue ();
148+ auto alignToQueue = rgbOut->createOutputQueue ();
149+ auto device = p.getDefaultDevice ();
150+ p.start ();
151+
152+ auto baselineAlignTo = alignToQueue->get <dai::ImgFrame>();
153+ REQUIRE (baselineAlignTo != nullptr );
154+
155+ auto baselineAligned = waitForFrameAlignedTo (alignedQueue, baselineAlignTo->transformation );
156+ REQUIRE (baselineAligned != nullptr );
157+ REQUIRE (baselineAligned->getInstanceNum () == baselineAlignTo->getInstanceNum ());
158+
159+ auto originalCalibration = device->getCalibration ();
160+ auto shiftedCalibration = makeShiftedCalibration (originalCalibration, rgbSize, kRuntimeCalibrationShiftPx );
161+ device->setCalibration (shiftedCalibration);
162+
163+ auto shiftedAlignTo = waitForFrameWithChangedTransform (alignToQueue, baselineAlignTo->transformation , kRuntimeCalibrationVerificationFrames );
164+ if (shiftedAlignTo == nullptr ) {
165+ shiftedAlignTo = waitForNextFrame (alignToQueue);
166+ }
167+ REQUIRE (shiftedAlignTo != nullptr );
168+
169+ auto shiftedAligned = waitForFrameAlignedTo (alignedQueue, shiftedAlignTo->transformation );
170+ REQUIRE (shiftedAligned != nullptr );
171+
172+ device->setCalibration (originalCalibration);
173+
174+ auto revertedAlignTo = waitForFrameAlignedTo (alignToQueue, baselineAlignTo->transformation , kRuntimeCalibrationVerificationFrames );
175+ if (revertedAlignTo == nullptr ) {
176+ revertedAlignTo = waitForNextFrame (alignToQueue);
177+ }
178+ REQUIRE (revertedAlignTo != nullptr );
179+
180+ for (size_t i = 0 ; i < kRuntimeCalibrationVerificationFrames ; ++i) {
181+ auto revertedAligned = alignedQueue->get <dai::ImgFrame>();
182+ REQUIRE (revertedAligned != nullptr );
183+ REQUIRE (revertedAligned->transformation .isAlignedTo (revertedAlignTo->transformation ));
184+ }
185+
186+ p.stop ();
187+ }
60188} // namespace
61189
62190TEST_CASE (" Test ImageAlign node image to image alignment" ) {
@@ -90,3 +218,11 @@ TEST_CASE("Test ImageAlign node depth to image alignment on host") {
90218 runImageAlignTest (useDepth, runOnHost, resizeMode);
91219 }
92220}
221+
222+ TEST_CASE (" Test ImageAlign node updates aligned transform after runtime calibration change" ) {
223+ runImageAlignRuntimeCalibrationTest (true );
224+ }
225+
226+ TEST_CASE (" Test ImageAlign node resets image-to-image alignment after runtime calibration change" ) {
227+ runImageAlignRuntimeCalibrationTest (false );
228+ }
0 commit comments