Skip to content

Add ITU-R BT.709 YCbCr conversions to the color conversion kernels - #6567

Open
jantonguirao wants to merge 7 commits into
NVIDIA:mainfrom
jantonguirao:janton/dali-4954-bt709-kernels
Open

jantonguirao wants to merge 7 commits into
NVIDIA:mainfrom
jantonguirao:janton/dali-4954-bt709-kernels

Conversation

@jantonguirao

@jantonguirao jantonguirao commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Category:

New feature (non-breaking change which adds functionality)

Description:

The color conversion kernels have the YCbCr <-> RGB conversions for ITU-R BT.601 (itu_r_bt_601, 16-235 range) and JPEG (jpeg, full range, also with the BT.601 matrix), but not for ITU-R BT.709, which is the matrix that HD video normally uses.

This PR adds them, so that the video decoders can convert with the matrix of the stream (follow-up PR):

  • itu_r_bt_709: the same API as itu_r_bt_601 (rgb_to_y, rgb_to_cb, rgb_to_cr, rgb_to_ycbcr, ycbcr_to_rgb, and the gray conversions), with the BT.709 matrix (Kr = 0.2126, Kb = 0.0722) and the 16-235 / 16-240 ranges.
  • itu_r_bt_709_full_range: the same API as jpeg, with the BT.709 matrix over the whole dynamic range of the type. The video decoders need its ycbcr_to_rgb for full range video.

To avoid copying the conversions for each matrix, itu_r_bt_601, itu_r_bt_709, jpeg and itu_r_bt_709_full_range are now instantiations of one template, detail::ycbcr_method<Weights, Range>, with the luma weights of the standard (Kr, Kb) and the range (limited_range or full_range). The results of the existing methods are bit-identical to the previous ones.

Nothing uses the new conversions yet.

Additional information:

Affected modules and functionalities:

  • dali/kernels/imgproc/color_manipulation/color_space_conversion_impl.h: new itu_r_bt_709 and itu_r_bt_709_full_range; itu_r_bt_601 and jpeg are expressed with the shared template.
  • dali/kernels/imgproc/color_manipulation/color_space_conversion_impl_test.cc: new tests.

Key points relevant for the review:

  • The RGB <-> YCbCr formulas are written once, in the comment at the start of the detail section of color_space_conversion_impl.h, and the coefficients of every method are computed from Kr, Kb and the scales of the range (219/255 for luma and 224/255 for chroma in the limited range). The compile-time values are the same floats as the constants that itu_r_bt_601 and jpeg had.
  • The tests for BT.709 do not rely on those formulas only: the BT.709 conversions have pinned values (the limited range matches the published ones), and the typed tests compare every method, input and output type with a double precision reference.

Tests:

  • Existing tests apply
  • New tests added
    • Python tests
    • GTests
    • Benchmark
    • Other
  • N/A

In color_space_conversion_impl_test.cc:

  • The existing typed tests (RGB/YCbCr/gray conversions for every combination of input and output type, against a double precision reference) now also run for itu_r_bt_709 and itu_r_bt_709_full_range, with the Kr, Kb of each.
  • New ColorSpaceConversionTest.*:
    • ITU_R_BT709_RGB2YCbCr_u8: pinned values for the primaries and secondaries.
    • ITU_R_BT709_FullRange_RGB2YCbCr_u8: pinned values of the full range method.
    • ITU_R_BT709_DiffersFromBT601: BT.709 differs from BT.601 for colors, and the gray conversions are the same.
    • ITU_R_BT709_YCbCr2RGB_RoundTripOfPrimaries: RGB -> YCbCr -> RGB gets the colors back up to quantization, and BT.601 doesn't.

I ran this test file on its own (compiled with the host compiler against gtest): all 135 tests in it pass. I also checked that the conversions of itu_r_bt_601, jpeg and itu_r_bt_709 give the same bits as before the refactor (all uint8 RGB/YCbCr values, and samples of float, int8, int16, uint16 and int32 inputs and outputs), and compiled all four methods in a CUDA kernel to PTX (sm_75). There is no GPU test: nothing uses the new conversions yet.

Checklist

Documentation

  • Existing documentation applies
  • Documentation updated
    • Docstring
    • Doxygen
    • RST
    • Jupyter
    • Other
  • N/A

DALI team only

Requirements

  • Implements new requirements
  • Affects existing requirements
  • N/A

REQ IDs: N/A

JIRA TASK: DALI-4954

Add itu_r_bt_709 (16-235 range, the same API as itu_r_bt_601; the gray
conversions do not depend on the matrix and are inherited) and
itu_r_bt_709_full_range (YCbCr to RGB over the whole range of the type,
the counterpart of jpeg with the BT.709 matrix).

HD video is normally BT.709, while only BT.601 was available.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 08:33
@copy-pr-bot

copy-pr-bot Bot commented Oct 28, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new BT.709 inverse-conversion tests cover only uint8_t input and output, leaving mixed-type scaling, wider integer biases, and floating-output clamping behavior unverified.

1 open issue
What changed in this PR

Adds BT.709 color conversion kernels alongside the existing BT.601 and JPEG conversions, preparing for stream-specific matrix selection in video decoders. No callers use the new conversions yet.

Changes:

  • Adds limited-range BT.709 RGB ↔ YCbCr conversions, reusing BT.601 gray conversions.
  • Adds full-range BT.709 YCbCr → RGB conversion.
  • Adds pinned-value, reference-grid, and round-trip tests.
File Description
dali/​kernels/​imgproc/​color_manipulation/​color_space_conversion_impl.h Implements limited- and full-range BT.709 conversions.
dali/​kernels/​imgproc/​color_manipulation/​color_space_conversion_impl_test.cc Adds BT.709 correctness and BT.601 comparison tests.

Review effort: Balanced

Comment on lines +1051 to +1053
auto limited = itu_r_bt_709::ycbcr_to_rgb<uint8_t>(ycbcr);
auto limited_ref = ycbcr_to_rgb_ref(0.2126, 0.0722, false, y, cb, cr);
auto full = itu_r_bt_709_full_range::ycbcr_to_rgb<uint8_t>(ycbcr);

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.

Since e8755f9 the typed suites (ColorSpaceConversionAnyTypeTest and the others) also run for itu_r_bt_709 and itu_r_bt_709_full_range, for every input and output type (unsigned and signed integers, float), against the double precision reference with the Kr, Kb of each method. The tests with uint8 only are the ones with pinned values.

@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Low impact] The PR appears safe to merge; no actionable issues remain.

Summary

Adds limited-range and full-range BT.709 conversions through the shared ycbcr_method template.

  • Callers can convert RGB and YCbCr with BT.709 in either range.

Tests were not run during this review.

Reviews (7) · Last reviewed commit: "Compute the BT.709 matrix rows at compil..." · Reviewed by Greptile

Comment thread dali/kernels/imgproc/color_manipulation/color_space_conversion_impl_test.cc Outdated
…all types

The BT.709 conversions inherited from BT.601 only to reuse the gray conversions. Move those to a
common base of both, which says what is shared.

Run the BT.709 conversions through the typed tests (all input/output type combinations, signed and
out-of-range inputs, clamping rules of floating point outputs) with a reference parametrized by
the luma weights.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Implement rgb_to_y/cb/cr, rgb_to_ycbcr and ycbcr_to_rgb once, in detail::ycbcr_method, from a
matrix (the scaled coefficients) and a range (limited or full, with its gray conversions).
itu_r_bt_601, itu_r_bt_709, jpeg and itu_r_bt_709_full_range are now one-line instantiations.
The results of the existing methods are unchanged. itu_r_bt_709_full_range gets the forward
conversion and the gray conversions too, and the tests cover it with the other methods; the
separate YCbCr -> RGB reference test is dropped, as the typed tests already compare against
the reference with the Kr, Kb of every method.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
State the RGB <-> YCbCr formulas once, in a comment, and compute the coefficients of every
method from the luma weights Kr, Kb of the standard (BT.601, BT.709) and the scales of the range
(limited or full), instead of listing the scaled constants of four matrices. The results are
bit-identical to the previous constants.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Move the coefficients into detail::ycbcr_coefficients so the conversion structs only expose the
conversion functions, share the clamp/dot/offset/round code of rgb_to_y, rgb_to_cb and
rgb_to_cr, use the range's y_bias in the gray conversions, note the floating point offsets in the
formulas comment and add pinned values for itu_r_bt_709_full_range. The results are unchanged.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Comment on lines +259 to +267
static constexpr DALI_HOST_DEV vec3 y_row() {
return vec3(kr * ys, kg * ys, kb * ys);
}
static constexpr DALI_HOST_DEV vec3 cb_row() {
return vec3(-kr / (2 * (1 - kb)) * cs, -kg / (2 * (1 - kb)) * cs, 0.5 * cs);
}
static constexpr DALI_HOST_DEV vec3 cr_row() {
return vec3(0.5 * cs, -kg / (2 * (1 - kr)) * cs, -kb / (2 * (1 - kr)) * cs);
}

@mzient mzient Oct 28, 2026 •

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.

This will cause debug builds to be very slow (these functions will be actually called and executed every time). Why not compute these vectors at compile time and just use a bunch of fields instead?

Suggested change
static constexpr DALI_HOST_DEV vec3 y_row() {
return vec3(kr * ys, kg * ys, kb * ys);
}
static constexpr DALI_HOST_DEV vec3 cb_row() {
return vec3(-kr / (2 * (1 - kb)) * cs, -kg / (2 * (1 - kb)) * cs, 0.5 * cs);
}
static constexpr DALI_HOST_DEV vec3 cr_row() {
return vec3(0.5 * cs, -kg / (2 * (1 - kr)) * cs, -kb / (2 * (1 - kr)) * cs);
}
static constexpr vec3 y_row = vec3(kr * ys, kg * ys, kb * ys);
static constexpr vec3 cb_row = vec3(-kr / (2 * (1 - kb)) * cs, -kg / (2 * (1 - kb)) * cs, 0.5 * cs);
static constexpr vec3 cr_row = vec3(0.5 * cs, -kg / (2 * (1 - kr)) * cs, -kb / (2 * (1 - kr)) * cs));

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.

Confirmed: with -O0 the row functions were called at run time (the rows were passed to my rgb_to_channel helper as arguments, and scaled there). The rows are now static constexpr data members as suggested, and each conversion function scales them in a constexpr local, so nothing is computed per call in a debug build. Checked: the -O0 assembly of rgb_to_y has no calls to the row functions any more; the results are bit-identical to the previous header at -O0 and -O1 (all uint8 triples and samples of the other types); the 135 gtests pass; the device code compiles, also with -G. Fixed in 6f8e4fa.

The rows of the matrix were constexpr functions whose results were passed to a helper as
arguments, so they were called, and the rows scaled for the types, at run time in builds without
optimization. Make the rows static constexpr data members and scale them in a constexpr in each
conversion function, as before the helper was introduced. The results are unchanged.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
@jantonguirao jantonguirao assigned mzient and unassigned banasraf Oct 28, 2026
@jantonguirao

Copy link
Copy Markdown
Collaborator Author

!build

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [77328866]: BUILD STARTED

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [77328866]: BUILD FAILED

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.

6 participants