Skip to content

Change that intrinsics are taken from transformations in DCL#1795

Merged
MaticTonin merged 2 commits into
developfrom
dev_mt_AddTransformations
May 14, 2026
Merged

Change that intrinsics are taken from transformations in DCL#1795
MaticTonin merged 2 commits into
developfrom
dev_mt_AddTransformations

Conversation

@MaticTonin
Copy link
Copy Markdown
Collaborator

@MaticTonin MaticTonin commented May 14, 2026

Purpose

Specification

None / not applicable

Dependencies & Potential Impact

None / not applicable

Deployment Plan

None / not applicable

Testing & Validation

None / not applicable

AI Usage

Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]

Submitted code was reviewed by a human: YES/NO

The author is taking the responsibility for the contribution: YES/NO

Summary by CodeRabbit

  • Refactor
    • Calibration now uses per-camera image transformation data instead of simple resolution information, improving stereo calibration accuracy and consistency across sensor setups.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

DynamicCalibration now stores per-side ImgTransformation and uses those transformations to derive camera intrinsics; utility signatures and camera-matrix types are updated to accept ImgTransformation references and fixed-size 3×3 arrays, and pipeline init/runtime wiring passes the transformations into the conversion helpers.

Changes

ImgTransformation Calibration Refactoring

Layer / File(s) Summary
State and type declarations
include/depthai/pipeline/node/DynamicCalibrationNode.hpp, src/pipeline/node/DynamicCalibrationUtils.hpp
DynamicCalibration adds imgTransformationA/imgTransformationB members. Utility signatures and includes updated to accept ImgTransformation references and std::array<std::array<float,3>,3> camera matrices.
Calibration conversion implementation
src/pipeline/node/DynamicCalibrationNode.cpp
convertDaiCalibrationToDcl now builds left/right DCL calibrations from each ImgTransformation's intrinsic matrix while still using distortion and inter-socket extrinsics from CalibrationHandler. createDclCalibration now accepts a fixed-size 3×3 std::array camera matrix.
Runtime wiring and pipeline initialization
src/pipeline/node/DynamicCalibrationNode.cpp
setCalibration and computeMetrics pass imgTransformationA/imgTransformationB to conversion helpers. Pipeline init captures transformations from incoming left/right ImgFrame objects and uses them when converting device calibration for sensor setup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

testable

Suggested reviewers

  • pheec

Poem

🐰 I hop through frames where intrinsics hide,
Left and right transformations side by side,
Vectors tidy into arrays three-by-three,
Calibration hums — geometry set free,
A rabbit cheers for clearer sight with pride! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Change that intrinsics are taken from transformations in DCL' directly reflects the main change: refactoring camera intrinsics to derive from ImgTransformation instead of resolution parameters.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev_mt_AddTransformations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pipeline/node/DynamicCalibrationNode.cpp (1)

53-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Finish the intrinsics migration for pixel-space quality outputs.

This helper now feeds DCL with transformation-derived intrinsics, but runQualityCheck() and runCalibration() still convert Sampson error back to pixels with getCameraIntrinsics(..., resolutionA). On cropped or scaled streams, sampsonErrorCurrent/New will be reported against a different camera matrix than the one used for calibration. Thread ImgTransformation into that scale calculation as well, or derive the scale directly from cameraMatrixA.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationNode.cpp` around lines 53 - 75,
convertDaiCalibrationToDcl now supplies transformation-derived intrinsics but
runQualityCheck() and runCalibration() still compute pixel-scale using
getCameraIntrinsics(..., resolutionA), causing Sampson errors to be reported
against the wrong intrinsics for cropped/scaled streams; fix by threading the
ImgTransformation (or its derived cameraMatrixA) into the scale calculation so
the same intrinsics used to create DCL calib are used when converting Sampson
error to pixels—specifically, update calls in runQualityCheck() and
runCalibration() that call getCameraIntrinsics(..., resolutionA) to instead
accept an ImgTransformation (or extract focal/px from cameraMatrixA returned by
convertDaiCalibrationToDcl) and use those values to compute the pixel-scale for
sampsonErrorCurrent/sampsonErrorNew consistently with
convertDaiCalibrationToDcl.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pipeline/node/DynamicCalibrationNode.cpp`:
- Around line 440-441: The node currently only sets imgTransformationA and
imgTransformationB at initialization, but runLoadImage() later replaces
leftFrame/rightFrame; update the cached transforms whenever new frames are
loaded by calling leftFrame->getTransformation() and
rightFrame->getTransformation() inside runLoadImage() (or explicitly
document/enforce that transformations are immutable for the node lifetime and
assert no change). Locate uses of leftFrame/rightFrame and update the
imgTransformationA/B assignments in the frame-loading path so downstream
calibration/metrics always use the fresh transforms.

---

Outside diff comments:
In `@src/pipeline/node/DynamicCalibrationNode.cpp`:
- Around line 53-75: convertDaiCalibrationToDcl now supplies
transformation-derived intrinsics but runQualityCheck() and runCalibration()
still compute pixel-scale using getCameraIntrinsics(..., resolutionA), causing
Sampson errors to be reported against the wrong intrinsics for cropped/scaled
streams; fix by threading the ImgTransformation (or its derived cameraMatrixA)
into the scale calculation so the same intrinsics used to create DCL calib are
used when converting Sampson error to pixels—specifically, update calls in
runQualityCheck() and runCalibration() that call getCameraIntrinsics(...,
resolutionA) to instead accept an ImgTransformation (or extract focal/px from
cameraMatrixA returned by convertDaiCalibrationToDcl) and use those values to
compute the pixel-scale for sampsonErrorCurrent/sampsonErrorNew consistently
with convertDaiCalibrationToDcl.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3cc53e3c-c35d-4f90-ba9f-c63c7d91f20b

📥 Commits

Reviewing files that changed from the base of the PR and between fa1a0f1 and 1965d0c.

📒 Files selected for processing (3)
  • include/depthai/pipeline/node/DynamicCalibrationNode.hpp
  • src/pipeline/node/DynamicCalibrationNode.cpp
  • src/pipeline/node/DynamicCalibrationUtils.hpp
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-03-24T22:39:04.364Z
Learnt from: MaticTonin
Repo: luxonis/depthai-core PR: 1732
File: src/pipeline/Pipeline.cpp:705-705
Timestamp: 2026-03-24T22:39:04.364Z
Learning: Do not flag the `!= ""` part of the auto-calibration condition as redundant when it appears in `PipelineImpl::build()` (or closely related pipeline build logic). If the code uses `utility::getEnvAs<std::string>(..., default)` with a default such as `"ON_START"`, the explicit empty-string guard may still be intentional to treat an explicitly empty env var as “OFF/disabled” (or to avoid special-casing elsewhere). Only consider removing `!= ""` if the codebase has an explicit, enforceable guarantee that `DEPTHAI_AUTOCALIBRATION` can never be set to an empty string (e.g., via validated parsing/CI checks); otherwise, keep the guard.

Applied to files:

  • src/pipeline/node/DynamicCalibrationNode.cpp
🔇 Additional comments (1)
src/pipeline/node/DynamicCalibrationUtils.hpp (1)

4-30: LGTM!

Comment on lines +440 to +441
imgTransformationA = leftFrame->getTransformation();
imgTransformationB = rightFrame->getTransformation();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Refresh the cached transformation when loading new frames.

imgTransformationA/B are only assigned during initialization, but later calibration and metrics runs operate on frames loaded in runLoadImage(). If the upstream path changes crop/resize/warp after startup, the node will keep calibrating newer images with stale intrinsics. Update these members from each loaded pair, or enforce that the transformation is immutable for the node lifetime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationNode.cpp` around lines 440 - 441, The
node currently only sets imgTransformationA and imgTransformationB at
initialization, but runLoadImage() later replaces leftFrame/rightFrame; update
the cached transforms whenever new frames are loaded by calling
leftFrame->getTransformation() and rightFrame->getTransformation() inside
runLoadImage() (or explicitly document/enforce that transformations are
immutable for the node lifetime and assert no change). Locate uses of
leftFrame/rightFrame and update the imgTransformationA/B assignments in the
frame-loading path so downstream calibration/metrics always use the fresh
transforms.

@moratom moratom requested a review from JakubFara May 14, 2026 13:49
@moratom
Copy link
Copy Markdown
Collaborator

moratom commented May 14, 2026

LFG!

@MaticTonin MaticTonin changed the title Change that intrinsics are taken from transformations Change that intrinsics are taken from transformations in DCL May 14, 2026
Copy link
Copy Markdown
Collaborator

@JakubFara JakubFara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just consts; we can check the intrinsic in loadImages, but I do not insist on it.

Comment on lines +57 to +58
ImgTransformation& imgTransformationA,
ImgTransformation& imgTransformationB) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add consts

Comment on lines +29 to +30
ImgTransformation& imgTransformationA,
ImgTransformation& imgTransformationB);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adressed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/pipeline/node/DynamicCalibrationNode.cpp (1)

61-76: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add bounds check before indexing distortion coefficients in createDclCalibration.

createDclCalibration (lines 131–132 for Perspective, 137–138 for Fisheye) directly accesses distortionCoefficients[i] up to index 13 or 3 without verifying the vector size. ImgTransformation::getDistortionCoefficients() returns an unpaddedstd::vector that can be any size, including empty (as shown in tests). If the vector has fewer elements than the loop expects, this causes out-of-bounds access and undefined behavior.

Ensure the distortion vector size matches the expected model size before indexing, or fall back to currentCalibration.getDistortionCoefficients(socketA/B) which is guaranteed to be padded to 14 entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationNode.cpp` around lines 61 - 76,
createDclCalibration currently indexes distortionCoefficients[i] up to
model-specific sizes (Perspective up to 14, Fisheye up to 4) without checking
vector length, causing OOB when ImgTransformation::getDistortionCoefficients()
returns a shorter vector; update createDclCalibration to check the incoming
distortionCoefficients.size() against the required size for the chosen
distortion model and if too short, use
currentCalibration.getDistortionCoefficients(socketA/B) (the padded 14-entry
fallback) or otherwise pad/initialize remaining entries safely before indexing
so loops in createDclCalibration never access beyond the vector bounds.
src/pipeline/node/DynamicCalibrationUtils.hpp (1)

25-30: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add explicit include for ImgTransformation.

The function signature uses const ImgTransformation& parameters, but this header does not explicitly include <depthai/common/ImgTransformations.hpp>. The type is not forward-declarable since ImgTransformation is a struct with substantial member data and is used as a const reference. The header currently relies on transitive includes from <DynamicCalibration.hpp>, which is fragile; if the include graph changes, this becomes a compile error. Add an explicit include to make the header self-contained.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationUtils.hpp` around lines 25 - 30, The
header declares convertDaiCalibrationToDcl using const ImgTransformation& but
never includes the ImgTransformation definition; add an explicit include for
depthai/common/ImgTransformations.hpp at the top of
src/pipeline/node/DynamicCalibrationUtils.hpp so the ImgTransformation type is
available (do not rely on transitive includes from DynamicCalibration.hpp) and
ensure the file becomes self-contained for the convertDaiCalibrationToDcl
declaration.
♻️ Duplicate comments (1)
src/pipeline/node/DynamicCalibrationNode.cpp (1)

440-441: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stale imgTransformationA/B after initialization (duplicate of prior review).

These members are still only written once during initializePipeline and not refreshed when runLoadImage() pulls a new pair of frames. Any change to the per-frame ImgTransformation between init and a later setCalibration / computeMetrics will cause those calls to use stale intrinsics. Either re-assign imgTransformationA = leftFrame->getTransformation(); and imgTransformationB = rightFrame->getTransformation(); inside runLoadImage() (alongside the frame load at lines 374–375), or document and enforce immutability of the transformations for the node lifetime.

🔧 Suggested fix to refresh inside runLoadImage()
     auto leftFrame = inSyncGroup->get<dai::ImgFrame>(leftInputName);
     auto rightFrame = inSyncGroup->get<dai::ImgFrame>(rightInputName);

     if(!leftFrame || !rightFrame) {
         logger->trace("WARNING: Missing image(s) in MessageGroup (left={}, right={})", leftFrame ? "ok" : "missing", rightFrame ? "ok" : "missing");
         return DynamicCalibration::ErrorCode::MISSING_IMAGE;
     }
+
+    // Keep cached transformations in sync with the most recent frames so
+    // downstream setCalibration()/computeMetrics() see up-to-date intrinsics.
+    imgTransformationA = leftFrame->getTransformation();
+    imgTransformationB = rightFrame->getTransformation();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationNode.cpp` around lines 440 - 441, The
imgTransformationA and imgTransformationB members are only set in
initializePipeline and become stale when runLoadImage replaces frames; update
runLoadImage to reassign imgTransformationA = leftFrame->getTransformation() and
imgTransformationB = rightFrame->getTransformation() immediately after the new
frames are loaded (where leftFrame/rightFrame are updated) so subsequent
setCalibration and computeMetrics use current intrinsics, or alternatively
document/enforce that transformations are immutable for the node lifetime in
initializePipeline and refuse frame updates in runLoadImage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pipeline/node/DynamicCalibrationNode.cpp`:
- Line 268: setCalibration and computeMetrics are using stale member fields
imgTransformationA/imgTransformationB (they were only set once in
initializePipeline) so subsequent calls use init-time intrinsics; modify both
setCalibration and computeMetrics to not read the members but instead use the
forwarded transformations passed into the call site (the
imgTransformationA/imgTransformationB variables that are being forwarded to
DclUtils::convertDaiCalibrationToDcl) or retrieve the current transforms from
the pipeline/handler at the start of each call; update references inside
setCalibration/computeMetrics (and any use around convertDaiCalibrationToDcl) to
consume the fresh transformations rather than the cached members.

---

Outside diff comments:
In `@src/pipeline/node/DynamicCalibrationNode.cpp`:
- Around line 61-76: createDclCalibration currently indexes
distortionCoefficients[i] up to model-specific sizes (Perspective up to 14,
Fisheye up to 4) without checking vector length, causing OOB when
ImgTransformation::getDistortionCoefficients() returns a shorter vector; update
createDclCalibration to check the incoming distortionCoefficients.size() against
the required size for the chosen distortion model and if too short, use
currentCalibration.getDistortionCoefficients(socketA/B) (the padded 14-entry
fallback) or otherwise pad/initialize remaining entries safely before indexing
so loops in createDclCalibration never access beyond the vector bounds.

In `@src/pipeline/node/DynamicCalibrationUtils.hpp`:
- Around line 25-30: The header declares convertDaiCalibrationToDcl using const
ImgTransformation& but never includes the ImgTransformation definition; add an
explicit include for depthai/common/ImgTransformations.hpp at the top of
src/pipeline/node/DynamicCalibrationUtils.hpp so the ImgTransformation type is
available (do not rely on transitive includes from DynamicCalibration.hpp) and
ensure the file becomes self-contained for the convertDaiCalibrationToDcl
declaration.

---

Duplicate comments:
In `@src/pipeline/node/DynamicCalibrationNode.cpp`:
- Around line 440-441: The imgTransformationA and imgTransformationB members are
only set in initializePipeline and become stale when runLoadImage replaces
frames; update runLoadImage to reassign imgTransformationA =
leftFrame->getTransformation() and imgTransformationB =
rightFrame->getTransformation() immediately after the new frames are loaded
(where leftFrame/rightFrame are updated) so subsequent setCalibration and
computeMetrics use current intrinsics, or alternatively document/enforce that
transformations are immutable for the node lifetime in initializePipeline and
refuse frame updates in runLoadImage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0b92acd-e511-4d84-9975-485ea1b1142b

📥 Commits

Reviewing files that changed from the base of the PR and between 1965d0c and 301248c.

📒 Files selected for processing (2)
  • src/pipeline/node/DynamicCalibrationNode.cpp
  • src/pipeline/node/DynamicCalibrationUtils.hpp
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-03-24T22:39:04.364Z
Learnt from: MaticTonin
Repo: luxonis/depthai-core PR: 1732
File: src/pipeline/Pipeline.cpp:705-705
Timestamp: 2026-03-24T22:39:04.364Z
Learning: Do not flag the `!= ""` part of the auto-calibration condition as redundant when it appears in `PipelineImpl::build()` (or closely related pipeline build logic). If the code uses `utility::getEnvAs<std::string>(..., default)` with a default such as `"ON_START"`, the explicit empty-string guard may still be intentional to treat an explicitly empty env var as “OFF/disabled” (or to avoid special-casing elsewhere). Only consider removing `!= ""` if the codebase has an explicit, enforceable guarantee that `DEPTHAI_AUTOCALIBRATION` can never be set to an empty string (e.g., via validated parsing/CI checks); otherwise, keep the guard.

Applied to files:

  • src/pipeline/node/DynamicCalibrationNode.cpp
🔇 Additional comments (2)
src/pipeline/node/DynamicCalibrationNode.cpp (2)

100-105: LGTM!


459-459: LGTM!

device->flashCalibration(handler);
}
auto [calibA, calibB] = DclUtils::convertDaiCalibrationToDcl(handler, daiSocketA, daiSocketB, resolutionA, resolutionB);
auto [calibA, calibB] = DclUtils::convertDaiCalibrationToDcl(handler, daiSocketA, daiSocketB, imgTransformationA, imgTransformationB);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Call sites correctly forward the cached transformations, but they ride on a stale-state bug.

Both setCalibration (line 268) and computeMetrics (line 274) read the member fields imgTransformationA/imgTransformationB. Those are only ever written once in initializePipeline (lines 440–441), so every subsequent invocation here uses init-time intrinsics. If the upstream pipeline ever changes crop/resize/warp after startup, both flows recalibrate / score with stale intrinsics. Root cause and recommended fix are noted at lines 440–441.

Also applies to: 274-274

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pipeline/node/DynamicCalibrationNode.cpp` at line 268, setCalibration and
computeMetrics are using stale member fields
imgTransformationA/imgTransformationB (they were only set once in
initializePipeline) so subsequent calls use init-time intrinsics; modify both
setCalibration and computeMetrics to not read the members but instead use the
forwarded transformations passed into the call site (the
imgTransformationA/imgTransformationB variables that are being forwarded to
DclUtils::convertDaiCalibrationToDcl) or retrieve the current transforms from
the pipeline/handler at the start of each call; update references inside
setCalibration/computeMetrics (and any use around convertDaiCalibrationToDcl) to
consume the fresh transformations rather than the cached members.

Copy link
Copy Markdown
Collaborator

@moratom moratom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doens't enable actual dyanmic calibration < 800p yet thought right?

@MaticTonin MaticTonin merged commit c356ea4 into develop May 14, 2026
1 check passed
@MaticTonin MaticTonin deleted the dev_mt_AddTransformations branch May 14, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants