Skip to content

[LTX-2.5] Add ACEScct colour transforms and HLG/EXR export for the SDR-To-HDR IC-LoRA - #14966

Closed
christopher5106 wants to merge 1 commit into
huggingface:mainfrom
scenario-labs:fix_ltx2_hdr_acescct_export
Closed

christopher5106 wants to merge 1 commit into
huggingface:mainfrom
scenario-labs:fix_ltx2_hdr_acescct_export

Conversation

@christopher5106

@christopher5106 christopher5106 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Lightricks' LTX-2.5 SDR-To-HDR IC-LoRA works in ACEScct rather than LogC3. The SDR clip is mapped to ACEScct before VAE encoding, and the decoded ACEScct is expanded back to scene-linear ACEScg / Rec.709. Its reference export writes a BT.2020 HLG 10-bit HEVC master plus half-float EXR frames.

Today diffusers' LTX2VideoHDRProcessor only implements the LTX-2.3 LogC3 decode, with no input transform: sRGB white enters the VAE as +1.0 where the reference uses +0.1096. And encode_hdr_tensor_to_mp4 is an 8-bit SDR preview. This PR adds the colour and export building blocks only. There are no pipeline or model changes; those come in follow-ups (below).

Added

  • Pure-torch colour transforms in pipelines/ltx2/image_processor.py, ported from Lightricks' ltx_core.hdr / ltx_core.color:
    • the sRGB EOTF
    • Bradford Rec.709↔AP1 and →Rec.2020 matrices, with constants written out, so no colour-science dependency
    • ACEScct encode/decode with the reference's clamps
    • the four input chains srgb_gamma / srgb / acescg / acescct → ACEScct, and ACEScct → ACEScg or Rec.709 linear
  • LTX2VideoHDRProcessor(hdr_transform="acescct") with input_colorspace / output_colorspace. "logc3" stays the default and its code path is unchanged.
  • encode_hdr_tensor_to_hlg_mp4:
    • maps diffuse white to HLG 0.75 (BT.2408) with a C¹ exponential highlight roll-off
    • applies the ARIB STD-B67 OETF and converts to BT.2020 NCL limited-range 10-bit 4:2:0
    • encodes with libx265 using the reference's settings, the hvc1 tag and full colour tags
    • verified byte-identical to the reference writer on the same frames
  • save_exr_frame / export_to_exr_sequence: half-float, ZIP, RGB, chromaticities + colorSpace header, behind a new optional is_openexr_available() guard.
    • The reference uses OpenImageIO. The official ASWF OpenEXR binding (≥ 3.3) writes equivalent files (only OpenImageIO's capDate attribute differs) at about a quarter of the wheel size.
    • Pixels are bit-identical to the reference.

Faithfulness

Checked against the reference code itself (Lightricks/LTX-2 9ec55f9f):

  • the matrices and transforms are bitwise identical, except a 1-ulp float32 difference on one tensor layout, which comes from the reference's column-major matrix in einsum
  • the HLG bitstream is byte-identical
  • the EXR pixels are bit-identical

Tests

tests/pipelines/ltx2/test_ltx2_hdr_color.py, 40 tests:

  • Transforms: exact values computed independently with colour-science (float64) and encode/decode round trips. Both processor modes are covered, including an unchanged LogC3.
  • HLG stream: codec, pix_fmt, hvc1, colour tags, fps, frame count, and diffuse-white luma. Odd frame sizes are rejected. Skipped without libx265.
  • EXR: read-back of dtype, compression, tags and pixels in all three colour spaces. Skipped without OpenEXR.

make fix-copies, check_dummies, check_forward_call_docstrings, ruff and doc-builder style are clean.

Follow-ups

  1. SDR-To-HDR in LTX2HDRPipeline: the ACEScct input transform on the reference, a video-only context fed by the LoRA's precomputed video_context / audio_context embeddings with no text encoder, fp32 VAE, distilled sigmas, and reflect-pad to /32 with crop-back.
  2. Seam keyframes: 1-frame SDR guides at 0.95 plus generated keyframe slots at DFR segment seams, reusing the DFR keyframe machinery ([modular] Add LTX-2.5 DFR blocks #14600).
  3. Keyframe-aware diffusion-VAE decoding (type_emb, joint keyframe-plane attention), coordinated with [LTX-2.5] Refactor LTX-2.5 Diffusion Decoder Forward Methods #14694.

Before submitting

  • Did you read the contributor guideline?
  • Did you write any new necessary tests?
  • Did you update the documentation? (docstrings)

Who can review?

@DN6 @sayakpaul @yiyixuxu

🤖 Generated with Claude Code

…-HDR

Port the colour pipeline of the LTX-2.5 SDR-To-HDR IC-LoRA from the Lightricks
LTX-2 reference (ltx_core.hdr, ltx_core.color, ltx_pipelines media_io):

- image_processor: sRGB EOTF, Bradford Rec.709/AP1/Rec.2020 matrices, ACEScct
  encode/decode, the srgb_gamma/srgb/acescg/acescct input transforms and the
  ACEScct -> ACEScg/Rec.709 linear output transform, as pure torch functions.
  LTX2VideoHDRProcessor gains hdr_transform="acescct" with input_colorspace /
  output_colorspace options; the LogC3 default is unchanged.
- export_utils: encode_hdr_tensor_to_hlg_mp4 (BT.2020 HLG 10-bit HEVC via
  PyAV/libx265) and save_exr_frame / export_to_exr_sequence (half-float ZIP
  EXR with chromaticities and colorSpace), behind a new optional
  is_openexr_available() guard.
- tests: exact values computed with colour-science, round trips, HLG stream
  properties and EXR read-back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@christopher5106

Copy link
Copy Markdown
Contributor Author

Follow-up stacked on this PR: #14974 adds the LTX-2.5 SDR-To-HDR path to LTX2HDRPipeline that uses these transforms and writers, with a real-weights parity check against Lightricks' reference.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Hi @christopher5106, thanks for the PR! It does not appear to link an issue it fixes. If this PR addresses an existing issue, please add a closing keyword (e.g. Fixes #1234) to the PR description so the issue is linked. See the contribution guide for more details. If this PR intentionally does not fix a tracked issue, a maintainer can add the no-issue-needed label to silence this reminder.

Please note that PRs without a linked issue are likely to be automatically closed 10 days after this notice.

Once the PR links an issue (or gets the no-issue-needed label), you can ignore this message — it stays here as a comment, but it no longer applies.

@christopher5106

Copy link
Copy Markdown
Contributor Author

Closing for now, as asked: I should have opened an issue first. The scope and structure are in #14981, and I'll reopen in whatever shape you prefer there. Thanks for the patience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant