fix(qfactor): correct 1/√2 offset scaling and LL-band visual weight#437
Merged
Conversation
Per the WG1 guideline (N100430), the Qfactor step-size formula applies
the constant 1/sqrt(2) offset as part of Delta_Q, before the sqrt(G_Y)
scaling:
Delta_Q = 2^P0 * alpha_Q * M_Q + 1/sqrt(2)
Delta_ref = Delta_Q * sqrt(G_Y)
The code added eps0 (the normalized 1/sqrt(2)) outside the sqrt(G_Y)
multiply, making the offset term too small by a factor of sqrt(G_Y)
(~1.73). The deviation is largest at high Qfactor, where the offset
dominates as M_Q -> 0 (the best-quality regime).
Separately, the LL sub-band visual weight was only forced to 1.0 when
dwt_levels == 5 (LL index == weight-table size). For fewer levels the
LL band picked up a stray detail-band weight - wrong for chroma (QCC)
at <5 levels. Force the last band (always LL) to 1.0.
Both fixes are applied symmetrically to QCD_marker and QCC_marker, and
to the estimate_qfactor inverse predictor so it stays exact against
corrected streams (verified: Q recovered with 0.0000 residual across
Q in {10..100} and dwt_levels in {2,3,4}).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The enclosing else already peels off qfactor==0xFF (handled by the preceding if), so the `(qfactor != 0xFF) ? ... : basestep/...` ternary was always true and its else arm unreachable. Simplify to the direct expression, matching QCC_marker which never carried the redundant check. No change to emitted step sizes. Closes #436 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR corrects two guideline-defined details in the Qfactor-based quantization step-size computation (encoder-side QCD_marker/QCC_marker) and keeps the estimate_qfactor inverse predictor mathematically consistent with the corrected encoder behavior.
Changes:
- Applies the
1/√2offset insideΔ_Q(before√G_Yscaling) so the offset scales correctly withG_c_sqrt[0]. - Forces the LL band’s visual weight to
1.0for alldwt_levelsby explicitly treating the last band as LL (while still defaulting to1.0for bands beyond the 5-level table whendwt_levels > 5). - Removes an effectively-dead
qfactor == 0xFFternary inside the qfactor-only branch (since0xFFis handled by the earlier branch).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| source/core/codestream/j2kmarkers.cpp | Fixes Qfactor quantization math for offset placement and LL-band weighting in emitted QCD/QCC marker step sizes. |
| source/apps/estimate_qfactor/main.cpp | Mirrors the same formula fixes so estimated Qfactor remains exact against streams produced by the corrected encoder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related correctness fixes to the Qfactor quantization path, validated against the WG1 guideline N100430 (Controlling JPEG 2000 image quality using a single parameter). Applied symmetrically to
QCD_marker,QCC_marker, and theestimate_qfactorinverse predictor.1.
1/√2offset scalingThe guideline applies the constant offset as part of
Δ_Q, before the√G_Yscaling:The code added
eps0(the normalized1/√2) outside the√G_Ymultiply, so the offset term was too small by a factor of√G_Y ≈ 1.73. The deviation is largest at high Qfactor, where the offset dominates asM_Q → 0(the best-quality regime).2. LL-band visual weight at
dwt_levels < 5The guideline states the LL sub-band is assigned visual weight
1.0in all cases. The old(i >= W_b.size())guard only catches the LL band whendwt_levels == 5(LL index == 15 == table size). For fewer levels the LL band picked up a stray detail-band weight — wrong for chroma (QCC) at<5levels. Now the last band (always LL) is forced to1.0, while thei >= sizeclause still covers extra low-freq bands whendwt_levels > 5.3.
estimate_qfactorkept in lock-stepThe tool inverts the same formula, so it carried both bugs. Fixed identically so it stays exact against streams from the corrected encoder.
Verification
Δ_refto√G_Y(not the per-component√G_c) is correct: swapping toG_c_sqrt[Cqcc]collapses all components' LL bands to the identical luminance value, dropping the per-component color weighting.estimate_qfactorrecovers the true Q with 0.0000 residual acrossQ ∈ {10,30,50,65,80,90,97,100}(all piecewise branches) anddwt_levels ∈ {2,3,4}(exercises the LL-weight fix). Before the fix it estimated Q=79 for a true-Q=80 stream.ctest -R "^enc_"and the Part 18 round-trip chain pass.🤖 Generated with Claude Code