From 36d1f8097594e22723d68d5b3f3f47eb74158ba4 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:52:07 +0800 Subject: [PATCH 01/27] chore: ignore pycache/agent-pack artifacts; fix codegraph MCP args codegraph serve resolves the workspace from its own cwd; the explicit --path ${workspaceFolder} argument was passed through unexpanded by some launchers and broke indexing. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- .gitignore | 8 +++++++- .mcp.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d1973c29..9498729d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,10 @@ target/ .agent/scratch/ # Source-export bundle produced by copy-code-only.py (regenerable). -JPXL/JPXL.zip +JPXL/JPXL.zip + +# Python bytecode caches from the tools/ test suites. +__pycache__/ + +# Agent-pack snapshot archives (regenerable exports, never history). +/jpegXL-rs-agent-pack-*.7z diff --git a/.mcp.json b/.mcp.json index d7314938..4021a6f2 100644 --- a/.mcp.json +++ b/.mcp.json @@ -8,7 +8,7 @@ "codegraph": { "type": "stdio", "command": "codegraph", - "args": ["serve", "--mcp", "--path", "${workspaceFolder}"] + "args": ["serve", "--mcp"] } } } From 6ee98cb5e703baa6478863d903e2787937a98462 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:52:39 +0800 Subject: [PATCH 02/27] docs(sources): register the 2026-08-24 one-shot quality-controller memo The outside advisor's answers to the one-shot brief (verified findings, PR 0-5 program, gates). Registered via AKR source tooling; the root-level working copy was byte-identical and is removed. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- sources/catalog.json | 11 + ...ty-controller-memo-2026-08-24--ab931f55.md | 1185 +++++++++++++++++ 2 files changed, 1196 insertions(+) create mode 100644 sources/external/jpxl-one-shot-quality-controller-memo-2026-08-24--ab931f55.md diff --git a/sources/catalog.json b/sources/catalog.json index 35d05563..e93798c7 100644 --- a/sources/catalog.json +++ b/sources/catalog.json @@ -23,6 +23,17 @@ "scope": "JPXL/**", "title": "Outside advice: a codec-optimization perceptual metric for JPXL (JPXL-PQ / JPXL-PCost)" }, + { + "added_at": "2026-08-24", + "availability": "full", + "byte_len": 53129, + "content_hash": "sha256:ab931f55436c9b169b1369718c4eaa1187e8d007ff7ed9d430f280d01a8bf991", + "id": "jpxl-one-shot-quality-controller-memo-2026-08-24", + "media_type": "text/markdown", + "origin": "external", + "path": "sources/external/jpxl-one-shot-quality-controller-memo-2026-08-24--ab931f55.md", + "title": "One-shot quality-controller design memo (advisor response, 2026-08-24)" + }, { "added_at": "2026-08-22", "availability": "full", diff --git a/sources/external/jpxl-one-shot-quality-controller-memo-2026-08-24--ab931f55.md b/sources/external/jpxl-one-shot-quality-controller-memo-2026-08-24--ab931f55.md new file mode 100644 index 00000000..9b39322e --- /dev/null +++ b/sources/external/jpxl-one-shot-quality-controller-memo-2026-08-24--ab931f55.md @@ -0,0 +1,1185 @@ +# Design memo: JPXL score-targeted encoding + +## Executive verdict + +JPXL should implement **common-case one shot**, not claim true one shot. + +A source- or transform-derived statistical predictor cannot guarantee the canonical decoded SSIMULACRA2 score of an arbitrary image. A conservative reserve can reduce the miss rate, but it cannot turn an empirical predictor into a proof. The only generally defensible true-one-shot guarantees would be: + +1. encode losslessly, or +2. derive a proven worst-case relationship between quantization and SSIMULACRA2. + +Neither is presently available as a byte-competitive lossy controller. + +The existing hard-floor contract can nevertheless remain intact: + +> A successful result is emitted only after its actual reconstructed pixels have been canonically scored at or above the requested target. + +The normal path should perform one predicted pixel plan, one reconstruction/metric evaluation, and one entropy attachment/emission. A miss should receive one slope-based corrective plan. Broad uncertainty, out-of-distribution content, likely saturation, or a second miss should route into the existing bounded controller while reusing all work already performed. + +This directly preserves the handoff’s requirement that successful output not silently fall below the target, while keeping saturation and work exhaustion explicit. + +--- + +## Review scope and limitations + +I reviewed: + +* the attached advisor handoff; +* `quality.rs`, `quality_features.rs`, `quality_predictor.rs`, `rate.rs`; +* the public `jpxl` facade and CLI quality path; +* `CandidateSearchContext`, `CandidateForwardCache`, the cover/CfL planning order, and entropy attachment; +* `calibrate_initial_rung.py`; +* `quality-corpus.json`; +* the included AKR decisions and evidence summaries. + +I did not inspect or derive anything from the included libjxl source. That remains consistent with the clean-room constraint. + +The raw `.agent/scratch` evidence named in the handoff was excluded from the agent pack, so I could not independently recalculate those rows from their JSONL files. I treated the handoff and AKR records as authoritative for those measurements. The environment also lacks the Rust toolchain, so this is a static code and evidence review rather than a fresh test run. + +--- + +# 1. Immediate code findings that should be fixed first + +## 1.1 The public facade currently violates the hard-floor contract + +This is the most important finding. + +In `crates/jpxl-encode-policy/src/quality.rs:1109-1128`, when no probe meets the requested score, the controller deliberately selects the finest verified under-target probe and exact-prices it: + +```rust +// Nothing met the target: emit the finest verified probe and say so +under_target = true; +... +ordered.push(index); +``` + +At `quality.rs:1684-1716`, both `SaturatedTop` and `UnderTargetWorkCap` still produce a normal `QualityOutcome` containing `codestream: winner.bytes`. + +The public facade then wraps those bytes and returns `Ok` at: + +* `crates/jpxl/src/lib.rs:692-736` +* especially `lib.rs:708` and `lib.rs:723-736`. + +The public status documentation is itself explicit: + +```rust +/// The bounded controller ran out of probes before any candidate met the +/// score; the emitted stream's `achieved_score` is below the request. +UnderTargetWorkCap, +``` + +Finally, `crates/jpxl-cli/src/main.rs:2455-2495` treats every `Ok(Perceptual(...))` as a successful encode and returns the bytes for writing. + +That is incompatible with the handoff’s stated minimum-score semantics. + +### Required correction + +The default hard-floor API must not return an under-target codestream as a successful result. + +A minimally disruptive API would be: + +```rust +pub enum PerceptualFailureKind { + LossyLadderSaturated, + WorkBudgetExhausted, +} + +pub struct PerceptualFailure { + pub kind: PerceptualFailureKind, + pub requested_score: f64, + pub best_verified_score: f64, + pub best_rung: u32, + pub probes: u32, + pub structural_builds: u32, + pub metric_version: MetricVersion, + pub trace_json: Option, +} +``` + +Then either: + +```rust +Error::PerceptualTargetNotMet(PerceptualFailure) +``` + +or a dedicated result enum should be returned. The CLI should exit nonzero and should not create or replace the requested output file. + +Two explicitly different fallback modes may be offered: + +* `--quality-fallback=lossless`: emit a lossless stream and report that the lossy ladder saturated. +* `--quality-fallback=best-effort`: knowingly emit the best verified under-target stream, with a distinct contract and status. + +Neither should happen silently. + +`SaturatedFloor` remains a successful result. `SaturatedTop` and `UnderTargetWorkCap` do not. + +--- + +## 1.2 The nominal probe budget is not actually hard + +`quality.rs:907-921` defines: + +```rust +/// One probe beyond the budget +fn rescue_probe(...) +``` + +The status documentation at `quality.rs:265` also refers to “the probe budget (plus its one rescue probe).” + +This conflicts with comments and tests that describe `pixel_probes` as a hard cap. It also makes the requested work-count table misleading. + +The correction should be one of: + +* reserve the final slot for rescue inside `pixel_probes`, or +* expose separate counters and limits such as `navigation_probes` and `rescue_probes`. + +I recommend the first. Balanced should have a **total maximum of five canonical pixel probes**, not five plus a conditionally hidden sixth. The new predictive path should continue the same total budget rather than receiving one or two prediction attempts and then restarting a five-probe controller. + +--- + +## 1.3 The current predictor is trained on the wrong endpoint + +The current generated predictor is not merely too small. Its labels do not correspond to the production decision that the proposed model must make. + +`quality_predictor.rs:8-11` states that it predicts fixed-quantizer `global_scale` with `HfMul = 1`. + +`calibrate_initial_rung.py:34-46` confirms that the calibration ladder ends at `global_scale = 73728`, while the production effective-scale ladder continues far beyond that by increasing `HfMul`. + +The production decision also includes quantizer-dependent structure: + +```text +quantizer + -> AQ setup + -> HF quantizers + -> cover selection + -> selected transforms + -> CfL + -> quantization + -> reconstruction +``` + +A fixed-global-scale calibration point is therefore not necessarily the same operating point as the final production Balanced stream, especially when a fresh-structure rescue changes cover or CfL. + +The replacement labels should be: + +> The coarsest effective rung whose **fresh production Balanced pixel plan** meets the target, over the complete effective-scale ladder, with saturation represented as censoring rather than as a clamped crossing. + +The current final corrected result can be recorded as a secondary label, but the oracle training label should not inherit the bounded navigator’s own approximation errors. + +--- + +## 1.4 The runtime predictor ignores most of its available features + +Although `SourceFeatures` contains: + +* dimensions; +* grayscale; +* luma q10, q50, and q90; +* chroma q50; +* flat fraction; +* edge proxy; + +`predicted_effective_scale()` in `quality.rs:613-663` uses only: + +* `luma_variance_q50`; +* `flat_fraction`; +* target score. + +The generated table’s `support` field is also unused. A cell supported by one image receives the same runtime trust as a well-supported cell. + +This explains why the current predictor is useful as a navigation seed but not as a final controller. Its recorded median absolute log-scale error is 0.30, while p90 is 2.68—a roughly 14.6× scale-ratio error at the tail. High targets are also heavily censored by the fixed-scale ceiling. + +The replacement should not be an expanded bucket table. It needs a calibrated curve prediction, uncertainty, and explicit saturation/OOD handling. + +--- + +## 1.5 Current “cheap” source features are not entirely free + +`quality_features.rs:91-139` creates two full vectors of per-atom values and sorts both to obtain quantiles. + +For a 50 MP image there are approximately 781,250 8×8 atoms. Two full `f32` arrays are not a major part of the encoder’s total memory, but two `O(n log n)` sorts are unnecessary for a supposedly cheap controller feature stage. + +Use deterministic fixed-bin log histograms or a deterministic selection algorithm. Histograms are preferable because they also provide useful tail and distribution features without additional storage. + +--- + +## 1.6 Existing controller tests are too idealized for this redesign + +Most `quality.rs` controller tests use `CurveEvaluator`, where score is a monotone quantizer-only power law. That validates navigation mechanics but cannot expose: + +* cover/CfL discontinuities; +* fresh-versus-reused structure changes; +* local score nonmonotonicity; +* saturation; +* content-class OOD; +* entropy-size inversions; +* public under-target success behavior. + +The public quality test uses one synthetic image at targets 50, 70, and 85. It does not test the public API’s terminal miss semantics. + +Add real-trace replay tests and explicit public tests asserting: + +```rust +assert!(matches!( + result, + Err(Error::PerceptualTargetNotMet(...)) +)); +``` + +for both work exhaustion and lossy-ladder saturation. + +--- + +## 1.7 The literal byte-minimization wording overstates the current controller + +The current controller exact-prices at most the two coarsest feasible finalists: + +```rust +let max_finalists = budget.exact_prices.clamp(1, 2); +``` + +It selects the smallest exact stream among those retained candidates, not the globally smallest JPXL codestream satisfying the score. + +This is reasonable for a bounded-effort encoder, but the contract should say so: + +> Produce a canonically verified stream meeting the requested score. Within the selected effort’s declared policy family and bounded evaluated candidate set, select the smallest exact-priced feasible finalist. + +If “minimize exact codestream bytes” is interpreted literally over all possible quantizers, structures, restoration settings, and entropy outcomes, neither the current controller nor a one-shot predictor satisfies it. + +This matters because exact entropy size can itself be locally nonmonotone. A one-shot controller can aim for the coarsest feasible rung, but it cannot prove that an unpriced neighboring rung would not entropy-code smaller. + +--- + +# 2. Contract comparison + +| Approach | Existing hard floor preserved? | Main limitation | Recommendation | +| ------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------- | +| Conservative source-only prediction plus reserve | **No**, not by itself | Reserve gives empirical coverage, not a per-image guarantee; large reserve wastes bytes | Do not use as the correctness mechanism | +| One predicted encode, verify once, explicit failure on miss | **Yes for successful outputs** | No repair; potentially poor completion rate | Useful diagnostic mode, not primary public mode | +| Common-case one shot with one corrective re-plan | **Yes** | Some inputs still require two plans or exact fallback | **Recommended** | +| Reduced-resolution or cheap pilot, then one final full encode | Only if the final result is also verified and failure is explicit | Pilot/full-resolution relationship is content-dependent; still adds work | Test only if transform-derived prediction fails | +| Existing bounded exact controller for uncertainty/OOD | **Yes after the facade fix** | Expensive but bounded | Recommended fallback | +| Percentile or expected-score promise without verification | **No** | Changes `--quality` semantics | Only as a separately named API contract | +| Always route uncertain cases to lossless | Yes | Potentially catastrophic byte expansion | Explicit opt-in fallback only | + +A “score reserve” is still useful for choosing the first candidate or correction aim. It simply must not be described as the reason the floor is guaranteed. The canonical verification gate is the guarantee. + +--- + +# 3. Proposed runtime architecture + +## 3.1 High-level flow + +```text +source pixels + -> PreparedFrame + production AnalysisAtlas + -> deterministic source summary + -> request-scoped CandidateSearchContext + -> fill/reuse quantizer-independent transform candidate cache + -> deterministic transform summary + -> QualityPredictionV2(target, effort, source, transform) + outputs: + median crossing + risk-adjusted candidate crossing + calibrated interval + local loss slope + saturation risk + OOD flags + structural-instability risk + -> route: + uncertain / OOD / likely saturated + -> existing bounded exact navigator, seeded by prediction + otherwise + -> one fresh predicted pixel plan + -> full reconstruction + canonical SSIMULACRA2 + meets tightly + -> one entropy attachment + -> one emission + misses + -> one slope-based corrective plan on warm transform cache + -> reconstruct + score + -> emit if feasible + -> otherwise continue existing navigator with existing observations + meets but substantially overshoots + -> optional one coarsening attempt when predicted byte saving is material + -> retain first feasible plan as fallback + -> if terminal result is below target: + explicit failure, lossless fallback, or explicit best-effort mode +``` + +The current architecture already provides most of the needed mechanical separation: + +* `CandidateSearchContext::pixel_plan_for()` builds scored pixels without entropy. +* `attach_entropy_for()` trains entropy without changing pixels. +* `CandidateForwardCache` is request-scoped and quantizer-independent. +* cover/CfL rebuilds can reuse cached DCT coefficients. + +That means a failed prediction does not need entropy training and does not need to recompute already cached transforms. + +--- + +## 3.2 Suggested pseudocode + +```rust +fn encode_score_targeted( + ctx: &mut CandidateSearchContext<'_>, + source: SourceFeatureSummary, + target: f64, + budget: QualityBudget, +) -> Result { + // Uses the same cache that final cover/CfL and quantization will consume. + let transform = ctx.prepare_transform_summary()?; + + let prediction = QUALITY_MODEL.predict( + target, + ctx.request().rate_preset, + &source, + &transform, + ); + + if prediction.ood.any() + || prediction.interval_log_width > FALLBACK_LOG_WIDTH + || prediction.saturation_risk.is_high() + { + return continue_exact_controller( + ctx, + target, + prediction.median_rung, + Vec::new(), + budget, + ); + } + + let first_rung = prediction.candidate_rung.ceil_finer(); + let first = plan_and_score_fresh(ctx, first_rung, target)?; + + if first.score >= target { + if first.score - target <= MET_OVERSHOOT_BAND + || prediction.predicted_tightening_saving < 0.03 + { + return entropy_attach_and_emit(ctx, first); + } + + // Optional rare byte-tightening attempt. Keep `first` until the + // coarser candidate has been verified. + let tighter_rung = + correction_rung(first_rung, first.score, target, prediction.local_beta, false); + + if tighter_rung < first_rung { + let tighter = plan_and_score(ctx, tighter_rung, ReusePolicy::ByDistance)?; + if tighter.score >= target { + return entropy_attach_and_emit(ctx, tighter); + } + } + + return entropy_attach_and_emit(ctx, first); + } + + let corrected_rung = + correction_rung(first_rung, first.score, target, prediction.local_beta, true); + + let corrected = plan_and_score(ctx, corrected_rung, ReusePolicy::ByDistance)?; + + if corrected.score >= target { + return entropy_attach_and_emit(ctx, corrected); + } + + // Do not restart. Seed the existing navigator with both measured probes, + // the warm transform cache, and the remaining total budget. + continue_exact_controller( + ctx, + target, + prediction.median_rung, + vec![first.into_probe(), corrected.into_probe()], + budget.remaining_after(2), + ) +} +``` + +The correction formula should use the same perceptual-loss domain already used by the navigator: + +```text +L(q) = max(100 - q, 1e-3) +β = -d ln(L) / d ln(scale), β > 0 + +ln(scale₂) = + ln(scale₁) + [ln L(observed_score) - ln L(aim_score)] / β +``` + +Then: + +* round to the finer legal rung when correcting a miss; +* clamp the jump to the existing bounded ratio; +* fall back to the current extrapolation logic when `β` is invalid; +* rebuild cover/CfL when the scale displacement exceeds the existing structural threshold or the model marks the region as structurally unstable. + +Unlike the current first-rung predictor, this model supplies the local slope needed to use the first measured score efficiently rather than geometrically exploring again. + +--- + +# 4. Exact work counts + +“One forward transform” here means one request-scoped population of the candidate transform banks: each legal `(LF group, origin, transform type)` is computed at most once and reused. It does not mean that the image contains only one DCT operation. + +| Runtime route | Transform-bank fills | Pixel plans | Full-frame reconstructions | Canonical metric evaluations | Entropy trainings | Codestream emissions/exact prices | Fresh cover/CfL builds | +| ------------------------------------------------------ | ---------------------: | -----------: | -------------------------: | ---------------------------: | ----------------: | --------------------------------: | ---------------------: | +| Predicted normal success | 1 | 1 | 1 | 1 | 1 | 1 | 1 | +| Predicted miss, one successful correction | 1 shared | 2 | 2 | 2 | 1 | 1 | 1–2 | +| Predicted overshoot, one byte-tightening attempt | 1 shared | 2 | 2 | 2 | 1 | 1 | 1–2 | +| Preflight uncertainty/OOD to Balanced exact controller | 1 shared | ≤5 | ≤5 | ≤5 | ≤2 | ≤2 | ≤2 | +| Prediction followed by exact continuation | 1 shared | **≤5 total** | **≤5 total** | **≤5 total** | ≤2 total | ≤2 total | ≤2 total | +| Saturation or work-cap failure under hard-floor API | 1 shared | ≤5 | ≤5 | ≤5 | 0 | 0 | ≤2 | +| Explicit lossless fallback | Separate lossless path | — | — | optional verification | lossless backend | 1 | — | + +Important implementation details: + +* Prediction attempts consume the normal total probe budget. +* There is no hidden sixth rescue probe. +* Failed plans receive no entropy training. +* The normal path never holds two frame-sized pixel plans. +* The byte-tightening path may temporarily retain two plans, but only under a rare explicit trigger. +* An under-target terminal result is not exact-priced unless the caller explicitly requested best effort. + +The handoff shows that current Balanced usually performs four probes and two exact prices, while the wall ratio is 3.07× to 8.00× the matched-rate path. Removing approximately three reconstructions/metric evaluations from the median request is therefore the correct performance target. + +--- + +# 5. What the model should predict + +## Primary output: the image-specific score/scale crossing curve + +Do not predict bitrate as the primary output. + +Use: + +```text +x = ln(effective_scale) +z = ln(max(100 - SSIMULACRA2, 1e-3)) +``` + +For the seven target knots 30, 50, 70, 80, 85, 90, and 95, predict: + +1. median fresh-structure crossing `x50`; +2. risk-adjusted candidate crossing, initially approximately `x90`; +3. lower and upper calibrated crossing quantiles; +4. local positive loss exponent `β`; +5. saturation/reachability risk; +6. out-of-distribution flags; +7. optional estimated exact bytes at the crossing; +8. optional structural-instability risk. + +For arbitrary target values, interpolate between target knots in log perceptual loss, not directly in score. + +A suitable runtime result is: + +```rust +pub struct QualityPredictionV2 { + pub median_rung: Rung, + pub candidate_rung: Rung, + pub interval_low: Rung, + pub interval_high: Rung, + pub local_loss_exponent: f32, + pub saturation_risk: SaturationRisk, + pub structural_risk: StructuralRisk, + pub ood: OodFlags, + pub predicted_bytes: Option, + pub model_version: QualityModelVersion, +} +``` + +## Why not predict bitrate? + +The existing rate controller still performs multiple exact writer prices and bounded corrections. Passing a predicted bitrate to it would reorganize the search rather than remove it. + +It also leaves two mappings to solve: + +```text +source + score target -> bitrate +bitrate -> quantizer/policy +``` + +Direct effective-rung prediction solves the actual expensive decision. + +The rate module remains useful for: + +* legal rung/effective-scale mappings; +* quantizer construction; +* auxiliary byte labels; +* comparative evaluation. + +It should not be the normal score-target backend. + +## Why not predict a local or per-frequency quantization field yet? + +That introduces many more degrees of freedom and changes the codec’s psychovisual policy at the same time as the controller is being replaced. It would make failures difficult to attribute. + +First establish that a scalar effective-rung predictor can remove repeated score probes without losing the byte advantage. Local/per-frequency optimization can then become a separate quality-efficiency project. + +## Why not predict policy choices initially? + +Keep the production Balanced policy fixed: + +* restoration fixed by the request/default; +* hierarchical cover; +* normal CfL behavior; +* Balanced entropy effort; +* no Quality policy bank. + +Entropy policy does not affect reconstructed pixels and therefore does not need score prediction. + +A later model may select between a very small, independently validated set of source-only policies, but each policy would need its own calibrated crossing curve. The failed Quality promotion screen is a reason not to make policy prediction part of the first controller. + +--- + +# 6. Model form and deterministic implementation + +## Recommended model + +Use a small, code-generated monotone generalized additive model: + +```text +crossing_k = + target_intercept_k + + Σ piecewise_linear_feature_term_jk(feature_j) + + a small number of predeclared interactions +``` + +Fit separate models for: + +* the median crossing; +* the candidate quantile; +* the upper uncertainty bound; +* local slope; +* saturation risk. + +The first implementation should contain no more than a small set of measured interactions, such as: + +* noise × flatness; +* high-frequency energy × bit depth; +* flatness × orientation coherence; +* chroma/luma energy ratio × CfL correlation. + +Do not add interactions simply because the trainer can fit them. + +## Monotonicity + +For each image, predict the seven target-knot crossings, then project them to a nondecreasing effective-scale sequence: + +```text +x30 <= x50 <= x70 <= x80 <= x85 <= x90 <= x95 +``` + +Round the candidate scale toward the finer legal rung. + +The same projection must be applied to median and upper-quantile curves. + +This guarantees that the **chosen predicted rung** does not become coarser as target increases. It does not provide a mathematical proof that canonical SSIMULACRA2 itself is monotone across every independently encoded request, because the real score curve can have local reversals. Promotion should therefore require zero achieved-score inversions on the full target grid, while the documentation should not describe this as a theorem for arbitrary images. + +## Determinism + +Model inference should: + +* run in scalar Rust; +* use generated constants; +* reduce per-group feature accumulators in fixed LF-group order; +* avoid worker-order floating-point reductions; +* use explicit rounding before converting to a rung; +* include exact model and feature-schema versions in traces. + +Fixed-point model constants are worth considering if f64 boundary behavior differs across supported architectures. A scalar f64 implementation with conservative rung rounding may already be sufficient, but this must be tested across SIMD modes and worker counts. + +--- + +# 7. Feature design + +The current production atlas is a useful foundation. The diagnostic `AnalysisAtlasV2` also already contains several promising feature candidates, but it currently performs a separate pass and stores a large diagnostic structure. It should be used for offline ablation first, not installed wholesale in the production path. + +## Proposed features and costs + +| Feature group | Purpose | Source-only analysis | Final forward transforms | Candidate reconstruction | Entropy/emission | +| --------------------------------------------------------------------------------------- | ------------------------------------------- | -------------------: | -----------------------: | -----------------------: | ---------------: | +| Width, height, log area, aspect ratio, edge-partial-atom fraction, bit depth, grayscale | Size and metric-scale effects | Yes | No | No | No | +| Luma variance histogram and q10/q50/q90/q99 | Flat/texture distribution | Yes | No | No | No | +| Chroma variance and chroma/luma ratios | Colour complexity | Yes | No | No | No | +| Flat fraction plus low-variance tail | Smooth fields and gradients | Yes | No | No | No | +| Channel clipping/saturation fractions and dynamic range | High-target and saturated-content risk | Yes | No | No | No | +| Horizontal/vertical gradient energy and cross term | Edge orientation and anisotropy | Yes | No | No | No | +| Laplacian energy and affine-plane residual | Detail versus smooth ramp separation | Yes | No | No | No | +| Noise MAD and noise-to-edge ratio | Low-light/noise quantization sensitivity | Yes | No | No | No | +| Orientation coherence and flat-side asymmetry | Text, line art, and edge leakage risk | Yes | No | No | No | +| Channel covariance | CfL benefit and chroma reconstruction risk | Yes | No | No | No | +| DCT energy by channel and radial frequency band | Direct quantization sensitivity | No | Yes | No | No | +| Frequency-energy q50/q90/q99 and tail ratios | Sparse detail versus broadband texture | No | Yes | No | No | +| Directional AC energy | Lines, hatching, directional textures | No | Yes | No | No | +| DCT8/DCT16/DCT32 candidate energy or cost gaps | Cover preference and structural instability | No | Yes | No | No | +| Coefficient zero/nonzero counts at a few reference scales | Approximate scale sensitivity | No | Yes | No | No | +| Transform-domain luma/chroma correlation | CfL sensitivity | No | Yes | No | No | +| Cheap token/run histogram proxy | Auxiliary byte prediction | No | Yes | No | No | + +No proposed final-decision feature requires a candidate reconstruction, canonical score, entropy training, or exact emission. + +## Production feature extraction rules + +1. Replace full vector sorting with deterministic histograms. +2. Accumulate source features during the existing atlas pass where practical. +3. Use `AnalysisAtlasV2` only to identify useful signals initially. +4. Fuse winning diagnostic signals into a compact streaming production summary. +5. Build transform summaries directly from `CandidateForwardCache`; do not copy all coefficient banks into a second representation. +6. Reduce transform summaries per LF group and combine them in fixed raster order. +7. Reject any feature set whose additional normal-path wall exceeds 5% of the matched-rate path. + +--- + +# 8. One final transform can support prediction and emission + +The precise dependency cycle in the current planner is: + +```text +quantizer + -> AqSetup::build + -> LfQuantizer and HfQuantizers + -> quantizer-dependent cover objective + -> selected transforms + -> CfL over selected coefficients + -> quantization +``` + +Therefore, the final cover and CfL cannot be selected before the quantizer is known. + +However, the forward transform coefficients themselves are quantizer-independent. The existing `CandidateForwardCache` is explicitly designed so that a given `(origin, transform)` is computed at most once per request. `ensure_cover_candidates_cached()` can populate aligned DCT8, DCT16, and DCT32 candidates. + +The cycle can therefore be broken as follows: + +```text +source + -> quantizer-independent candidate transform bank + -> aggregate transform features + -> predict quantizer + -> quantizer-dependent AQ and cover selection + -> selected transforms read from same bank + -> CfL + -> quantization +``` + +This is genuinely one transform bank supporting both prediction and final emission. + +### Required refactor + +Add a method approximately like: + +```rust +impl CandidateSearchContext<'_> { + pub(crate) fn prepare_quality_transform_summary( + &mut self, + ) -> Result; +} +``` + +It should: + +* prepare geometry; +* fill the transform candidates required by the Balanced cover; +* calculate compact feature accumulators; +* leave all cache entries available to the later pixel planner. + +There is one caveat: eagerly filling every candidate may compute more than the current serial lazy cover path on some images. The existing parallel hierarchical path already has a complete-cache mechanism, but the exact incremental cost and 50 MP memory behavior must be measured. If full prefill is too expensive, begin with DCT8 summaries or another subset that the final cover path already necessarily computes. + +Restoration policy must remain fixed in the first version because Gaborish changes the transform frame itself. Predicting restoration would move the policy decision before this shared transform stage and reopen the dependency problem. + +--- + +# 9. Training and calibration procedure + +## 9.1 Training labels + +Create a new oracle dataset from the actual production Balanced pixel policy. + +For each independent source family: + +1. build source and transform features once; +2. evaluate the full effective-scale ladder, including `HfMul` segments; +3. adaptively densify around target crossings; +4. rebuild cover/CfL fresh for oracle crossing points; +5. record canonical SSIMULACRA2; +6. record the coarsest fresh rung meeting each target; +7. record local score/loss slope; +8. exact-price the crossing and nearby feasible rungs; +9. record top-rung score and whether the target is reachable; +10. optionally record anchor-reuse versus fresh-structure differences. + +The seven target rows from one image are correlated observations, not seven independent samples. Weight each source family equally, dividing its weight across targets and derived resolutions. + +## 9.2 Saturation is censored data + +Do not assign the top rung as if it were the actual crossing when the top score misses. + +Record: + +```text +crossing > top_rung +``` + +and train a separate transparent reachability/saturation model. + +At runtime: + +* model saturation risk is only a routing hint; +* actual `SaturatedTop` requires canonically scoring the real top candidate below target; +* uncertainty and saturation remain separate concepts. + +This is especially important because the current calibration reports saturation fractions of 0.81 and 0.94 at targets 90 and 95. + +## 9.3 Loss functions + +Fit at least three crossing models: + +### Median crossing + +Use Huber or median quantile loss on: + +```text +ln(required_effective_scale) +``` + +### Candidate crossing + +Initially use quantile loss at approximately `τ = 0.90`: + +```text +ρτ(error) +``` + +At `τ = 0.90`, predicting too coarse is penalized approximately nine times as much as predicting too fine. + +This quantile is not a safety guarantee. It is a wall-versus-byte operating point. The verification gate supplies safety. + +### Upper uncertainty bound + +Fit a higher quantile such as `τ = 0.95` and calibrate its empirical coverage by whole family on development data. + +### Byte-regret term + +Add a modest penalty for unnecessarily fine predictions: + +```text +λbytes * max(0, ln(bytes(predicted_rung) / bytes(crossing_rung))) +``` + +This prevents the asymmetric floor penalty from collapsing into a permanently overfine controller. + +### Slope model + +Use robust regression for: + +```text +β = -d ln(perceptual_loss) / d ln(effective_scale) +``` + +with a positivity constraint and bounded runtime range. + +## 9.4 Uncertainty and OOD detection + +Use both model residuals and feature support. + +Fallback should fire when any of the following occurs: + +* calibrated log-scale interval width exceeds `ln(1.5) ≈ 0.405`; +* a hard feature lies outside the calibrated production envelope; +* distance to the nearest training prototype exceeds the development-set 99th percentile; +* predicted upper crossing reaches the top 2% of the effective ladder; +* saturation risk exceeds the development-calibrated threshold; +* transform-size preference is unusually ambiguous; +* target/bit-depth/content combination has insufficient family support. + +The runtime OOD implementation can remain simple: + +* robust min/max ranges; +* median and MAD standardization; +* a small code-generated set of feature-space medoids; +* deterministic L1 or diagonal-distance calculation. + +Do not describe these intervals as universal confidence guarantees. They are empirical in-domain calibration used to decide whether the cheap path is worthwhile. + +## 9.5 Generated-model provenance + +Every generated model should embed or accompany: + +* model schema version; +* feature schema version; +* SSIMULACRA2 metric version; +* encoder git revision; +* trainer git revision; +* corpus-manifest hash; +* split-manifest hash; +* label-generation command; +* label dataset hash; +* training configuration; +* generated Rust checksum; +* full development and holdout report. + +A single reproducible command should regenerate both the Rust constants and the report. Generated diffs should be human-reviewable. + +--- + +# 10. Corpus assessment and expansion + +The current corpus contains 47 images split 19/15/13 across calibration, development, and holdout. It includes the requested broad classes, but it is not sufficient to calibrate production uncertainty. + +It is sufficient for: + +* rejecting obviously inadequate features; +* comparing source-only versus transform-derived predictors; +* determining whether the median and tail errors improve materially; +* building the first shadow model. + +It is not sufficient for: + +* a trustworthy 90th or 95th percentile crossing model; +* OOD thresholds; +* high-target saturation calibration; +* public promotion across every content class. + +## Required manifest changes + +Add: + +```json +{ + "family_id": "...", + "variant_id": "...", + "generator_family": "...", + "source_capture_id": "..." +} +``` + +All crops, resolutions, colour variants, recompressions, and derivatives of one source must remain in the same split. + +The existing manifest includes multiple resolutions of individual photographs. They appear to remain within one split, but without `family_id` that cannot be mechanically audited or weighted correctly. + +## Expansion targets + +For a first feature-gated implementation: + +* at least **150 independent source families**; +* at least 25 independent families in each critical non-photo class; +* at least 30 blind holdout families. + +For public promotion: + +* at least **300 independent source families**; +* at least 60 never-tuned promotion-holdout families; +* 8-, 10-, 12-, and 16-bit inputs; +* dimensions from metric minimums through 50 MP; +* multiple camera and rendering sources; +* real and synthetic text/UI; +* line art and diagrams; +* smooth ramps and sky gradients; +* saturated wide-colour stress; +* low-light and structured noise; +* grayscale; +* tiny images; +* high-detail natural photographs. + +The current 13-image locked holdout should remain untouched as a legacy regression holdout. The larger expansion should create a new blind promotion holdout rather than recycling those 13 images for threshold selection. + +--- + +# 11. Quantitative experiment and promotion gates + +The handoff requires Balanced wall no more than 2.0× the matched-rate path on the two anchors. + +The following thresholds should be frozen before running the experiment. + +## 11.1 Prediction quality, non-saturated family holdouts + +| Metric | Required | | | +| ---------------------------------------------------------- | ---------------------------------------: | - | ----- | +| Median ` | ln(predicted crossing / oracle crossing) | ` | ≤0.10 | +| p90 absolute log error | ≤0.30 | | | +| p99 absolute log error | ≤0.70 | | | +| First-plan success, reachable in-domain targets 30–90 | ≥90% | | | +| First or one-correction success | ≥98% | | | +| Exact-controller continuation on reachable in-domain cells | ≤5% | | | +| Successful outputs below requested score | **0** | | | + +Target 95 should be reported separately until its current saturation rate is reduced or properly routed. + +A quick falsification gate may be looser: + +* p90 log error ≤0.45; +* at least 80% first-plan success; +* no more than 1.5% simulated byte regression. + +Failure there means the transparent one-shot model is not yet viable and production controller work should stop. + +## 11.2 Byte efficiency + +The current Balanced score controller has a 0.99281 geometric-mean ratio to the interpolated rate curve. That leaves only approximately 0.724% before the ratio reaches 1.0. + +Therefore the public promotion limits should be: + +| Metric | Required | +| ---------------------------------------------------- | -------: | +| Geometric-mean bytes versus current score controller | ≤1.007 | +| Geometric-mean bytes versus same-effort rate curve | ≤1.000 | +| Mean per-image BD-rate versus same-effort rate curve | ≤0% | +| p95 per-cell byte ratio versus current controller | ≤1.03 | +| Worst reachable cell byte ratio | ≤1.08 | +| Median score overshoot on first-plan successes | ≤0.35 | +| p90 score overshoot | ≤1.0 | + +A shadow prototype may initially allow a 1.5% geometric-mean regression, but that is not enough for promotion because it would discard the controller’s existing aggregate rate advantage. + +## 11.3 Wall + +| Measurement | Acceptance | Design target | +| ----------------------------------------- | ---------: | ------------: | +| 4.3 MP anchor / matched-rate | ≤2.0× | ≤1.50× | +| 12 MP anchor / matched-rate | ≤2.0× | ≤1.75× | +| Incremental source/transform feature wall | ≤5% | ≤3% | +| Normal-path full reconstructions | 1 | 1 | +| Normal-path entropy trainings/emissions | 1 | 1 | + +A canonical metric evaluation remains unavoidable. Therefore the practical lower bound is: + +```text +one Balanced pixel plan ++ one full reconstruction ++ one canonical metric ++ one entropy attachment/emission +``` + +If that isolated path still exceeds 2.0×, the remaining problem is metric/render cost rather than controller navigation. That should be measured directly rather than hidden behind further prediction work. + +## 11.4 Memory + +| Measurement | Required | +| --------------------------------------------- | -------------------------------: | +| Normal-path peak RSS versus matched-rate path | ≤1.10× | +| Fallback peak RSS | no worse than current controller | +| Normal retained frame-sized pixel plans | 1 | +| Normal duplicate transform payloads | 0 | + +## 11.5 Determinism and monotonicity + +Promotion requires: + +* byte-identical output at supported worker counts; +* identical decision path and status across worker counts; +* identical result across supported SIMD modes; +* nondecreasing predicted rung across the full target grid; +* zero achieved-score inversions on the promotion corpus; +* zero byte inversions on the target grid unless explicitly justified by a smaller exact stream with a higher achieved score; +* independent decoding and re-scoring equal to the reported score within the established metric tolerance. + +--- + +# 12. Minimal implementation sequence + +## PR 0: repair the existing contract + +This is separate from the predictive experiment. + +1. Do not return under-target bytes as `Ok`. +2. Make saturation and work exhaustion structured failures. +3. Make CLI output atomic and avoid creating an output file on failure. +4. Add explicit lossless and best-effort fallback policies if desired. +5. remove the hidden extra rescue probe or put it inside the documented cap. +6. Add public API tests for saturation and work exhaustion. + +This changes behavior only where the current implementation already contradicts the public hard-floor contract. + +## PR 1: trace and corpus schema, no encoding change + +Create `jpxl.quality-trace/2` with: + +```text +model_version +feature_schema +median_rung +candidate_rung +interval_low/high +local_loss_exponent +saturation_risk +ood_flags +fallback_reason +first_observed_score +correction_rung +decision_path +total pixel plans +total reconstructions +total metric evaluations +total entropy trainings +total emissions +``` + +Add `family_id` and related fields to the corpus manifest. + +The current controller remains authoritative. + +## PR 2: production-endpoint label generator + +Replace or supplement `calibrate_initial_rung.py` with a trainer that: + +* operates over the full effective-scale ladder; +* labels production Balanced fresh-structure crossings; +* represents saturation as censoring; +* records local slopes and neighboring exact bytes; +* splits and weights by family; +* emits reproducible dataset and model provenance. + +Existing controller traces can be used as provisional labels for the first falsification pass, but not as final oracle labels. + +## PR 3: source-only shadow model + +Implement `QualityPredictionV2`, but only log its counterfactual decision. + +Compare: + +* current two-feature table; +* all existing `SourceFeatures`; +* histogram-based source features; +* selected `AnalysisAtlasV2` aggregates. + +Do not let the model affect a bitstream. + +## PR 4: transform-summary shadow model + +Add the request-scoped transform-summary API to `CandidateSearchContext`. + +Again, only log: + +* candidate rung; +* interval; +* predicted correction; +* whether exact fallback would have fired; +* counterfactual one-shot work counts; +* predicted byte regret. + +The current exact controller still chooses output. + +## Gate before production integration + +Proceed only when family-held-out results satisfy: + +* median log-scale error ≤0.10; +* p90 ≤0.30; +* at least 80% first-plan success in the quick screen; +* no more than 1.5% simulated byte regression; +* incremental feature cost ≤5%; +* no opaque model required. + +If transform features cannot reach that gate, do not move candidate reconstructions into the feature extractor and call the result one shot. + +## PR 5: feature-gated common-case path + +Only after the shadow gate: + +* make the predicted plan the first real plan; +* use canonical verification before entropy; +* add one slope correction; +* continue the existing navigator from measured observations; +* maintain the same total work caps; +* retain current exact controller as the default fallback. + +Public promotion then uses the stricter wall and byte gates above. + +--- + +# 13. Expected tradeoffs + +## Wall + +The proposed normal route eliminates: + +* geometric expansion probes; +* repeated bracket tightening; +* repeated candidate reconstruction; +* one of the usual exact finalist prices. + +Given the current median of four probes and two exact prices, it should materially approach the single Balanced path, although one full canonical metric remains unavoidable. + +## Memory + +Normal-path memory should improve because only one pixel plan needs to survive until entropy attachment. The transform cache is already request-scoped and shared. + +The main memory risk is eagerly completing all transform candidates before prediction on 50 MP inputs. That must be measured before promotion. + +## Score floor + +The score floor becomes stronger than the current public implementation because: + +* under-target streams are no longer returned as ordinary successes; +* every emitted lossy stream has passed canonical verification; +* uncertainty changes the work path, not the correctness promise. + +## Bytes + +Byte efficiency is the hard part. + +A highly conservative predictor can easily turn a target of 85 into an achieved score above 90 and waste substantial bytes. The model therefore needs both: + +* an asymmetric candidate quantile to keep first-plan misses uncommon; +* an explicit byte-regret term and optional rare coarsening correction. + +The existing controller’s modest rate advantage leaves little room for aggregate regression. A model that is fast but routinely overfine should not be promoted. + +## Complexity + +Runtime complexity remains modest: + +* one compact feature summary; +* one generated additive model; +* one prediction structure; +* one continuation path into the existing navigator. + +No new heavyweight dependency or neural runtime is justified at this stage. + +--- + +# 14. Principal risks + +1. **Family leakage.** Resolutions or synthetic variants of one source can make prediction appear substantially better than it is. + +2. **High-target censoring.** Treating top-rung saturation as an ordinary crossing corrupts both the scale model and uncertainty. + +3. **Structural discontinuities.** Cover and CfL can change around the crossing, making a smooth source-only score curve inaccurate. + +4. **Metric nonmonotonicity.** Running-max training curves hide local reversals that the runtime may still encounter. + +5. **Entropy nonmonotonicity.** The coarsest feasible rung is not always the smallest exact stream. + +6. **Overconservative calibration.** A large reserve can meet the floor while silently discarding the present byte advantage. + +7. **Understated feature cost.** A second source pass, full coefficient copy, or eager transform work can erase the saved controller wall. + +8. **Transform-cache memory at 50 MP.** Full candidate prefill may alter peak RSS even when it avoids recomputation. + +9. **Model staleness.** Changes to quantization, AQ, cover cost, CfL, restoration, metric version, or entropy policy invalidate labels. + +10. **Misrepresented uncertainty.** Empirical quantiles do not provide arbitrary-image guarantees. The canonical score gate must remain authoritative. + +11. **Hidden fallback work.** Restarting the exact controller after one or two prediction attempts would make the apparent one-shot path more expensive than the current controller. + +12. **Benchmark-specific routing.** Tuning fallback thresholds to the 13 locked images would create a demonstration rather than a general controller. + +13. **Literal contract mismatch.** Neither current bounded search nor one-shot prediction globally minimizes all possible exact codestreams. The bounded effort domain must be explicit. + +--- + +# Final recommendation + +Implement this in three conceptual layers: + +1. **Correctness layer:** under-target output is never a normal success. +2. **Prediction layer:** a transparent monotone model predicts the production Balanced fresh-structure crossing, local slope, interval, saturation risk, and OOD state from source plus shared transform features. +3. **Execution layer:** one predicted plan is canonically verified; one correction is allowed; the existing navigator continues from those observations under the same total cap. + +Do not route through the bitrate controller, do not predict a per-frequency field yet, and do not make policy-bank selection part of the first model. + +The key design principle is: + +> The predictor determines where JPXL should look first. Canonical verification determines whether JPXL is allowed to emit. The existing bounded controller remains the recovery mechanism, not the normal path. + +That is the narrowest design that can plausibly move Balanced from four full-frame probes toward one while honestly preserving the hard per-image score floor and the encoder’s existing byte-efficiency advantage. From b57c9d4061fbfc0ad7e907517944939d723e8b29 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:53:48 +0800 Subject: [PATCH 03/27] feat(jpxl-encode-policy): one-shot crossing predictor is the default --quality seed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one-shot program's policy-side PRs, per the registered 2026-08-24 advisor memo: - PR 1: jpxl.quality-trace/2 groundwork — QualityWork counters (pixel plans, reconstructions, metric evaluations, entropy trainings, emissions) threaded through probe/price/rescue/reducer and folded across trials; prediction and transform_features blocks on QualityOutcome. - PR 2: sweep_frame_perceptual — fresh production-policy pixel plans at pinned effective-scale rungs for oracle labelling; effective_scale / rung_for_effective_scale made pub. - PR 4: TransformFeatureSummary — ten quantizer-independent DCT8 features reduced per LF group in parallel through the shared candidate forward cache (deterministic across worker counts; in-search the prefill warms the cover's own cache and measured net-neutral wall). - PR 5: the generated crossing model (qpv2-st-1, source + transform features, 130 image families) seeds the navigator — risk-adjusted candidate when confident, median when uncertain, legacy table only out of distribution — and its local slope prior steers the first correction under unchanged probe/price caps and canonical verification. one-shot-controller joins the crate's default features after the promotion A/B on 441 never-tuned holdout cells: zero floor violations on either arm, byte geomean 0.997 (locked holdout) / 0.990 (painting families), reconstructions -18% and wall -16% on the paintings, 12 MP anchor wall -21%. Also closes the PR 7 reducer question's stale docs and corrects the probe-cap accounting docs (observable maximum is pixel_probes + 1 including the rescue probe). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- JPXL/crates/jpxl-encode-policy/Cargo.toml | 12 +- .../jpxl-encode-policy/src/candidate.rs | 17 + JPXL/crates/jpxl-encode-policy/src/lib.rs | 257 ++++- JPXL/crates/jpxl-encode-policy/src/quality.rs | 554 ++++++++++- .../src/quality_features.rs | 60 ++ .../src/quality_prediction.rs | 487 +++++++++ .../src/quality_predictor_v2.rs | 941 ++++++++++++++++++ JPXL/crates/jpxl-encode-policy/src/rate.rs | 4 +- 8 files changed, 2271 insertions(+), 61 deletions(-) create mode 100644 JPXL/crates/jpxl-encode-policy/src/quality_prediction.rs create mode 100644 JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs diff --git a/JPXL/crates/jpxl-encode-policy/Cargo.toml b/JPXL/crates/jpxl-encode-policy/Cargo.toml index 79b674e9..cf97091e 100644 --- a/JPXL/crates/jpxl-encode-policy/Cargo.toml +++ b/JPXL/crates/jpxl-encode-policy/Cargo.toml @@ -19,7 +19,7 @@ jpxl-entropy.workspace = true wide = { workspace = true, optional = true } [features] -default = ["parallel", "simd", "anchor-sketch", "g5-bounded-entropy"] +default = ["parallel", "simd", "anchor-sketch", "g5-bounded-entropy", "one-shot-controller"] parallel = ["jpxl-encode/parallel"] # Leaf SIMD for HF quantize choose (and transitive DCT/Gaborish). simd = ["dep:wide", "jpxl-encode/simd", "jpxl-core/simd"] @@ -31,6 +31,16 @@ anchor-sketch = [] # alternative ranked from the near-target anchor and finalist censuses. The # feature remains explicit as a byte-identical-off control after promotion. g5-bounded-entropy = [] +# One-shot program PR 5: the common-case one-shot start — the generated +# crossing model's risk-adjusted candidate (or, when uncertain, its median) +# becomes the first fresh plan and its slope prior steers the first +# correction, with the exact navigator continuing under the same total caps +# and canonical verification before every emission. Default since the +# 2026-08-24 promotion A/B: zero floor violations on 441 never-tuned +# holdout cells, byte geomean 0.997/0.990 vs the previous default, and +# fewer reconstructions. Build without default features for the legacy +# table-seeded controller. +one-shot-controller = [] # S8 Phase D (AKR source `outside-advice-2026-08-06` §8; # `jpegxl-rs.work.arch-s8-full-redesign-scoped`): # the Phase C-validated (zero safety violations, exhaustively checked) diff --git a/JPXL/crates/jpxl-encode-policy/src/candidate.rs b/JPXL/crates/jpxl-encode-policy/src/candidate.rs index ac84a14b..e8528972 100644 --- a/JPXL/crates/jpxl-encode-policy/src/candidate.rs +++ b/JPXL/crates/jpxl-encode-policy/src/candidate.rs @@ -139,6 +139,23 @@ impl<'a> CandidateSearchContext<'a> { ) } + /// Reduces the frame's aligned DCT8x8 candidates into the one-shot + /// program's [`TransformFeatureSummary`](crate::quality_features::TransformFeatureSummary), + /// filling this context's shared forward cache so the later pixel plans + /// read the same warm entries (PR 4). Quantizer-independent: safe to call + /// before any quantizer choice exists. + pub(crate) fn prepare_quality_transform_summary( + &mut self, + request: &EncodeRequest, + ) -> Result { + crate::quality_transform_summary( + self.transform_frame, + request, + &mut self.fwd_cache, + Some(self.executor), + ) + } + /// Trains entropy for already planned pixels and returns the writer-ready /// plan. Pixels are untouched: the candidate's score is unchanged. pub(crate) fn attach_entropy( diff --git a/JPXL/crates/jpxl-encode-policy/src/lib.rs b/JPXL/crates/jpxl-encode-policy/src/lib.rs index 9b164d0b..9e979198 100644 --- a/JPXL/crates/jpxl-encode-policy/src/lib.rs +++ b/JPXL/crates/jpxl-encode-policy/src/lib.rs @@ -74,7 +74,9 @@ pub mod field; pub mod policy_bank; pub mod quality; pub mod quality_features; +pub mod quality_prediction; pub mod quality_predictor; +pub mod quality_predictor_v2; pub mod quantize; pub mod rate; pub mod reducer; @@ -125,14 +127,16 @@ pub use field::{AqMode, AqTuning}; use field::{DesiredQuantField, mul_lattice_for}; pub use policy_bank::{PerceptualPolicy, rank_alternatives}; pub use quality::{ - PerceptualEvaluator, PerceptualObservation, PolicyTrial, ProbeKind, QualityBudget, - QualityOutcome, QualityProbe, QualityStats, QualityStatus, StructureSource, - search_frame_perceptual, search_frame_perceptual_with_budget, status_name, + LadderPoint, PerceptualEvaluator, PerceptualObservation, PolicyTrial, ProbeKind, QualityBudget, + QualityOutcome, QualityPredictionTrace, QualityProbe, QualityStats, QualityStatus, QualityWork, + StructureSource, search_frame_perceptual, search_frame_perceptual_with_budget, status_name, + sweep_frame_perceptual, }; -pub use quality_features::{SourceFeatures, source_features}; +pub use quality_features::{SourceFeatures, TransformFeatureSummary, source_features}; +pub use quality_prediction::{QualityPredictionV2, predict_v2, shadow_prediction_trace}; pub use rate::{ LadderSearch, QuantizerChoice, RateOutcome, RatePhase, RateProbeStats, RateStatus, RateStep, - Rung, search_frame, + Rung, effective_scale, rung_for_effective_scale, search_frame, }; pub use request::{ AdaptiveSharpness, ChromaHfPolicy, CoverFrequencyWeight, CoverMode, CoverRateModel, @@ -2700,6 +2704,249 @@ fn ensure_cover_candidates_cached( Ok(()) } +/// Per-LF-group partial accumulators of the transform summary. +struct TransformPartial { + histogram: Vec, + blocks: u64, + ac_cells: u64, + total_y: f64, + total_low: f64, + total_high: f64, + total_row: f64, + total_col: f64, + total_xb: f64, + near_1e3: u64, + near_1e2: u64, + dc_sum: f64, + dc_sumsq: f64, +} + +/// Reduces the frame's aligned DCT8x8 candidates into a +/// [`TransformFeatureSummary`](quality_features::TransformFeatureSummary), +/// filling the shared forward cache as it goes (one-shot program PR 4). +/// +/// Every coefficient computed here is one the hierarchical cover search +/// would compute anyway — the fill goes through the same +/// [`CandidateGroupBank::get_or_insert`] the cover reads — so the later +/// pixel plan reuses the warm entries rather than re-transforming. LF +/// groups fill in parallel on the request executor exactly like cover +/// construction; each group's partials accumulate in block raster order +/// and combine in LF-group index order, so the result is deterministic +/// across worker counts and SIMD modes. +pub(crate) fn quality_transform_summary( + transform_frame: &PreparedFrame, + request: &EncodeRequest, + cache: &mut CandidateForwardCache, + executor: Option<&jpxl_encode::EncodeExecutor>, +) -> Result { + const EPS: f64 = 1e-30; + // Fixed-bin histogram of per-block ln(Y AC energy): [-46, 18) at 0.125. + const HIST_LO: f64 = -46.0; + const HIST_WIDTH: f64 = 0.125; + const HIST_BINS: usize = 512; + + let decision = FrameDecision { + width: transform_frame.width(), + height: transform_frame.height(), + group_size_shift: VARDCT_GROUP_SIZE_SHIFT, + num_passes: 1, + bits_per_sample: request.bits_per_sample, + }; + let geometry = decision.geometry()?; + cache.prepare(&geometry)?; + let shared: &CandidateForwardCache = cache; + let n_groups = usize::try_from(geometry.num_lf_groups()).unwrap_or(usize::MAX); + + let summarize_group = |index: usize| -> Result { + let mut scratch = ForwardScratch::new(); + let mut partial = TransformPartial { + histogram: vec![0u64; HIST_BINS], + blocks: 0, + ac_cells: 0, + total_y: 0.0, + total_low: 0.0, + total_high: 0.0, + total_row: 0.0, + total_col: 0.0, + total_xb: 0.0, + near_1e3: 0, + near_1e2: 0, + dc_sum: 0.0, + dc_sumsq: 0.0, + }; + let lock = shared.group(index)?; + let mut bank = lock.write().map_err(|_| PolicyError::Unsupported { + what: "a poisoned forward-cache bank", + })?; + let (rect, grid) = (bank.rect, bank.blocks); + for by in 0..grid.height { + for bx in 0..grid.width { + let px = rect.x0.saturating_add(bx.saturating_mul(8)); + let py = rect.y0.saturating_add(by.saturating_mul(8)); + let fwd = bank.get_or_insert( + transform_frame, + TransformType::Dct8x8, + px, + py, + &mut scratch, + )?; + let [cx, cy, cb] = fwd.coeffs; + let mut block_y = 0.0f64; + for (cell, &value) in cy.iter().enumerate() { + if cell == 0 { + let dc = f64::from(value); + partial.dc_sum += dc; + partial.dc_sumsq += dc * dc; + continue; + } + let (row, col) = (cell / 8, cell % 8); + let energy = f64::from(value) * f64::from(value); + block_y += energy; + if row + col <= 2 { + partial.total_low += energy; + } + if row.max(col) >= 4 { + partial.total_high += energy; + } + if row > col { + partial.total_row += energy; + } else if col > row { + partial.total_col += energy; + } + let magnitude = f64::from(value).abs(); + if magnitude < 1e-3 { + partial.near_1e3 += 1; + } + if magnitude < 1e-2 { + partial.near_1e2 += 1; + } + partial.ac_cells += 1; + } + for lane in [cx, cb] { + for &value in lane.iter().skip(1) { + partial.total_xb += f64::from(value) * f64::from(value); + } + } + partial.total_y += block_y; + partial.blocks += 1; + let ln_energy = (block_y + EPS).ln(); + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "clamped into 0..HIST_BINS before the narrowing" + )] + let bin = (((ln_energy - HIST_LO) / HIST_WIDTH).clamp(0.0, (HIST_BINS - 1) as f64)) + as usize; + if let Some(count) = partial.histogram.get_mut(bin) { + *count += 1; + } + } + } + Ok(partial) + }; + + let partials: Vec = if let Some(executor) = executor { + executor.map_ordered(n_groups, summarize_group)? + } else { + (0..n_groups).map(summarize_group).collect::>()? + }; + + // Fixed-order combination of the per-group partials. + let mut histogram = vec![0u64; HIST_BINS]; + let mut blocks = 0u64; + let mut ac_cells = 0u64; + let (mut total_y, mut total_low, mut total_high) = (0.0f64, 0.0f64, 0.0f64); + let (mut total_row, mut total_col) = (0.0f64, 0.0f64); + let mut total_xb = 0.0f64; + let (mut near_1e3, mut near_1e2) = (0u64, 0u64); + let (mut dc_sum, mut dc_sumsq) = (0.0f64, 0.0f64); + for partial in &partials { + for (total, &count) in histogram.iter_mut().zip(partial.histogram.iter()) { + *total += count; + } + blocks += partial.blocks; + ac_cells += partial.ac_cells; + total_y += partial.total_y; + total_low += partial.total_low; + total_high += partial.total_high; + total_row += partial.total_row; + total_col += partial.total_col; + total_xb += partial.total_xb; + near_1e3 += partial.near_1e3; + near_1e2 += partial.near_1e2; + dc_sum += partial.dc_sum; + dc_sumsq += partial.dc_sumsq; + } + + let quantile = |q: f64| -> f64 { + #[allow( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "block counts stay far inside f64's exact-integer range" + )] + { + let want = (q * blocks as f64).min(blocks.saturating_sub(1) as f64) as u64; + let mut seen = 0u64; + for (bin, &count) in histogram.iter().enumerate() { + seen += count; + if seen > want { + return HIST_LO + (bin as f64 + 0.5) * HIST_WIDTH; + } + } + HIST_LO + (HIST_BINS as f64 - 0.5) * HIST_WIDTH + } + }; + + #[allow( + clippy::cast_precision_loss, + reason = "block and cell counts stay far inside f64's exact-integer range" + )] + Ok(quality_features::TransformFeatureSummary { + blocks, + ln_ac_y_mean: (total_y / (blocks as f64).max(1.0) + EPS).ln(), + ln_ac_y_q50: quantile(0.5), + ln_ac_y_q90: quantile(0.9), + ln_ac_y_q99: quantile(0.99), + high_low_ratio: total_high / (total_low + EPS), + directional_asymmetry: (total_row - total_col).abs() / (total_row + total_col + EPS), + chroma_ac_ratio: total_xb / (total_y + EPS), + near_zero_frac_1e3: near_1e3 as f64 / (ac_cells as f64).max(1.0), + near_zero_frac_1e2: near_1e2 as f64 / (ac_cells as f64).max(1.0), + dc_variance_y: { + let n = (blocks as f64).max(1.0); + let mean = dc_sum / n; + (dc_sumsq / n - mean * mean).max(0.0) + }, + }) +} + +/// Standalone [`TransformFeatureSummary`](quality_features::TransformFeatureSummary) +/// of a frame, for calibration tooling (`jpxl features --transform-summary`). +/// +/// Builds a throwaway forward cache; the in-search path +/// (`QualityBudget::transform_shadow`) shares the cover's cache instead. +/// Applies the request's Gaborish preconditioning so the coefficients are +/// the ones the encode itself would transform. +/// +/// # Errors +/// +/// Whatever the preconditioner or forward transform refuses. +pub fn transform_feature_summary( + frame: &PreparedFrame, + request: &EncodeRequest, +) -> Result { + let transform_owned = if request.restoration.gaborish { + Some(prepare_gaborish_frame(frame)?) + } else { + None + }; + let transform_frame = transform_owned.as_ref().unwrap_or(frame); + let mut cache = CandidateForwardCache::new(); + let executor = request.resources.executor(); + quality_transform_summary(transform_frame, request, &mut cache, Some(&executor)) +} + /// Borrows the (already-cached) forward coefficients for every selected /// varblock, in `varblocks` order. See [`ensure_forwards_cached`]. fn gather_forward_refs<'cache>( diff --git a/JPXL/crates/jpxl-encode-policy/src/quality.rs b/JPXL/crates/jpxl-encode-policy/src/quality.rs index ef2ca048..3a9549f7 100644 --- a/JPXL/crates/jpxl-encode-policy/src/quality.rs +++ b/JPXL/crates/jpxl-encode-policy/src/quality.rs @@ -19,9 +19,16 @@ //! //! Every probe is a full-frame score of reconstructed pixels; nothing here //! infers a score from a rate. Budgets are hard: a production effort stops at -//! its probe and price caps and reports what it could verify. The target is a -//! floor — a stream is never emitted below it unless the finest quantizer -//! cannot reach it, and then it says so ([`QualityStatus::SaturatedTop`]). +//! its navigation probe and price caps (plus at most one rescue probe beyond +//! the navigation cap when nothing has met the target yet) and reports what +//! it could verify. "Smallest" is bounded the same way: the selection is the +//! smallest among the exact-priced finalists this budget retained, not a +//! global minimum over every conceivable stream meeting the score. The +//! target is a floor — an outcome whose stream is below it says so +//! explicitly: [`QualityStatus::SaturatedTop`] when the ladder's finest rung +//! missed, [`QualityStatus::UnderTargetWorkCap`] when the probes ran out +//! first. The public facade refuses both by default rather than returning +//! them as ordinary successes. use std::time::Instant; @@ -134,7 +141,12 @@ pub const MIN_AIM_MARGIN: f64 = 0.25; /// [`TRIAL_EXACT_PRICES`] cap. #[derive(Debug, Clone, Copy, PartialEq)] pub struct QualityBudget { - /// Full-frame render-and-score evaluations in the baseline solve. + /// Full-frame render-and-score evaluations the baseline solve's + /// *navigation* may spend. When they run out with nothing meeting the + /// target, one rescue probe (`rescue_probe`) may still run beyond this + /// cap, so the observable per-solve maximum is `pixel_probes + 1`; the + /// trace and [`QualityStats::pixel_probes`] count it like any other + /// probe. pub pixel_probes: u32, /// Entropy trainings followed by an exact emission in the baseline solve. pub exact_prices: u32, @@ -155,6 +167,14 @@ pub struct QualityBudget { /// its exact size is smaller and its canonical score still meets the /// target. pub reducer: Option, + /// Whether the search reduces the frame's DCT8x8 candidates into a + /// [`TransformFeatureSummary`](crate::quality_features::TransformFeatureSummary) + /// before solving. On in Fast and Balanced with the (default) + /// `one-shot-controller` feature: the summary feeds the crossing + /// predictor's seed, and its parallel prefill warms the same forward + /// cache the cover search reads, so in-search it measured net-neutral + /// wall (promotion A/B, 2026-08-24). + pub transform_shadow: bool, } /// Hard pixel-probe cap of one policy-bank trial: probe the baseline crossing @@ -199,10 +219,13 @@ pub const MIN_TRIAL_SAVING_FRACTION: f64 = 0.005; /// [`search_frame_perceptual_with_budget`] to opt in. pub const BALANCED_DEFAULT_POLICY_TRIALS: u32 = 0; -/// Whether Balanced runs the terminal reducer by default. Off until PR 7's -/// gate (matched-score bytes down on the locked holdout with zero floor -/// violations and bounded wall) is measured and recorded; the feature-gated -/// Quality effort always runs it. +/// Whether Balanced runs the terminal reducer by default. +/// +/// Off after PR 7's full locked-holdout closure: the reducer stayed bounded +/// and floor-safe, but the complete Quality candidate failed the standing +/// Contract B promotion screen, while the earlier Balanced development screen +/// exceeded its wall bound. The feature-gated Quality reference still runs it. +/// See AKR evidence `pqc-pr7-holdout-complete-2026-08-24`. pub const BALANCED_DEFAULT_REDUCER: Option = None; impl QualityBudget { @@ -222,6 +245,7 @@ impl QualityBudget { policy_trials: 0, reserve: 0.06, reducer: None, + transform_shadow: cfg!(feature = "one-shot-controller"), }, RateSearchPreset::Balanced => Self { pixel_probes: 5, @@ -230,6 +254,7 @@ impl QualityBudget { policy_trials: BALANCED_DEFAULT_POLICY_TRIALS, reserve: 0.03, reducer: BALANCED_DEFAULT_REDUCER, + transform_shadow: cfg!(feature = "one-shot-controller"), }, RateSearchPreset::Quality => Self { pixel_probes: 10, @@ -238,6 +263,7 @@ impl QualityBudget { policy_trials: 11, reserve: 0.02, reducer: Some(ReducerLimits::QUALITY), + transform_shadow: false, }, } } @@ -362,6 +388,70 @@ pub struct QualityStats { pub reducer_edits: u32, /// Exact bytes the terminal reducer saved (0 when its stream was not kept). pub reducer_bytes_saved: u64, + /// Whole-search work totals (baseline, policy trials and reducer alike), + /// split by the units the one-shot program prices separately. A probe is + /// one pixel plan, one full-frame reconstruction and one canonical metric + /// evaluation; an exact price is one entropy training and one emission; a + /// finalist rebuilt from a dropped plan is one extra pixel plan. + pub work: QualityWork, +} + +/// Whole-search work totals for the `jpxl.quality-trace/2` record. +/// +/// Unlike the effort-budget counters above these are *totals across the whole +/// search* — baseline solve, policy-bank trials and the terminal reducer — +/// so the trace shows what a request actually cost, not just what counted +/// against the baseline caps. (A reducer pass that finds nothing to remove +/// reports no evaluations, so its canonical scores are not included in that +/// one case.) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct QualityWork { + /// Candidate pixel plans built (scored probes and unscored rebuilds). + pub pixel_plans: u32, + /// Full-frame reconstructions rendered for scoring. + pub reconstructions: u32, + /// Canonical metric evaluations of a reconstruction. + pub metric_evaluations: u32, + /// Entropy trainings. + pub entropy_trainings: u32, + /// Exact codestream emissions. + pub emissions: u32, +} + +/// A shadow predictor's counterfactual decision for one search, recorded in +/// the `jpxl.quality-trace/2` record without influencing the search (the +/// one-shot program's PR 3/4 shadow stage; the exact controller stays +/// authoritative). `None` until a shadow model is wired in. +#[derive(Debug, Clone, PartialEq)] +pub struct QualityPredictionTrace { + /// The generated model's version string. + pub model_version: &'static str, + /// The feature-vector schema the model consumed. + pub feature_schema: &'static str, + /// Predicted median crossing rung for the target. + pub median_rung: u32, + /// Risk-adjusted candidate rung a one-shot controller would plan first. + pub candidate_rung: u32, + /// Calibrated lower crossing-interval rung. + pub interval_low: u32, + /// Calibrated upper crossing-interval rung. + pub interval_high: u32, + /// Predicted local loss exponent `-d ln(loss) / d ln(scale)`. + pub local_loss_exponent: f64, + /// Predicted risk (0..=1) that the target saturates the ladder. + pub saturation_risk: f64, + /// Out-of-distribution flags that fired, by name. + pub ood_flags: Vec<&'static str>, + /// Why the shadow would have routed to the exact controller, if it would. + pub fallback_reason: Option<&'static str>, + /// The canonical score the search observed at (or nearest to) the + /// candidate rung, for counterfactual first-plan accounting. + pub first_observed_score: Option, + /// The slope-corrected rung the shadow would have re-planned at. + pub correction_rung: Option, + /// The counterfactual route: `one_shot`, `corrected`, or + /// `fallback_exact`. + pub decision_path: &'static str, } /// What a completed score-targeted search chose. @@ -393,6 +483,10 @@ pub struct QualityOutcome { pub policy_trials: Vec, /// The source features the prediction was made from. pub features: SourceFeatures, + /// The shadow predictor's counterfactual record, when one is wired in. + pub prediction: Option, + /// The PR 4 transform-feature summary, when the budget asked for one. + pub transform_features: Option, /// The metric the scores come from. pub metric_version: &'static str, } @@ -405,7 +499,7 @@ impl QualityOutcome { self.achieved_score - self.requested_score } - /// The `jpxl.quality-trace/1` record of this search, one JSON line. + /// The `jpxl.quality-trace/2` record of this search, one JSON line. #[must_use] pub fn trace_json(&self, effort: &str) -> String { let probes: Vec = self @@ -458,11 +552,43 @@ impl QualityOutcome { || "null".to_owned(), |(lo, hi)| format!("[{},{}]", lo.get(), hi.get()), ); + let prediction = self.prediction.as_ref().map_or_else( + || "null".to_owned(), + |p| { + let flags: Vec = p.ood_flags.iter().map(|f| format!("\"{f}\"")).collect(); + format!( + "{{\"model_version\":\"{}\",\"feature_schema\":\"{}\",\"median_rung\":{},\ + \"candidate_rung\":{},\"interval_low\":{},\"interval_high\":{},\ + \"local_loss_exponent\":{},\"saturation_risk\":{},\"ood_flags\":[{}],\ + \"fallback_reason\":{},\"first_observed_score\":{},\"correction_rung\":{},\ + \"decision_path\":\"{}\"}}", + p.model_version, + p.feature_schema, + p.median_rung, + p.candidate_rung, + p.interval_low, + p.interval_high, + p.local_loss_exponent, + p.saturation_risk, + flags.join(","), + p.fallback_reason + .map_or_else(|| "null".to_owned(), |r| format!("\"{r}\"")), + p.first_observed_score + .map_or_else(|| "null".to_owned(), |s| format!("{s}")), + p.correction_rung + .map_or_else(|| "null".to_owned(), |r| format!("{r}")), + p.decision_path, + ) + }, + ); format!( - "{{\"schema\":\"jpxl.quality-trace/1\",\"metric_version\":\"{}\",\"score_guard\":{},\ + "{{\"schema\":\"jpxl.quality-trace/2\",\"metric_version\":\"{}\",\"score_guard\":{},\ \"effort\":\"{}\",\"source_features\":{},\"predicted_rung\":{},\"bracket\":{},\ \"pixel_probes\":{},\"exact_prices\":{},\"structural_builds\":{},\"policy_trials\":{},\ \"policy_winner_margin_bytes\":{},\"reducer\":{{\"evaluations\":{},\"edits\":{},\"bytes_saved\":{}}},\ + \"work\":{{\"pixel_plans\":{},\"reconstructions\":{},\"metric_evaluations\":{},\ + \"entropy_trainings\":{},\"emissions\":{}}},\"prediction\":{},\ + \"transform_features\":{},\ \"requested_score\":{},\"achieved_score\":{},\ \"guard_margin\":{},\"final_exact_bytes\":{},\"status\":\"{}\",\"saturated\":{},\ \"wall_by_phase\":{{\"plan\":{},\"render_metric\":{},\"entropy\":{},\"emit\":{}}},\ @@ -483,6 +609,15 @@ impl QualityOutcome { self.stats.reducer_evaluations, self.stats.reducer_edits, self.stats.reducer_bytes_saved, + self.stats.work.pixel_plans, + self.stats.work.reconstructions, + self.stats.work.metric_evaluations, + self.stats.work.entropy_trainings, + self.stats.work.emissions, + prediction, + self.transform_features + .as_ref() + .map_or_else(|| "null".to_owned(), |t| t.to_json()), self.requested_score, self.achieved_score, self.achieved_score - self.requested_score - self.guard, @@ -694,6 +829,11 @@ struct Navigator<'c, 'a, 'r, 't, 'e> { policy_id: u32, anchor: Option, anchor_rung: Option, + /// A model-predicted local loss exponent used in place of + /// [`PRIOR_LOSS_EXPONENT`] while only one probe exists (the one-shot + /// program's slope prior). Measured slopes always win once two probes + /// bracket a direction. + prior_beta: Option, probes: Vec, trace: &'t mut Vec, local: QualityStats, @@ -759,6 +899,9 @@ impl Navigator<'_, '_, '_, '_, '_> { let millis = u64::try_from(score_start.elapsed().as_millis()).unwrap_or(u64::MAX); self.local.render_metric_ms = self.local.render_metric_ms.saturating_add(millis); self.local.pixel_probes = self.local.pixel_probes.saturating_add(1); + self.local.work.pixel_plans = self.local.work.pixel_plans.saturating_add(1); + self.local.work.reconstructions = self.local.work.reconstructions.saturating_add(1); + self.local.work.metric_evaluations = self.local.work.metric_evaluations.saturating_add(1); let feasible = score >= self.threshold(); self.trace.push(QualityProbe { policy_id: self.policy_id, @@ -859,7 +1002,7 @@ impl Navigator<'_, '_, '_, '_, '_> { let dy = loss(from.1).ln() - loss(o.1).ln(); (dx.abs() > f64::EPSILON && dy / dx < 0.0).then(|| (-dy / dx).clamp(0.2, 3.0)) }) - .unwrap_or(PRIOR_LOSS_EXPONENT); + .unwrap_or_else(|| self.prior_beta.unwrap_or(PRIOR_LOSS_EXPONENT)); let margin = EXPANSION_MARGIN.powi(i32::try_from(attempt.saturating_add(1)).unwrap_or(1)); let mut ratio = (loss(from.1) / loss(self.threshold())).powf(1.0 / alpha); ratio = if finer { @@ -987,6 +1130,8 @@ fn price_pixels( let emit_ms = u64::try_from(emit_start.elapsed().as_millis()).unwrap_or(u64::MAX); nav.local.emit_ms = nav.local.emit_ms.saturating_add(emit_ms); nav.local.exact_prices = nav.local.exact_prices.saturating_add(1); + nav.local.work.entropy_trainings = nav.local.work.entropy_trainings.saturating_add(1); + nav.local.work.emissions = nav.local.work.emissions.saturating_add(1); let feasible = score >= nav.threshold(); nav.trace.push(QualityProbe { policy_id: nav.policy_id, @@ -1061,6 +1206,7 @@ fn solve_baseline( structure_tier: EntropySearch, finalist_entropy: EntropySearch, predicted: Rung, + prior_beta: Option, trace: &mut Vec, ) -> Result<(PolicySolve, QualityStats, Option)> { let mut nav = Navigator { @@ -1076,6 +1222,7 @@ fn solve_baseline( policy_id: 0, anchor: None, anchor_rung: None, + prior_beta, probes: Vec::new(), trace, local: QualityStats { @@ -1152,14 +1299,17 @@ fn solve_baseline( .and_then(|p| p.pixels.take()); let (pixels, geometry) = match retained { Some(planned) => planned, - None => nav.ctx.pixel_plan_for( - nav.request, - quantizer, - nav.enable_cfl, - nav.structure_tier, - AnchorReuse::None, - None, - )?, + None => { + nav.local.work.pixel_plans = nav.local.work.pixel_plans.saturating_add(1); + nav.ctx.pixel_plan_for( + nav.request, + quantizer, + nav.enable_cfl, + nav.structure_tier, + AnchorReuse::None, + None, + )? + } }; let priced = price_pixels( &mut nav, @@ -1184,6 +1334,7 @@ fn solve_baseline( (Some(anchor), StructureSource::Reused) => AnchorReuse::CoverAndCfl(anchor), _ => AnchorReuse::None, }; + nav.local.work.pixel_plans = nav.local.work.pixel_plans.saturating_add(1); nav.ctx.pixel_plan_for( nav.request, quantizer, @@ -1243,20 +1394,15 @@ fn solve_baseline( )) } -/// One bank alternative's bounded solve: a priced finalist and its work. -struct TrialSolve { - finalist: PricedFinalist, - local: QualityStats, -} - /// Solves one policy-bank alternative to the same target under a small budget /// ([`TRIAL_PIXEL_PROBES`] pixel probes, [`TRIAL_EXACT_PRICES`] exact price), /// starting from the baseline's crossing rung `seed`. /// /// `shared_anchor` is `Some` only for a quantizer-side alternative that reuses /// the baseline's cover and CfL; a CfL or restoration alternative passes `None` -/// and builds a fresh plan. Returns `None` when the alternative found no -/// feasible stream inside its budget. +/// and builds a fresh plan. The finalist is `None` when the alternative found +/// no feasible stream inside its budget; its stats come back either way so +/// the search's whole-work totals stay honest. /// /// `ctx` is the baseline's warm context: a quantizer-side trial reads its /// forward coefficients from that cache and spends zero fresh structural @@ -1278,7 +1424,7 @@ fn solve_trial( shared_anchor: Option<&StructuralAnchor>, policy_id: u32, trace: &mut Vec, -) -> Result> { +) -> Result<(Option, QualityStats)> { let seed_fresh = shared_anchor.is_none(); let anchor_rung = shared_anchor.as_ref().map(|_| seed); let mut nav = Navigator { @@ -1294,6 +1440,7 @@ fn solve_trial( policy_trials: 0, reserve, reducer: None, + transform_shadow: false, }, enable_cfl, structure_tier, @@ -1301,6 +1448,7 @@ fn solve_trial( policy_id, anchor: shared_anchor.cloned(), anchor_rung, + prior_beta: None, probes: Vec::new(), trace, local: QualityStats::default(), @@ -1326,11 +1474,11 @@ fn solve_trial( .min_by_key(|(_, p)| p.rung) .map(|(i, _)| i); let Some(index) = chosen_index else { - return Ok(None); + return Ok((None, nav.local)); }; let (quantizer, score, structure) = { let Some(p) = nav.probes.get(index) else { - return Ok(None); + return Ok((None, nav.local)); }; (p.quantizer, p.score, p.structure) }; @@ -1342,6 +1490,7 @@ fn solve_trial( (Some(anchor), StructureSource::Reused) => AnchorReuse::CoverAndCfl(anchor), _ => AnchorReuse::None, }; + nav.local.work.pixel_plans = nav.local.work.pixel_plans.saturating_add(1); nav.ctx.pixel_plan_for( nav.request, quantizer, @@ -1354,11 +1503,11 @@ fn solve_trial( }; let finalist = price_pixels(&mut nav, quantizer, score, structure, &pixels, &geometry)?; let local = nav.local; - Ok(Some(TrialSolve { finalist, local })) + Ok((Some(finalist), local)) } -/// Folds a trial's timings (not its probe/price counts, which stay baseline- -/// scoped) into the combined stats. +/// Folds a trial's timings and whole-search work totals (not its probe/price +/// counts, which stay baseline-scoped) into the combined stats. fn fold_timings(combined: &mut QualityStats, trial: &QualityStats) { combined.plan_ms = combined.plan_ms.saturating_add(trial.plan_ms); combined.render_metric_ms = combined @@ -1366,6 +1515,23 @@ fn fold_timings(combined: &mut QualityStats, trial: &QualityStats) { .saturating_add(trial.render_metric_ms); combined.entropy_ms = combined.entropy_ms.saturating_add(trial.entropy_ms); combined.emit_ms = combined.emit_ms.saturating_add(trial.emit_ms); + combined.work.pixel_plans = combined + .work + .pixel_plans + .saturating_add(trial.work.pixel_plans); + combined.work.reconstructions = combined + .work + .reconstructions + .saturating_add(trial.work.reconstructions); + combined.work.metric_evaluations = combined + .work + .metric_evaluations + .saturating_add(trial.work.metric_evaluations); + combined.work.entropy_trainings = combined + .work + .entropy_trainings + .saturating_add(trial.work.entropy_trainings); + combined.work.emissions = combined.work.emissions.saturating_add(trial.work.emissions); } /// Runs the terminal reducer on the winning finalist and replaces it when the @@ -1410,6 +1576,14 @@ fn reduce_winner( return Ok(()); }; stats.reducer_evaluations = reduced.stats.evaluations; + stats.work.reconstructions = stats + .work + .reconstructions + .saturating_add(reduced.stats.evaluations); + stats.work.metric_evaluations = stats + .work + .metric_evaluations + .saturating_add(reduced.stats.evaluations); let ctx = CandidateSearchContext::new(frame, transform_frame, atlas, request, executor); let entropy_start = Instant::now(); @@ -1421,6 +1595,8 @@ fn reduce_winner( let emit_ms = u64::try_from(emit_start.elapsed().as_millis()).unwrap_or(u64::MAX); stats.emit_ms = stats.emit_ms.saturating_add(emit_ms); stats.exact_prices = stats.exact_prices.saturating_add(1); + stats.work.entropy_trainings = stats.work.entropy_trainings.saturating_add(1); + stats.work.emissions = stats.work.emissions.saturating_add(1); let kept = emission.sizing.total < winner.sizing.total; trace.push(QualityProbe { policy_id: REDUCER_POLICY_ID, @@ -1447,6 +1623,121 @@ fn reduce_winner( /// The `policy_id` the trace gives the reducer's exact price. pub const REDUCER_POLICY_ID: u32 = u32::MAX; +/// One fresh-structure point of a quality-oracle ladder sweep. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct LadderPoint { + /// The rung that was built and scored. + pub rung: Rung, + /// The quantizer at that rung (with the effort's `quant_lf` coupling). + pub quantizer: QuantizerChoice, + /// `global_scale * HfMul` of that quantizer. + pub effective_scale: u64, + /// The canonical score of the fresh-structure reconstruction. + pub score: f64, + /// The exact codestream size, when pricing was requested. + pub exact_bytes: Option, + /// Milliseconds in pixel planning. + pub plan_ms: u64, + /// Milliseconds in rendering and scoring. + pub render_metric_ms: u64, + /// Milliseconds in entropy training and emission (0 when not priced). + pub price_ms: u64, +} + +/// Sweeps `rungs` with the production quality pixel policy of `request`'s +/// effort — a fresh cover/CfL build per rung over one shared forward-DCT +/// cache — scoring every rung canonically and exact-pricing it when `price` +/// is set. +/// +/// This is the one-shot program's oracle-label measurement (PR 2): a +/// crossing predictor must train on the same fresh-structure operating +/// points the production controller emits, not on fixed-quantizer +/// approximations of them. No navigation happens here; every requested rung +/// is built, scored and dropped before the next, so at most one frame-sized +/// pixel plan is alive at a time. +/// +/// # Errors +/// +/// As [`search_frame_perceptual`]. +pub fn sweep_frame_perceptual( + frame: &PreparedFrame, + atlas: &AnalysisAtlas, + request: &EncodeRequest, + rungs: &[Rung], + price: bool, + evaluator: &mut dyn PerceptualEvaluator, + executor: &jpxl_encode::EncodeExecutor, +) -> Result> { + let preset = request.rate_preset; + let (enable_cfl, structure_tier, finalist_entropy) = planning_tiers(preset); + let transform_owned = if request.restoration.gaborish { + Some(crate::prepare_gaborish_frame(frame)?) + } else { + None + }; + let transform_frame = transform_owned.as_ref().unwrap_or(frame); + let baseline_policy = crate::policy_bank::PerceptualPolicy::baseline(preset); + let base_request = baseline_policy.apply(request); + let mut ctx = + CandidateSearchContext::new(frame, transform_frame, atlas, &base_request, executor); + + let mut points = Vec::with_capacity(rungs.len()); + for &rung in rungs { + let quantizer = QuantizerChoice::at(rung, base_request.quant_lf)?; + let plan_start = Instant::now(); + let (pixels, geometry) = ctx.pixel_plan_for( + &base_request, + quantizer, + enable_cfl, + structure_tier, + AnchorReuse::None, + None, + )?; + let plan_ms = u64::try_from(plan_start.elapsed().as_millis()).unwrap_or(u64::MAX); + let score_start = Instant::now(); + let (observation, retained) = evaluator.evaluate_owned(pixels)?; + let render_metric_ms = u64::try_from(score_start.elapsed().as_millis()).unwrap_or(u64::MAX); + let mut price_ms = 0u64; + let exact_bytes = if price { + // A memory-bounded evaluator may have dropped the plan; the fresh + // rebuild is byte-identical by construction. + let pixels = match retained { + Some(pixels) => pixels, + None => { + ctx.pixel_plan_for( + &base_request, + quantizer, + enable_cfl, + structure_tier, + AnchorReuse::None, + None, + )? + .0 + } + }; + let price_start = Instant::now(); + let plan = + ctx.attach_entropy_for(&base_request, &pixels, &geometry, finalist_entropy)?; + let emission = emit_codestream_with_executor(&plan, ctx.executor())?; + price_ms = u64::try_from(price_start.elapsed().as_millis()).unwrap_or(u64::MAX); + Some(emission.sizing.total) + } else { + None + }; + points.push(LadderPoint { + rung, + quantizer, + effective_scale: effective_scale(rung), + score: observation.score, + exact_bytes, + plan_ms, + render_metric_ms, + price_ms, + }); + } + Ok(points) +} + /// Runs the score-targeted search over a prepared frame at the preset's budget. /// /// `request` carries the effort (`rate_preset`) and the starting policy; its @@ -1515,6 +1806,38 @@ pub fn search_frame_perceptual_with_budget( let base_request = baseline_policy.apply(request); let mut ctx = CandidateSearchContext::new(frame, transform_frame, atlas, &base_request, executor); + // PR 4 shadow measurement: reduce the DCT8x8 candidates before the solve + // so the summary is quantizer-independent and the fill warms the cover's + // own cache. Off in every production preset. + let transform_features = if budget.transform_shadow { + Some(ctx.prepare_quality_transform_summary(&base_request)?) + } else { + None + }; + // PR 5 (feature-gated): the generated crossing model's risk-adjusted + // candidate becomes the first fresh plan and its slope steers the first + // correction; a fallback signal (OOD, wide interval, saturation risk, + // tiny frame, or no generated model) keeps the standard predictor start. + // Everything downstream — canonical verification before entropy, the + // bounded navigator, the total caps — is unchanged. The summary the + // prediction reads warmed the cover's own cache, so this costs no + // duplicated transform work. + #[cfg(feature = "one-shot-controller")] + let (predicted, prior_beta) = match transform_features + .as_ref() + .and_then(|tf| crate::quality_prediction::predict_v2(&features, tf, target_score)) + { + // Confident: the risk-adjusted candidate is the first fresh plan. + Some(p) if p.fallback_reason.is_none() => (p.candidate_rung, Some(p.local_loss_exponent)), + // Uncertain but in distribution (wide interval, saturation risk): + // the navigator still runs its full bounded search, so the model's + // median is simply a better seed than the legacy table. + Some(p) if p.ood_flags.is_empty() => (p.median_rung, Some(p.local_loss_exponent)), + // Out of distribution: keep the legacy predictor's start. + _ => (predicted, None), + }; + #[cfg(not(feature = "one-shot-controller"))] + let prior_beta: Option = None; let (baseline, mut stats, baseline_anchor) = solve_baseline( &mut ctx, &base_request, @@ -1526,6 +1849,7 @@ pub fn search_frame_perceptual_with_budget( structure_tier, finalist_entropy, predicted, + prior_beta, &mut trace, )?; @@ -1580,7 +1904,7 @@ pub fn search_frame_perceptual_with_budget( None }; let trial_request = policy.apply(request); - let trial = solve_trial( + let (trial_finalist, trial_stats) = solve_trial( &mut ctx, &trial_request, evaluator, @@ -1596,10 +1920,10 @@ pub fn search_frame_perceptual_with_budget( &mut trace, )?; stats.policy_trials = stats.policy_trials.saturating_add(1); - match trial { - Some(t) => { - fold_timings(&mut stats, &t.local); - let bytes = t.finalist.sizing.total; + fold_timings(&mut stats, &trial_stats); + match trial_finalist { + Some(finalist) => { + let bytes = finalist.sizing.total; let saving = incumbent_bytes.saturating_sub(bytes); // Quality demands a minimum saving; a single Balanced // pass keeps any strict improvement. @@ -1612,16 +1936,16 @@ pub fn search_frame_perceptual_with_budget( } else { saving > 0 }; - let keep = t.finalist.feasible && bytes < incumbent_bytes && enough; + let keep = finalist.feasible && bytes < incumbent_bytes && enough; policy_trials.push(PolicyTrial { id: policy_id, - rung: t.finalist.quantizer.rung.get(), - score: t.finalist.score, + rung: finalist.quantizer.rung.get(), + score: finalist.score, bytes, kept: false, }); if keep { - winner = t.finalist; + winner = finalist; winner_policy = policy; winner_is_trial = true; winner_trial_id = Some(policy_id); @@ -1695,6 +2019,12 @@ pub fn search_frame_perceptual_with_budget( QualityStatus::MetWorkCap }; let metric_version = evaluator.metric_version(); + // PR 3/4 shadow: the counterfactual one-shot record, computed from the + // finished search's own probes. Never touches the emitted bytes; absent + // when no transform summary was computed. + let prediction = transform_features.as_ref().and_then(|tf| { + crate::quality_prediction::shadow_prediction_trace(&features, tf, target_score, &trace) + }); Ok(QualityOutcome { codestream: winner.bytes, plan: winner.plan, @@ -1709,6 +2039,8 @@ pub fn search_frame_perceptual_with_budget( stats, policy_trials, features, + prediction, + transform_features, metric_version, }) } @@ -1762,8 +2094,7 @@ mod tests { } } - fn frame() -> PreparedFrame { - let (w, h) = (96u32, 80u32); + fn sized_frame(w: u32, h: u32) -> PreparedFrame { let rgb: Vec = (0..w * h) .flat_map(|i| { let x = i % w; @@ -1778,6 +2109,10 @@ mod tests { PreparedFrame::from_srgb8(w, h, &rgb).expect("frame") } + fn frame() -> PreparedFrame { + sized_frame(96, 80) + } + fn run(preset: RateSearchPreset, target: f64) -> (QualityOutcome, u32) { run_with_budget(preset, target, QualityBudget::for_preset(preset)) } @@ -1819,6 +2154,116 @@ mod tests { (outcome, evaluator.calls) } + /// PR 5 (feature-gated): the one-shot start plans the generated model's + /// risk-adjusted candidate as the first fresh plan when the model routes + /// there, and keeps the standard predictor start when any fallback + /// signal fires. The caps and floor semantics are unchanged either way. + #[cfg(feature = "one-shot-controller")] + #[test] + fn the_one_shot_start_follows_the_model_routing() { + let (w, h) = (160u32, 144u32); + let frame = sized_frame(w, h); + let atlas = AnalysisAtlas::analyze(&frame); + let mut request = EncodeRequest::for_quality(RateSearchPreset::Balanced); + request.restoration.gaborish = false; + let executor = request.resources.executor(); + let mut evaluator = CurveEvaluator { + calls: 0, + discard: false, + }; + let target = PerceptualTarget::new(PerceptualMetric::Ssimulacra2, 85.0).expect("target"); + let budget = QualityBudget::for_preset(RateSearchPreset::Balanced); + let outcome = search_frame_perceptual_with_budget( + &frame, + &atlas, + &request, + target, + &mut evaluator, + &executor, + budget, + ) + .expect("search"); + + let features = source_features(&atlas, w, h, frame.is_grayscale()); + let mut check_cache = crate::CandidateForwardCache::new(); + let summary = + crate::quality_transform_summary(&frame, &request, &mut check_cache, Some(&executor)) + .expect("transform summary"); + match crate::quality_prediction::predict_v2(&features, &summary, 85.0) { + Some(p) if p.fallback_reason.is_none() => { + assert_eq!( + outcome.stats.predicted, + Some(p.candidate_rung), + "the model's candidate is the first fresh plan" + ); + assert_eq!( + outcome.trace.first().map(|probe| probe.quantizer.rung), + Some(p.candidate_rung) + ); + } + Some(p) if p.ood_flags.is_empty() => { + assert_eq!( + outcome.stats.predicted, + Some(p.median_rung), + "an uncertain in-distribution route seeds the navigator with the median" + ); + } + _ => { + let standard = rung_for_scale(predicted_effective_scale(&features, 85.0)); + assert_eq!( + outcome.stats.predicted, + Some(standard), + "an out-of-distribution frame keeps the legacy predictor start" + ); + } + } + assert!(outcome.achieved_score >= 85.0); + // The one rescue probe beyond the navigation cap stays the maximum. + assert!(outcome.stats.pixel_probes <= budget.pixel_probes + 1); + assert!(outcome.prediction.is_some(), "the shadow trace still fills"); + } + + /// A solve that runs out of probes before anything meets a reachable + /// target reports [`QualityStatus::UnderTargetWorkCap`] — not a success + /// status — and its counters show the one rescue probe that ran beyond + /// the navigation cap. + #[test] + fn running_out_of_probes_reports_under_target_work_cap() { + let budget = QualityBudget { + pixel_probes: 1, + exact_prices: 1, + structural_builds: 1, + policy_trials: 0, + reserve: 0.03, + reducer: None, + transform_shadow: false, + }; + // The curve tops out near 99.96, so 99.9 is reachable on the ladder; + // one navigation probe plus one bounded rescue jump cannot get there. + let (outcome, _) = run_with_budget(RateSearchPreset::Balanced, 99.9, budget); + assert_eq!(outcome.status, QualityStatus::UnderTargetWorkCap); + assert!(!outcome.saturated, "{:?}", outcome.stats); + assert!(outcome.achieved_score < 99.9); + // The navigation cap of one, plus the single documented rescue probe. + assert_eq!(outcome.stats.pixel_probes, 2); + assert_eq!(outcome.stats.exact_prices, 1); + } + + /// Proves the failed Quality promotion screen cannot leak its policy-bank + /// or reducer work into either production effort's default budget. + #[test] + fn production_budgets_keep_quality_only_work_disabled() { + for preset in [RateSearchPreset::Fast, RateSearchPreset::Balanced] { + let budget = QualityBudget::for_preset(preset); + assert_eq!(budget.policy_trials, 0); + assert_eq!(budget.reducer, None); + } + + let quality = QualityBudget::for_preset(RateSearchPreset::Quality); + assert!(quality.policy_trials > 0); + assert_eq!(quality.reducer, Some(ReducerLimits::QUALITY)); + } + #[test] fn dropping_probe_plans_rebuilds_the_byte_identical_finalist() { let budget = QualityBudget::for_preset(RateSearchPreset::Balanced); @@ -2022,7 +2467,7 @@ mod tests { fn the_trace_is_machine_readable() { let (outcome, _) = run(RateSearchPreset::Fast, 70.0); let json = outcome.trace_json("fast"); - assert!(json.starts_with("{\"schema\":\"jpxl.quality-trace/1\"")); + assert!(json.starts_with("{\"schema\":\"jpxl.quality-trace/2\"")); assert!(json.contains("\"probes\":[{\"kind\":\"pixel\"")); assert!(json.contains("\"policy_id\":0")); assert!(json.contains("\"policy_trials\":0")); @@ -2044,10 +2489,12 @@ mod tests { }; let with = QualityBudget { reducer: Some(limits), + transform_shadow: false, ..QualityBudget::for_preset(RateSearchPreset::Balanced) }; let without = QualityBudget { reducer: None, + transform_shadow: false, ..QualityBudget::for_preset(RateSearchPreset::Balanced) }; let (reduced, _) = run_with_budget(RateSearchPreset::Balanced, 70.0, with); @@ -2132,6 +2579,7 @@ mod tests { structure_tier, finalist_entropy, predicted, + None, &mut trace, ) .expect("baseline solve"); @@ -2149,7 +2597,7 @@ mod tests { "a chroma-only alternative must reuse structure" ); let qs_request = qs_policy.apply(&request); - let qs = solve_trial( + let (qs_finalist, qs_stats) = solve_trial( &mut ctx, &qs_request, &mut evaluator, @@ -2164,10 +2612,10 @@ mod tests { 1, &mut trace, ) - .expect("quantizer-side trial") - .expect("a feasible quantizer-side finalist"); + .expect("quantizer-side trial"); + assert!(qs_finalist.is_some(), "a feasible quantizer-side finalist"); assert_eq!( - qs.local.structural_builds, 0, + qs_stats.structural_builds, 0, "a quantizer-side trial rebuilt structure" ); @@ -2180,7 +2628,7 @@ mod tests { "a CfL flip must not reuse structure" ); let st_request = st_policy.apply(&request); - let st = solve_trial( + let (st_finalist, st_stats) = solve_trial( &mut ctx, &st_request, &mut evaluator, @@ -2195,10 +2643,10 @@ mod tests { 2, &mut trace, ) - .expect("structural trial") - .expect("a feasible structural finalist"); + .expect("structural trial"); + assert!(st_finalist.is_some(), "a feasible structural finalist"); assert_eq!( - st.local.structural_builds, 1, + st_stats.structural_builds, 1, "a structural trial did not build exactly one cover" ); } diff --git a/JPXL/crates/jpxl-encode-policy/src/quality_features.rs b/JPXL/crates/jpxl-encode-policy/src/quality_features.rs index d124a375..01f6c659 100644 --- a/JPXL/crates/jpxl-encode-policy/src/quality_features.rs +++ b/JPXL/crates/jpxl-encode-policy/src/quality_features.rs @@ -79,6 +79,66 @@ impl SourceFeatures { } } +/// Compact quantizer-independent transform features of one frame (the +/// one-shot program's PR 4 summary). +/// +/// Reduced from the aligned DCT8x8 coefficients of the shared candidate +/// forward cache — the same bank the cover search reads — in fixed LF-group +/// then block-raster order, so the values are deterministic across worker +/// counts and the prefilled coefficients are reused, never recomputed, by +/// the later pixel plan. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TransformFeatureSummary { + /// 8x8 blocks reduced. + pub blocks: u64, + /// `ln(mean per-block Y AC energy + eps)`. + pub ln_ac_y_mean: f64, + /// Median of per-block `ln(Y AC energy + eps)` (fixed-bin histogram). + pub ln_ac_y_q50: f64, + /// 90th percentile of per-block `ln(Y AC energy + eps)`. + pub ln_ac_y_q90: f64, + /// 99th percentile of per-block `ln(Y AC energy + eps)`. + pub ln_ac_y_q99: f64, + /// Frame high-band over low-band Y AC energy (radial split at + /// `row+col <= 2` / `max(row,col) >= 4`). + pub high_low_ratio: f64, + /// `|row - col|` energy asymmetry of the Y AC cells, in `0..=1`. + pub directional_asymmetry: f64, + /// Chroma (X+B) over Y AC energy. + pub chroma_ac_ratio: f64, + /// Fraction of Y AC cells with `|coefficient| < 1e-3`. + pub near_zero_frac_1e3: f64, + /// Fraction of Y AC cells with `|coefficient| < 1e-2`. + pub near_zero_frac_1e2: f64, + /// Variance of the per-block Y DC coefficient. + pub dc_variance_y: f64, +} + +impl TransformFeatureSummary { + /// A one-line JSON object, for the CLI, the trace and the calibration + /// tooling. + #[must_use] + pub fn to_json(&self) -> String { + format!( + "{{\"blocks\":{},\"ln_ac_y_mean\":{},\"ln_ac_y_q50\":{},\"ln_ac_y_q90\":{},\ + \"ln_ac_y_q99\":{},\"high_low_ratio\":{},\"directional_asymmetry\":{},\ + \"chroma_ac_ratio\":{},\"near_zero_frac_1e3\":{},\"near_zero_frac_1e2\":{},\ + \"dc_variance_y\":{}}}", + self.blocks, + self.ln_ac_y_mean, + self.ln_ac_y_q50, + self.ln_ac_y_q90, + self.ln_ac_y_q99, + self.high_low_ratio, + self.directional_asymmetry, + self.chroma_ac_ratio, + self.near_zero_frac_1e3, + self.near_zero_frac_1e2, + self.dc_variance_y, + ) + } +} + /// The quantile of `values` at fraction `q` in `0..=1`: the element at /// `floor((n - 1) q)` of the ascending total order. `0.0` for an empty slice. #[must_use] diff --git a/JPXL/crates/jpxl-encode-policy/src/quality_prediction.rs b/JPXL/crates/jpxl-encode-policy/src/quality_prediction.rs new file mode 100644 index 00000000..6b0d02ca --- /dev/null +++ b/JPXL/crates/jpxl-encode-policy/src/quality_prediction.rs @@ -0,0 +1,487 @@ +//! Shadow crossing predictor for the one-shot quality program (PR 3). +//! +//! Evaluates the generated [`crate::quality_predictor_v2`] model on a +//! frame's [`SourceFeatures`] and, after the exact controller has finished, +//! reconstructs the counterfactual one-shot decision for the +//! `jpxl.quality-trace/2` record. **Nothing here influences the search or +//! the emitted bytes** — the exact controller stays authoritative; this +//! module only says what a one-shot controller *would have done*, so the +//! offline evaluation can measure it. +//! +//! Determinism: scalar `f64` arithmetic in fixed order over generated +//! constants; no worker-count or SIMD dependence. + +use crate::quality::{ProbeKind, QualityProbe}; +use crate::quality_features::{SourceFeatures, TransformFeatureSummary}; +use crate::quality_predictor_v2::{ + QPV2_FEATURE_CENTERS, QPV2_FEATURE_DIM, QPV2_FEATURE_SCALES, QPV2_FEATURE_SCHEMA, QPV2_KNOTS, + QPV2_MODEL_VERSION, Qpv2Knot, +}; +use crate::rate::{Rung, effective_scale, rung_for_effective_scale}; + +/// Matches the trainer's `EPS`: logs stay finite on zero-valued features. +const FEATURE_EPS: f64 = 1e-9; + +/// Loss floor, as in the navigator's crossing interpolation. +const LOSS_EPSILON: f64 = 1e-3; + +/// Calibrated-interval width (in `ln scale`) beyond which the shadow routes +/// to the exact controller (the memo's `ln(1.5)` threshold). +pub const FALLBACK_LOG_WIDTH: f64 = 0.405_465; + +/// Saturation risk beyond which the shadow routes to the exact controller. +pub const FALLBACK_SATURATION_RISK: f64 = 0.5; + +/// Margin the training-time standardized feature range is widened by before +/// a feature counts as out of distribution. +const OOD_RANGE_MARGIN: f64 = 0.25; + +/// The model's declared domain floor: below this shortest side the metric +/// sits against its own floor and the shadow routes straight to the exact +/// controller. Mirrors the trainer's `MIN_DOMAIN_SIDE`. +pub const MIN_DOMAIN_SIDE: u32 = 128; + +/// Runtime clamp of the predicted local loss exponent, mirroring the +/// navigator's own slope clamp. +const BETA_RANGE: (f64, f64) = (0.2, 3.0); + +/// What the shadow model predicts for one `(frame, target)` request. +#[derive(Debug, Clone, PartialEq)] +pub struct QualityPredictionV2 { + /// Median crossing rung. + pub median_rung: Rung, + /// Risk-adjusted candidate rung (rounded toward the finer legal rung). + pub candidate_rung: Rung, + /// Lower calibrated crossing-interval rung. + pub interval_low: Rung, + /// Upper calibrated crossing-interval rung. + pub interval_high: Rung, + /// Predicted local loss exponent, clamped into [`BETA_RANGE`]. + pub local_loss_exponent: f64, + /// Predicted probability that the target saturates the ladder. + pub saturation_risk: f64, + /// Out-of-distribution flags that fired, by name. + pub ood_flags: Vec<&'static str>, + /// Why the shadow would route to the exact controller, if it would. + pub fallback_reason: Option<&'static str>, +} + +fn loss(score: f64) -> f64 { + (100.0 - score).max(LOSS_EPSILON) +} + +fn ln_eps(value: f32) -> f64 { + (f64::from(value).max(0.0) + FEATURE_EPS).ln() +} + +/// The `qpv2-st/1` feature vector of a frame: the nine source features +/// followed by the ten transform-summary fields in sorted key order. Must +/// mirror the trainer's `feature_vector(..., "source+transform")` exactly; +/// the schema string and the generated `QPV2_FEATURE_DIM` pin it — a +/// regenerated model with a different schema fails to compile here. +#[must_use] +pub fn qpv2_features( + features: &SourceFeatures, + transform: &TransformFeatureSummary, +) -> [f64; QPV2_FEATURE_DIM] { + let width = f64::from(features.width.max(1)); + let height = f64::from(features.height.max(1)); + [ + ln_eps(features.luma_variance_q10), + ln_eps(features.luma_variance_q50), + ln_eps(features.luma_variance_q90), + ln_eps(features.chroma_variance_q50), + f64::from(features.flat_fraction), + ln_eps(features.edge_proxy), + (width * height).log2(), + (width / height).ln(), + if features.grayscale { 1.0 } else { 0.0 }, + transform.chroma_ac_ratio, + transform.dc_variance_y, + transform.directional_asymmetry, + transform.high_low_ratio, + transform.ln_ac_y_mean, + transform.ln_ac_y_q50, + transform.ln_ac_y_q90, + transform.ln_ac_y_q99, + transform.near_zero_frac_1e2, + transform.near_zero_frac_1e3, + ] +} + +fn standardized( + features: &SourceFeatures, + transform: &TransformFeatureSummary, +) -> [f64; QPV2_FEATURE_DIM] { + let raw = qpv2_features(features, transform); + let mut out = [0.0; QPV2_FEATURE_DIM]; + for (slot, ((&value, ¢er), &scale)) in out.iter_mut().zip( + raw.iter() + .zip(QPV2_FEATURE_CENTERS.iter()) + .zip(QPV2_FEATURE_SCALES.iter()), + ) { + *slot = (value - center) / scale; + } + out +} + +/// `[intercept, w_0, ..]` dot a standardized feature vector. +fn affine(weights: &[f64; QPV2_FEATURE_DIM + 1], z: &[f64; QPV2_FEATURE_DIM]) -> f64 { + let mut acc = weights.first().copied().unwrap_or(0.0); + for (&weight, &value) in weights.iter().skip(1).zip(z.iter()) { + acc += weight * value; + } + acc +} + +/// Per-knot raw outputs before monotone projection. +struct KnotEval { + ln_loss: f64, + lower: f64, + median: f64, + candidate: f64, + upper: f64, + ln_beta: f64, + saturation_logit: f64, +} + +fn evaluate_knot(knot: &Qpv2Knot, z: &[f64; QPV2_FEATURE_DIM]) -> KnotEval { + KnotEval { + ln_loss: loss(knot.target).ln(), + lower: affine(&knot.lower, z), + median: affine(&knot.median, z), + candidate: affine(&knot.candidate, z), + upper: affine(&knot.upper, z), + ln_beta: affine(&knot.beta, z), + saturation_logit: affine(&knot.saturation, z), + } +} + +/// Linear interpolation of `field` against `ln loss(target)`, clamped to the +/// end knots. Returns 0 only on an empty table, which the caller precludes. +fn interpolate(evals: &[KnotEval], target_ln_loss: f64, field: impl Fn(&KnotEval) -> f64) -> f64 { + // Knots ascend in target, so their ln-loss descends. + let (Some(first), Some(last)) = (evals.first(), evals.last()) else { + return 0.0; + }; + if target_ln_loss >= first.ln_loss { + return field(first); + } + if target_ln_loss <= last.ln_loss { + return field(last); + } + for pair in evals.windows(2) { + let [a, b] = pair else { continue }; + if target_ln_loss <= a.ln_loss && target_ln_loss >= b.ln_loss { + let span = a.ln_loss - b.ln_loss; + if span <= f64::EPSILON { + return field(a); + } + let t = (a.ln_loss - target_ln_loss) / span; + return field(a) + t * (field(b) - field(a)); + } + } + field(last) +} + +/// The finer legal rung at or above an effective scale. +fn rung_ceil(scale: f64) -> Rung { + if !scale.is_finite() { + return Rung::TOP; + } + let clamped = scale.clamp(1.0, 9.0e18); + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "clamped positive and far inside u64 range" + )] + let floor = rung_for_effective_scale(clamped as u64); + #[allow( + clippy::cast_precision_loss, + reason = "effective scales stay far inside f64's exact-integer range" + )] + if (effective_scale(floor) as f64) < scale { + Rung::new(floor.get().saturating_add(1)) + } else { + floor + } +} + +fn rung_floor(scale: f64) -> Rung { + if !scale.is_finite() { + return Rung::TOP; + } + let clamped = scale.clamp(1.0, 9.0e18); + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "clamped positive and far inside u64 range" + )] + rung_for_effective_scale(clamped as u64) +} + +/// Evaluates the generated model for one request. `None` when no model has +/// been generated (empty knot table). +#[must_use] +pub fn predict_v2( + features: &SourceFeatures, + transform: &TransformFeatureSummary, + target: f64, +) -> Option { + if QPV2_KNOTS.is_empty() { + return None; + } + let z = standardized(features, transform); + + let mut ood_flags: Vec<&'static str> = Vec::new(); + if features.width.min(features.height) < MIN_DOMAIN_SIDE { + ood_flags.push("tiny_frame"); + } + for (i, (&value, &(lo, hi))) in z + .iter() + .zip(crate::quality_predictor_v2::QPV2_FEATURE_Z_RANGE.iter()) + .enumerate() + { + let span = (hi - lo).max(1e-6); + if value < lo - OOD_RANGE_MARGIN * span || value > hi + OOD_RANGE_MARGIN * span { + ood_flags.push(qpv2_feature_name(i)); + } + } + + // Monotone projection: a higher target must never predict a coarser + // scale, per output. Knots ascend in target, so run a max-accumulate. + let mut projected: Vec = QPV2_KNOTS.iter().map(|k| evaluate_knot(k, &z)).collect(); + let mut running = ( + f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + ); + for eval in &mut projected { + running.0 = running.0.max(eval.lower); + running.1 = running.1.max(eval.median); + running.2 = running.2.max(eval.candidate); + running.3 = running.3.max(eval.upper); + eval.lower = running.0; + eval.median = running.1; + eval.candidate = running.2; + eval.upper = running.3; + } + + let x = loss(target).ln(); + let ln_lower = interpolate(&projected, x, |e| e.lower); + let ln_median = interpolate(&projected, x, |e| e.median); + let ln_candidate = interpolate(&projected, x, |e| e.candidate); + let ln_upper = interpolate(&projected, x, |e| e.upper); + let ln_beta = interpolate(&projected, x, |e| e.ln_beta); + let saturation_logit = interpolate(&projected, x, |e| e.saturation_logit); + let saturation_risk = 1.0 / (1.0 + (-saturation_logit.clamp(-30.0, 30.0)).exp()); + + let interval_width = (ln_upper - ln_lower).max(0.0); + let fallback_reason = if !ood_flags.is_empty() { + Some("ood_feature") + } else if interval_width > FALLBACK_LOG_WIDTH { + Some("wide_interval") + } else if saturation_risk > FALLBACK_SATURATION_RISK { + Some("saturation_risk") + } else { + None + }; + + Some(QualityPredictionV2 { + median_rung: rung_floor(ln_median.exp()), + candidate_rung: rung_ceil(ln_candidate.exp()), + interval_low: rung_floor(ln_lower.exp()), + interval_high: rung_ceil(ln_upper.exp()), + local_loss_exponent: ln_beta.exp().clamp(BETA_RANGE.0, BETA_RANGE.1), + saturation_risk, + ood_flags, + fallback_reason, + }) +} + +/// The schema name of feature `index`, for OOD flags. +#[must_use] +pub const fn qpv2_feature_name(index: usize) -> &'static str { + match index { + 0 => "ln_luma_q10", + 1 => "ln_luma_q50", + 2 => "ln_luma_q90", + 3 => "ln_chroma_q50", + 4 => "flat_fraction", + 5 => "ln_edge_proxy", + 6 => "log2_pixels", + 7 => "ln_aspect", + 8 => "grayscale", + 9 => "chroma_ac_ratio", + 10 => "dc_variance_y", + 11 => "directional_asymmetry", + 12 => "high_low_ratio", + 13 => "ln_ac_y_mean", + 14 => "ln_ac_y_q50", + 15 => "ln_ac_y_q90", + 16 => "ln_ac_y_q99", + 17 => "near_zero_frac_1e2", + 18 => "near_zero_frac_1e3", + _ => "unknown", + } +} + +/// The counterfactual one-shot record of a finished exact search. +/// +/// The exact controller's probes bound what the one-shot path would have +/// seen: above the coarsest canonically feasible probe the score curve is +/// assumed monotone (the same assumption the navigator's bracketing makes), +/// so a candidate at or finer than it would have met the target with its +/// first plan. `first_observed_score` is the score of the probe nearest the +/// candidate in log scale — a proxy, since the search did not necessarily +/// probe the candidate rung itself; the offline oracle evaluation measures +/// the exact counterfactual. +#[must_use] +pub fn shadow_prediction_trace( + features: &SourceFeatures, + transform: &TransformFeatureSummary, + target: f64, + probes: &[QualityProbe], +) -> Option { + let prediction = predict_v2(features, transform, target)?; + let pixel_probes: Vec<&QualityProbe> = probes + .iter() + .filter(|p| p.kind == ProbeKind::Pixel) + .collect(); + let coarsest_feasible = pixel_probes + .iter() + .filter(|p| p.feasible == Some(true)) + .map(|p| p.effective_scale) + .min(); + let candidate_scale = effective_scale(prediction.candidate_rung); + #[allow( + clippy::cast_precision_loss, + reason = "effective scales stay far inside f64's exact-integer range" + )] + let nearest = pixel_probes.iter().min_by(|a, b| { + let da = ((a.effective_scale as f64).ln() - (candidate_scale as f64).ln()).abs(); + let db = ((b.effective_scale as f64).ln() - (candidate_scale as f64).ln()).abs(); + da.total_cmp(&db) + }); + let first_observed_score = nearest.and_then(|p| p.score); + + // One slope correction from the proxy observation, aimed at the target. + #[allow( + clippy::cast_precision_loss, + reason = "effective scales stay far inside f64's exact-integer range" + )] + let correction_scale = first_observed_score.map(|observed| { + let beta = prediction.local_loss_exponent; + let shift = (loss(observed).ln() - loss(target).ln()) / beta; + ((candidate_scale as f64).ln() + shift).exp() + }); + let correction_rung = correction_scale.map(rung_ceil); + + let would_one_shot = coarsest_feasible.is_some_and(|feasible| candidate_scale >= feasible); + let would_correct = correction_rung + .map(effective_scale) + .zip(coarsest_feasible) + .is_some_and(|(corrected, feasible)| corrected >= feasible); + let decision_path = if prediction.fallback_reason.is_some() { + "fallback_exact" + } else if would_one_shot { + "one_shot" + } else if would_correct { + "corrected" + } else { + "fallback_exact" + }; + + Some(crate::quality::QualityPredictionTrace { + model_version: QPV2_MODEL_VERSION, + feature_schema: QPV2_FEATURE_SCHEMA, + median_rung: prediction.median_rung.get(), + candidate_rung: prediction.candidate_rung.get(), + interval_low: prediction.interval_low.get(), + interval_high: prediction.interval_high.get(), + local_loss_exponent: prediction.local_loss_exponent, + saturation_risk: prediction.saturation_risk, + ood_flags: prediction.ood_flags, + fallback_reason: prediction.fallback_reason, + first_observed_score, + correction_rung: correction_rung.map(Rung::get), + decision_path, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn features() -> SourceFeatures { + SourceFeatures { + width: 640, + height: 480, + grayscale: false, + luma_variance_q10: 1e-6, + luma_variance_q50: 1e-4, + luma_variance_q90: 1e-2, + chroma_variance_q50: 1e-5, + flat_fraction: 0.1, + edge_proxy: 1e-2, + } + } + + fn transform() -> TransformFeatureSummary { + TransformFeatureSummary { + blocks: 4800, + ln_ac_y_mean: -5.0, + ln_ac_y_q50: -6.0, + ln_ac_y_q90: -3.0, + ln_ac_y_q99: -2.0, + high_low_ratio: 0.2, + directional_asymmetry: 0.3, + chroma_ac_ratio: 0.15, + near_zero_frac_1e3: 0.6, + near_zero_frac_1e2: 0.8, + dc_variance_y: 0.02, + } + } + + #[test] + fn feature_vector_matches_the_schema_order() { + let raw = qpv2_features(&features(), &transform()); + assert!((raw[0] - (f64::from(1e-6f32) + FEATURE_EPS).ln()).abs() < 1e-12); + assert!((raw[6] - (640.0f64 * 480.0).log2()).abs() < 1e-12); + assert!((raw[7] - (640.0f64 / 480.0).ln()).abs() < 1e-12); + assert!(raw[8].abs() < f64::EPSILON); + // Transform fields follow in sorted key order. + assert!((raw[9] - 0.15).abs() < 1e-12, "chroma_ac_ratio first"); + assert!((raw[13] - (-5.0)).abs() < 1e-12, "ln_ac_y_mean fifth"); + assert!((raw[18] - 0.6).abs() < 1e-12, "near_zero_frac_1e3 last"); + } + + #[test] + fn predictions_are_monotone_in_the_target() { + // With any generated model, a higher target must never predict a + // coarser candidate scale. + let f = features(); + let t = transform(); + let mut previous = 0u64; + for target in [30.0, 50.0, 70.0, 80.0, 85.0, 90.0, 95.0] { + let Some(p) = predict_v2(&f, &t, target) else { + return; // no generated model in this build + }; + let scale = effective_scale(p.candidate_rung); + assert!( + scale >= previous, + "candidate scale fell from {previous} to {scale} at target {target}" + ); + previous = scale; + } + } + + #[test] + fn rung_ceil_rounds_toward_the_finer_rung() { + let r = rung_ceil(1000.5); + assert!(effective_scale(r) >= 1001); + let exact = rung_ceil(1000.0); + assert_eq!(effective_scale(exact), 1000); + } +} diff --git a/JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs b/JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs new file mode 100644 index 00000000..d78138ee --- /dev/null +++ b/JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs @@ -0,0 +1,941 @@ +//! Generated crossing-predictor model for the one-shot quality program. +//! +//! GENERATED by `tools/quality_predictor_v2.py 1.0.0` — do not edit +//! by hand; retrain and regenerate. The exact controller stays +//! authoritative: without the `one-shot-controller` feature this model +//! only feeds the shadow trace, never a bitstream. +//! +//! model_version: qpv2-st-1 +//! feature_schema: qpv2-st/1 +//! labels: 392effdab51fab52619614a59c314364b7d773eb9aa0ce7bf5f6bb110b49c950 +//! trained_at: 2026-08-24T15:10:14.303200+00:00 +//! git: 41a2e3f20631e000954b9ea76612c70f1847e745 (dirty: True) + +/// The generated model's version string. +pub const QPV2_MODEL_VERSION: &str = "qpv2-st-1"; +/// The feature-vector schema the coefficients expect. +pub const QPV2_FEATURE_SCHEMA: &str = "qpv2-st/1"; +/// The feature-vector dimension the coefficient arrays expect. The +/// runtime feature builder is sized against this at compile time. +pub const QPV2_FEATURE_DIM: usize = 19; + +/// Robust per-feature centers (medians over the training corpus). +pub const QPV2_FEATURE_CENTERS: [f64; 19] = [ + -9.804432031649899, + -7.527123141389171, + -5.094806725732717, + -7.578809711477392, + 0.022668848, + -5.285449316183245, + 20.69740200750271, + 0.28768207245178085, + 0.0, + 0.9391887812286073, + 0.03084669961656289, + 0.12828016954152927, + 0.33538596970509227, + -6.049970993368567, + -7.5625, + -5.0625, + -3.8125, + 0.9519056543634857, + 0.6940668783068783, +]; +/// Robust per-feature scales (1.4826 x MAD, floored). +pub const QPV2_FEATURE_SCALES: [f64; 19] = [ + 1.7434782317247506, + 1.6071485038785283, + 1.1280981048610355, + 1.5083122188406448, + 0.033274915118802, + 1.133360277934255, + 1.649302812754329, + 0.27030994010271736, + 1.0, + 0.05797373270690192, + 0.015056661603060293, + 0.1281692462974138, + 0.2569004217225202, + 1.0147714110886856, + 1.4826, + 1.11195, + 0.7413, + 0.04076729688731605, + 0.16466970129379055, +]; +/// Standardized-feature range seen in training, for OOD detection. +pub const QPV2_FEATURE_Z_RANGE: [(f64, f64); 19] = [ + (-6.262672860844934, 4.164861502241656), + (-8.210904383578129, 3.132408616811535), + (-13.853812043358483, 2.4359539240747887), + (-8.714678540211274, 3.3719360810073935), + (-0.6812593786960845, 29.371409318720058), + (-13.621278971326976, 2.2304082316550726), + (-1.7126795063878064, 1.7092853057315096), + (-3.6285356455593627, 2.269207202761088), + (0.0, 1.0), + (-16.20024685967468, 41.45050553876453), + (-2.042234699960017, 2.980187744798955), + (-1.0008654435234647, 6.801318222903994), + (-1.3055096112973485, 78.9263064391509), + (-62.11012756935515, 3.4515346765027033), + (-25.88358289491434, 3.4567651423175505), + (-36.75974639147444, 2.4731327847475155), + (-56.82584648590315, 2.3607176581680833), + (-15.220903994458967, 1.1797285890563396), + (-3.625112992913436, 1.8578592132580616), +]; + +/// One target knot's fitted linear models over standardized features. +/// Every coefficient array is `[intercept, w_0, .., w_n]`; crossing +/// outputs are `ln(effective_scale)`, `beta` is `ln(beta)`, and +/// `saturation` is a logit. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Qpv2Knot { + /// The SSIMULACRA2 target this knot was fitted at. + pub target: f64, + /// Rows the crossing fits saw (uncensored). + pub rows: u32, + /// tau = 0.10 crossing quantile. + pub lower: [f64; 20], + /// tau = 0.50 crossing quantile. + pub median: [f64; 20], + /// tau = 0.90 crossing quantile (the one-shot candidate). + pub candidate: [f64; 20], + /// tau = 0.95 crossing quantile. + pub upper: [f64; 20], + /// ln(beta) least squares, or all-zero when too few rows. + pub beta: [f64; 20], + /// Saturation-risk logit. + pub saturation: [f64; 20], +} + +/// The fitted knots, ascending in target. +pub const QPV2_KNOTS: &[Qpv2Knot] = &[ + Qpv2Knot { + target: 30.0, + rows: 120, + lower: [ + 8.114267187113095, + -0.08228926450496746, + -0.035979849974954146, + -0.13988211404720818, + -0.3076403802731068, + -0.10340925312497726, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.10688071800844812, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 8.326558131015705, + -0.027905864283035566, + -0.10399385231316674, + -0.46603046061674286, + 0.0779756166300174, + -0.013273056686474605, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.017121227078915797, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 8.50667862429574, + -0.06933995160515433, + 0.13523416346587877, + -0.25453416624877684, + -0.09339691707056483, + -0.028861639096780363, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.03819215247543441, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 8.563199847168615, + -0.05320482494928357, + 0.10762571042744223, + -0.15157121758954228, + -0.04457075214341936, + -0.02212236311624152, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.05640535378764008, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.41539412831554556, + -0.038269239125313985, + 0.4827660200910804, + -0.813234037882281, + -0.20364398146863608, + 0.042994046614659207, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.004041951767086818, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 50.0, + rows: 120, + lower: [ + 8.56447381353096, + -0.08228926450496746, + -0.02762538786775922, + -0.13988211404720818, + -0.3076403802731068, + -0.0959612385248504, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.10558317785670433, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 8.770075795795721, + -0.027905864283035566, + -0.1094743229654219, + -0.46603046061674286, + 0.0779756166300174, + -0.011023632799554595, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.01551082761154858, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 8.955111231206477, + -0.06933995160515433, + 0.13498530038145756, + -0.25453416624877684, + -0.09339691707056483, + -0.02462073423726832, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.034626256325926884, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 9.01012687416021, + -0.05320482494928357, + 0.10195301530398537, + -0.15157121758954228, + -0.04457075214341936, + -0.01997235954572073, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.053134849491821026, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.39999861818939886, + -0.038269239125313985, + 0.4664120354776818, + -0.813234037882281, + -0.20364398146863608, + 0.033889492171596564, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.007145290132814428, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 70.0, + rows: 120, + lower: [ + 9.247968782009997, + -0.08228926450496746, + -0.014941805531242256, + -0.13988211404720818, + -0.3076403802731068, + -0.08465380721303281, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.10361327756409233, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 9.443415712448457, + -0.027905864283035566, + -0.11779466721605637, + -0.46603046061674286, + 0.0779756166300174, + -0.007608601081964171, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.013065950373857306, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 9.635912917676867, + -0.06933995160515433, + 0.13460748127338384, + -0.25453416624877684, + -0.09339691707056483, + -0.018182274731822518, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.029212581952229446, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 9.688642818398053, + -0.05320482494928357, + 0.09334083958936223, + -0.15157121758954228, + -0.04457075214341936, + -0.01670826582423255, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.04816963330127428, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.37662545331149566, + -0.038269239125313985, + 0.4415837332228263, + -0.813234037882281, + -0.20364398146863608, + 0.020067134198791168, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.011856718250476218, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 80.0, + rows: 120, + lower: [ + 9.790489259433608, + -0.08228926450496746, + -0.004874279855140785, + -0.13988211404720818, + -0.3076403802731068, + -0.07567859405620016, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.10204967973746466, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 9.977875671771509, + -0.027905864283035566, + -0.12439889598635413, + -0.46603046061674286, + 0.0779756166300174, + -0.004897937912099513, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.011125342136190267, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 10.176295616903781, + -0.06933995160515433, + 0.13430758937608425, + -0.25453416624877684, + -0.09339691707056483, + -0.013071781917896005, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.024915506858362992, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 10.227211221946934, + -0.05320482494928357, + 0.08650497115458725, + -0.15157121758954228, + -0.04457075214341936, + -0.014117408881894293, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.04422851949292957, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.3580731286372142, + -0.038269239125313985, + 0.4218764010344182, + -0.813234037882281, + -0.20364398146863608, + 0.009095711449017933, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.015596389132661061, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 85.0, + rows: 120, + lower: [ + 10.175413663955043, + -0.08228926450496746, + 0.0022687432880238896, + -0.13988211404720818, + -0.3076403802731068, + -0.06931057897914127, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.10094028942433975, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 10.357081047686453, + -0.027905864283035566, + -0.12908467086954853, + -0.46603046061674286, + 0.0779756166300174, + -0.0029746917860300126, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.009748458679475859, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 10.559703243656253, + -0.06933995160515433, + 0.13409481268876539, + -0.25453416624877684, + -0.09339691707056483, + -0.00944582956204468, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.021866683559479638, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 10.60933158542999, + -0.05320482494928357, + 0.08165484531392139, + -0.15157121758954228, + -0.04457075214341936, + -0.012279166627028998, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.04143225474540787, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.3449100447999933, + -0.038269239125313985, + 0.40789382632432863, + -0.813234037882281, + -0.20364398146863608, + 0.0013113631258636054, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.01824972784950946, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 90.0, + rows: 120, + lower: [ + 10.717934141378654, + -0.08228926450496746, + 0.012336268964125348, + -0.13988211404720818, + -0.3076403802731068, + -0.06033536582230862, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.09937669159771208, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 10.891541007009502, + -0.027905864283035566, + -0.1356888996398463, + -0.46603046061674286, + 0.0779756166300174, + -0.00026402861616535607, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.007807850441808824, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 11.100085942883167, + -0.06933995160515433, + 0.13379492079146577, + -0.25453416624877684, + -0.09339691707056483, + -0.004335336748118172, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.017569608465613187, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 11.14789998897887, + -0.05320482494928357, + 0.07481897687914642, + -0.15157121758954228, + -0.04457075214341936, + -0.009688309684690744, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.03749114093706316, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.3263577201257119, + -0.038269239125313985, + 0.38818649413592055, + -0.813234037882281, + -0.20364398146863608, + -0.009660059623909615, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.0219893987316943, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, + Qpv2Knot { + target: 95.0, + rows: 120, + lower: [ + 11.6453790233237, + -0.08228926450496746, + 0.0295468177833915, + -0.13988211404720818, + -0.3076403802731068, + -0.04499213758841707, + 0.24846174892886125, + 0.10118970836416148, + 0.01714749498939879, + -0.04059456546624838, + 0.023948490246457652, + 0.032747942818945344, + 0.009869219636491513, + -0.01746895538671726, + -0.09670370345795948, + 0.08243659064140416, + -0.05761006875297063, + 0.1397602914320146, + -0.12825290922215582, + -0.1790292259105136, + ], + median: [ + 11.805206342247498, + -0.027905864283035566, + -0.14697890329333843, + -0.46603046061674286, + 0.0779756166300174, + 0.004369880679768804, + 0.38046624283275293, + 0.0775637003066588, + 0.0010359257772471573, + -0.13721640679997588, + -0.01188775720990278, + -0.009256909203737758, + -0.013510311358363936, + 0.00833541366196533, + -0.004490358747427376, + 0.010611252598317671, + 0.04738672782396992, + 0.05164329678211175, + 0.00785653659463203, + -0.186589504365656, + ], + candidate: [ + 12.023876268862551, + -0.06933995160515433, + 0.13328225220684728, + -0.25453416624877684, + -0.09339691707056483, + 0.004401108421659668, + 0.08909729054020864, + 0.07452275903068231, + 0.0015514437296684763, + -0.20180215242465555, + 0.0028171503107653774, + 0.008544032212880864, + -0.00631294438531654, + 0.015266467397116429, + -0.010223710072863377, + -0.014991574919852809, + 0.07821162327823956, + 0.059255403532592794, + 0.0046808983555065185, + -0.16926610160313862, + ], + upper: [ + 12.068588756010806, + -0.05320482494928357, + 0.06313298260370558, + -0.15157121758954228, + -0.04457075214341936, + -0.00525921048748719, + -0.00469827180321417, + 0.053429486870360574, + -0.003065210953815426, + -0.1170463120141785, + 0.0024890621194557138, + 0.012482006834372618, + 0.014671341805076553, + 0.020702418247172033, + -0.03075376238119675, + -0.01835549989417961, + 0.07350561505793092, + 0.08129301706816137, + 0.0036466903063132336, + -0.15396948678804814, + ], + beta: [ + -0.2946423116142096, + -0.038269239125313985, + 0.3544965872374229, + -0.813234037882281, + -0.20364398146863608, + -0.02841583069683718, + 0.6638402356517423, + 0.0155425400805581, + -0.017248888193020114, + -0.0413544204655544, + -0.0007077226572284477, + 0.027678918068457018, + 3.604402143431094e-05, + 0.017872920354533223, + 0.028382408330727544, + -0.05328027163908902, + 0.05645081430217192, + -0.05734065113534405, + -0.050635552274912655, + 0.030131288157661782, + ], + saturation: [ + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + ], + }, +]; diff --git a/JPXL/crates/jpxl-encode-policy/src/rate.rs b/JPXL/crates/jpxl-encode-policy/src/rate.rs index f55281b0..0e61b58e 100644 --- a/JPXL/crates/jpxl-encode-policy/src/rate.rs +++ b/JPXL/crates/jpxl-encode-policy/src/rate.rs @@ -700,14 +700,14 @@ fn cold_step(current: Rung, up: bool) -> Rung { /// lower segment. Interpolating on the index therefore aims badly across that /// kink — which is exactly where high-rate targets live. This quantity is /// smooth and strictly increasing across the whole ladder. -pub(crate) fn effective_scale(rung: Rung) -> u64 { +pub fn effective_scale(rung: Rung) -> u64 { let (scale, mul) = rung_fields(rung); u64::from(scale) * u64::from(mul) } /// The inverse of [`effective_scale`], rounded down to the finest /// representable rung whose effective scale does not exceed `scale`. -pub(crate) fn rung_for_effective_scale(scale: u64) -> Rung { +pub fn rung_for_effective_scale(scale: u64) -> Rung { let max = u64::from(MAX_GLOBAL_SCALE); if scale <= max { return Rung::new(u32::try_from(scale.saturating_sub(1)).unwrap_or(u32::MAX)); From 0fd5b3b0e89c398f97532341a6ba94fa2892251f Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:54:58 +0800 Subject: [PATCH 04/27] feat(jpxl, jpxl-cli): --quality is a hard floor; fallbacks, trace/2, quality-ladder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 0 of the one-shot program: an encode whose bounded search cannot verify the requested score now fails at the public surface — Error::TargetNotMet carrying a QualityMiss (kind, requested and best verified scores, probe/price counts, metric version, trace), CLI exit 1 with no output file — instead of writing an under-target stream with exit 0. Encoder::with_quality_fallback / --quality-fallback opt into the two explicit alternatives: lossless (mathematically lossless stream, status fallback_lossless) and best-effort (the previous behavior, now explicit, under its true status). A refused encode still appends its quality-trace record so failed searches remain calibration input; every met-path stream is byte-identical. Calibration surfaces for PR 1-4: the trace is now jpxl.quality-trace/2 (work, prediction and transform_features blocks; /1 stays readable by the harness); new 'jpxl quality-ladder' subcommand (fresh production-policy plans at pinned effective scales, canonically scored, optionally exact-priced, JSONL); 'jpxl features --transform-summary'. The CLI forwards the policy crate's one-shot-controller feature. Decision record: jpegxl-rs.decision.quality-miss-fallback-semantics. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- JPXL/crates/jpxl-cli/Cargo.toml | 3 + JPXL/crates/jpxl-cli/src/main.rs | 343 ++++++++++++++++++++-- JPXL/crates/jpxl-cli/tests/cli_quality.rs | 100 +++++++ JPXL/crates/jpxl/src/lib.rs | 208 ++++++++++++- JPXL/crates/jpxl/tests/quality_encode.rs | 129 +++++++- JPXL/docs/CHANGELOG.md | 39 +++ README.md | 2 +- 7 files changed, 776 insertions(+), 48 deletions(-) diff --git a/JPXL/crates/jpxl-cli/Cargo.toml b/JPXL/crates/jpxl-cli/Cargo.toml index b937fa85..fecaeec4 100644 --- a/JPXL/crates/jpxl-cli/Cargo.toml +++ b/JPXL/crates/jpxl-cli/Cargo.toml @@ -28,6 +28,9 @@ simd = ["jpxl-encode/simd", "jpxl-encode-policy/simd", "jpxl-core/simd"] # Compatibility feature for explicitly forwarding Fast rate-preset support. # Policy enables it normally; Quality remains the runtime default. anchor-sketch = ["jpxl-encode-policy/anchor-sketch"] +# One-shot program PR 5: forwards the policy crate's feature-gated +# common-case one-shot start to the binary, for promotion A/B runs. +one-shot-controller = ["jpxl-encode-policy/one-shot-controller"] # Exposes the exhaustive-reference `quality` lossy effort on `--effort` / # `--lossy-preset`. Forwards to `jpxl/quality-effort`; off by default. quality-effort = ["jpxl/quality-effort"] diff --git a/JPXL/crates/jpxl-cli/src/main.rs b/JPXL/crates/jpxl-cli/src/main.rs index 1548c904..74e29425 100644 --- a/JPXL/crates/jpxl-cli/src/main.rs +++ b/JPXL/crates/jpxl-cli/src/main.rs @@ -47,8 +47,17 @@ Usage: jpxl analyze-atlas Export the diagnostic AnalysisAtlasV2; this research command does not affect encoding - jpxl features [--json] Print the quality controller's frame source - features as one JSON line (calibration tool) + jpxl features [--json] [--transform-summary] + Print the quality controller's frame source + features as one JSON line (calibration tool); + --transform-summary adds the DCT8-derived + transform features + jpxl quality-ladder --scales s1,s2,... [--effort fast|balanced] [--price] + [--threads N] + Build the production quality pixel plan + fresh at each effective scale, score each + canonically, optionally exact-price it, and + print JSONL (oracle-label calibration tool) jpxl bench [opts] Time one encode path (see `jpxl bench --help`) jpxl --help Show this message jpxl --version Show the version @@ -80,6 +89,13 @@ Lossy options (8- or 16-bit RGB; any one selects the VarDCT path): effort's default (fast 70, balanced 85). This is the normal way to ask for lossy output. --lossy --quality with the effort's default score. + --quality-fallback What to emit when the bounded search cannot + verify the requested score: lossless (a + mathematically lossless stream) or + best-effort (the finest verified under-target + stream, reported by its true score). Without + this flag such an encode fails, exits 1, and + writes nothing. --effort Lossy effort fast|balanced (search-latency budget, also picks the --quality default), or a digit 1..9 for lossless Modular effort. @@ -254,6 +270,7 @@ fn run(args: &[String]) -> u8 { "compare" => cmd_compare(rest), "analyze-atlas" => cmd_analyze_atlas(rest), "features" => cmd_features(rest), + "quality-ladder" => cmd_quality_ladder(rest), "bench" => cmd_bench(rest), other => { fail(&format!("unknown command `{other}`")); @@ -474,6 +491,9 @@ fn cmd_encode(args: &[String]) -> u8 { // `Some(score)` is an explicit score, inner `None` means "use the effort's // default score". let mut quality: Option> = None; + // What `--quality` emits when the bounded search cannot verify the score; + // `Refuse` (fail, write nothing) unless `--quality-fallback` says otherwise. + let mut quality_fallback = jpxl::QualityFallback::Refuse; // Fixed-quantizer expert mode. let mut global_scale: Option = None; // Lossy effort (search-latency budget); also picks the `--quality` default. @@ -524,6 +544,20 @@ fn cmd_encode(args: &[String]) -> u8 { } } "--lossy" => quality = Some(None), + "--quality-fallback" => { + let Some(mode) = rest.next() else { + fail("`--quality-fallback` needs `lossless` or `best-effort`"); + return EXIT_ERROR; + }; + quality_fallback = match mode.as_str() { + "lossless" => jpxl::QualityFallback::Lossless, + "best-effort" => jpxl::QualityFallback::BestEffort, + _ => { + fail("`--quality-fallback` needs `lossless` or `best-effort`"); + return EXIT_ERROR; + } + }; + } "--global-scale" => { let Some(value) = rest.next().and_then(|v| v.parse::().ok()) else { fail("`--global-scale` needs a positive representable integer"); @@ -924,7 +958,7 @@ fn cmd_encode(args: &[String]) -> u8 { let mut mode_label: Option = None; let encoded = if let Some(explicit) = quality { let score = explicit.unwrap_or_else(|| lossy_effort.default_score()); - match encode_quality(&image, score, &options, lossy_effort) { + match encode_quality(&image, score, &options, lossy_effort, quality_fallback) { Ok((bytes, line, mode)) => { perceptual_line = Some(line); mode_label = Some(mode); @@ -2133,13 +2167,30 @@ fn cmd_analyze_atlas(args: &[String]) -> u8 { /// `jpxl features [--json]`: print the quality controller's frame /// source features as one JSON line, for the initial-rung calibration tooling. fn cmd_features(args: &[String]) -> u8 { - let input = match args { - [input] => input, - [input, flag] | [flag, input] if flag == "--json" => input, - _ => { - fail("`features` takes an input raster and an optional `--json` flag"); - return EXIT_ERROR; + let mut input: Option<&String> = None; + let mut transform_summary = false; + for arg in args { + match arg.as_str() { + "--json" => {} + "--transform-summary" => transform_summary = true, + other if other.starts_with("--") => { + fail( + "`features` takes an input raster and optional `--json` / `--transform-summary` flags", + ); + return EXIT_ERROR; + } + _ if input.is_none() => input = Some(arg), + _ => { + fail("`features` takes exactly one input raster"); + return EXIT_ERROR; + } } + } + let Some(input) = input else { + fail( + "`features` takes an input raster and optional `--json` / `--transform-summary` flags", + ); + return EXIT_ERROR; }; let bytes = match read_path(input) { Ok(bytes) => bytes, @@ -2169,7 +2220,224 @@ fn cmd_features(args: &[String]) -> u8 { image.height(), frame.is_grayscale(), ); - println!("{}", features.to_json()); + if transform_summary { + let mut request = jpxl_encode_policy::EncodeRequest::for_quality( + jpxl_encode_policy::RateSearchPreset::Balanced, + ); + request.bits_per_sample = image.bits_per_sample(); + match jpxl_encode_policy::transform_feature_summary(&frame, &request) { + Ok(summary) => println!( + "{{\"source_features\":{},\"transform_features\":{}}}", + features.to_json(), + summary.to_json() + ), + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + } + } else { + println!("{}", features.to_json()); + } + EXIT_OK +} + +/// `jpxl quality-ladder --scales s1,s2,... [--effort fast|balanced] [--price] +/// [--threads N] `: the one-shot program's oracle-label sweep. +/// +/// Builds the production quality pixel plan fresh at every requested +/// effective scale, scores each reconstruction canonically, optionally +/// exact-prices it, and prints one `jpxl.quality-ladder/1` JSONL record per +/// point after a header record carrying the source features. No navigation +/// and no output file: this is measurement for the offline crossing trainer +/// (`tools/quality_oracle_labels.py`), not an encoder mode. +fn cmd_quality_ladder(args: &[String]) -> u8 { + let mut scales: Vec = Vec::new(); + let mut effort = jpxl::Effort::Balanced; + let mut price = false; + let mut threads: Option = None; + let mut positional: Vec<&String> = Vec::new(); + let mut rest = args.iter(); + while let Some(arg) = rest.next() { + match arg.as_str() { + "--scales" => { + let Some(list) = rest.next() else { + fail("`--scales` needs a comma-separated list of effective scales"); + return EXIT_ERROR; + }; + for part in list.split(',') { + match part.trim().parse::() { + Ok(scale) if scale >= 1 => scales.push(scale), + _ => { + fail("`--scales` entries must be positive integers"); + return EXIT_ERROR; + } + } + } + } + "--effort" => { + let Some(mode) = rest.next() else { + fail("`--effort` needs fast or balanced"); + return EXIT_ERROR; + }; + effort = match mode.as_str() { + "fast" => jpxl::Effort::Fast, + "balanced" => jpxl::Effort::Balanced, + _ => { + fail("`--effort` needs fast or balanced"); + return EXIT_ERROR; + } + }; + } + "--price" => price = true, + "--threads" => { + let Some(value) = rest.next().and_then(|v| v.parse::().ok()) else { + fail("`--threads` needs a positive worker count"); + return EXIT_ERROR; + }; + if value == 0 { + fail("`--threads` needs a positive worker count"); + return EXIT_ERROR; + } + threads = Some(value); + } + other if other.starts_with("--") => { + fail(&format!("unknown quality-ladder option `{other}`")); + return EXIT_ERROR; + } + _ => positional.push(arg), + } + } + let [input] = positional.as_slice() else { + fail("`quality-ladder` takes exactly one input raster"); + return EXIT_ERROR; + }; + if scales.is_empty() { + fail("`quality-ladder` needs `--scales s1,s2,...`"); + return EXIT_ERROR; + } + if scales.len() > 4096 { + fail("`quality-ladder` caps a sweep at 4096 points"); + return EXIT_ERROR; + } + + let bytes = match read_path(input) { + Ok(bytes) => bytes, + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + }; + let image = match image_io::decode_input(&bytes, None) { + Ok(image) => image, + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + }; + let (width, height, bits_per_sample, rgb) = match image_to_rgb16(&image) { + Ok(parts) => parts, + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + }; + if width < jpxl_perceptual::MIN_DIMENSION || height < jpxl_perceptual::MIN_DIMENSION { + fail(&format!( + "{input}: below the perceptual metric's {0}x{0} floor", + jpxl_perceptual::MIN_DIMENSION + )); + return EXIT_ERROR; + } + + // Map requested effective scales onto ladder rungs, ascending, deduped. + let mut rungs: Vec = scales + .iter() + .map(|&scale| jpxl_encode_policy::rung_for_effective_scale(scale)) + .collect(); + rungs.sort_unstable(); + rungs.dedup(); + + let mut request = jpxl_encode_policy::EncodeRequest::for_quality(effort.into()); + if let Some(threads) = threads { + request.resources = jpxl_encode::EncodeResources::groups(threads); + } + request.bits_per_sample = bits_per_sample; + let executor = request.resources.executor(); + let frame = match jpxl_encode_policy::PreparedFrame::from_srgb16_with( + width, + height, + &rgb, + bits_per_sample, + Some(&executor), + ) { + Ok(frame) => frame, + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + }; + let mut evaluator = match jpxl_perceptual::PlanRenderEvaluator::from_srgb16( + width, + height, + &rgb, + bits_per_sample, + &executor, + ) { + Ok(evaluator) => evaluator, + Err(_) => { + fail(&format!( + "{input}: the frame cannot be scored by the perceptual metric" + )); + return EXIT_ERROR; + } + }; + let atlas = jpxl_encode_policy::AnalysisAtlas::analyze(&frame); + let features = jpxl_encode_policy::source_features(&atlas, width, height, frame.is_grayscale()); + use jpxl_encode_policy::PerceptualEvaluator as _; + println!( + "{{\"schema\":\"jpxl.quality-ladder/1\",\"input\":\"{}\",\"width\":{width},\ + \"height\":{height},\"bit_depth\":{bits_per_sample},\"effort\":\"{}\",\ + \"metric_version\":\"{}\",\"price\":{price},\"points\":{},\"source_features\":{}}}", + input.replace('\\', "/"), + effort_name(effort), + evaluator.metric_version(), + rungs.len(), + features.to_json(), + ); + let points = match jpxl_encode_policy::sweep_frame_perceptual( + &frame, + &atlas, + &request, + &rungs, + price, + &mut evaluator, + &executor, + ) { + Ok(points) => points, + Err(error) => { + fail(&format!("{input}: {error}")); + return EXIT_ERROR; + } + }; + for p in points { + println!( + "{{\"rung\":{},\"global_scale\":{},\"hf_mul\":{},\"quant_lf\":{},\ + \"effective_scale\":{},\"score\":{},\"bytes\":{},\"plan_ms\":{},\ + \"render_metric_ms\":{},\"price_ms\":{}}}", + p.rung.get(), + p.quantizer.global_scale.get(), + p.quantizer.hf_mul.get(), + p.quantizer.quant_lf.get(), + p.effective_scale, + p.score, + p.exact_bytes + .map_or_else(|| "null".to_owned(), |b| format!("{b}")), + p.plan_ms, + p.render_metric_ms, + p.price_ms, + ); + } EXIT_OK } @@ -2395,6 +2663,7 @@ fn perceptual_status_str(status: jpxl::PerceptualStatus) -> &'static str { jpxl::PerceptualStatus::UnderTargetWorkCap => "under_target_work_cap", jpxl::PerceptualStatus::RescuedFreshStructure => "rescued_fresh_structure", jpxl::PerceptualStatus::RoutedToLossless => "routed_to_lossless", + jpxl::PerceptualStatus::FallbackLossless => "fallback_lossless", jpxl::PerceptualStatus::UnsupportedTooSmall => "unsupported_too_small", } } @@ -2451,12 +2720,15 @@ fn image_to_rgb16(image: &jpxl_encode::Image) -> Result<(u32, u32, u32, Vec /// /// Returns the codestream, the perceptual report line, and the summary mode /// string. A score of 100 routes to the lossless encoder; anything lower runs -/// the quality controller. +/// the quality controller. When the controller cannot verify the score and +/// `fallback` is [`jpxl::QualityFallback::Refuse`], this returns `Err` and +/// the caller writes nothing. fn encode_quality( image: &jpxl_encode::Image, score: f64, options: &jpxl_encode::EncodeOptions, effort: jpxl::Effort, + fallback: jpxl::QualityFallback, ) -> Result<(Vec, String, String), String> { let (width, height, bits_per_sample, rgb) = image_to_rgb16(image)?; let encoder = jpxl::Encoder::new() @@ -2464,37 +2736,50 @@ fn encode_quality( .with_container(options.container) .with_jxlp_fragment_size(options.jxlp_fragment_size) .with_effort(effort) + .with_quality_fallback(fallback) .with_ssimulacra2_score(score) .map_err(|error| error.to_string())?; match encoder.encode_rgb16_reported(width, height, bits_per_sample, &rgb) { Ok((bytes, jpxl::EncodeReport::Perceptual(outcome))) => { let line = format_perceptual_line(&outcome, effort_name(effort)); - // `JPXL_QUALITY_TRACE=` appends the controller's - // `jpxl.quality-trace/1` record, the harness's and the - // predictor calibration's input. - if let (Some(path), Some(trace)) = ( - std::env::var_os("JPXL_QUALITY_TRACE"), - outcome.trace_json.as_deref(), - ) { - use std::io::Write as _; - let appended = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .and_then(|mut file| writeln!(file, "{trace}")); - if let Err(error) = appended { - eprintln!("warning: could not write JPXL_QUALITY_TRACE: {error}"); - } - } - let mode = format!("lossy VarDCT (perceptual), ssimulacra2>={score:.4}"); + append_quality_trace(outcome.trace_json.as_deref()); + let mode = if outcome.status == jpxl::PerceptualStatus::FallbackLossless { + format!("lossless Modular (fallback: ssimulacra2>={score:.4} unmet)") + } else { + format!("lossy VarDCT (perceptual), ssimulacra2>={score:.4}") + }; Ok((bytes, line, mode)) } Ok(_) => Err("perceptual encode produced an unexpected report".to_owned()), Err(jpxl::Error::Unsupported(what)) => Err(format!("ssimulacra2>={score:.4}: {what}")), + // A refused under-target encode still ran a full search; its trace is + // calibration input, so the harness sees it even though nothing is + // written. + Err(jpxl::Error::TargetNotMet(miss)) => { + append_quality_trace(miss.trace_json.as_deref()); + Err(jpxl::Error::TargetNotMet(miss).to_string()) + } Err(error) => Err(error.to_string()), } } +/// Appends one `jpxl.quality-trace/2` record to the `JPXL_QUALITY_TRACE` +/// path, when both exist. The harness's and the predictor calibration's +/// input; a write failure warns and never fails the encode. +fn append_quality_trace(trace: Option<&str>) { + if let (Some(path), Some(trace)) = (std::env::var_os("JPXL_QUALITY_TRACE"), trace) { + use std::io::Write as _; + let appended = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&path) + .and_then(|mut file| writeln!(file, "{trace}")); + if let Err(error) = appended { + eprintln!("warning: could not write JPXL_QUALITY_TRACE: {error}"); + } + } +} + /// Wraps a lossy codestream the way [`jpxl_encode::encode`] wraps a lossless /// one, so `--container` and `--jxlp` reach the VarDCT paths too. /// diff --git a/JPXL/crates/jpxl-cli/tests/cli_quality.rs b/JPXL/crates/jpxl-cli/tests/cli_quality.rs index 073872af..dbfd1b15 100644 --- a/JPXL/crates/jpxl-cli/tests/cli_quality.rs +++ b/JPXL/crates/jpxl-cli/tests/cli_quality.rs @@ -121,6 +121,106 @@ fn conflicting_targets_rejected() { assert!(stderr.contains("one lossy target"), "{stderr}"); } +/// The 64x64 gradient tops out below 99.5 on this metric, so `--quality +/// 99.5 --effort fast` is a deterministic miss. Without a fallback the CLI +/// must fail, exit 1, and leave no output file behind. +#[test] +fn an_unmet_quality_refuses_by_default_and_writes_nothing() { + let (input, output) = fixture("quality_refused", 64, 64); + let out = encode( + &input, + &output, + &["--effort", "fast", "--quality", "99.5", "--threads", "1"], + ); + assert_eq!(out.status.code(), Some(1)); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!( + stderr.contains("quality target not met"), + "the refusal names itself: {stderr}" + ); + assert!( + !output.exists(), + "a refused encode must not create the output file" + ); +} + +#[test] +fn quality_fallback_lossless_emits_and_says_so() { + let (input, output) = fixture("quality_fb_lossless", 64, 64); + let out = encode( + &input, + &output, + &[ + "--effort", + "fast", + "--quality", + "99.5", + "--quality-fallback", + "lossless", + "--threads", + "1", + ], + ); + assert_eq!( + out.status.code(), + Some(0), + "{}", + String::from_utf8_lossy(&out.stderr) + ); + let stdout = String::from_utf8_lossy(&out.stdout); + assert!( + stdout.contains("status=fallback_lossless") && stdout.contains("achieved=100.0000"), + "the fallback is reported: {stdout}" + ); + assert!(output.exists() && output.metadata().map(|m| m.len()).unwrap_or(0) > 0); + let info = run(&["info", output.to_str().expect("utf8 path")]); + assert_eq!(info.status.code(), Some(0), "output is a JPEG XL stream"); +} + +#[test] +fn quality_fallback_best_effort_emits_the_under_target_stream() { + let (input, output) = fixture("quality_fb_best_effort", 64, 64); + let out = encode( + &input, + &output, + &[ + "--effort", + "fast", + "--quality", + "99.5", + "--quality-fallback", + "best-effort", + "--threads", + "1", + ], + ); + assert_eq!( + out.status.code(), + Some(0), + "{}", + String::from_utf8_lossy(&out.stderr) + ); + let stdout = String::from_utf8_lossy(&out.stdout); + assert!( + stdout.contains("status=saturated_top"), + "the true terminal status is reported: {stdout}" + ); + assert!(output.exists() && output.metadata().map(|m| m.len()).unwrap_or(0) > 0); +} + +#[test] +fn a_bad_quality_fallback_value_is_rejected() { + let (input, output) = fixture("quality_fb_bad", 64, 64); + let out = encode( + &input, + &output, + &["--quality", "--quality-fallback", "nope"], + ); + assert_eq!(out.status.code(), Some(1)); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(stderr.contains("`--quality-fallback` needs"), "{stderr}"); +} + #[test] fn global_scale_encodes() { let (input, output) = fixture("global_scale", 64, 64); diff --git a/JPXL/crates/jpxl/src/lib.rs b/JPXL/crates/jpxl/src/lib.rs index 194259ce..d7553bbc 100644 --- a/JPXL/crates/jpxl/src/lib.rs +++ b/JPXL/crates/jpxl/src/lib.rs @@ -9,10 +9,15 @@ //! //! [`Encoder::with_ssimulacra2_score`] is the normal way to ask for lossy //! output: name the minimum perceptual quality and let the encoder find the -//! bytes. [`Encoder::with_target_bpp`] / [`Encoder::with_target_bytes`] (an -//! exact size) and [`Encoder::with_global_scale`] (a pinned quantizer) are -//! expert modes. Exactly one lossy target may be set; call -//! [`Encoder::lossless`] to reset before choosing another. +//! bytes. The score is a hard floor: a successful perceptual encode has had +//! its actual reconstruction canonically scored at or above the request. +//! When the bounded controller cannot verify such a stream, the encode fails +//! with [`Error::TargetNotMet`] unless [`Encoder::with_quality_fallback`] +//! opted into an explicit fallback. [`Encoder::with_target_bpp`] / +//! [`Encoder::with_target_bytes`] (an exact size) and +//! [`Encoder::with_global_scale`] (a pinned quantizer) are expert modes. +//! Exactly one lossy target may be set; call [`Encoder::lossless`] to reset +//! before choosing another. //! //! # Lossless RGB //! @@ -68,6 +73,14 @@ pub enum Error { /// honoured in a later release, so callers can special-case it rather than /// treat it as their own bug. Unsupported(&'static str), + /// A perceptual encode whose bounded controller stopped without any + /// stream canonically verified at the requested minimum score, while the + /// encoder was left at [`QualityFallback::Refuse`] (the default). + /// + /// Nothing was emitted. The carried [`QualityMiss`] says how close the + /// search got and why it stopped; [`Encoder::with_quality_fallback`] + /// selects what to emit instead of failing. + TargetNotMet(QualityMiss), } impl fmt::Display for Error { @@ -77,6 +90,19 @@ impl fmt::Display for Error { Self::Encode(error) => write!(f, "encode failed: {error}"), Self::Policy(error) => write!(f, "encode policy failed: {error}"), Self::InvalidOption(message) | Self::Unsupported(message) => f.write_str(message), + Self::TargetNotMet(miss) => { + let why = match miss.kind { + QualityMissKind::LadderSaturated => "even the finest quantizer", + QualityMissKind::WorkBudgetExhausted => "the probe budget's best candidate", + }; + write!( + f, + "quality target not met: {why} verified {:.4}, below the requested \ + minimum {:.4} ({}); nothing was written — choose a quality fallback \ + to emit anyway", + miss.best_score, miss.requested_score, miss.metric_version, + ) + } } } } @@ -87,7 +113,7 @@ impl std::error::Error for Error { Self::Decode(error) => Some(error), Self::Encode(error) => Some(error), Self::Policy(error) => Some(error), - Self::InvalidOption(_) | Self::Unsupported(_) => None, + Self::InvalidOption(_) | Self::Unsupported(_) | Self::TargetNotMet(_) => None, } } } @@ -203,6 +229,64 @@ pub struct RateSummary { pub full_prices: u32, } +/// What a perceptual encode emits when the bounded controller stops without +/// any stream canonically verified at the requested minimum score. +/// +/// The score is a hard floor: an under-target stream is never an ordinary +/// success. [`Self::Refuse`] (the default) turns such an encode into +/// [`Error::TargetNotMet`]; the two alternatives are explicit contracts a +/// caller opts into with [`Encoder::with_quality_fallback`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum QualityFallback { + /// Fail with [`Error::TargetNotMet`] and emit nothing. The default. + #[default] + Refuse, + /// Emit a mathematically lossless stream instead, at the encoder's + /// lossless effort, reported as [`PerceptualStatus::FallbackLossless`]. + /// The floor holds (lossless trivially meets any score) at whatever byte + /// cost lossless carries — often far more than the lossy request implied. + Lossless, + /// Emit the finest canonically verified under-target stream, reported by + /// [`PerceptualStatus::SaturatedTop`] or + /// [`PerceptualStatus::UnderTargetWorkCap`] with its true + /// `achieved_score`. This knowingly weakens the floor for this encode; + /// the report says so explicitly. + BestEffort, +} + +/// Why a refused perceptual encode could not verify the requested score. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum QualityMissKind { + /// Even the quantizer ladder's finest rung scored below the request: the + /// lossy path cannot reach this score on this image. + LadderSaturated, + /// The effort's bounded probe budget ran out before any candidate met + /// the request; a finer candidate may exist but was never verified. + WorkBudgetExhausted, +} + +/// What a refused perceptual encode verified before it stopped, carried by +/// [`Error::TargetNotMet`]. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct QualityMiss { + /// Why the search stopped short. + pub kind: QualityMissKind, + /// The minimum score the caller asked for. + pub requested_score: f64, + /// The canonical score of the finest verified candidate — the closest + /// the bounded search got to the request. + pub best_score: f64, + /// The metric definition the scores are on. + pub metric_version: MetricVersion, + /// How many candidate streams were scored. + pub probes: u32, + /// How many exact writer prices were paid. + pub prices: u32, + /// The controller's `jpxl.quality-trace/2` record. + pub trace_json: Option, +} + /// Why a perceptual encode stopped where it did: the quality controller's /// terminal state, or one of the two routes that bypass it. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -215,15 +299,23 @@ pub enum PerceptualStatus { MetWorkCap, /// The coarsest quantizer already exceeds the score (a floor). SaturatedFloor, - /// The finest quantizer still misses the score (a ceiling). + /// The finest quantizer still misses the score (a ceiling). Reported only + /// under [`QualityFallback::BestEffort`]; the default refuses instead + /// with [`Error::TargetNotMet`]. SaturatedTop, /// The bounded controller ran out of probes before any candidate met the /// score; the emitted stream's `achieved_score` is below the request. + /// Reported only under [`QualityFallback::BestEffort`]; the default + /// refuses instead with [`Error::TargetNotMet`]. UnderTargetWorkCap, /// A bounded fresh-structure rescue supplied the selected stream. RescuedFreshStructure, /// A score of 100 was satisfied by the mathematically lossless path. RoutedToLossless, + /// The bounded lossy controller could not verify the requested score and + /// [`QualityFallback::Lossless`] emitted a mathematically lossless stream + /// in its place. `probes`/`prices` count the failed lossy search. + FallbackLossless, /// The frame is too small for the perceptual path to apply. UnsupportedTooSmall, } @@ -247,7 +339,7 @@ pub struct PerceptualOutcome { pub prices: u32, /// Whether the quantizer ladder ran out of rungs. pub saturated: bool, - /// The controller's `jpxl.quality-trace/1` record, when a search ran. + /// The controller's `jpxl.quality-trace/2` record, when a search ran. pub trace_json: Option, } @@ -293,6 +385,7 @@ pub struct Encoder { resources: jpxl_encode::EncodeResources, container: bool, jxlp_fragment_size: Option, + quality_fallback: QualityFallback, } impl Default for Encoder { @@ -304,6 +397,7 @@ impl Default for Encoder { resources: jpxl_encode::EncodeResources::default(), container: false, jxlp_fragment_size: None, + quality_fallback: QualityFallback::Refuse, } } } @@ -377,7 +471,11 @@ impl Encoder { /// contract). /// /// A score of 100 means mathematically lossless. The score must be finite - /// and in `0.0..=100.0`. + /// and in `0.0..=100.0`. The score is a hard floor: when the bounded + /// controller cannot canonically verify a stream at or above it, the + /// encode fails with [`Error::TargetNotMet`] unless + /// [`with_quality_fallback`](Self::with_quality_fallback) chose an + /// explicit fallback. pub fn with_ssimulacra2_score(self, score: f64) -> Result { let target = PerceptualTarget::new(PerceptualMetric::Ssimulacra2, score) .map_err(|_| Error::InvalidOption("ssimulacra2 score must be finite and in 0..=100"))?; @@ -429,6 +527,18 @@ impl Encoder { self } + /// Choose what a perceptual encode emits when its bounded controller + /// cannot verify a stream at the requested minimum score. + /// + /// The default is [`QualityFallback::Refuse`]: such an encode fails with + /// [`Error::TargetNotMet`] rather than returning under-target bytes as a + /// success. + #[must_use] + pub const fn with_quality_fallback(mut self, fallback: QualityFallback) -> Self { + self.quality_fallback = fallback; + self + } + /// Choose the lossless Modular search effort, from 1 (fastest) to 9 /// (densest). pub fn with_lossless_effort(mut self, effort: u8) -> Result { @@ -621,8 +731,15 @@ impl Encoder { where F: FnOnce() -> Result>, { - let routed = |status: PerceptualStatus| -> Result<(Vec, EncodeReport)> { - let bytes = lossless()?; + // A lossless stream trivially meets any score, so every lossless + // route reports `achieved_score` 100; `probes`/`prices` are those of + // whatever lossy search ran first (zero on the two early routes). + let routed = |bytes: Vec, + status: PerceptualStatus, + probes: u32, + prices: u32, + trace_json: Option| + -> (Vec, EncodeReport) { let exact = u64::try_from(bytes.len()).unwrap_or(u64::MAX); let outcome = PerceptualOutcome { requested_score: target.minimum_score, @@ -630,21 +747,33 @@ impl Encoder { exact_bytes: exact, metric_version: target.metric.version(), status, - probes: 0, - prices: 0, + probes, + prices, saturated: false, - trace_json: None, + trace_json, }; - Ok((bytes, EncodeReport::Perceptual(outcome))) + (bytes, EncodeReport::Perceptual(outcome)) }; // `minimum_score` is validated into `0.0..=100.0`, so `>= 100.0` is the // exact-lossless request. if target.minimum_score >= 100.0 { - return routed(PerceptualStatus::RoutedToLossless); + return Ok(routed( + lossless()?, + PerceptualStatus::RoutedToLossless, + 0, + 0, + None, + )); } let (width, height, bits) = source.dimensions(); if width < jpxl_perceptual::MIN_DIMENSION || height < jpxl_perceptual::MIN_DIMENSION { - return routed(PerceptualStatus::UnsupportedTooSmall); + return Ok(routed( + lossless()?, + PerceptualStatus::UnsupportedTooSmall, + 0, + 0, + None, + )); } let mut request = jpxl_encode_policy::EncodeRequest::for_quality(self.effort.into()); @@ -705,6 +834,40 @@ impl Encoder { Effort::Quality => "quality", }; let trace_json = Some(outcome.trace_json(effort_name)); + + // The hard floor: a stream the controller verified below the request + // is never an ordinary success. What happens instead is the encoder's + // [`QualityFallback`]; only `BestEffort` proceeds to emit it. + if matches!( + outcome.status, + jpxl_encode_policy::QualityStatus::SaturatedTop + | jpxl_encode_policy::QualityStatus::UnderTargetWorkCap + ) { + match self.quality_fallback { + QualityFallback::Refuse => { + return Err(Error::TargetNotMet(QualityMiss { + kind: quality_miss_kind(outcome.status), + requested_score: target.minimum_score, + best_score: outcome.achieved_score, + metric_version: target.metric.version(), + probes: outcome.stats.pixel_probes, + prices: outcome.stats.exact_prices, + trace_json, + })); + } + QualityFallback::Lossless => { + return Ok(routed( + lossless()?, + PerceptualStatus::FallbackLossless, + outcome.stats.pixel_probes, + outcome.stats.exact_prices, + trace_json, + )); + } + QualityFallback::BestEffort => {} + } + } + let bytes = self.wrap_lossy(outcome.codestream, bits); let report = PerceptualOutcome { requested_score: target.minimum_score, @@ -787,6 +950,19 @@ impl Encoder { } } +/// Which [`QualityMissKind`] an under-target terminal status names. +/// +/// Only [`QualityStatus::SaturatedTop`](jpxl_encode_policy::QualityStatus) and +/// [`QualityStatus::UnderTargetWorkCap`](jpxl_encode_policy::QualityStatus) +/// are misses; every other status carries a verified at-or-above-target +/// stream and never reaches this mapping. +const fn quality_miss_kind(status: jpxl_encode_policy::QualityStatus) -> QualityMissKind { + match status { + jpxl_encode_policy::QualityStatus::SaturatedTop => QualityMissKind::LadderSaturated, + _ => QualityMissKind::WorkBudgetExhausted, + } +} + /// Distils a policy [`RateOutcome`](jpxl_encode_policy::RateOutcome) into the /// facade's [`RateSummary`]. fn rate_summary(outcome: &jpxl_encode_policy::RateOutcome) -> RateSummary { diff --git a/JPXL/crates/jpxl/tests/quality_encode.rs b/JPXL/crates/jpxl/tests/quality_encode.rs index bc68a219..e2481cee 100644 --- a/JPXL/crates/jpxl/tests/quality_encode.rs +++ b/JPXL/crates/jpxl/tests/quality_encode.rs @@ -13,7 +13,10 @@ clippy::cast_sign_loss )] -use jpxl::{Decoder, Effort, EncodeReport, Encoder, PerceptualStatus}; +use jpxl::{ + Decoder, Effort, EncodeReport, Encoder, Error, PerceptualStatus, QualityFallback, + QualityMissKind, +}; use jpxl_perceptual::{LinearRgbView, score_pair}; struct Rng(u32); @@ -147,7 +150,7 @@ fn every_effort_meets_every_target_and_reports_the_score_the_file_has() { outcome .trace_json .as_deref() - .is_some_and(|t| t.starts_with("{\"schema\":\"jpxl.quality-trace/1\"")) + .is_some_and(|t| t.starts_with("{\"schema\":\"jpxl.quality-trace/2\"")) ); let independent = rescore(w, h, &rgb, &bytes); assert!( @@ -231,6 +234,128 @@ fn tiny_frames_and_a_perfect_score_route_to_lossless() { ); } +/// The hard floor at the facade: a target the bounded Fast search cannot +/// verify is refused by default with [`Error::TargetNotMet`] and emits +/// nothing; the two fallbacks are explicit and say what they did. The 64x64 +/// gradient tops out near 99.13 on this metric, so 99.5 is a deterministic +/// miss. +#[test] +fn an_unmet_target_refuses_by_default_and_falls_back_only_on_request() { + let (w, h) = (64u32, 64u32); + let rgb = synthetic(w, h, 11); + let encoder = || { + Encoder::new() + .with_ssimulacra2_score(99.5) + .unwrap() + .with_effort(Effort::Fast) + .with_threads(1) + .unwrap() + }; + + // Default: refuse, with the miss fully described. + let refused = encoder().encode_rgb8_reported(w, h, &rgb); + let miss = match refused { + Err(Error::TargetNotMet(miss)) => miss, + other => panic!("expected TargetNotMet, got {other:?}"), + }; + assert_eq!(miss.kind, QualityMissKind::LadderSaturated); + assert!((miss.requested_score - 99.5).abs() < f64::EPSILON); + assert!(miss.best_score < 99.5, "{}", miss.best_score); + assert!(miss.probes >= 1 && miss.probes <= 4, "{}", miss.probes); + assert!( + miss.trace_json + .as_deref() + .is_some_and(|t| t.starts_with("{\"schema\":\"jpxl.quality-trace/2\"")) + ); + + // Lossless fallback: the floor holds (achieved 100), the status says how, + // and the bytes are exactly the lossless encoder's. + let (bytes, report) = encoder() + .with_quality_fallback(QualityFallback::Lossless) + .encode_rgb8_reported(w, h, &rgb) + .unwrap(); + let outcome = perceptual(&report); + assert_eq!(outcome.status, PerceptualStatus::FallbackLossless); + assert_eq!(outcome.achieved_score, Some(100.0)); + assert_eq!(outcome.probes, miss.probes); + assert_eq!( + bytes, + Encoder::new().lossless().encode_rgb8(w, h, &rgb).unwrap() + ); + + // Best effort: the under-target stream is emitted, but only with its + // true score and an explicit saturation status. + let (bytes, report) = encoder() + .with_quality_fallback(QualityFallback::BestEffort) + .encode_rgb8_reported(w, h, &rgb) + .unwrap(); + let outcome = perceptual(&report); + assert_eq!(outcome.status, PerceptualStatus::SaturatedTop); + assert!(outcome.saturated); + let achieved = outcome.achieved_score.expect("a measured score"); + assert!((achieved - miss.best_score).abs() < f64::EPSILON); + let independent = rescore(w, h, &rgb, &bytes); + assert!((independent - achieved).abs() < 1e-6); +} + +/// PR 4 wall measurement: the *in-search* cost of the transform summary, +/// which shares the cover's forward cache (unlike the standalone +/// `jpxl features --transform-summary` path, which pays for a throwaway +/// DCT8 pass). Ignored by default — run explicitly on a quiet machine: +/// `cargo test -p jpxl --profile fast-debug --test quality_encode +/// transform_shadow_wall -- --ignored --nocapture`. +#[test] +#[ignore = "timing measurement; run on a quiet machine"] +fn transform_shadow_wall_delta() { + use jpxl_encode_policy::request::{PerceptualMetric, PerceptualTarget}; + use jpxl_encode_policy::{ + AnalysisAtlas, EncodeRequest, PreparedFrame, QualityBudget, RateSearchPreset, + search_frame_perceptual_with_budget, + }; + use jpxl_perceptual::PlanRenderEvaluator; + + let (w, h) = (2400u32, 1800u32); + let rgb = synthetic(w, h, 21); + let request = EncodeRequest::for_quality(RateSearchPreset::Balanced); + let executor = request.resources.executor(); + let target = PerceptualTarget::new(PerceptualMetric::Ssimulacra2, 85.0).unwrap(); + let base = QualityBudget::for_preset(RateSearchPreset::Balanced); + + let measure = |transform_shadow: bool| { + let mut best = f64::INFINITY; + for _ in 0..3 { + let frame = PreparedFrame::from_srgb8_with(w, h, &rgb, Some(&executor)).unwrap(); + let atlas = AnalysisAtlas::analyze(&frame); + let mut ev = PlanRenderEvaluator::from_srgb8(w, h, &rgb, &executor).unwrap(); + let start = std::time::Instant::now(); + let outcome = search_frame_perceptual_with_budget( + &frame, + &atlas, + &request, + target, + &mut ev, + &executor, + QualityBudget { + transform_shadow, + ..base + }, + ) + .unwrap(); + assert!(outcome.achieved_score >= 85.0); + best = best.min(start.elapsed().as_secs_f64()); + } + best + }; + + let off = measure(false); + let on = measure(true); + eprintln!( + "transform_shadow off: {off:.3}s, on: {on:.3}s, delta {:+.3}s ({:+.1}% of the search)", + on - off, + (on - off) / off * 100.0 + ); +} + /// PR 5: the Balanced perceptual policy bank meets the target and is never /// larger than the baseline-only (fixed-policy) result at the same score. /// diff --git a/JPXL/docs/CHANGELOG.md b/JPXL/docs/CHANGELOG.md index 70eebea5..a6f9bc78 100644 --- a/JPXL/docs/CHANGELOG.md +++ b/JPXL/docs/CHANGELOG.md @@ -13,6 +13,45 @@ Format: [Keep a Changelog](https://keepachangelog.com/), semantic versioning. ## Unreleased +- 2026-08-25 — The one-shot crossing predictor is now the default `--quality` + controller seed: a generated transparent model (`qpv2-st-1`, source + + DCT8-summary features, trained on 130 image families) picks the first + fresh plan — its risk-adjusted candidate when confident, its median when + uncertain — and the bounded navigator continues under the same probe/price + caps with canonical verification before every emission, so the hard score + floor is unchanged. Promotion A/B on 441 never-tuned holdout cells: zero + floor violations on either arm, byte geomean 0.997 (locked 13-image + holdout) and 0.990 (50 painting families), reconstructions −18% and wall + −16% on the paintings, 12 MP anchor wall −21%. Known bounded regressions: + sub-kilobyte saturated fixtures (worst +131 bytes) and target-95 cells + (≤1.12× with higher achieved scores). Build `jpxl-encode-policy` without + default features for the legacy table-seeded controller. +- 2026-08-24 — Quality trace schema is now `jpxl.quality-trace/2`: adds a + whole-search `work` block (pixel plans, reconstructions, metric + evaluations, entropy trainings, emissions — policy trials and the reducer + included), a shadow `prediction` block (null until a generated crossing + model is present), and a `transform_features` block (null unless the + research budget asked for it). `/1` traces stay readable by the harness. + New calibration tooling: `jpxl quality-ladder` (fresh production-policy + pixel plans at pinned effective scales, canonically scored, optionally + exact-priced, as JSONL), `jpxl features --transform-summary` (DCT8-derived + transform features), and `tools/quality_oracle_labels.py` / + `tools/quality_predictor_v2.py` (oracle-label sweeps over the full + effective ladder and the trained crossing predictor). The quality-corpus + manifest gains `family_id`/`variant_id`/`generator_family`/ + `source_capture_id`, and the fixture generator fails if any image family + crosses a split. +- 2026-08-24 — The `--quality` score is now a hard floor at the public + surface: an encode whose bounded search cannot verify the requested score + fails (`quality target not met` on stderr, exit 1, no output file) instead + of writing an under-target stream with exit 0. `--quality-fallback + lossless` emits a mathematically lossless stream instead + (`status=fallback_lossless`); `--quality-fallback best-effort` emits the + finest verified under-target stream with its true `saturated_top` / + `under_target_work_cap` status (the previous behavior, now explicit). The + `jpxl` facade gained `Error::TargetNotMet(QualityMiss)`, `QualityFallback`, + and `Encoder::with_quality_fallback`; a refused encode still appends its + `JPXL_QUALITY_TRACE` record, and every met-path stream is byte-identical. - 2026-08-22 — Lossy encoding now leads with quality: `jpxl encode --quality [N]` (alias `--ssimulacra2`, `--lossy`) sets a minimum SSIMULACRA2 score (0..100, 100 = lossless); `--bpp`, `--target-bytes`, and the new diff --git a/README.md b/README.md index 8bfe60c0..cb5ed2ca 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ a viewer would see it. `under_target_work_cap`, `saturated_floor`, `routed_to_lossless` (score 100) and `unsupported_too_small` (below the metric's 8×8 floor). Setting `JPXL_QUALITY_TRACE=` appends a machine-readable - `jpxl.quality-trace/1` record per encode: source features, the predicted + `jpxl.quality-trace/2` record per encode: source features, the predicted rung, every probe's quantizer/score/bytes, and wall time by phase. - **Determinism.** The same input produces the same codestream across worker counts: the metric reduces fixed-size row bands in fixed order, the renderer From e38060a3024d1a8d2ce55a81a8609105aac9d3f3 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:55:44 +0800 Subject: [PATCH 05/27] feat(tools): oracle-label sweeps, pooled crossing trainer, corpus intake, promotion A/B MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one-shot program's PR 2-3 calibration pipeline and the promotion instrument: - quality_oracle_labels.py: resumable ladder sweeps (sweep) and censoring-aware label extraction (labels) — coarsest-meeting-rung, log-loss interpolated crossing, local slope, priced neighbors; --time-budget-minutes gives a clean resumable stop for time-capped corpus runs. - quality_predictor_v2.py: the pooled ln-loss crossing trainer — IRLS smoothed-pinball quantiles, robust standardization, ridge by held-out p90, family-grouped CV (LOFO or salted 10-fold), a blind never-tuned ext-holdout split excluded from tuning and from the shipped model, route simulation, and Rust codegen for the policy crate's generated predictor (run cargo fmt after regenerating). - quality_corpus_extend.py: external-corpus intake — family grouping from filename stems, deterministic sha256 cal/dev/ext-holdout split, round-robin per-directory sampling, PPM conversion, a manifest under jpxl.codec-corpus-ext/1 (originals are never modified). - one_shot_promotion_ab.py: interleaved A/B of two jpxl builds over manifest splits — floor violations, byte geomean, work counters, wall — the memo section 11 promotion screen. - make-quality-guard-fixtures.py: family identity fields (family_id/variant_id/generator_family/source_capture_id), a guard that fails the build if any family crosses a split, and nine new fixtures (56 images / 38 families). - codec_compare.py: reads quality-trace/2 (and still /1), passes --quality-fallback best-effort so RD sweeps grade under-target cells instead of dying on the new hard floor. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- JPXL/tools/README-codec-compare.md | 25 +- JPXL/tools/codec_compare.py | 22 +- JPXL/tools/make-quality-guard-fixtures.py | 95 +- JPXL/tools/one_shot_promotion_ab.py | 167 +++ JPXL/tools/quality_corpus_extend.py | 227 ++++ JPXL/tools/quality_oracle_labels.py | 529 +++++++++ JPXL/tools/quality_predictor_v2.py | 1017 +++++++++++++++++ JPXL/tools/tests/test_codec_compare.py | 2 +- .../tests/test_make_quality_guard_fixtures.py | 15 +- .../tools/tests/test_quality_oracle_labels.py | 154 +++ JPXL/tools/tests/test_quality_predictor_v2.py | 200 ++++ 11 files changed, 2434 insertions(+), 19 deletions(-) create mode 100644 JPXL/tools/one_shot_promotion_ab.py create mode 100644 JPXL/tools/quality_corpus_extend.py create mode 100644 JPXL/tools/quality_oracle_labels.py create mode 100644 JPXL/tools/quality_predictor_v2.py create mode 100644 JPXL/tools/tests/test_quality_oracle_labels.py create mode 100644 JPXL/tools/tests/test_quality_predictor_v2.py diff --git a/JPXL/tools/README-codec-compare.md b/JPXL/tools/README-codec-compare.md index 5732ee09..ccdc0350 100644 --- a/JPXL/tools/README-codec-compare.md +++ b/JPXL/tools/README-codec-compare.md @@ -28,23 +28,30 @@ A jpxl **quality** curve row adds: `jpxl encode --quality Q [--effort fast|balanced] --threads N in.ppm out.jxl` selects the perceptual VarDCT path with a minimum SSIMULACRA2 score `Q` -(0..100; 100 = lossless) and prints exactly one line: +(0..100; 100 = lossless) and, on success, prints exactly one line: ``` quality_target=85.0000 achieved=85.1372 bytes=412883 metric=ssimulacra2-jpxl-1 effort=balanced probes=3 prices=2 status=met ``` `status` is one of: `met`, `met_adjacent_rungs`, `met_work_cap`, -`saturated_floor`, `saturated_top`, `rescued_fresh_structure`, -`routed_to_lossless`, `unsupported_too_small`. - -When `JPXL_QUALITY_TRACE=` is set, a `jpxl.quality-trace/1` JSONL file is -written. The harness sets this per curve point (under the work dir) unless +`saturated_floor`, `saturated_top`, `under_target_work_cap`, +`rescued_fresh_structure`, `routed_to_lossless`, `fallback_lossless`, +`unsupported_too_small`. + +The score is a hard floor by default: an encode whose bounded search cannot +verify `Q` exits 1 with `quality target not met` on stderr and writes no +output file. The harness therefore passes `--quality-fallback best-effort`, +which emits the finest verified under-target stream with its true +`saturated_top` / `under_target_work_cap` status so a curve point is always +measurable (`--quality-fallback lossless` instead emits a lossless stream as +`fallback_lossless`). + +When `JPXL_QUALITY_TRACE=` is set, a `jpxl.quality-trace/2` JSONL file is +written (also for a refused encode — the failed search is still calibration +input). The harness sets this per curve point (under the work dir) unless `--no-quality-trace` is given, and merges `wall_by_phase` into the record. -> Note: targets below 100 currently error from the binary ("perceptual quality -> targets land in PR 4"). `--quality 100` runs live (status `routed_to_lossless`). - ## Subcommands ### `curve` — quality axis diff --git a/JPXL/tools/codec_compare.py b/JPXL/tools/codec_compare.py index 6fc98ac5..b79b8c90 100644 --- a/JPXL/tools/codec_compare.py +++ b/JPXL/tools/codec_compare.py @@ -43,7 +43,10 @@ # /2 adds a same-effort JPXL rate baseline and keeps controller-achieved scores # separate from the common decoded in-tree score used for matched-rate work. QUALITY_SUMMARY_SCHEMA = "jpxl.codec-quality-summary/2" -QUALITY_TRACE_SCHEMA = "jpxl.quality-trace/1" +# Accepted quality-trace schemas, preferred first: /2 adds the whole-search +# `work` block and the shadow `prediction` block; /1 traces predate them and +# stay readable. +QUALITY_TRACE_SCHEMAS = ("jpxl.quality-trace/2", "jpxl.quality-trace/1") METRIC_VARIATION_SCHEMA = "jpxl.metric-variation/1" METRIC_VARIATION_INPUT_SCHEMA = "jpxl.metric-variation-input/1" RISK_INPUT_SCHEMA = "jpxl.edge-risk-input/1" @@ -662,12 +665,21 @@ def quality_encode_command( threads: int, effort: str, ) -> list[str]: - """The perceptual VarDCT encode: a minimum SSIMULACRA2 target of ``score``.""" + """The perceptual VarDCT encode: a minimum SSIMULACRA2 target of ``score``. + + The harness opts into ``--quality-fallback best-effort``: by default an + unmet target now exits 1 and writes nothing, but a curve point wants the + under-target stream with its true ``saturated_top`` / + ``under_target_work_cap`` status and measured score, which is what this + mode emits. + """ return [ str(binary), "encode", "--quality", f"{score:.4f}", + "--quality-fallback", + "best-effort", "--effort", effort, "--threads", @@ -706,16 +718,16 @@ def find(name: str, cast: Any) -> Any: def read_quality_trace(path: Path) -> dict[str, Any]: - """Return the ``jpxl.quality-trace/1`` object written to a trace file.""" + """Return the ``jpxl.quality-trace/*`` object written to a trace file.""" records = [ json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip() ] for record in records: - if record.get("schema") == QUALITY_TRACE_SCHEMA: + if record.get("schema") in QUALITY_TRACE_SCHEMAS: return record - raise HarnessError(f"no {QUALITY_TRACE_SCHEMA} record in trace file {path}") + raise HarnessError(f"no {QUALITY_TRACE_SCHEMAS[0]} record in trace file {path}") def curve_point( diff --git a/JPXL/tools/make-quality-guard-fixtures.py b/JPXL/tools/make-quality-guard-fixtures.py index a9690702..6a88f113 100644 --- a/JPXL/tools/make-quality-guard-fixtures.py +++ b/JPXL/tools/make-quality-guard-fixtures.py @@ -40,6 +40,8 @@ import hashlib import io import json +import os +import re import struct import sys import zlib @@ -500,6 +502,12 @@ def registry() -> list[Fixture]: fx.append(_synth("text-screenshot-holdout-1600x900", "text-screenshot", "holdout", "synthetic/text-screenshot", lambda: build_text(1600, 900, 19001, dark=False), 19001, "Holdout paragraphs on white, fresh seed")) + fx.append(_synth("text-screenshot-white2-1280x800", "text-screenshot", "calibration", + "synthetic/text-screenshot", lambda: build_text(1280, 800, 1005, dark=False), + 1005, "Second white-background paragraph layout, fresh seed")) + fx.append(_synth("text-screenshot-ui2-1600x1000", "text-screenshot", "development", + "synthetic/text-screenshot", lambda: build_text_ui(1600, 1000, 1006), + 1006, "Second UI mock layout, fresh seed (UI class in both splits)")) # ---- line-art (synthetic) ---------------------------------------------- # fx.append(_synth("line-art-aa-shapes-1024x1024", "line-art", "calibration", @@ -517,6 +525,9 @@ def registry() -> list[Fixture]: fx.append(_synth("line-art-holdout-900x900", "line-art", "holdout", "synthetic/line-art", lambda: build_line_art(900, 900, 29001, anti_alias=True), 29001, "Holdout anti-aliased vector shapes, fresh seed")) + fx.append(_synth("line-art-hatch2-640x640", "line-art", "development", + "synthetic/line-art", lambda: build_hatch(640, 640, 2005), + 2005, "Second hatch-pattern layout, fresh seed (hatch in both splits)")) # ---- gradient (synthetic) ---------------------------------------------- # fx.append(_synth("gradient-horizontal-1024x512", "gradient", "calibration", @@ -537,6 +548,12 @@ def registry() -> list[Fixture]: fx.append(_synth("gradient-holdout-radial-700x700", "gradient", "holdout", "synthetic/gradient", lambda: build_gradient(700, 700, "radial", 39001), 39001, "Holdout radial ramp, fresh seed")) + fx.append(_synth("gradient-sky-noise2-1024x512", "gradient", "calibration", + "synthetic/gradient", lambda: build_gradient(1024, 512, "sky", 3006), + 3006, "Second sky-like noisy gradient, fresh seed (banding stress in both splits)")) + fx.append(_synth("gradient-sky-noise3-800x600", "gradient", "development", + "synthetic/gradient", lambda: build_gradient(800, 600, "sky", 3007), + 3007, "Third sky-like noisy gradient, fresh seed")) # ---- saturated (synthetic + one photo crop) ---------------------------- # fx.append(_synth("saturated-primaries-hard-512x512", "saturated", "calibration", @@ -580,7 +597,8 @@ def registry() -> list[Fixture]: "64x64 crop at (x=1200,y=900), sRGB 8-bit")) _ = tiny_sizes # documented full set (7x7,8x8,16x16,33x20,64x64) spread across splits - # ---- noise-lowlight (derived, development scene only) ------------------- # + # ---- noise-lowlight (derived; one family per capture, split follows the + # capture so the class exists in both calibration and development) ------- # fx.append(_derived( "noise-lowlight-184356-1024x768", "noise-lowlight", "development", "derived/noise-lowlight", @@ -588,6 +606,27 @@ def registry() -> list[Fixture]: "test-set/20240502_184356.png", "linear-light x0.25 darken, then seeded Poisson(scale=500)+Gaussian(sigma=0.006) noise, re-encode sRGB 8-bit", seed=5001)) + fx.append(_derived( + "noise-lowlight-151356-1024x768", "noise-lowlight", "calibration", + "derived/noise-lowlight", + lambda: build_lowlight(load_rgb("test-set/20240502_151356.png"), 5002), + "test-set/20240502_151356.png", + "linear-light x0.25 darken, then seeded Poisson(scale=500)+Gaussian(sigma=0.006) noise, re-encode sRGB 8-bit", + seed=5002)) + fx.append(_derived( + "noise-lowlight-105759-1024x768", "noise-lowlight", "development", + "derived/noise-lowlight", + lambda: build_lowlight(load_rgb("test-set/20240503_105759.png"), 5003), + "test-set/20240503_105759.png", + "linear-light x0.25 darken, then seeded Poisson(scale=500)+Gaussian(sigma=0.006) noise, re-encode sRGB 8-bit", + seed=5003)) + fx.append(_derived( + "noise-lowlight-110934-1024x768", "noise-lowlight", "calibration", + "derived/noise-lowlight", + lambda: build_lowlight(load_rgb("test-set/20240501_110934.png"), 5004), + "test-set/20240501_110934.png", + "linear-light x0.25 darken, then seeded Poisson(scale=500)+Gaussian(sigma=0.006) noise, re-encode sRGB 8-bit", + seed=5004)) # ---- grayscale (derived) ----------------------------------------------- # fx.append(_derived( @@ -596,6 +635,12 @@ def registry() -> list[Fixture]: lambda: build_grayscale(load_rgb("test-set/20240502_192515.png")), "test-set/20240502_192515.png", "Rec.709 linear-light luma, re-encode sRGB, replicated to R=G=B")) + fx.append(_derived( + "grayscale-scene-151800-1024x768", "grayscale", "calibration", + "derived/grayscale", + lambda: build_grayscale(load_rgb("test-set/20240502_151800.png")), + "test-set/20240502_151800.png", + "Rec.709 linear-light luma, re-encode sRGB, replicated to R=G=B")) fx.append(_derived( "grayscale-photo-203230-crop-1024x1024", "grayscale", "calibration", "derived/grayscale", @@ -683,9 +728,43 @@ def sidecar_for(fx: Fixture, ppm_sha: str, png_sha: str | None) -> dict[str, Any return doc +CAPTURE_STEM_RE = re.compile(r"(20\d{6}_\d{6})") + + +def source_capture_id(fx: Fixture) -> str | None: + """The camera-capture stem a fixture ultimately comes from, or ``None``. + + Parsed from the parent path's basename (``20260606_203230_4mp.png`` and + ``20260606_203230_result.png`` are one capture), so every crop, resolution + and colour variant of one photograph shares the id. + """ + if fx.parent is None: + return None + m = CAPTURE_STEM_RE.search(os.path.basename(fx.parent["path"])) + return m.group(1) if m else None + + +def family_fields(fx: Fixture) -> dict[str, Any]: + """The split-hygiene fields of one fixture. + + ``family_id`` groups every derivative of one independent source: the + capture stem for photographic sources and their derivatives, the fixture's + own id for synthetic fixtures (fresh seeds are independent families by + design). All members of a family must share one split — ``cmd_build`` + enforces it — so training/holdout separation is mechanically auditable. + """ + capture = source_capture_id(fx) + return { + "family_id": capture if capture is not None else fx.fixture_id, + "variant_id": fx.fixture_id, + "generator_family": fx.klass if fx.kind in ("synthetic", "derived") else None, + "source_capture_id": capture, + } + + def manifest_entry(fx: Fixture, ppm_sha: str) -> dict[str, Any]: w, h, depth = fx.dims() - return { + entry = { "id": fx.fixture_id, "path": fx.ppm_rel(), "sha256": ppm_sha, @@ -698,6 +777,8 @@ def manifest_entry(fx: Fixture, ppm_sha: str) -> dict[str, Any]: "provenance": fx.provenance, "sidecar": fx.sidecar_rel(), } + entry.update(family_fields(fx)) + return entry def realise(fx: Fixture) -> tuple[bytes, bytes | None]: @@ -721,6 +802,16 @@ def cmd_build(check: bool) -> int: if dupes: print(f"ERROR: duplicate fixture ids: {sorted(dupes)}", file=sys.stderr) return 2 + # Family split-hygiene guard: every derivative of one source must stay in + # one split, or family leakage silently flatters any model trained on the + # calibration/development splits. + family_splits: dict[str, set[str]] = {} + for f in fixtures: + family_splits.setdefault(family_fields(f)["family_id"], set()).add(f.split) + leaking = {fam: sorted(s) for fam, s in family_splits.items() if len(s) > 1} + if leaking: + print(f"ERROR: image families cross splits: {leaking}", file=sys.stderr) + return 2 manifest_images: list[dict[str, Any]] = [] ppm_mismatch = 0 diff --git a/JPXL/tools/one_shot_promotion_ab.py b/JPXL/tools/one_shot_promotion_ab.py new file mode 100644 index 00000000..1a4bcd55 --- /dev/null +++ b/JPXL/tools/one_shot_promotion_ab.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""A/B the one-shot controller build against the default build (promotion +screen for the one-shot program, memo section 11). + +Runs `jpxl encode --quality T` for every image of the requested manifest +splits at every target, once per binary, capturing the report line and the +`jpxl.quality-trace/2` record, then prints the promotion comparison: + +* floor: any achieved < requested on either arm; +* bytes: per-cell and geometric-mean byte ratio one-shot/default; +* work: pixel plans / reconstructions / metric evaluations per arm; +* wall: per-cell encode wall and the ratio. + +Both arms run serially and interleaved (A, B, A, B ...) on the same images +so drift hits both equally. Standard library only. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import subprocess +import sys +import tempfile +import time + + +def parse_line(stdout: str) -> dict: + out = {} + for token in stdout.split(): + if "=" in token: + key, value = token.split("=", 1) + out[key] = value + return out + + +def run_cell(binary: str, image_path: str, target: float, threads: int, trace_path: str) -> dict: + env = dict(os.environ, JPXL_QUALITY_TRACE=trace_path) + out_path = os.path.join(tempfile.gettempdir(), "oneshot-ab.jxl") + start = time.perf_counter() + completed = subprocess.run( + [ + binary, + "encode", + "--quality", + f"{target}", + "--threads", + str(threads), + image_path, + out_path, + ], + capture_output=True, + text=True, + env=env, + ) + wall = time.perf_counter() - start + if completed.returncode != 0: + return {"error": completed.stderr.strip()[-160:], "wall": wall} + line = parse_line(completed.stdout) + trace = {} + try: + with open(trace_path, encoding="utf-8") as fh: + for raw in fh: + record = json.loads(raw) + if record.get("schema", "").startswith("jpxl.quality-trace/"): + trace = record + except OSError: + pass + finally: + try: + os.remove(trace_path) + except OSError: + pass + return { + "achieved": float(line.get("achieved", "nan")), + "bytes": int(line.get("bytes", "0")), + "status": line.get("status", "?"), + "probes": int(line.get("probes", "0")), + "prices": int(line.get("prices", "0")), + "work": trace.get("work"), + "predicted_rung": trace.get("predicted_rung"), + "wall": wall, + } + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--manifest", required=True) + parser.add_argument("--splits", nargs="+", default=["holdout"]) + parser.add_argument("--default-binary", required=True) + parser.add_argument("--one-shot-binary", required=True) + parser.add_argument("--targets", nargs="+", type=float, + default=[30.0, 50.0, 70.0, 80.0, 85.0, 90.0, 95.0]) + parser.add_argument("--threads", type=int, default=4) + parser.add_argument("--max-pixels", type=int, default=None, + help="skip images above this pixel count") + parser.add_argument("--output", required=True) + args = parser.parse_args(argv) + + with open(args.manifest, encoding="utf-8") as fh: + manifest = json.load(fh) + base_dir = os.path.dirname(os.path.abspath(args.manifest)) + images = [im for im in manifest["images"] if im["split"] in set(args.splits)] + + rows = [] + for image in images: + path = os.path.join(base_dir, image["path"]) + with open(path, "rb") as fh: + magic = fh.readline().strip() + dims = fh.readline().split() + if magic != b"P6": + continue + width, height = int(dims[0]), int(dims[1]) + if min(width, height) < 64: + continue + if args.max_pixels and width * height > args.max_pixels: + print(f"skip (>{args.max_pixels}px): {image['id']}") + continue + for target in args.targets: + cell = {"image_id": image["id"], "class": image.get("class"), + "pixels": width * height, "target": target} + trace = os.path.join(tempfile.gettempdir(), "oneshot-ab-trace.jsonl") + cell["default"] = run_cell(args.default_binary, path, target, args.threads, trace) + cell["one_shot"] = run_cell(args.one_shot_binary, path, target, args.threads, trace) + rows.append(cell) + d, o = cell["default"], cell["one_shot"] + print( + f"{image['id']} t={target}: bytes {d.get('bytes')}->{o.get('bytes')} " + f"achieved {d.get('achieved'):.2f}->{o.get('achieved'):.2f} " + f"recon {d.get('work', {}).get('reconstructions')}->" + f"{o.get('work', {}).get('reconstructions')} " + f"wall {d.get('wall'):.2f}s->{o.get('wall'):.2f}s", + flush=True, + ) + + ok = [r for r in rows if "error" not in r["default"] and "error" not in r["one_shot"]] + floor_default = [r for r in ok if r["default"]["achieved"] < r["target"]] + floor_one_shot = [r for r in ok if r["one_shot"]["achieved"] < r["target"]] + ratios = [r["one_shot"]["bytes"] / r["default"]["bytes"] for r in ok if r["default"]["bytes"]] + geomean = math.exp(sum(math.log(x) for x in ratios) / len(ratios)) if ratios else None + recon = lambda arm: sum(r[arm]["work"]["reconstructions"] for r in ok if r[arm].get("work")) + wall_ratio = [r["one_shot"]["wall"] / r["default"]["wall"] for r in ok] + summary = { + "cells": len(rows), + "compared": len(ok), + "floor_violations_default": len(floor_default), + "floor_violations_one_shot": len(floor_one_shot), + "byte_geomean_one_shot_over_default": geomean, + "worst_cell_byte_ratio": max(ratios) if ratios else None, + "total_reconstructions_default": recon("default"), + "total_reconstructions_one_shot": recon("one_shot"), + "wall_geomean_ratio": ( + math.exp(sum(math.log(x) for x in wall_ratio) / len(wall_ratio)) + if wall_ratio + else None + ), + } + with open(args.output, "w", encoding="utf-8", newline="\n") as fh: + fh.write(json.dumps({"summary": summary, "rows": rows}, indent=1) + "\n") + print(json.dumps(summary, indent=2)) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/JPXL/tools/quality_corpus_extend.py b/JPXL/tools/quality_corpus_extend.py new file mode 100644 index 00000000..7a56f859 --- /dev/null +++ b/JPXL/tools/quality_corpus_extend.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""Extend the quality corpus from an external image collection (one-shot +program corpus growth, memo section 10). + +Scans a directory tree of JPEG/PNG images, samples a diverse subset (capped +per subdirectory so one prolific source cannot dominate), converts each pick +to an 8-bit P6 PPM under ``test-set//``, and writes a manifest with +the same family fields the main corpus carries: + +* every distinct source file is one image **family** (same-directory files + whose normalised name stems match are folded into one family — duplicate + scans of one artwork must not straddle splits); +* families are assigned calibration / development / ext-holdout splits by a + deterministic hash of the family id, so the assignment is reproducible + and never depends on scan order; +* exact byte-duplicates are dropped. + +The originals are never touched; the manifest records the source path, +its sha256 and the conversion command. Standard library plus Pillow (the +same dependency the fixture generator uses). +""" + +from __future__ import annotations + +import argparse +import datetime +import hashlib +import json +import os +import re +import sys + +from PIL import Image + +TOOL_VERSION = "1.0.0" +MANIFEST_SCHEMA = "jpxl.codec-corpus-ext/1" +IMAGE_EXTS = (".jpg", ".jpeg", ".png") +# Reproductions of one artwork often differ only by a resolution or copy +# suffix; fold those into one family. +STEM_NOISE = re.compile(r"[\s_\-]*(\(\d+\)|copy|\d{3,4}x\d{3,4}|small|large|hd)$", re.I) + + +def normalised_stem(filename: str) -> str: + stem = os.path.splitext(filename)[0].lower() + previous = None + while previous != stem: + previous = stem + stem = STEM_NOISE.sub("", stem).strip() + return stem or filename.lower() + + +def family_key(rel_dir: str, filename: str) -> str: + return f"{rel_dir}/{normalised_stem(filename)}".replace("\\", "/") + + +def split_for_family(family: str, holdout_fraction: float, dev_fraction: float) -> str: + """Deterministic split assignment from the family id alone.""" + digest = hashlib.sha256(family.encode("utf-8")).digest() + value = int.from_bytes(digest[:8], "big") / float(1 << 64) + if value < holdout_fraction: + return "ext-holdout" + if value < holdout_fraction + dev_fraction: + return "development" + return "calibration" + + +def scan(root: str) -> list[tuple[str, str, int]]: + """Every image as (relative dir, filename, byte size), sorted.""" + out = [] + for dirpath, _, filenames in os.walk(root): + rel = os.path.relpath(dirpath, root) + for name in sorted(filenames): + if os.path.splitext(name)[1].lower() not in IMAGE_EXTS: + continue + try: + size = os.path.getsize(os.path.join(dirpath, name)) + except OSError: + continue + out.append((rel, name, size)) + out.sort() + return out + + +def sample( + images: list[tuple[str, str, int]], + max_images: int, + per_dir_cap: int, +) -> list[tuple[str, str]]: + """A diverse deterministic subset: round-robin over directories, each + directory's files ordered by a content-independent hash of their family + key, capped per directory.""" + by_dir: dict[str, list[tuple[str, str]]] = {} + seen_families: set[str] = set() + for rel, name, _ in images: + family = family_key(rel, name) + if family in seen_families: + continue + seen_families.add(family) + by_dir.setdefault(rel, []).append((rel, name)) + for entries in by_dir.values(): + # Salted into its own domain: ordering by the *split* hash would bias + # the picked families toward one split. + entries.sort( + key=lambda e: hashlib.sha256(b"order:" + family_key(*e).encode()).hexdigest() + ) + del entries[per_dir_cap:] + picked: list[tuple[str, str]] = [] + queues = sorted(by_dir.items()) + index = 0 + while len(picked) < max_images and any(q for _, q in queues): + rel, queue = queues[index % len(queues)] + if queue: + picked.append(queue.pop(0)) + index += 1 + return picked + + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def to_ppm(image: Image.Image) -> bytes: + rgb = image.convert("RGB") + header = f"P6\n{rgb.width} {rgb.height}\n255\n".encode() + return header + rgb.tobytes() + + +def cmd_build(args: argparse.Namespace) -> int: + images = scan(args.source_dir) + if not images: + print(f"no images under {args.source_dir}", file=sys.stderr) + return 1 + picked = sample(images, args.max_images, args.per_dir_cap) + out_dir = os.path.join("test-set", args.name) + os.makedirs(out_dir, exist_ok=True) + + entries = [] + dropped = 0 + seen_hashes: set[str] = set() + for index, (rel, name) in enumerate(picked): + source = os.path.join(args.source_dir, rel, name) + try: + with open(source, "rb") as fh: + raw = fh.read() + source_sha = sha256_bytes(raw) + if source_sha in seen_hashes: + dropped += 1 + continue + seen_hashes.add(source_sha) + with Image.open(source) as im: + im.load() + if min(im.width, im.height) < args.min_side: + dropped += 1 + continue + if im.width * im.height > args.max_pixels: + dropped += 1 + continue + ppm = to_ppm(im) + except Exception as error: # noqa: BLE001 - a bad file is data, not a bug + print(f"skip (unreadable): {name} ({type(error).__name__})", file=sys.stderr) + continue + family = family_key(rel, name) + image_id = f"{args.name}-{index:04d}-{sha256_bytes(family.encode())[:8]}" + ppm_name = f"{image_id}.ppm" + with open(os.path.join(out_dir, ppm_name), "wb") as fh: + fh.write(ppm) + entries.append( + { + "id": image_id, + "path": f"{args.name}/{ppm_name}", + "sha256": sha256_bytes(ppm), + "split": split_for_family(family, args.holdout_fraction, args.dev_fraction), + "class": args.image_class, + "kind": "external", + "bit_depth": 8, + "license": "private source collection; corpus use only, not redistributed", + "provenance": f"decoded from {source} (sha256 {source_sha})", + "family_id": family, + "variant_id": image_id, + "generator_family": None, + "source_capture_id": None, + } + ) + + manifest = { + "schema": MANIFEST_SCHEMA, + "generated_by": f"quality_corpus_extend.py {TOOL_VERSION}", + "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), + "source_dir": args.source_dir, + "splits": ["calibration", "development", "ext-holdout"], + "images": entries, + } + manifest_path = os.path.join("test-set", f"{args.name}-manifest.json") + with open(manifest_path, "w", encoding="utf-8", newline="\n") as fh: + fh.write(json.dumps(manifest, indent=1, ensure_ascii=False) + "\n") + by_split: dict[str, int] = {} + for e in entries: + by_split[e["split"]] = by_split.get(e["split"], 0) + 1 + print( + f"wrote {len(entries)} images ({dropped} dropped) to {out_dir}; " + f"splits {by_split}; manifest {manifest_path}" + ) + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-dir", required=True) + parser.add_argument("--name", required=True, help="corpus name under test-set/") + parser.add_argument("--max-images", type=int, default=240) + parser.add_argument("--per-dir-cap", type=int, default=6) + parser.add_argument("--min-side", type=int, default=128) + parser.add_argument("--max-pixels", type=int, default=13_000_000) + parser.add_argument("--holdout-fraction", type=float, default=0.2) + parser.add_argument("--dev-fraction", type=float, default=0.3) + parser.add_argument("--image-class", default="painting") + parser.set_defaults(func=cmd_build) + return parser + + +def main(argv: list[str]) -> int: + args = build_parser().parse_args(argv) + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/JPXL/tools/quality_oracle_labels.py b/JPXL/tools/quality_oracle_labels.py new file mode 100644 index 00000000..1a6a4373 --- /dev/null +++ b/JPXL/tools/quality_oracle_labels.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python3 +"""Production-endpoint oracle labels for the one-shot quality program (PR 2). + +For every requested image of the quality corpus this tool sweeps the full +effective-scale ladder with ``jpxl quality-ladder`` — a *fresh* production +Balanced (or Fast) pixel plan per rung, scored by the canonical in-tree +SSIMULACRA2 — then locates each target knot's crossing by adaptive geometric +densification, exact-prices the crossing neighbourhood, and reduces the raw +measurements to one labels row per image x target: + +* the **coarsest measured rung whose fresh plan meets the target** (the label + a crossing predictor must reproduce), plus the log-loss interpolated + crossing scale for regression smoothness; +* **censoring** instead of a fake crossing when even the ladder's top rung + misses the target (``crossing > top``), and a ``floor`` flag when the + ladder's coarsest rung already meets it; +* the **local loss slope** ``beta = -d ln(100 - score) / d ln(scale)`` fitted + around the crossing; +* **exact bytes** at the label rung and its measured neighbours; +* the frame's deterministic source features and the manifest's family/split + fields, so training can split and weight by image family. + +Unlike ``calibrate_initial_rung.py`` (fixed-quantizer ``--global-scale`` +sweeps with ``HfMul = 1``), every point here is the production quality +pixel policy over the complete effective ladder including its ``HfMul`` +segments — the same operating points the score controller emits. + +Subcommands: + + sweep Run the ladder sweeps; one raw JSONL file per image (resumable). + labels Reduce raw sweeps + manifest to a labels JSONL with provenance. + +Standard library only. The pure helpers are importable for the unit tests in +``tools/tests/test_quality_oracle_labels.py``. +""" + +from __future__ import annotations + +import argparse +import datetime +import hashlib +import json +import math +import os +import subprocess +import sys +import time + +TOOL_VERSION = "1.0.0" +RAW_SCHEMA = "jpxl.quality-oracle-raw/1" +LABELS_SCHEMA = "jpxl.quality-oracle-labels/1" +DEFAULT_TARGETS = [30.0, 50.0, 70.0, 80.0, 85.0, 90.0, 95.0] +LOSS_EPSILON = 1e-3 +# A sentinel far above any representable effective scale; the encoder clamps +# it to the ladder's top rung and reports the real value back. +TOP_SENTINEL = 2_000_000_000 +# Below this many pixels on a side SSIMULACRA2 is under its own floor and the +# encoder refuses the sweep; such images carry no oracle signal. +MIN_SIDE = 64 + + +# -------------------------------------------------------------------------- +# Pure helpers (unit-tested) +# -------------------------------------------------------------------------- + + +def loss(score: float) -> float: + """The metric loss of a score, floored so its logarithm is finite.""" + return max(100.0 - score, LOSS_EPSILON) + + +def geometric_grid(lo: int, hi: int, ratio: float) -> list[int]: + """Integer effective scales from ``lo`` to ``hi`` at ``ratio`` steps. + + Both endpoints are included; consecutive duplicates are dropped. + """ + if lo < 1 or hi < lo or ratio <= 1.0: + raise ValueError("need 1 <= lo <= hi and ratio > 1") + out = [] + value = float(lo) + while value < hi: + out.append(round(value)) + value *= ratio + out.append(hi) + deduped = [] + for v in out: + if not deduped or v > deduped[-1]: + deduped.append(v) + return deduped + + +def crossing_state(points: list[tuple[int, float]], target: float) -> dict: + """Where ``target`` crosses a measured (scale, score) ladder. + + ``points`` must be sorted ascending by scale with unique scales. The + label definition is the *coarsest measured point meeting the target*, so + a local score reversal never hides a coarser feasible point. Returns:: + + {"state": "censored"} # nothing meets it + {"state": "floor", "above": (s, score)} # the first point does + {"state": "crossed", "below": (..), "above": (..)} + + where ``above`` is the coarsest meeting point and ``below`` the next + coarser measured point. + """ + meeting = [i for i, (_, score) in enumerate(points) if score >= target] + if not meeting: + return {"state": "censored"} + first = meeting[0] + if first == 0: + return {"state": "floor", "above": points[0]} + return {"state": "crossed", "below": points[first - 1], "above": points[first]} + + +def refine_scales(below: int, above: int, count: int) -> list[int]: + """``count`` geometric scales strictly inside ``(below, above)``.""" + if above <= below + 1 or count <= 0: + return [] + out = [] + for i in range(1, count + 1): + t = i / (count + 1) + s = round(math.exp(math.log(below) + t * (math.log(above) - math.log(below)))) + if below < s < above and (not out or s > out[-1]): + out.append(s) + return out + + +def interp_crossing(below: tuple[int, float], above: tuple[int, float], target: float) -> float: + """The log-loss interpolated crossing scale between two bracket points. + + Mirrors the encoder's ``log_loss_crossing``: linear in + ``ln(100 - score)`` against ``ln(scale)``. Falls back to the geometric + midpoint when the bracket does not order in loss. + """ + (s_lo, score_lo), (s_hi, score_hi) = below, above + x_lo, x_hi = math.log(s_lo), math.log(s_hi) + y_lo, y_hi = math.log(loss(score_lo)), math.log(loss(score_hi)) + if y_hi >= y_lo or x_hi <= x_lo: + return math.exp((x_lo + x_hi) / 2.0) + slope = (y_hi - y_lo) / (x_hi - x_lo) + x = x_lo + (math.log(loss(target)) - y_lo) / slope + return math.exp(min(max(x, x_lo), x_hi)) + + +def local_beta(points: list[tuple[int, float]], crossing_scale: float, k: int = 4) -> float | None: + """Least-squares ``-d ln(loss) / d ln(scale)`` over the ``k`` nearest points. + + ``None`` when fewer than two distinct points exist or the fit is not a + positive slope (loss must fall as the scale rises). + """ + if len(points) < 2: + return None + nearest = sorted(points, key=lambda p: abs(math.log(p[0]) - math.log(crossing_scale)))[:k] + xs = [math.log(s) for s, _ in nearest] + ys = [math.log(loss(score)) for _, score in nearest] + n = len(xs) + mean_x = sum(xs) / n + mean_y = sum(ys) / n + var_x = sum((x - mean_x) ** 2 for x in xs) + if var_x <= 0.0: + return None + slope = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) / var_x + beta = -slope + return beta if beta > 0.0 else None + + +def merge_points(rounds: list[list[dict]]) -> list[dict]: + """Merge ladder records from several runs, keyed by rung. + + A later record replaces an earlier one only when it adds pricing; scores + are deterministic per rung, so duplicates otherwise carry no news. + """ + by_rung: dict[int, dict] = {} + for records in rounds: + for r in records: + rung = r["rung"] + held = by_rung.get(rung) + if held is None or (held.get("bytes") is None and r.get("bytes") is not None): + by_rung[rung] = r + return [by_rung[k] for k in sorted(by_rung)] + + +def sha256_file(path: str) -> str: + h = hashlib.sha256() + with open(path, "rb") as fh: + for chunk in iter(lambda: fh.read(1 << 20), b""): + h.update(chunk) + return h.hexdigest() + + +# -------------------------------------------------------------------------- +# Sweep driver +# -------------------------------------------------------------------------- + + +def run_ladder( + jpxl: str, + image_path: str, + scales: list[int], + threads: int, + effort: str, + price: bool, +) -> tuple[dict, list[dict]]: + """One ``jpxl quality-ladder`` invocation: (header, point records).""" + command = [ + jpxl, + "quality-ladder", + "--scales", + ",".join(str(s) for s in scales), + "--effort", + effort, + "--threads", + str(threads), + ] + if price: + command.append("--price") + command.append(image_path) + completed = subprocess.run(command, capture_output=True, text=True, check=False) + if completed.returncode != 0: + raise RuntimeError( + f"quality-ladder failed on {image_path}: {completed.stderr.strip()}" + ) + records = [json.loads(line) for line in completed.stdout.splitlines() if line.strip()] + if not records or records[0].get("schema") != "jpxl.quality-ladder/1": + raise RuntimeError(f"unexpected quality-ladder output on {image_path}") + return records[0], records[1:] + + +def sweep_image( + jpxl: str, + image_path: str, + targets: list[float], + threads: int, + effort: str, + coarse_ratio: float, + refine_points: int, + refine_rounds: int, +) -> dict: + """The full adaptive sweep of one image: coarse, refine, price.""" + coarse_scales = geometric_grid(1, 73728, coarse_ratio) + [TOP_SENTINEL] + header, coarse = run_ladder(jpxl, image_path, coarse_scales, threads, effort, price=False) + rounds = [coarse] + + for _ in range(refine_rounds): + merged = merge_points(rounds) + pts = [(r["effective_scale"], r["score"]) for r in merged] + wanted: list[int] = [] + for target in targets: + state = crossing_state(pts, target) + if state["state"] != "crossed": + continue + below_scale = state["below"][0] + above_scale = state["above"][0] + wanted.extend(refine_scales(below_scale, above_scale, refine_points)) + wanted = sorted(set(wanted)) + if not wanted: + break + _, extra = run_ladder(jpxl, image_path, wanted, threads, effort, price=False) + rounds.append(extra) + + # Price the crossing neighbourhood: the label rung and its measured + # neighbours on each side, per target, deduped. + merged = merge_points(rounds) + pts = [(r["effective_scale"], r["score"]) for r in merged] + price_scales: set[int] = set() + for target in targets: + state = crossing_state(pts, target) + if state["state"] == "censored": + continue + above_scale = state["above"][0] + index = next(i for i, (s, _) in enumerate(pts) if s == above_scale) + for j in (index - 1, index, index + 1): + if 0 <= j < len(pts): + price_scales.add(pts[j][0]) + if price_scales: + _, priced = run_ladder( + jpxl, image_path, sorted(price_scales), threads, effort, price=True + ) + rounds.append(priced) + + return { + "schema": RAW_SCHEMA, + "header": header, + "targets": targets, + "points": merge_points(rounds), + } + + +def cmd_sweep(args: argparse.Namespace) -> int: + started = time.monotonic() + with open(args.manifest, encoding="utf-8") as fh: + manifest = json.load(fh) + # Corpus image paths are relative to the manifest's own directory + # (test-set/quality-corpus.json sits beside quality-guard/). + base_dir = os.path.dirname(os.path.abspath(args.manifest)) + os.makedirs(args.out_dir, exist_ok=True) + + wanted_splits = set(args.splits) + selected = [ + image + for image in manifest["images"] + if image["split"] in wanted_splits and (not args.only or image["id"] in args.only) + ] + skipped = 0 + for image in selected: + if args.time_budget_minutes is not None: + elapsed = (time.monotonic() - started) / 60.0 + if elapsed >= args.time_budget_minutes: + print( + f"time budget of {args.time_budget_minutes} min reached after " + f"{elapsed:.1f} min; the sweep is resumable — run again to continue" + ) + break + out_path = os.path.join(args.out_dir, f"{image['id']}.jsonl") + if os.path.exists(out_path) and not args.force: + skipped += 1 + continue + image_path = os.path.join(base_dir, image["path"]) + # The metric floor: tiny fixtures carry no oracle signal. + with open(image_path, "rb") as fh: + magic = fh.readline() + dims = fh.readline().split() + if magic.strip() != b"P6" or len(dims) < 2: + print(f"skip (not P6): {image['id']}", file=sys.stderr) + continue + width, height = int(dims[0]), int(dims[1]) + if min(width, height) < MIN_SIDE: + print(f"skip (below {MIN_SIDE}px metric floor): {image['id']}") + continue + print(f"sweep {image['id']} ({width}x{height}, {image['split']})", flush=True) + raw = sweep_image( + args.jpxl, + image_path, + [float(t) for t in args.targets], + args.threads, + args.effort, + args.coarse_ratio, + args.refine_points, + args.refine_rounds, + ) + raw["image"] = { + "id": image["id"], + "path": image["path"], + "sha256": image["sha256"], + "split": image["split"], + "class": image["class"], + "family_id": image.get("family_id"), + "variant_id": image.get("variant_id"), + "source_capture_id": image.get("source_capture_id"), + } + with open(out_path, "w", encoding="utf-8", newline="\n") as fh: + fh.write(json.dumps(raw, sort_keys=False) + "\n") + if skipped: + print(f"{skipped} image(s) already swept (use --force to redo)") + return 0 + + +# -------------------------------------------------------------------------- +# Label reduction +# -------------------------------------------------------------------------- + + +def labels_for_raw(raw: dict) -> list[dict]: + """Reduce one raw sweep to per-target label rows.""" + points = raw["points"] + pts = [(r["effective_scale"], r["score"]) for r in points] + by_scale = {r["effective_scale"]: r for r in points} + top = points[-1] + rows = [] + for target in raw["targets"]: + state = crossing_state(pts, target) + row: dict = { + "image_id": raw["image"]["id"], + "family_id": raw["image"]["family_id"], + "split": raw["image"]["split"], + "class": raw["image"]["class"], + "target": target, + "state": state["state"], + "top_rung": top["rung"], + "top_effective_scale": top["effective_scale"], + "top_score": top["score"], + } + if state["state"] == "censored": + # The crossing exists beyond the ladder: a right-censored label. + row.update( + { + "label_rung": None, + "label_effective_scale": None, + "crossing_scale": None, + "beta": None, + "label_bytes": None, + "neighbor_bytes": [], + } + ) + else: + above = state["above"] + above_record = by_scale[above[0]] + crossing = ( + interp_crossing(state["below"], above, target) + if state["state"] == "crossed" + else float(above[0]) + ) + index = next(i for i, (s, _) in enumerate(pts) if s == above[0]) + neighbor_bytes = [] + for j in (index - 1, index, index + 1): + if 0 <= j < len(pts): + record = by_scale[pts[j][0]] + if record.get("bytes") is not None: + neighbor_bytes.append( + { + "rung": record["rung"], + "effective_scale": record["effective_scale"], + "score": record["score"], + "bytes": record["bytes"], + } + ) + row.update( + { + "label_rung": above_record["rung"], + "label_effective_scale": above_record["effective_scale"], + "label_score": above[1], + "crossing_scale": crossing, + "beta": local_beta(pts, crossing), + "label_bytes": above_record.get("bytes"), + "neighbor_bytes": neighbor_bytes, + } + ) + rows.append(row) + return rows + + +def git_provenance(repo_root: str) -> dict: + def run(*argv: str) -> str: + return subprocess.run( + ["git", *argv], cwd=repo_root, capture_output=True, text=True, check=True + ).stdout.strip() + + dirty = run("status", "--porcelain") != "" + return {"commit": run("rev-parse", "HEAD"), "dirty": dirty} + + +def cmd_labels(args: argparse.Namespace) -> int: + repo_root = os.path.dirname(os.path.dirname(os.path.abspath(args.manifest))) + raw_files = sorted( + os.path.join(sweep_dir, name) + for sweep_dir in args.sweep_dir + for name in os.listdir(sweep_dir) + if name.endswith(".jsonl") + ) + if not raw_files: + print(f"no raw sweeps in {args.sweep_dir}", file=sys.stderr) + return 1 + all_rows = [] + headers = [] + for path in raw_files: + with open(path, encoding="utf-8") as fh: + raw = json.loads(fh.read()) + if raw.get("schema") != RAW_SCHEMA: + print(f"skip (wrong schema): {path}", file=sys.stderr) + continue + headers.append(raw["header"]) + for row in labels_for_raw(raw): + row["source_features"] = raw["header"]["source_features"] + all_rows.append(row) + + provenance = { + "schema": LABELS_SCHEMA, + "generated_by": f"quality_oracle_labels.py {TOOL_VERSION}", + "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), + "git": git_provenance(repo_root), + "manifest_sha256": sha256_file(args.manifest), + "metric_version": headers[0]["metric_version"] if headers else None, + "effort": headers[0]["effort"] if headers else None, + "images": len(raw_files), + "rows": len(all_rows), + "censored_rows": sum(1 for r in all_rows if r["state"] == "censored"), + } + with open(args.output, "w", encoding="utf-8", newline="\n") as fh: + fh.write(json.dumps(provenance, sort_keys=False) + "\n") + for row in all_rows: + fh.write(json.dumps(row, sort_keys=False) + "\n") + print( + f"wrote {len(all_rows)} label rows ({provenance['censored_rows']} censored) " + f"from {len(raw_files)} sweeps to {args.output}" + ) + return 0 + + +# -------------------------------------------------------------------------- +# Entry +# -------------------------------------------------------------------------- + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + + sweep = sub.add_parser("sweep", help="run the adaptive ladder sweeps") + sweep.add_argument("--manifest", required=True) + sweep.add_argument("--jpxl", required=True, help="path to the jpxl binary") + sweep.add_argument("--out-dir", required=True) + sweep.add_argument("--splits", nargs="+", default=["calibration", "development"]) + sweep.add_argument("--targets", nargs="+", default=DEFAULT_TARGETS, type=float) + sweep.add_argument("--threads", type=int, default=4) + sweep.add_argument("--effort", choices=["fast", "balanced"], default="balanced") + sweep.add_argument("--coarse-ratio", type=float, default=1.6) + sweep.add_argument("--refine-points", type=int, default=5) + sweep.add_argument("--refine-rounds", type=int, default=2) + sweep.add_argument("--force", action="store_true") + sweep.add_argument("--only", nargs="*", default=None, help="restrict to these image ids") + sweep.add_argument("--time-budget-minutes", type=float, default=None, + help="stop cleanly (resumable) once this much wall time has passed") + sweep.set_defaults(func=cmd_sweep) + + labels = sub.add_parser("labels", help="reduce raw sweeps to a labels JSONL") + labels.add_argument("--manifest", required=True) + labels.add_argument("--sweep-dir", required=True, nargs="+") + labels.add_argument("--output", required=True) + labels.set_defaults(func=cmd_labels) + return parser + + +def main(argv: list[str]) -> int: + args = build_parser().parse_args(argv) + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/JPXL/tools/quality_predictor_v2.py b/JPXL/tools/quality_predictor_v2.py new file mode 100644 index 00000000..d81dd3fd --- /dev/null +++ b/JPXL/tools/quality_predictor_v2.py @@ -0,0 +1,1017 @@ +#!/usr/bin/env python3 +"""Train the one-shot program's crossing predictor (PR 3) from oracle labels. + +Consumes the ``jpxl.quality-oracle-labels/1`` JSONL that +``quality_oracle_labels.py labels`` produced and fits, per target knot, a +small transparent linear model in robust-standardized feature space: + +* median crossing (pinball tau = 0.50) of ``ln(effective scale)``; +* candidate crossing (tau = 0.90) — the risk-adjusted rung a one-shot + controller would plan first; +* lower/upper interval (tau = 0.10 / 0.95); +* local loss exponent ``beta`` (least squares on ``ln beta``); +* saturation risk (logistic on the censored indicator). + +Censored rows (``crossing > top``) never enter a crossing fit — they train +only the saturation model — and every family carries equal weight, split +across its rows, so resolution variants of one photograph cannot dominate. + +Evaluation is leave-one-family-out over the calibration+development +families: median/p90/p99 absolute log-scale error of the median model, +first-plan success of the candidate model (candidate scale at or above the +oracle crossing), and simulated byte regression from the priced crossing +neighbourhood. The memo's gates are printed next to the measurements. + +``train`` also emits the generated Rust table for the production feature +set and a JSON report with full provenance. Standard library only; fits are +deterministic (fixed iteration counts, fixed summation order). +""" + +from __future__ import annotations + +import argparse +import datetime +import hashlib +import json +import math +import os +import subprocess +import sys + +TOOL_VERSION = "1.0.0" +# Model/schema identity per emitted feature set. The runtime's feature +# builder must mirror the schema exactly; the generated dim const guards the +# match at compile time. +EMIT_IDENTITY = { + "source": ("qpv2-source-1", "qpv2-source/1"), + "source+transform": ("qpv2-st-1", "qpv2-st/1"), +} +REPORT_SCHEMA = "jpxl.qpv2-report/1" +EPS = 1e-9 +TARGET_KNOTS = [30.0, 50.0, 70.0, 80.0, 85.0, 90.0, 95.0] +QUANTILES = {"lower": 0.10, "median": 0.50, "candidate": 0.90, "upper": 0.95} +RIDGE = 1e-3 +GD_ITERS = 1500 +GD_LR = 0.05 +# Predictions are clamped into the ladder's ln-effective-scale range before +# they are exponentiated or scored; a runaway extrapolation on a held-out +# family lands on the ladder's end, exactly as the runtime rung mapping +# would clamp it. +LN_SCALE_RANGE = (0.0, 16.0) +# The model's declared domain: below this shortest side the metric sits too +# close to its own floor and the runtime routes straight to the exact +# controller (an OOD flag, not a prediction). +MIN_DOMAIN_SIDE = 128 +# Mirrors the navigator: overshoot beyond this band triggers the optional +# byte-tightening attempt, aimed this margin above the target. +MET_OVERSHOOT_BAND = 1.0 +MIN_AIM_MARGIN = 0.25 +# Full-frame reconstructions a routed-to-exact request is charged in the +# simulated work accounting (the current controller's holdout median). +FALLBACK_WORK = 4 + + +def clamp_ln_scale(value: float) -> float: + return min(max(value, LN_SCALE_RANGE[0]), LN_SCALE_RANGE[1]) + + +# -------------------------------------------------------------------------- +# Features +# -------------------------------------------------------------------------- + + +def ln(value: float) -> float: + return math.log(max(value, 0.0) + EPS) + + +FEATURE_NAMES = [ + "ln_luma_q10", + "ln_luma_q50", + "ln_luma_q90", + "ln_chroma_q50", + "flat_fraction", + "ln_edge_proxy", + "log2_pixels", + "ln_aspect", + "grayscale", +] + + +def feature_vector(sf: dict, feature_set: str, extra: dict | None = None) -> list[float]: + """The raw (unstandardized) feature vector of one image.""" + width = float(sf["width"]) + height = float(sf["height"]) + base = [ + ln(sf["luma_variance_q10"]), + ln(sf["luma_variance_q50"]), + ln(sf["luma_variance_q90"]), + ln(sf["chroma_variance_q50"]), + float(sf["flat_fraction"]), + ln(sf["edge_proxy"]), + math.log2(max(width * height, 1.0)), + math.log(max(width, 1.0) / max(height, 1.0)), + 1.0 if sf["grayscale"] else 0.0, + ] + if feature_set == "table2": + return [ln(sf["luma_variance_q50"]), float(sf["flat_fraction"])] + if feature_set == "source": + return base + if feature_set == "source+transform": + if extra is None: + raise ValueError("source+transform needs the per-image transform summary") + # `blocks` duplicates log2_pixels; every other summary field enters raw + # (they are already log/ratio/fraction shaped), in sorted key order. + return base + [float(extra[k]) for k in sorted(extra) if k != "blocks"] + raise ValueError(f"unknown feature set {feature_set}") + + +def robust_standardizer(rows: list[list[float]]) -> tuple[list[float], list[float]]: + """Per-feature (median, scale) with scale = 1.4826 * MAD. + + A feature whose MAD is zero (binary flags, near-constant corpora) keeps + scale 1.0: standardizing by a floored-tiny MAD would blow its values up + by orders of magnitude and let one rare row dominate every fit. + """ + dim = len(rows[0]) + centers, scales = [], [] + for j in range(dim): + values = sorted(row[j] for row in rows) + med = values[len(values) // 2] + mad = sorted(abs(v - med) for v in values)[len(values) // 2] + centers.append(med) + scales.append(1.4826 * mad if mad > 1e-12 else 1.0) + return centers, scales + + +def standardize(row: list[float], centers: list[float], scales: list[float]) -> list[float]: + return [(v - c) / s for v, c, s in zip(row, centers, scales)] + + +# -------------------------------------------------------------------------- +# Deterministic fitting +# -------------------------------------------------------------------------- + + +def solve_linear(matrix: list[list[float]], rhs: list[float]) -> list[float]: + """Gaussian elimination with partial pivoting (small dense systems).""" + n = len(rhs) + a = [row[:] + [rhs[i]] for i, row in enumerate(matrix)] + for col in range(n): + pivot = max(range(col, n), key=lambda r: abs(a[r][col])) + if abs(a[pivot][col]) < 1e-12: + a[col][col] += 1e-9 + else: + a[col], a[pivot] = a[pivot], a[col] + for r in range(col + 1, n): + factor = a[r][col] / a[col][col] + for c in range(col, n + 1): + a[r][c] -= factor * a[col][c] + out = [0.0] * n + for r in range(n - 1, -1, -1): + s = a[r][n] - sum(a[r][c] * out[c] for c in range(r + 1, n)) + out[r] = s / a[r][r] + return out + + +def fit_ols(xs: list[list[float]], ys: list[float], weights: list[float], ridge: float = RIDGE) -> list[float]: + """Weighted ridge least squares with an intercept column prepended.""" + n = len(xs) + dim = len(xs[0]) + 1 + xtx = [[0.0] * dim for _ in range(dim)] + xty = [0.0] * dim + for i in range(n): + row = [1.0] + xs[i] + w = weights[i] + for a in range(dim): + xty[a] += w * row[a] * ys[i] + for b in range(dim): + xtx[a][b] += w * row[a] * row[b] + for a in range(1, dim): + xtx[a][a] += ridge + return solve_linear(xtx, xty) + + +def fit_quantile( + xs: list[list[float]], + ys: list[float], + weights: list[float], + tau: float, + ridge: float = RIDGE, + iterations: int = 40, +) -> list[float]: + """Smoothed pinball-loss linear fit by deterministic IRLS. + + Each round solves a weighted ridge least-squares problem whose per-row + weight is the pinball subgradient magnitude over ``max(|residual|, + delta)`` — the standard iteratively reweighted approximation of quantile + regression. Fixed iteration count and summation order keep the result + reproducible. + """ + delta = 1e-3 + w = fit_ols(xs, ys, weights, ridge=ridge) + for _ in range(iterations): + irls_weights = [] + for i, x in enumerate(xs): + row = [1.0] + x + residual = ys[i] - sum(w[a] * row[a] for a in range(len(w))) + side = tau if residual > 0 else (1.0 - tau) + irls_weights.append(weights[i] * side / max(abs(residual), delta)) + w = fit_ols(xs, ys, irls_weights, ridge=ridge) + return w + + +def fit_logistic(xs: list[list[float]], ys: list[float], weights: list[float]) -> list[float]: + """Weighted logistic regression by deterministic gradient descent. + + Degenerate one-class knots return an intercept-only model saturating at + the observed class (clamped so the probability stays in (0, 1)). + """ + dim = len(xs[0]) + 1 + positives = sum(1 for y in ys if y > 0.5) + if positives == 0 or positives == len(ys): + logit = -6.0 if positives == 0 else 6.0 + return [logit] + [0.0] * (dim - 1) + w = [0.0] * dim + total = sum(weights) + for step in range(GD_ITERS): + lr = 0.5 / (1.0 + step / 200.0) + grad = [0.0] * dim + for i, x in enumerate(xs): + row = [1.0] + x + z = sum(w[a] * row[a] for a in range(dim)) + p = 1.0 / (1.0 + math.exp(-max(min(z, 30.0), -30.0))) + wi = weights[i] / total + for a in range(dim): + grad[a] += wi * (p - ys[i]) * row[a] + for a in range(dim): + if a > 0: + grad[a] += RIDGE * w[a] / max(total, 1.0) + w[a] -= lr * grad[a] + return w + + +def predict(w: list[float], x: list[float]) -> float: + return w[0] + sum(a * b for a, b in zip(w[1:], x)) + + +# -------------------------------------------------------------------------- +# Dataset assembly +# -------------------------------------------------------------------------- + + +def load_labels(path: str) -> tuple[dict, list[dict]]: + with open(path, encoding="utf-8") as fh: + lines = [json.loads(line) for line in fh if line.strip()] + header, rows = lines[0], lines[1:] + if header.get("schema") != "jpxl.quality-oracle-labels/1": + raise ValueError(f"unexpected labels schema in {path}") + return header, rows + + +def family_weights(rows: list[dict]) -> list[float]: + """Equal weight per family, split across that family's rows.""" + counts: dict[str, int] = {} + for row in rows: + counts[row["family_id"]] = counts.get(row["family_id"], 0) + 1 + return [1.0 / counts[row["family_id"]] for row in rows] + + +def rows_for_knot(rows: list[dict], target: float) -> list[dict]: + return [r for r in rows if abs(r["target"] - target) < 1e-9] + + +def load_raw_curves(*sweep_dirs: str) -> dict[str, list[tuple[int, float]]]: + """Per-image measured (effective_scale, score) curves from raw sweeps.""" + curves: dict[str, list[tuple[int, float]]] = {} + for sweep_dir in sweep_dirs: + for name in sorted(os.listdir(sweep_dir)): + if not name.endswith(".jsonl"): + continue + with open(os.path.join(sweep_dir, name), encoding="utf-8") as fh: + raw = json.loads(fh.read()) + if raw.get("schema") != "jpxl.quality-oracle-raw/1": + continue + curves[raw["image"]["id"]] = [ + (p["effective_scale"], p["score"]) for p in raw["points"] + ] + return curves + + +def score_at(curve: list[tuple[int, float]], scale: float) -> float: + """The measured curve's score at ``scale``: piecewise-linear in + ``(ln scale, ln loss)``, clamped at the measured ends.""" + if scale <= curve[0][0]: + return curve[0][1] + if scale >= curve[-1][0]: + return curve[-1][1] + x = math.log(scale) + for (s0, v0), (s1, v1) in zip(curve, curve[1:]): + if s0 <= scale <= s1: + x0, x1 = math.log(s0), math.log(s1) + if x1 <= x0: + return v1 + t = (x - x0) / (x1 - x0) + y = math.log(max(100.0 - v0, 1e-3)) + t * ( + math.log(max(100.0 - v1, 1e-3)) - math.log(max(100.0 - v0, 1e-3)) + ) + return 100.0 - math.exp(y) + return curve[-1][1] + + +def simulate_bytes_ratio(row: dict, predicted_scale: float) -> float | None: + """Bytes(predicted) / bytes(label), from the priced crossing points. + + Interpolates (or locally extrapolates) ``ln bytes`` against ``ln scale`` + over the priced neighbourhood; ``None`` when fewer than two priced points + exist or the label itself was not priced. + """ + priced = [ + (n["effective_scale"], n["bytes"]) + for n in row.get("neighbor_bytes", []) + if n.get("bytes") + ] + if len(priced) < 2 or not row.get("label_bytes"): + return None + priced.sort() + xs = [math.log(s) for s, _ in priced] + ys = [math.log(b) for _, b in priced] + x = math.log(predicted_scale) + if x <= xs[0]: + i = 0 + elif x >= xs[-1]: + i = len(xs) - 2 + else: + i = next(j for j in range(len(xs) - 1) if xs[j] <= x <= xs[j + 1]) + slope = (ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i]) if xs[i + 1] > xs[i] else 0.0 + predicted_bytes = math.exp(ys[i] + slope * (x - xs[i])) + return predicted_bytes / row["label_bytes"] + + +# -------------------------------------------------------------------------- +# Training and evaluation +# +# The crossing curve of one image is close to linear in `ln loss(target)`, +# so instead of fitting each target knot separately (30 rows against 10+ +# parameters — the per-knot ablation was variance-dominated), one pooled +# model is fitted over every uncensored row: +# +# ln scale = A(z) + B(z) * (ln loss(target) - X_T_CENTER) +# +# with `A` affine in all standardized features and `B` affine in a small, +# predeclared slope subset. Folding the pooled fit at a fixed target is +# exactly an affine model over the features again, so the generated per-knot +# Rust table (and the runtime) is unchanged. +# -------------------------------------------------------------------------- + +X_T_CENTER = 3.0 # about ln(100 - 80): centers the slope term near target 80 + +# Raw-feature indices whose interaction with `ln loss(target)` the pooled +# slope may use, per feature set. Small and predeclared, per the memo. +SLOPE_FEATURES = { + "table2": [0], + "source": [1, 4], # ln_luma_q50, flat_fraction + "source+transform": [1, 4, 13], # + ln_ac_y_mean +} + + +def design_row(z: list[float], x_t: float, slope_idx: list[int]) -> list[float]: + xc = x_t - X_T_CENTER + return list(z) + [xc] + [xc * z[i] for i in slope_idx] + + +def fold_at_target(w: list[float], target: float, dim: int, slope_idx: list[int]) -> list[float]: + """The pooled fit as an affine model over the features at one target.""" + xc = math.log(max(100.0 - target, 1e-3)) - X_T_CENTER + intercept = w[0] + w[1 + dim] * xc + coefs = list(w[1 : 1 + dim]) + for j, i in enumerate(slope_idx): + coefs[i] += w[2 + dim + j] * xc + return [intercept] + coefs + + +def pooled_fit(rows: list[dict], feature_set: str, ridge: float) -> dict: + """Standardizer plus pooled quantile/beta fits over all uncensored rows.""" + raw = [ + feature_vector(r["source_features"], feature_set, r.get("transform_features")) + for r in rows + ] + centers, scales = robust_standardizer(raw) + zs = [standardize(r, centers, scales) for r in raw] + z_range = [ + (min(z[j] for z in zs), max(z[j] for z in zs)) for j in range(len(centers)) + ] + slope_idx = SLOPE_FEATURES[feature_set] + crossing = [ + (z, r) for z, r in zip(zs, rows) if r["state"] != "censored" + ] + xs = [ + design_row(z, math.log(max(100.0 - r["target"], 1e-3)), slope_idx) + for z, r in crossing + ] + ys = [math.log(r["crossing_scale"]) for _, r in crossing] + weights = family_weights([r for _, r in crossing]) + fits = { + name: fit_quantile(xs, ys, weights, tau, ridge=ridge) + for name, tau in QUANTILES.items() + } + beta_rows = [ + (x, math.log(r["beta"])) + for x, (_, r) in zip(xs, crossing) + if r.get("beta") + ] + fits["beta"] = ( + fit_ols( + [x for x, _ in beta_rows], + [y for _, y in beta_rows], + [1.0] * len(beta_rows), + ridge=ridge, + ) + if len(beta_rows) >= 8 + else None + ) + return { + "centers": centers, + "scales": scales, + "z_range": z_range, + "slope_idx": slope_idx, + "fits": fits, + "rows": len(crossing), + } + + +def pooled_predict(model: dict, row: dict, feature_set: str, output: str) -> float: + z = standardize( + feature_vector(row["source_features"], feature_set, row.get("transform_features")), + model["centers"], + model["scales"], + ) + x = design_row(z, math.log(max(100.0 - row["target"], 1e-3)), model["slope_idx"]) + return predict(model["fits"][output], x) + + +def train_all(rows: list[dict], feature_set: str, ridge: float = RIDGE) -> dict: + """The pooled fit, folded into the per-knot table the runtime consumes.""" + pooled = pooled_fit(rows, feature_set, ridge) + dim = len(pooled["centers"]) + knots = {} + for target in TARGET_KNOTS: + knot_rows = rows_for_knot(rows, target) + if not knot_rows: + continue + knot: dict = { + "rows": sum(1 for r in knot_rows if r["state"] != "censored"), + "censored": sum(1 for r in knot_rows if r["state"] == "censored"), + } + for name in QUANTILES: + knot[name] = fold_at_target(pooled["fits"][name], target, dim, pooled["slope_idx"]) + if pooled["fits"]["beta"] is not None: + knot["beta"] = fold_at_target(pooled["fits"]["beta"], target, dim, pooled["slope_idx"]) + zs_all = [ + standardize( + feature_vector(r["source_features"], feature_set, r.get("transform_features")), + pooled["centers"], + pooled["scales"], + ) + for r in knot_rows + ] + censored_ys = [1.0 if r["state"] == "censored" else 0.0 for r in knot_rows] + knot["saturation"] = fit_logistic(zs_all, censored_ys, family_weights(knot_rows)) + knots[target] = knot + return { + "centers": pooled["centers"], + "scales": pooled["scales"], + "z_range": pooled["z_range"], + "knots": knots, + } + + +def family_fold(family: str, folds: int) -> int: + """Deterministic fold index of a family (salted hash, scan-order free).""" + digest = hashlib.sha256(b"fold:" + family.encode("utf-8")).digest() + return int.from_bytes(digest[:4], "big") % folds + + +def new_accumulator() -> dict: + return { + "errors": [], + "errors_by_class": {}, + "errors_by_target": {t: [] for t in TARGET_KNOTS}, + "first_plan": [], + "corrected_ok": [], + "fallback_fired": [], + "byte_ratios": [], + "routes": [], + "work": [], + "route_byte_ratios": [], + } + + +def score_rows( + model: dict, + rows: list[dict], + feature_set: str, + curves: dict[str, list[tuple[int, float]]] | None, + acc: dict, +) -> None: + """Score held-out rows against one fitted pooled model, accumulating the + gate metrics and the simulated common-case route (memo section 3.1).""" + errors = acc["errors"] + errors_by_target = acc["errors_by_target"] + first_plan = acc["first_plan"] + corrected_ok = acc["corrected_ok"] + fallback_fired = acc["fallback_fired"] + byte_ratios = acc["byte_ratios"] + routes = acc["routes"] + work = acc["work"] + route_byte_ratios = acc["route_byte_ratios"] + if True: + for row in rows: + if row["state"] == "censored": + continue + oracle = math.log(row["crossing_scale"]) + err = clamp_ln_scale(pooled_predict(model, row, feature_set, "median")) - oracle + errors.append(err) + errors_by_target[row["target"]].append(err) + acc["errors_by_class"].setdefault(row.get("class", "?"), []).append(err) + ln_candidate = clamp_ln_scale(pooled_predict(model, row, feature_set, "candidate")) + candidate_scale = math.exp(ln_candidate) + success = candidate_scale >= row["crossing_scale"] + first_plan.append(success) + ratio = simulate_bytes_ratio(row, candidate_scale) + if ratio is not None and success: + byte_ratios.append(ratio) + + # Runtime fallback simulation: wide interval or out-of-envelope + # feature, matching quality_prediction.rs. + width = clamp_ln_scale( + pooled_predict(model, row, feature_set, "upper") + ) - clamp_ln_scale(pooled_predict(model, row, feature_set, "lower")) + z = standardize( + feature_vector( + row["source_features"], feature_set, row.get("transform_features") + ), + model["centers"], + model["scales"], + ) + ood = any( + v < lo - 0.25 * max(hi - lo, 1e-6) or v > hi + 0.25 * max(hi - lo, 1e-6) + for v, (lo, hi) in zip(z, model["z_range"]) + ) + preflight_fallback = ood or width > 0.405465 + fallback_fired.append(preflight_fallback) + + # Counterfactual common-case route over the measured curve, + # mirroring the memo's section 3.1: one predicted plan, then + # either one byte-tightening attempt (large overshoot), one + # slope correction (miss), or continuation of the exact + # controller. Work is counted in full-frame reconstructions; + # bytes are relative to the oracle crossing (the operating point + # the current controller emits). + curve = (curves or {}).get(row["image_id"]) + if curve is not None: + if model["fits"]["beta"] is not None: + x = design_row( + z, math.log(max(100.0 - row["target"], 1e-3)), model["slope_idx"] + ) + beta = min(max(math.exp(predict(model["fits"]["beta"], x)), 0.2), 3.0) + else: + beta = 0.9 + target = row["target"] + if preflight_fallback: + routes.append("fallback_preflight") + work.append(FALLBACK_WORK) + route_ratio = 1.0 + corrected_ok.append(True) + elif success: + corrected_ok.append(True) + observed = score_at(curve, candidate_scale) + overshoot = observed - target + if overshoot > MET_OVERSHOOT_BAND: + # One coarsening attempt aimed just above the target. + shift = ( + math.log(max(100.0 - observed, 1e-3)) + - math.log(max(100.0 - (target + MIN_AIM_MARGIN), 1e-3)) + ) / beta + tightened = math.exp(clamp_ln_scale(ln_candidate + shift)) + work.append(2) + if tightened >= row["crossing_scale"]: + routes.append("tightened") + route_ratio = simulate_bytes_ratio(row, tightened) + else: + # Keep the verified first plan. + routes.append("tighten_kept_first") + route_ratio = simulate_bytes_ratio(row, candidate_scale) + else: + routes.append("one_shot") + work.append(1) + route_ratio = simulate_bytes_ratio(row, candidate_scale) + else: + observed = score_at(curve, candidate_scale) + shift = ( + math.log(max(100.0 - observed, 1e-3)) + - math.log(max(100.0 - (target + MIN_AIM_MARGIN), 1e-3)) + ) / beta + corrected = math.exp(clamp_ln_scale(ln_candidate + shift)) + if corrected >= row["crossing_scale"]: + corrected_ok.append(True) + routes.append("corrected") + work.append(2) + route_ratio = simulate_bytes_ratio(row, corrected) + else: + corrected_ok.append(False) + routes.append("fallback_exact") + work.append(2 + FALLBACK_WORK) + route_ratio = 1.0 + if route_ratio is not None: + route_byte_ratios.append(route_ratio) + + +def finalize_metrics(acc: dict, families: int) -> dict: + errors = acc["errors"] + errors_by_target = acc["errors_by_target"] + first_plan = acc["first_plan"] + corrected_ok = acc["corrected_ok"] + fallback_fired = acc["fallback_fired"] + byte_ratios = acc["byte_ratios"] + routes = acc["routes"] + work = acc["work"] + route_byte_ratios = acc["route_byte_ratios"] + + def quantile(sorted_values: list[float], q: float) -> float | None: + if not sorted_values: + return None + index = min(int(q * (len(sorted_values) - 1) + 0.9999), len(sorted_values) - 1) + return sorted_values[index] + + abs_sorted = sorted(abs(e) for e in errors) + geomean_bytes = ( + math.exp(sum(math.log(r) for r in byte_ratios) / len(byte_ratios)) + if byte_ratios + else None + ) + return { + "families": families, + "rows_scored": len(errors), + "median_abs_ln_error": quantile(abs_sorted, 0.5), + "p90_abs_ln_error": quantile(abs_sorted, 0.9), + "p99_abs_ln_error": quantile(abs_sorted, 0.99), + "per_target_median_abs": { + str(t): (sorted(abs(e) for e in v)[len(v) // 2] if v else None) + for t, v in errors_by_target.items() + }, + "per_class": { + name: { + "rows": len(v), + "median_abs": sorted(abs(e) for e in v)[len(v) // 2], + "p90_abs": quantile(sorted(abs(e) for e in v), 0.9), + } + for name, v in sorted(acc["errors_by_class"].items()) + }, + "first_plan_success": (sum(first_plan) / len(first_plan)) if first_plan else None, + "first_plan_rows": len(first_plan), + "first_or_correction_success": ( + (sum(corrected_ok) / len(corrected_ok)) if corrected_ok else None + ), + "fallback_rate": ( + (sum(fallback_fired) / len(fallback_fired)) if fallback_fired else None + ), + # Aggregate over the simulated common-case routes (tightening and + # correction included; routed-to-exact rows land on the oracle). + "simulated_byte_geomean": ( + math.exp(sum(math.log(r) for r in route_byte_ratios) / len(route_byte_ratios)) + if route_byte_ratios + else geomean_bytes + ), + "first_plan_byte_geomean": geomean_bytes, + "byte_rows": len(route_byte_ratios) or len(byte_ratios), + "expected_reconstructions": (sum(work) / len(work)) if work else None, + "route_fractions": ( + {name: routes.count(name) / len(routes) for name in sorted(set(routes))} + if routes + else None + ), + } + + +def evaluate_cv( + rows: list[dict], + feature_set: str, + ridge: float = RIDGE, + curves: dict[str, list[tuple[int, float]]] | None = None, + folds: int | None = None, +) -> dict: + """Family-grouped cross-validation of the pooled models. + + Leave-one-family-out when the family count is small; otherwise families + are bucketed into ``folds`` deterministic groups (salted hash of the + family id) so the fit count stays bounded. Rows of one family never + straddle a train/held boundary either way. + """ + families = sorted(set(r["family_id"] for r in rows)) + if folds is None: + folds = 0 if len(families) <= 40 else 10 + if folds and folds < len(families): + groups: dict[int, set[str]] = {} + for family in families: + groups.setdefault(family_fold(family, folds), set()).add(family) + held_sets = [groups[k] for k in sorted(groups)] + else: + held_sets = [{family} for family in families] + acc = new_accumulator() + for held_families in held_sets: + train = [r for r in rows if r["family_id"] not in held_families] + held = [r for r in rows if r["family_id"] in held_families] + model = pooled_fit(train, feature_set, ridge) + score_rows(model, held, feature_set, curves, acc) + return finalize_metrics(acc, len(families)) + + +# Kept as the historical name for the small-corpus path. +evaluate_lofo = evaluate_cv + + +GATES = { + "quick_screen": { + "p90_abs_ln_error": ("<=", 0.45), + "first_plan_success": (">=", 0.80), + "simulated_byte_geomean": ("<=", 1.015), + }, + "production": { + "median_abs_ln_error": ("<=", 0.10), + "p90_abs_ln_error": ("<=", 0.30), + "p99_abs_ln_error": ("<=", 0.70), + }, +} + + +def gate_verdicts(metrics: dict) -> dict: + out = {} + for gate, rules in GATES.items(): + checks = {} + for key, (op, bound) in rules.items(): + value = metrics.get(key) + if value is None: + checks[key] = None + elif op == "<=": + checks[key] = value <= bound + else: + checks[key] = value >= bound + out[gate] = { + "checks": checks, + "passed": all(v for v in checks.values() if v is not None) + and all(v is not None for v in checks.values()), + } + return out + + +# -------------------------------------------------------------------------- +# Rust generation +# -------------------------------------------------------------------------- + + +def rust_array(values: list[float]) -> str: + return "[" + ", ".join(f"{v!r}" for v in values) + "]" + + +def generate_rust(model: dict, provenance: dict, feature_set: str) -> str: + model_version, feature_schema = EMIT_IDENTITY[feature_set] + lines = [ + "//! Generated crossing-predictor model for the one-shot quality program.", + "//!", + f"//! GENERATED by `tools/quality_predictor_v2.py {TOOL_VERSION}` — do not edit", + "//! by hand; retrain and regenerate. The exact controller stays", + "//! authoritative: without the `one-shot-controller` feature this model", + "//! only feeds the shadow trace, never a bitstream.", + "//!", + f"//! model_version: {model_version}", + f"//! feature_schema: {feature_schema}", + f"//! labels: {provenance['labels_sha256']}", + f"//! trained_at: {provenance['generated_at']}", + f"//! git: {provenance['git']['commit']} (dirty: {provenance['git']['dirty']})", + "", + "/// The generated model's version string.", + f'pub const QPV2_MODEL_VERSION: &str = "{model_version}";', + "/// The feature-vector schema the coefficients expect.", + f'pub const QPV2_FEATURE_SCHEMA: &str = "{feature_schema}";', + "/// The feature-vector dimension the coefficient arrays expect. The", + "/// runtime feature builder is sized against this at compile time.", + f"pub const QPV2_FEATURE_DIM: usize = {len(model['centers'])};", + "", + "/// Robust per-feature centers (medians over the training corpus).", + f"pub const QPV2_FEATURE_CENTERS: [f64; {len(model['centers'])}] = {rust_array(model['centers'])};", + "/// Robust per-feature scales (1.4826 x MAD, floored).", + f"pub const QPV2_FEATURE_SCALES: [f64; {len(model['scales'])}] = {rust_array(model['scales'])};", + "/// Standardized-feature range seen in training, for OOD detection.", + f"pub const QPV2_FEATURE_Z_RANGE: [(f64, f64); {len(model['scales'])}] = [" + + ", ".join(f"({lo!r}, {hi!r})" for lo, hi in model["z_range"]) + + "];", + "", + "/// One target knot's fitted linear models over standardized features.", + "/// Every coefficient array is `[intercept, w_0, .., w_n]`; crossing", + "/// outputs are `ln(effective_scale)`, `beta` is `ln(beta)`, and", + "/// `saturation` is a logit.", + "#[derive(Debug, Clone, Copy, PartialEq)]", + "pub struct Qpv2Knot {", + " /// The SSIMULACRA2 target this knot was fitted at.", + " pub target: f64,", + " /// Rows the crossing fits saw (uncensored).", + " pub rows: u32,", + " /// tau = 0.10 crossing quantile.", + f" pub lower: [f64; {len(model['centers']) + 1}],", + " /// tau = 0.50 crossing quantile.", + f" pub median: [f64; {len(model['centers']) + 1}],", + " /// tau = 0.90 crossing quantile (the one-shot candidate).", + f" pub candidate: [f64; {len(model['centers']) + 1}],", + " /// tau = 0.95 crossing quantile.", + f" pub upper: [f64; {len(model['centers']) + 1}],", + " /// ln(beta) least squares, or all-zero when too few rows.", + f" pub beta: [f64; {len(model['centers']) + 1}],", + " /// Saturation-risk logit.", + f" pub saturation: [f64; {len(model['centers']) + 1}],", + "}", + "", + "/// The fitted knots, ascending in target.", + f"pub const QPV2_KNOTS: &[Qpv2Knot] = &[", + ] + for target in TARGET_KNOTS: + knot = model["knots"].get(target) + if not knot or "median" not in knot: + continue + dim = len(model["centers"]) + 1 + beta = knot.get("beta", [0.0] * dim) + lines.extend( + [ + " Qpv2Knot {", + f" target: {float(target)!r},", + f" rows: {knot['rows']},", + f" lower: {rust_array(knot['lower'])},", + f" median: {rust_array(knot['median'])},", + f" candidate: {rust_array(knot['candidate'])},", + f" upper: {rust_array(knot['upper'])},", + f" beta: {rust_array(beta)},", + f" saturation: {rust_array(knot['saturation'])},", + " },", + ] + ) + lines.append("];") + lines.append("") + return "\n".join(lines) + "\n" + + +# -------------------------------------------------------------------------- +# Entry +# -------------------------------------------------------------------------- + + +def sha256_file(path: str) -> str: + h = hashlib.sha256() + with open(path, "rb") as fh: + for chunk in iter(lambda: fh.read(1 << 20), b""): + h.update(chunk) + return h.hexdigest() + + +def git_provenance(root: str) -> dict: + def run(*argv: str) -> str: + return subprocess.run( + ["git", *argv], cwd=root, capture_output=True, text=True, check=True + ).stdout.strip() + + return {"commit": run("rev-parse", "HEAD"), "dirty": run("status", "--porcelain") != ""} + + +def cmd_train(args: argparse.Namespace) -> int: + _, rows = load_labels(args.labels) + # The declared model domain: tiny frames sit against the metric's own + # floor and route straight to the exact controller at runtime, so they + # neither train nor score the model. + in_domain = [ + r + for r in rows + if min(r["source_features"]["width"], r["source_features"]["height"]) + >= MIN_DOMAIN_SIDE + ] + out_of_domain = len(rows) - len(in_domain) + if out_of_domain: + print(f"{out_of_domain} rows below the {MIN_DOMAIN_SIDE}px domain floor route to the exact controller") + rows = in_domain + if args.transform_features: + with open(args.transform_features, encoding="utf-8") as fh: + transform_map = json.load(fh) + for row in rows: + row["transform_features"] = transform_map.get(row["image_id"]) + missing = sum(1 for r in rows if r.get("transform_features") is None) + if missing: + print(f"warning: {missing} label rows lack transform features", file=sys.stderr) + rows = [r for r in rows if r.get("transform_features") is not None] + provenance = { + "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), + "labels_sha256": sha256_file(args.labels), + "git": git_provenance(args.repo_root), + "tool_version": TOOL_VERSION, + } + + report: dict = { + "schema": REPORT_SCHEMA, + "provenance": provenance, + "feature_sets": {}, + } + chosen_ridge: dict[str, float] = {} + for feature_set in args.feature_sets: + if feature_set == "source+transform" and not args.transform_features: + print("note: source+transform needs --transform-features; skipped") + continue + per_ridge = {} + best_ridge, best_metrics = None, None + curves = load_raw_curves(*args.sweep_dir) if args.sweep_dir else None + train_rows = [r for r in rows if r["split"] != args.blind_split] + blind_rows = [r for r in rows if r["split"] == args.blind_split] + for ridge in args.ridge_grid: + metrics = evaluate_cv( + train_rows, feature_set, ridge, curves=curves, folds=args.cv_folds + ) + per_ridge[str(ridge)] = metrics + if best_metrics is None or metrics["p90_abs_ln_error"] < best_metrics["p90_abs_ln_error"]: + best_ridge, best_metrics = ridge, metrics + chosen_ridge[feature_set] = best_ridge + blind_metrics = None + if blind_rows: + # Never-tuned families: fit on every training row at the chosen + # ridge and score the blind split once. + blind_model = pooled_fit(train_rows, feature_set, best_ridge) + blind_acc = new_accumulator() + score_rows(blind_model, blind_rows, feature_set, curves, blind_acc) + blind_metrics = finalize_metrics( + blind_acc, len(set(r["family_id"] for r in blind_rows)) + ) + report["feature_sets"][feature_set] = { + "ridge_grid": per_ridge, + "chosen_ridge": best_ridge, + "lofo": best_metrics, + "gates": gate_verdicts(best_metrics), + "blind_holdout": blind_metrics, + "blind_gates": gate_verdicts(blind_metrics) if blind_metrics else None, + } + print(f"[{feature_set}] ridge={best_ridge} {json.dumps(best_metrics, indent=2)}") + print(f"[{feature_set}] gates: {json.dumps(gate_verdicts(best_metrics))}") + if blind_metrics: + print(f"[{feature_set}] blind {args.blind_split}: {json.dumps(blind_metrics, indent=2)}") + print(f"[{feature_set}] blind gates: {json.dumps(gate_verdicts(blind_metrics))}") + + # The blind split stays out of the shipped model too, so it remains a + # valid never-tuned holdout for later revisions. + emit_rows = [r for r in rows if r["split"] != args.blind_split] + model = train_all(emit_rows, args.emit_feature_set, chosen_ridge.get(args.emit_feature_set, RIDGE)) + rust = generate_rust(model, provenance, args.emit_feature_set) + with open(args.rust_out, "w", encoding="utf-8", newline="\n") as fh: + fh.write(rust) + report["emitted"] = { + "feature_set": args.emit_feature_set, + "rust": args.rust_out, + "rust_sha256": hashlib.sha256(rust.encode()).hexdigest(), + "knots": {str(t): {"rows": k["rows"], "censored": k["censored"]} for t, k in model["knots"].items()}, + } + with open(args.report_out, "w", encoding="utf-8", newline="\n") as fh: + fh.write(json.dumps(report, indent=2, sort_keys=False) + "\n") + print(f"wrote {args.rust_out} and {args.report_out}") + print("note: run `cargo fmt --all` — the emitted table is not rustfmt-shaped") + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + train = sub.add_parser("train", help="fit, cross-validate, and emit the model") + train.add_argument("--labels", required=True) + train.add_argument("--repo-root", default=".") + train.add_argument( + "--feature-sets", + nargs="+", + default=["table2", "source"], + choices=["table2", "source", "source+transform"], + ) + train.add_argument("--transform-features", default=None, + help="per-image transform-summary JSON map (for source+transform)") + train.add_argument("--ridge-grid", nargs="+", type=float, default=[0.03, 0.3, 3.0], + help="ridge strengths tried per feature set; chosen by LOFO p90") + train.add_argument("--sweep-dir", nargs="+", default=None, + help="raw sweep dir(s); enables the one-correction counterfactual") + train.add_argument("--blind-split", default="ext-holdout", + help="split name held out of all tuning and scored once") + train.add_argument("--cv-folds", type=int, default=None, + help="family-grouped CV folds (default: LOFO up to 40 families, else 10)") + train.add_argument("--emit-feature-set", default="source+transform", + choices=["source", "source+transform"]) + train.add_argument("--rust-out", required=True) + train.add_argument("--report-out", required=True) + train.set_defaults(func=cmd_train) + return parser + + +def main(argv: list[str]) -> int: + args = build_parser().parse_args(argv) + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/JPXL/tools/tests/test_codec_compare.py b/JPXL/tools/tests/test_codec_compare.py index 781c88c9..9375625b 100644 --- a/JPXL/tools/tests/test_codec_compare.py +++ b/JPXL/tools/tests/test_codec_compare.py @@ -239,7 +239,7 @@ def test_quality_trace_reader(self): json.dumps({"schema": "other/1"}) + "\n" + json.dumps( { - "schema": codec_compare.QUALITY_TRACE_SCHEMA, + "schema": codec_compare.QUALITY_TRACE_SCHEMAS[0], "requested_score": 85.0, "achieved_score": 85.1, "wall_by_phase": { diff --git a/JPXL/tools/tests/test_make_quality_guard_fixtures.py b/JPXL/tools/tests/test_make_quality_guard_fixtures.py index bc42cc72..14ea0c41 100644 --- a/JPXL/tools/tests/test_make_quality_guard_fixtures.py +++ b/JPXL/tools/tests/test_make_quality_guard_fixtures.py @@ -163,11 +163,22 @@ def test_each_synthetic_class_has_all_three_splits(self): ) def test_split_and_class_counts_are_stable(self): + # 2026-08-24: the one-shot program's coverage expansion added nine + # calibration/development fixtures (noise-lowlight and grayscale from + # more captures; second text/UI/hatch/sky families). The locked + # holdout is frozen at 13. by_split = Counter(f.split for f in self.fixtures) - self.assertEqual(by_split["calibration"], 19) - self.assertEqual(by_split["development"], 15) + self.assertEqual(by_split["calibration"], 24) + self.assertEqual(by_split["development"], 19) self.assertEqual(by_split["holdout"], 13) + def test_families_never_cross_splits(self): + by_family = defaultdict(set) + for fx in self.fixtures: + by_family[mqgf.family_fields(fx)["family_id"]].add(fx.split) + leaking = {fam: s for fam, s in by_family.items() if len(s) > 1} + self.assertEqual(leaking, {}, "image families must stay in one split") + if __name__ == "__main__": unittest.main() diff --git a/JPXL/tools/tests/test_quality_oracle_labels.py b/JPXL/tools/tests/test_quality_oracle_labels.py new file mode 100644 index 00000000..ff0ae64f --- /dev/null +++ b/JPXL/tools/tests/test_quality_oracle_labels.py @@ -0,0 +1,154 @@ +"""Unit tests for the pure helpers of quality_oracle_labels.py.""" + +from __future__ import annotations + +import math +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +import quality_oracle_labels as qol + + +class GridTest(unittest.TestCase): + def test_geometric_grid_includes_endpoints_strictly_increasing(self): + grid = qol.geometric_grid(1, 73728, 1.6) + self.assertEqual(grid[0], 1) + self.assertEqual(grid[-1], 73728) + self.assertTrue(all(a < b for a, b in zip(grid, grid[1:]))) + + def test_geometric_grid_rejects_bad_input(self): + with self.assertRaises(ValueError): + qol.geometric_grid(10, 5, 1.6) + with self.assertRaises(ValueError): + qol.geometric_grid(1, 10, 1.0) + + +class CrossingTest(unittest.TestCase): + POINTS = [(100, 20.0), (200, 40.0), (400, 60.0), (800, 80.0), (1600, 95.0)] + + def test_crossed_picks_the_coarsest_meeting_point(self): + state = qol.crossing_state(self.POINTS, 70.0) + self.assertEqual(state["state"], "crossed") + self.assertEqual(state["below"], (400, 60.0)) + self.assertEqual(state["above"], (800, 80.0)) + + def test_local_reversal_cannot_hide_a_coarser_feasible_point(self): + # 400 meets 55 but 800 dips below it again: the label is still 400. + points = [(100, 20.0), (400, 60.0), (800, 50.0), (1600, 95.0)] + state = qol.crossing_state(points, 55.0) + self.assertEqual(state["above"], (400, 60.0)) + + def test_censored_when_nothing_meets(self): + self.assertEqual(qol.crossing_state(self.POINTS, 99.0)["state"], "censored") + + def test_floor_when_the_first_point_meets(self): + state = qol.crossing_state(self.POINTS, 10.0) + self.assertEqual(state["state"], "floor") + self.assertEqual(state["above"], (100, 20.0)) + + +class RefineTest(unittest.TestCase): + def test_refine_scales_are_strictly_inside_and_geometric(self): + inner = qol.refine_scales(1000, 8000, 3) + self.assertTrue(all(1000 < s < 8000 for s in inner)) + self.assertEqual(inner, sorted(set(inner))) + # Geometric spacing: successive ratios are near-equal. + ratios = [b / a for a, b in zip([1000] + inner, inner + [8000])] + self.assertLess(max(ratios) / min(ratios), 1.05) + + def test_refine_scales_empty_for_adjacent_bracket(self): + self.assertEqual(qol.refine_scales(1000, 1001, 3), []) + + +class InterpTest(unittest.TestCase): + def test_interp_matches_exact_power_law(self): + # loss = 60 * (scale/1000)^-0.8, the curve the encoder tests use. + def score(scale: float) -> float: + return 100.0 - 60.0 * (scale / 1000.0) ** -0.8 + + target = 85.0 + exact = 1000.0 * (60.0 / (100.0 - target)) ** (1.0 / 0.8) + below = (2000, score(2000)) + above = (40000, score(40000)) + got = qol.interp_crossing(below, above, target) + self.assertLess(abs(math.log(got / exact)), 1e-6) + + def test_interp_falls_back_to_midpoint_on_unordered_loss(self): + got = qol.interp_crossing((1000, 90.0), (2000, 80.0), 85.0) + self.assertLess(abs(math.log(got / math.sqrt(1000 * 2000))), 1e-9) + + +class BetaTest(unittest.TestCase): + def test_beta_recovers_the_power_law_exponent(self): + points = [ + (s, 100.0 - 60.0 * (s / 1000.0) ** -0.8) + for s in (2000, 4000, 8000, 16000, 32000) + ] + beta = qol.local_beta(points, 8000.0) + self.assertIsNotNone(beta) + self.assertLess(abs(beta - 0.8), 1e-6) + + def test_beta_is_none_for_flat_or_rising_loss(self): + self.assertIsNone(qol.local_beta([(1000, 50.0), (2000, 40.0)], 1500.0)) + self.assertIsNone(qol.local_beta([(1000, 50.0)], 1000.0)) + + +class MergeTest(unittest.TestCase): + def test_merge_prefers_priced_records_and_sorts_by_rung(self): + first = [{"rung": 10, "effective_scale": 11, "score": 50.0, "bytes": None}] + second = [ + {"rung": 10, "effective_scale": 11, "score": 50.0, "bytes": 123}, + {"rung": 5, "effective_scale": 6, "score": 30.0, "bytes": None}, + ] + merged = qol.merge_points([first, second]) + self.assertEqual([r["rung"] for r in merged], [5, 10]) + self.assertEqual(merged[1]["bytes"], 123) + + def test_merge_never_downgrades_a_priced_record(self): + first = [{"rung": 10, "effective_scale": 11, "score": 50.0, "bytes": 123}] + second = [{"rung": 10, "effective_scale": 11, "score": 50.0, "bytes": None}] + merged = qol.merge_points([first, second]) + self.assertEqual(merged[0]["bytes"], 123) + + +class LabelsTest(unittest.TestCase): + RAW = { + "schema": qol.RAW_SCHEMA, + "targets": [50.0, 99.0], + "image": { + "id": "img", + "family_id": "fam", + "split": "calibration", + "class": "photo", + "variant_id": "img", + "source_capture_id": None, + }, + "points": [ + {"rung": 99, "effective_scale": 100, "score": 20.0, "bytes": None}, + {"rung": 399, "effective_scale": 400, "score": 45.0, "bytes": 900}, + {"rung": 799, "effective_scale": 800, "score": 60.0, "bytes": 1400}, + {"rung": 1599, "effective_scale": 1600, "score": 95.0, "bytes": None}, + ], + } + + def test_crossed_and_censored_rows(self): + rows = qol.labels_for_raw(self.RAW) + crossed = rows[0] + self.assertEqual(crossed["state"], "crossed") + self.assertEqual(crossed["label_rung"], 799) + self.assertEqual(crossed["label_bytes"], 1400) + self.assertTrue(400 < crossed["crossing_scale"] < 800) + self.assertEqual( + [n["bytes"] for n in crossed["neighbor_bytes"]], [900, 1400] + ) + censored = rows[1] + self.assertEqual(censored["state"], "censored") + self.assertIsNone(censored["label_rung"]) + self.assertEqual(censored["top_score"], 95.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/JPXL/tools/tests/test_quality_predictor_v2.py b/JPXL/tools/tests/test_quality_predictor_v2.py new file mode 100644 index 00000000..63b971a8 --- /dev/null +++ b/JPXL/tools/tests/test_quality_predictor_v2.py @@ -0,0 +1,200 @@ +"""Unit tests for the pure fitting helpers of quality_predictor_v2.py.""" + +from __future__ import annotations + +import math +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +import quality_predictor_v2 as qpv2 + + +class SolveTest(unittest.TestCase): + def test_solve_linear_recovers_exact_solution(self): + matrix = [[2.0, 1.0], [1.0, 3.0]] + rhs = [5.0, 10.0] + x = qpv2.solve_linear(matrix, rhs) + self.assertAlmostEqual(x[0], 1.0, places=9) + self.assertAlmostEqual(x[1], 3.0, places=9) + + +class OlsTest(unittest.TestCase): + def test_ols_recovers_a_noiseless_line(self): + xs = [[float(i)] for i in range(10)] + ys = [3.0 + 2.0 * i for i in range(10)] + w = qpv2.fit_ols(xs, ys, [1.0] * 10, ridge=0.0) + self.assertAlmostEqual(w[0], 3.0, places=6) + self.assertAlmostEqual(w[1], 2.0, places=6) + + +class QuantileTest(unittest.TestCase): + def test_median_fit_splits_asymmetric_noise(self): + # y = 5 + x with one gross positive outlier; the median fit should + # stay near the line while OLS is dragged upward. + xs = [[float(i)] for i in range(20)] + ys = [5.0 + i for i in range(20)] + ys[10] += 50.0 + w = qpv2.fit_quantile(xs, ys, [1.0] * 20, tau=0.5) + predicted = qpv2.predict(w, [10.0]) + self.assertLess(abs(predicted - 15.0), 1.0) + + def test_higher_tau_sits_above_lower_tau(self): + # Constant-feature rows with spread: quantiles must order. + xs = [[0.0]] * 50 + ys = [float(i) for i in range(50)] + w50 = qpv2.fit_quantile(xs, ys, [1.0] * 50, tau=0.5) + w90 = qpv2.fit_quantile(xs, ys, [1.0] * 50, tau=0.9) + self.assertGreater(qpv2.predict(w90, [0.0]), qpv2.predict(w50, [0.0]) + 5.0) + + +class LogisticTest(unittest.TestCase): + def test_degenerate_single_class_saturates(self): + w = qpv2.fit_logistic([[0.0]] * 5, [0.0] * 5, [1.0] * 5) + self.assertLess(w[0], -5.0) + w = qpv2.fit_logistic([[0.0]] * 5, [1.0] * 5, [1.0] * 5) + self.assertGreater(w[0], 5.0) + + def test_separable_classes_order_by_feature(self): + xs = [[-1.0]] * 10 + [[1.0]] * 10 + ys = [0.0] * 10 + [1.0] * 10 + w = qpv2.fit_logistic(xs, ys, [1.0] * 20) + self.assertGreater(w[1], 0.5) + + +class StandardizerTest(unittest.TestCase): + def test_median_and_mad(self): + rows = [[float(v)] for v in (1, 2, 3, 4, 100)] + centers, scales = qpv2.robust_standardizer(rows) + self.assertEqual(centers[0], 3.0) + # MAD of [2,1,0,1,97] sorted -> 1; scale = 1.4826. + self.assertAlmostEqual(scales[0], 1.4826, places=4) + + +class BytesTest(unittest.TestCase): + ROW = { + "label_bytes": 1000, + "neighbor_bytes": [ + {"effective_scale": 500, "bytes": 800}, + {"effective_scale": 1000, "bytes": 1000}, + {"effective_scale": 2000, "bytes": 1300}, + ], + } + + def test_interpolates_between_priced_points(self): + ratio = qpv2.simulate_bytes_ratio(self.ROW, 1000.0) + self.assertAlmostEqual(ratio, 1.0, places=6) + finer = qpv2.simulate_bytes_ratio(self.ROW, 2000.0) + self.assertAlmostEqual(finer, 1.3, places=6) + + def test_none_without_enough_pricing(self): + row = {"label_bytes": 1000, "neighbor_bytes": [{"effective_scale": 1000, "bytes": 1000}]} + self.assertIsNone(qpv2.simulate_bytes_ratio(row, 1000.0)) + + +class FamilyWeightTest(unittest.TestCase): + def test_each_family_carries_equal_total_weight(self): + rows = [ + {"family_id": "a"}, + {"family_id": "a"}, + {"family_id": "b"}, + ] + weights = qpv2.family_weights(rows) + self.assertAlmostEqual(weights[0] + weights[1], 1.0) + self.assertAlmostEqual(weights[2], 1.0) + + +class GateTest(unittest.TestCase): + def test_gate_verdicts(self): + metrics = { + "median_abs_ln_error": 0.08, + "p90_abs_ln_error": 0.25, + "p99_abs_ln_error": 0.5, + "first_plan_success": 0.9, + "simulated_byte_geomean": 1.01, + } + verdicts = qpv2.gate_verdicts(metrics) + self.assertTrue(verdicts["quick_screen"]["passed"]) + self.assertTrue(verdicts["production"]["passed"]) + metrics["p90_abs_ln_error"] = 0.5 + verdicts = qpv2.gate_verdicts(metrics) + self.assertFalse(verdicts["quick_screen"]["passed"]) + self.assertFalse(verdicts["production"]["passed"]) + + +class EndToEndTest(unittest.TestCase): + def synthetic_rows(self) -> list[dict]: + # Crossing law: ln(scale) = 6 + 1.5*ln(loss ratio) + 0.8*ln(q50) style — + # exactly linear in the trainer's feature space, so LOFO must nail it. + rows = [] + for fam in range(8): + q50 = 10.0 ** (-6 + fam * 0.4) + flat = 0.1 * fam / 8.0 + sf = { + "width": 640, + "height": 480, + "grayscale": False, + "luma_variance_q10": q50 * 0.5, + "luma_variance_q50": q50, + "luma_variance_q90": q50 * 2.0, + "chroma_variance_q50": q50 * 0.7, + "flat_fraction": flat, + "edge_proxy": q50, + } + for t_idx, target in enumerate(qpv2.TARGET_KNOTS): + # Linear in ln(loss) — the pooled model's family — plus a + # deterministic residual spread so the quantile fits have + # something to separate on. + y = ( + 6.0 + - 0.35 * math.log(qpv2.EPS + q50) + - 0.9 * (math.log(100.0 - target) - qpv2.X_T_CENTER) + + 0.15 * math.sin(2.7 * (fam * 7 + t_idx)) + ) + scale = math.exp(y) + rows.append( + { + "image_id": f"img{fam}", + "family_id": f"fam{fam}", + "split": "calibration", + "class": "photo", + "target": target, + "state": "crossed", + "crossing_scale": scale, + "label_rung": int(scale), + "beta": 0.8, + "label_bytes": 1000, + "neighbor_bytes": [ + {"effective_scale": scale / 1.3, "bytes": 900}, + {"effective_scale": scale, "bytes": 1000}, + {"effective_scale": scale * 1.3, "bytes": 1100}, + ], + "source_features": sf, + } + ) + return rows + + def test_lofo_recovers_a_linear_law(self): + rows = self.synthetic_rows() + metrics = qpv2.evaluate_lofo(rows, "source") + self.assertIsNotNone(metrics["median_abs_ln_error"]) + # The law is representable; the deterministic +-0.15 spread bounds + # the median error from above, and the tau=0.9 candidate should + # clear most of that spread. + self.assertLess(metrics["median_abs_ln_error"], 0.2) + self.assertGreaterEqual(metrics["first_plan_success"], 0.75) + + def test_candidate_quantile_sits_above_the_median(self): + model = qpv2.train_all(self.synthetic_rows(), "source") + for target, knot in model["knots"].items(): + self.assertGreater( + knot["candidate"][0], + knot["median"][0], + f"tau=0.9 must sit above tau=0.5 at target {target}", + ) + + +if __name__ == "__main__": + unittest.main() From 8746713b1aba8714c76a1c19b0f01dcaad325382 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 00:56:28 +0800 Subject: [PATCH 06/27] chore(.akr): record the one-shot program - decision, evidence, promotion Ledger for the memo-driven session: the quality-miss fallback-semantics decision; evidence for the falsification gate (honest FAIL on the 38-family corpus: LOFO median .22/p90 .81), the expanded-corpus gate (blind painting holdout median .113/p90 .258, PASS) and the promotion A/B (441 never-tuned cells, zero floor violations, PASS); the pqc-one-shot-controller work record at rev 2 with the open items (12 MP wall anchor 6.31x vs the 2.0x requirement, saturated/banding corpus coverage, 108 unswept ext-polart images); the proposed JPEG bitstream-recompression work record; and the regenerated views. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- .akr/akr.lock | 164 ++++++++++++++- .akr/records/jpegxl-rs/decisions.akr | 29 +++ .akr/records/jpegxl-rs/evidence.akr | 136 ++++++++++++ .akr/records/jpegxl-rs/observations.akr | 72 +++++++ .akr/records/jpegxl-rs/papercuts.akr | 23 ++ .akr/records/jpegxl-rs/work.akr | 265 +++++++++++++++++++++--- docs/generated/ACTIVE-WORK.md | 16 +- docs/generated/CURRENT-STATE.md | 100 ++++++++- docs/generated/DECISION-HISTORY.md | 14 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 4 +- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 7 +- 13 files changed, 786 insertions(+), 48 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 9f386709..a0b24333 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:29947c3fadfc107253854896393926aa0698b43a301121176a032e4f7a7260f4" + source_graph "sha256:c236729fcb7dc6ee76cdcbed13105424fee3cdac625e963fb4c4d230dd4b1dd9" } source ".akr/project.akr" { @@ -24,13 +24,13 @@ source ".akr/records/jpegxl-rs/constraints.akr" { } source ".akr/records/jpegxl-rs/decisions.akr" { - hash "sha256:a0a027029a3e5cca15f8b8da6c57bcd78beca92aa6be8e16329a84c652149570" - records 22 + hash "sha256:a7c07810b408069dab2854fdacf83ddb5d502da1a39974cef192ca016a15eb88" + records 23 } source ".akr/records/jpegxl-rs/evidence.akr" { - hash "sha256:05e3371b89c14b5329e59ee4650f9251e6666ee682beffbf3588af23a2562f11" - records 538 + hash "sha256:67c44836cf09099abe12a6ac71e16e3bbeab8649fc36412b09ba48d74d72832f" + records 549 } source ".akr/records/jpegxl-rs/milestones.akr" { @@ -39,13 +39,13 @@ source ".akr/records/jpegxl-rs/milestones.akr" { } source ".akr/records/jpegxl-rs/observations.akr" { - hash "sha256:a4ac22959d397d2d3ccc7794c19793623a94b360565f0003b43a4e5722f3d121" - records 95 + hash "sha256:9cc252028877e6d8d60957f5b5d8b05e5ca6f176bbd864023fabfecdb251cdb0" + records 98 } source ".akr/records/jpegxl-rs/papercuts.akr" { - hash "sha256:d10a9e7e42b1b89437934e9a3a4360d484c38094d1a44c91243ce8036f88b24d" - records 41 + hash "sha256:e72aa62ec85dba557e935911debdbc32965208354d8361dbf0d8e3953d8c1c67" + records 43 } source ".akr/records/jpegxl-rs/policies.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:fc2591b8fd0431514a35c1cfb4ab1ba44b639dbefbf7fe455ab85001d621362b" - records 177 + hash "sha256:28ac87edba6875e61db20985b8b69fa30a2e7dcf898b6e876267e34f09397993" + records 182 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -1423,6 +1423,30 @@ resolution @jpegxl-rs.observation.phase5-quality-policy-screen-2026-08-11/11 { hash "sha256:94cf4ec999e131054d664d2c758933fa05d0e47d880baa25751f51ac108ded1e" } +resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 { + slot derived_from + to @jpegxl-rs.work.pqc-one-shot-controller/2 + hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" +} + +resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 { + slot verified_by + to @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 + hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" +} + +resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 { + slot derived_from + to @jpegxl-rs.work.pqc-one-shot-controller/2 + hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" +} + +resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 { + slot verified_by + to @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 + hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" +} + resolution @jpegxl-rs.observation.quantizer-normalised-distortion-is-the-lever-2026-08-12/1 { slot derived_from to @jpegxl-rs.observation.distortion-currency-misprices-frequency-2026-08-12/1 @@ -3667,6 +3691,24 @@ resolution @jpegxl-rs.work.opt-v2-rate-work-sharing/2 { hash "sha256:f7733beff3d25a93a7ba717c5686463ca7c969d91eae48dbaacfeefae835d73d" } +resolution @jpegxl-rs.work.pqc-one-shot-controller/1 { + slot depends_on + to @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 + hash "sha256:b835fe82f41cf362b357e9b22d4f9cc5b6ed0c8ed7a256da9c4e51d18576c366" +} + +resolution @jpegxl-rs.work.pqc-one-shot-controller/2 { + slot depends_on + to @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 + hash "sha256:b835fe82f41cf362b357e9b22d4f9cc5b6ed0c8ed7a256da9c4e51d18576c366" +} + +resolution @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 { + slot implements + to @jpegxl-rs.decision.quality-miss-fallback-semantics/1 + hash "sha256:52c2c39f382fd60234500876d6de823679e2b836bff13d4a5b2ec3a1c67bec12" +} + resolution @jpegxl-rs.work.quality-q0-harness-attribution/1 { slot part_of to @jpegxl-rs.track.encoder-optimization/1 @@ -5880,11 +5922,31 @@ seal @jpegxl-rs.evidence.pqc-low-memory-xlarge-identity-2026-08-23/1 { hash "sha256:79a3f38d1ea158a2cf0d801e7183885188f146d970d654988c55de059ca11d39" } +seal @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 { + state verified + hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" +} + +seal @jpegxl-rs.evidence.pqc-one-shot-pr1-5-gates-2026-08-24/1 { + state verified + hash "sha256:b419d8755a391df8409ca076112e2aa557841ad9dc9fa7666b7949bffc3614db" +} + +seal @jpegxl-rs.evidence.pqc-one-shot-promotion-ab-2026-08-25/1 { + state verified + hash "sha256:a6abe403b04e1ff0e28d938ac91d68e24042b6211256ca2706f68219484603dd" +} + seal @jpegxl-rs.evidence.pqc-pr0-corpus-manifest-2026-08-22/1 { state verified hash "sha256:7e2025d7b999667cc087b45cd550f86dbbc68a07de58532eada59a38898e857f" } +seal @jpegxl-rs.evidence.pqc-pr0-hard-floor-tests-2026-08-24/1 { + state verified + hash "sha256:05ccad4755a3b8fae64850e88f830a53c9424175ddec1200d9bc3a646e788bdb" +} + seal @jpegxl-rs.evidence.pqc-pr0-predictor-table-2026-08-22/1 { state verified hash "sha256:240c39820db7a47f61f4ad8c3ab109dd51cb48fc7ed6bee2de98c6c704d22501" @@ -5895,6 +5957,11 @@ seal @jpegxl-rs.evidence.pqc-pr0-sources-registered-2026-08-22/1 { hash "sha256:a225dddef75f255ed5115cfb36782388278c7e3897f40a3cacb9e30eb21fc143" } +seal @jpegxl-rs.evidence.pqc-pr0-workspace-gates-2026-08-24/1 { + state verified + hash "sha256:a1d17ff10b705658a94f18f7d468b629cd3a48cc35bd71687e96c36eabb7c602" +} + seal @jpegxl-rs.evidence.pqc-pr1-api-semantics-2026-08-22/1 { state verified hash "sha256:454da8cf4e2f11f3a2347547973189179866e3fc7c13501f476b4d44fea4bcef" @@ -6020,11 +6087,36 @@ seal @jpegxl-rs.evidence.pqc-pr7-balanced-single-shot-2026-08-22/1 { hash "sha256:91eddafebcc680bde1d197530f6aeb86bb3ef9d24eb4cfe8623e539dd493b622" } +seal @jpegxl-rs.evidence.pqc-pr7-closure-workspace-gates-2026-08-24/1 { + state verified + hash "sha256:41c0eb5952e24d5bb2a72a6eaca2f9cd2d5019d4044112671839901bc64fdb83" +} + seal @jpegxl-rs.evidence.pqc-pr7-dev-split-2026-08-22/1 { state verified hash "sha256:98914411a1b245d74cc49faab93ae8a79cc0594122b842bb78eb39490933d35d" } +seal @jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1 { + state verified + hash "sha256:0d86d7c3470b6cd109ac2200256479601f0a5dcfe2c394d9816dea71153884d8" +} + +seal @jpegxl-rs.evidence.pqc-pr7-policy-gating-test-2026-08-24/1 { + state verified + hash "sha256:24c29e34a49849206ca8f873c4abc657c945875d2d8e380d3c57e7482d3d0bf2" +} + +seal @jpegxl-rs.evidence.pqc-pr7-promotion-disposition-2026-08-24/1 { + state verified + hash "sha256:607f815f2c55ee821124b57e89128b733e6826f3f838b7d0a000d6adb184130a" +} + +seal @jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1 { + state verified + hash "sha256:40063888bc1e845191c0dff5e77c70d344adeeaaeebf361758909677e98b35e9" +} + seal @jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1 { state verified hash "sha256:4c42aeb2d62fb81496e253780d2f4c586d7274abf1a1d6a6e04504050c03cae5" @@ -6035,6 +6127,11 @@ seal @jpegxl-rs.evidence.pqc-pr7-reducer-holdout-partial-2026-08-22/1 { hash "sha256:a3d9cc6819973d8f86a583b502c3aaf79848765afd538a54430885068eb54774" } +seal @jpegxl-rs.evidence.pqc-qpv2-expanded-corpus-gate-2026-08-25/1 { + state verified + hash "sha256:f2bcdb8fe27e27f07ecdbdc02473d08f900730068580a5d7010dfbac15e777e2" +} + seal @jpegxl-rs.evidence.pqc-quality-rate-curve-exact-10of13-2026-08-23/1 { state verified hash "sha256:1d790e523631ee2a2718fdd8d36e9f771abcc6e87a6a6b19d2f9574a227079a7" @@ -6875,6 +6972,16 @@ seal @jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1 { hash "sha256:cad2fc5acdba36cfe38becc4b019e94721e78eaafbab1bb3c2cfe4ac46af0295" } +seal @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 { + state superseded + hash "sha256:e4bb7745b41276fc39ac8e64510ed636a2b72af5d566527f6cd1285ceb29f014" +} + +seal @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 { + state verified + hash "sha256:ca161c3f5e33a8423510798f8f02a42c9ff8a249e0253a4b30746b80dc823195" +} + seal @jpegxl-rs.observation.pqc-pr4-development-split-2026-08-22/1 { state verified hash "sha256:551aa85444ac169493e69682d6e76d070126b8b8489b847ed95c918029ac3302" @@ -6885,6 +6992,11 @@ seal @jpegxl-rs.observation.pqc-pr4-rate-curve-axis-audit-2026-08-23/1 { hash "sha256:b5cc2fc639a5ff68cf86ff841e24eb7cb8b57268addaaa0386c62fa29815569f" } +seal @jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1 { + state verified + hash "sha256:1109634526a1ddc0fa6bb5b91274107c3d16b335ed5c6b8e6c1bbd72cd89b882" +} + seal @jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/1 { state superseded hash "sha256:237d838644e7329dac763d29c12a89e23016f8eea889b92bb29faba53b3cc089" @@ -7165,6 +7277,11 @@ seal @jpegxl-rs.papercut.knowledge-propose-exposes-topic-for-every/1 { hash "sha256:b99598934c10fe506bb7e86efdd5010c579d466bb4f4f315ad679a1de6b81e28" } +seal @jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1 { + state verified + hash "sha256:1fef80d0caae9853d9d3e19e4690b5461e69a0bc0c860b1dbe9175fc7ac1444a" +} + seal @jpegxl-rs.papercut.knowledge-propose-says-observation-requires/1 { state verified hash "sha256:b33e9338149f0f018960d14444e79d81216c226c2abe324f84c777b21b8c00c2" @@ -7205,6 +7322,11 @@ seal @jpegxl-rs.papercut.the-frozen-pqc-holdout-reducer-scripts-and/1 { hash "sha256:e738634f68ea43c20be6b63cae06d85bd456f4d92da73b8d1df463f37b1a62ca" } +seal @jpegxl-rs.papercut.the-frozen-pr7-holdout-parser-assumed/1 { + state verified + hash "sha256:8a26bf882b01e30ce17f1b0fe4814bed149f6d7bd3a92e70e94f1f92c59abba4" +} + seal @jpegxl-rs.papercut.the-installed-akr-0-3-1-rejected-four-existing/1 { state verified hash "sha256:afd89dd7ff8b6d1849dd88765b833898038ff37d09b17d51edb174065b593850" @@ -7990,6 +8112,21 @@ seal @jpegxl-rs.work.opt-v2-rate-work-sharing/2 { hash "sha256:93522c79a39a3f4c6d63f5859638303fd543d1dc0511601607377870e2c75224" } +seal @jpegxl-rs.work.pqc-one-shot-controller/1 { + state superseded + hash "sha256:01b5735443d7e1701fca2610f7b14de046ef700387824ffb4e7a8f58ca252e68" +} + +seal @jpegxl-rs.work.pqc-one-shot-controller/2 { + state active + hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" +} + +seal @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 { + state completed + hash "sha256:b835fe82f41cf362b357e9b22d4f9cc5b6ed0c8ed7a256da9c4e51d18576c366" +} + seal @jpegxl-rs.work.pqc-pr0-provenance-corpus-calibration/1 { state completed hash "sha256:4a9c3697ecf25305f271d44a852cb36969c221b9e24ffd3e9d72169b7d930a70" @@ -8020,6 +8157,11 @@ seal @jpegxl-rs.work.pqc-pr5-policy-bank/1 { hash "sha256:ad3fd1c19cd3e3e0f4072a699a19bf89319a89ac7416708a14f2b83e5287a9e4" } +seal @jpegxl-rs.work.pqc-pr7-holdout-closure/1 { + state completed + hash "sha256:18aac74973a69a91a06178e90647d485892f52bb74f71604424c374a6ac90567" +} + seal @jpegxl-rs.work.pqc-pr7-terminal-reducer/1 { state abandoned hash "sha256:635adcd9a015d6c13ad9ba16f7e9780acf274ff714c5d5cf05464dc992c401f2" diff --git a/.akr/records/jpegxl-rs/decisions.akr b/.akr/records/jpegxl-rs/decisions.akr index 7218f20a..8e53b5e5 100644 --- a/.akr/records/jpegxl-rs/decisions.akr +++ b/.akr/records/jpegxl-rs/decisions.akr @@ -435,6 +435,35 @@ record jpegxl-rs.decision.perceptual-quality-contract/1 : decision { } } +record jpegxl-rs.decision.quality-miss-fallback-semantics/1 : decision { + title "An under-target perceptual result is refused at the facade by default; lossless and best-effort are explicit opt-in fallbacks" + state proposed + scope [ + path "JPXL/crates/jpxl-cli/**", + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl/**" + ] + decision """ + An under-target perceptual result is never an ordinary success at the public facade. When the bounded controller stops at SaturatedTop or UnderTargetWorkCap, the default is to refuse: Encoder returns Error::TargetNotMet carrying a QualityMiss (kind LadderSaturated | WorkBudgetExhausted, requested and best verified scores, probe/price counts, metric version, quality trace), and the CLI exits 1 without creating the output file. Two explicit fallbacks exist via Encoder::with_quality_fallback / --quality-fallback: Lossless emits a mathematically lossless stream reported as PerceptualStatus::FallbackLossless with achieved score 100, and BestEffort emits the finest canonically verified under-target stream under its true status and score. SaturatedFloor remains a success. The policy layer (search_frame_perceptual) still returns every terminal status with bytes; the facade owns the refusal. The rescue probe stays one probe beyond the navigation cap and is documented as an observable per-solve maximum of pixel_probes + 1; folding it inside the cap is a deliberate search-policy change that would require the standing Contract B screen. + """ + context """ + The 2026-08-24 one-shot advisor memo's first code finding, verified against quality.rs, jpxl/src/lib.rs and jpxl-cli/src/main.rs: the facade wrapped SaturatedTop and UnderTargetWorkCap outcomes as Ok and the CLI wrote the output file and exited 0, so a scripted caller checking only the exit code received a silently under-target stream — incompatible with the hard-floor reading of the perceptual-quality contract. The completed 91-cell locked holdout contains no under-target cells, so refusing changes no canonical-sweep behavior. + """ + consequences """ + Met, MetAdjacentRungs, MetWorkCap, SaturatedFloor and RescuedFreshStructure outputs stay byte-identical. The public API adds Error::TargetNotMet, QualityMiss, QualityMissKind, QualityFallback, PerceptualStatus::FallbackLossless and Encoder::with_quality_fallback; the CLI adds --quality-fallback lossless|best-effort. A refused encode still appends its jpxl.quality-trace/1 record to JPXL_QUALITY_TRACE so failed searches remain calibration input. Facade, policy-layer and CLI tests now pin the miss path, which previously had no coverage anywhere. + """ + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + author "GitHub Uploader" + source { + kind external + role origin + document "jpxl-one-shot-quality-controller-memo-2026-08-24" + use """ + Adopted the memo's PR 0 contract repair: refusal by default, explicit lossless/best-effort fallback modes, structured failure payload, CLI exit-1 with no output file, and public miss-path tests. + """ + } +} + record jpegxl-rs.decision.quant-bias-defaults/1 : decision { title "quant_bias defaults are 1 minus x" state proposed diff --git a/.akr/records/jpegxl-rs/evidence.akr b/.akr/records/jpegxl-rs/evidence.akr index c1fed104..0f2537fa 100644 --- a/.akr/records/jpegxl-rs/evidence.akr +++ b/.akr/records/jpegxl-rs/evidence.akr @@ -5093,6 +5093,45 @@ record jpegxl-rs.evidence.pqc-low-memory-xlarge-identity-2026-08-23/1 : evidence """ } +record jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 : evidence { + title "One-shot falsification screen: fails on corpus coverage, transform features validated" + state verified + result fail + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "python JPXL/tools/quality_predictor_v2.py train --labels oracle-labels.jsonl --feature-sets table2 source source+transform --transform-features transform-features.json --sweep-dir oracle-sweeps --ridge-grid 0.03 0.3 3.0" + summary """ + The memo's pre-PR5 falsification screen fails on the expanded 38-family corpus: best model (pooled quantile regression, source+transform features, ridge 3.0) reaches leave-one-family-out median |ln err| 0.220 and p90 0.805 vs the quick-screen p90<=0.45 and production median<=0.10/p90<=0.30 bounds. First-plan success 0.88 and simulated common-case bytes 1.0016 pass their checks, and first-or-one-correction success is 1.00, but the honest uncertainty intervals route 99% of requests to the exact controller (expected reconstructions 3.98, no wall win). Tail is concentrated in saturated (p90 3.58) and sky-noise gradient (p90 4.07) classes - single-content-family coverage - while photo/scene/text/noise/line-art/grayscale sit at p90 0.36-0.67. Standalone transform-summary cost on the 12 MP anchor is 21.9% of a matched-rate encode (fails the 5% bolt-on gate; in-search shared-cache marginal cost unmeasured). Report kept in .agent/scratch/one-shot-qpv2-20260824/qpv2-report.json. + """ + author "GitHub Uploader" +} + +record jpegxl-rs.evidence.pqc-one-shot-pr1-5-gates-2026-08-24/1 : evidence { + title "One-shot PR 1-5 implementation: all workspace gates pass" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "cargo test --workspace && cargo test -p jpxl-encode-policy --features one-shot-controller && cargo clippy --workspace --all-targets [--features one-shot-controller] && cargo fmt --all --check && python -m unittest discover -s JPXL/tools/tests && python JPXL/tools/make-quality-guard-fixtures.py build --check" + summary """ + The one-shot program's PR 1-5 implementation passes every workspace gate: full test suite green (71 suites) in the default build and 155 policy tests under the one-shot-controller feature (including the PR 5 routing test and the trace/2 assertions), clippy clean in both configurations, formatting clean, 75 Python tool tests green (trainer, oracle-label, fixture-generator suites), and the fixture generator's check mode reproduces all 56 corpus PPMs byte-identically with the family split-hygiene guard passing. Default-build behavior is unchanged: the shadow predictor only logs, and the one-shot start is compiled out. + """ + author "GitHub Uploader" +} + +record jpegxl-rs.evidence.pqc-one-shot-promotion-ab-2026-08-25/1 : evidence { + title "One-shot promotion A/B: floor held, bytes and work strictly better in aggregate" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "python JPXL/tools/one_shot_promotion_ab.py --manifest test-set/quality-corpus.json --splits holdout ... ; python JPXL/tools/one_shot_promotion_ab.py --manifest test-set/ext-polart-manifest.json --splits ext-holdout ..." + summary """ + Promotion A/B of the one-shot-controller build against the default controller, interleaved on the same machine. Locked 13-image holdout (91 cells): zero floor violations both arms, byte geomean 0.99744 (bound 1.007), wall geomean 0.989, reconstructions 348 vs 339; worst cells are three sub-kilobyte saturated fixtures (max ratio 1.51 = +131 bytes absolute). Never-tuned ext-holdout paintings (50 families, 350 cells): zero floor violations, byte geomean 0.99018, reconstructions 1287 to 1056 (-18%), wall geomean 0.838, worst cell 1.117 at target 95 with a higher achieved score. Achieved-score dips above the floor exist in both arms (1 default, 2 one-shot) - a pre-existing bounded-search artifact, no hard inversion below target anywhere. Wall anchors at q85 vs matched-rate: 4.3 MP 1.36x both arms; 12 MP 8.02x default vs 6.31x one-shot (metric/render remains the dominant residual, as the memo predicted). Raw rows kept in .agent/scratch/one-shot-qpv2-20260824/ (ab-locked-holdout.json, ab-ext-holdout.json). + """ + author "GitHub Uploader" +} + record jpegxl-rs.evidence.pqc-pr0-corpus-manifest-2026-08-22/1 : evidence { title "The generator reproduces 47 fixtures (47 PPM + 29 PNG + 47 JSON provenance sidecars, idempotent sha256) and the jpxl.codec-corpus/1 manifest test-set/quality-corpus.json (gitignored) validates; splits are calibration 19, development 15, holdout 13 with no source family in two splits; classes text-screenshot 5, line-art 5, gradient 6, saturated 4, tiny 6, noise-lowlight 1, grayscale 2, photo-scene 7, photo 11. 14 generator unit tests pass." state verified @@ -5106,6 +5145,19 @@ record jpegxl-rs.evidence.pqc-pr0-corpus-manifest-2026-08-22/1 : evidence { """ } +record jpegxl-rs.evidence.pqc-pr0-hard-floor-tests-2026-08-24/1 : evidence { + title "PR0 hard floor: targeted miss-path tests pass" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "cargo test -p jpxl-encode-policy running_out_of_probes && cargo test -p jpxl --test quality_encode && cargo test -p jpxl-cli --test cli_quality" + summary """ + New miss-path tests all pass: policy-layer UnderTargetWorkCap with rescue accounting (pixel_probes 1 + 1 rescue), facade refuse/lossless/best-effort on the deterministic 64x64 gradient miss at 99.5 Fast (TargetNotMet with LadderSaturated, FallbackLossless bytes equal to the lossless encoder's, best-effort re-scored independently), and 8/8 CLI quality tests including exit-1-with-no-output-file refusal and --quality-fallback validation. + """ + author "GitHub Uploader" +} + record jpegxl-rs.evidence.pqc-pr0-predictor-table-2026-08-22/1 : evidence { title "256 fixed-quantizer points (16 calibration images x 16 global_scale rungs 400..73728) scored with the reference SSIMULACRA2 in 604 s produced quality_predictor.rs: 56 table cells over 5 luma-variance x 3 flat-fraction buckets plus a fallback log fit [15.62, -1.60, 0.0099, -2.57]. Leave-one-image-out |ln(pred/actual)| median 0.30, p90 2.68 (cells hold 1-4 images); loss-vs-scale slope median -2.34; saturation at 73728 is 0 up to target 70 and 0.06/0.19/0.81/0.94 at 80/85/90/95." state verified @@ -5130,6 +5182,19 @@ record jpegxl-rs.evidence.pqc-pr0-sources-registered-2026-08-22/1 : evidence { """ } +record jpegxl-rs.evidence.pqc-pr0-workspace-gates-2026-08-24/1 : evidence { + title "PR0 hard floor: workspace and tools gates pass" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "cargo test --workspace && cargo test --workspace --profile fast-debug && cargo clippy --workspace --all-targets && cargo fmt --all --check && python -m unittest discover -s JPXL/tools/tests" + summary """ + Workspace gates pass after the hard-floor change: full test suite green in both debug and fast-debug profiles (zero failures), clippy emits no warnings, formatting is clean, and the 46 Python tool tests pass (codec_compare now opts into --quality-fallback best-effort so curve points on an unmet target stay measurable). + """ + author "GitHub Uploader" +} + record jpegxl-rs.evidence.pqc-pr1-api-semantics-2026-08-22/1 : evidence { title "6/6 facade target tests pass: score 100 is byte-identical to the lossless path, scores outside 0..=100 are rejected, conflicting targets (quality+bpp, bpp+global_scale) are rejected, a perceptual score below 100 runs the quality controller, --global-scale encodes and decodes, and the rate report matches the bytes." state verified @@ -5430,6 +5495,18 @@ record jpegxl-rs.evidence.pqc-pr7-balanced-single-shot-2026-08-22/1 : evidence { """ } +record jpegxl-rs.evidence.pqc-pr7-closure-workspace-gates-2026-08-24/1 : evidence { + title "PR7 holdout closure workspace and feature gates pass" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "cd JPXL && cargo build --workspace; cargo test --workspace --release; cargo clippy --workspace --all-targets -- -D warnings; cargo fmt --all --check; cargo test -p jpxl-cli --release --features quality-effort,perceptual" + summary """ + Workspace build, full release suite, strict Clippy, formatting, and the quality-effort+perceptual feature-enabled CLI release tests all pass after the policy note and regression-test update. + """ +} + record jpegxl-rs.evidence.pqc-pr7-dev-split-2026-08-22/1 : evidence { title "Development split, Balanced with the reducer on versus off: bytes geomean 0.9825 at target 70 (photos 0.9903, min 0.888) and 0.9912 at 85 (photos 0.9886, min 0.9835); zero floor violations in 30 cells (the reduced stream is kept only when its exact size is smaller and every accepted batch is re-scored canonically); at most 6 evaluations; wall geomean 1.53x / 1.63x. Bytes down at matched-or-better score with bounded work, but the wall exceeds Balanced's +25% budget, so the reducer ships default-off on Balanced (BALANCED_DEFAULT_REDUCER = None) and on for the feature-gated Quality effort; the locked-holdout gate is still to be measured." state verified @@ -5442,6 +5519,52 @@ record jpegxl-rs.evidence.pqc-pr7-dev-split-2026-08-22/1 : evidence { """ } +record jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1 : evidence { + title "Full PR7 locked-holdout Quality/reducer sweep completes after low-memory changes" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "python3 .agent/scratch/pr7-holdout-closure-20260824/resume_holdout.py && python3 .agent/scratch/pr7-holdout-closure-20260824/analyze.py" + summary """ + Combined historical checkpoints with 53 resumed rows to close 91/91 Quality and 91/91 Balanced cells on all 13 locked holdout images. All 182 successful rows had zero canonical-floor and decoder failures; reducer work stayed at <=6 evaluations, saved 263,266 exact bytes total, and produced 0.991552 final/before geomean. The formerly blocked 50 MP Quality rows completed serially under a 12 GiB cap; worst measured peak was 11,163,372 KiB. Kept report: .agent/scratch/pr7-holdout-closure-20260824/report.json (SHA-256 fd78df93...f282e). + """ +} + +record jpegxl-rs.evidence.pqc-pr7-policy-gating-test-2026-08-24/1 : evidence { + title "Production budgets keep rejected Quality-only work disabled" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "cd JPXL && cargo test -p jpxl-encode-policy --profile fast-debug production_budgets_keep_quality_only_work_disabled" + summary """ + The new policy regression test passes and proves Fast/Balanced default budgets retain zero policy trials and no reducer while the feature-gated Quality budget retains its bank and terminal reducer. + """ +} + +record jpegxl-rs.evidence.pqc-pr7-promotion-disposition-2026-08-24/1 : evidence { + title "Full Contract B result is dispositioned without weakening the public gate" + state verified + result pass + method observation + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + summary """ + The full 79-cell common-score report was evaluated against the standing Quality promotion conditions. Overall matched bytes pass at 0.958292, but Butteraugli mean 1.047881 and worst pnorm3 1.868818 fail their 1.02/1.05 limits, so the existing quality-effort feature gate remains in force. No search-policy change is justified; the source note now records the completed gate and a regression test pins Quality-only work out of Fast/Balanced. + """ +} + +record jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1 : evidence { + title "Full locked holdout rejects public Quality promotion under Contract B" + state verified + result fail + method observation + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + summary """ + Across 79 common-score cells, Quality/Balanced bytes pass at 0.958292 geomean, but Butteraugli mean ratio is 1.047881 (>1.02) and worst pnorm3 ratio is 1.868818 (>1.05), so Contract B fails. Photos are nearly byte-neutral at 0.991367 with BA mean 1.000790 and worst pnorm3 1.042495; non-photo savings drive the aggregate while saturated, tiny, and text cells drive the guard failure. Quality remains feature-gated. + """ +} + record jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1 : evidence { title "Partial locked holdout definitively rejects public Quality promotion" state verified @@ -5468,6 +5591,19 @@ record jpegxl-rs.evidence.pqc-pr7-reducer-holdout-partial-2026-08-22/1 : evidenc author "GitHub Uploader" } +record jpegxl-rs.evidence.pqc-qpv2-expanded-corpus-gate-2026-08-25/1 : evidence { + title "Expanded-corpus qpv2 model: blind holdout hits production p90; transform features win" + state verified + result pass + method command + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + command "python JPXL/tools/quality_corpus_extend.py --source-dir \"D:\\Pol Desktop\\Pol Art Folder 08-24-2017\" --name ext-polart --max-images 220; quality_oracle_labels.py sweep (2h budget); quality_predictor_v2.py train --feature-sets table2 source source+transform" + summary """ + Corpus expanded with 211 independent painting families from the user's collection (104 calibration / 57 development / 50 never-tuned ext-holdout, deterministic split by family hash, originals untouched); 103 swept within the 2-hour budget for 994 total label rows (zero censored). Retrained pooled model, 10-fold family-grouped CV: source+transform wins (CV median 0.121 / p90 0.461 vs source-only 0.176/0.595); blind never-tuned ext-holdout: median 0.113, p90 0.258 (production p90 bound met), p99 0.478, first-plan 0.925, first-or-one-correction 1.00. Remaining CV tail is entirely the saturated (p90 3.58) and sky-noise gradient classes, still effectively class-held-out. Emitted runtime model qpv2-st-1 (19 features: 9 source + 10 DCT8-summary); the in-search transform summary measured -3.9% wall at 4.3 MP (shares the cover's cache; standalone 7.8% after parallelizing the fill from 21.9% serial). Artifacts in .agent/scratch/one-shot-qpv2-20260824/. + """ + author "GitHub Uploader" +} + record jpegxl-rs.evidence.pqc-quality-rate-curve-exact-10of13-2026-08-23/1 : evidence { title "Corrected common-axis rate curve is exact on 10/13 holdout images; xlarge quality remains memory-blocked" state verified diff --git a/.akr/records/jpegxl-rs/observations.akr b/.akr/records/jpegxl-rs/observations.akr index 452785bc..8c2dca38 100644 --- a/.akr/records/jpegxl-rs/observations.akr +++ b/.akr/records/jpegxl-rs/observations.akr @@ -1784,6 +1784,48 @@ record jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1 : observation { ] } +record jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 : observation { + title "The one-shot falsification screen fails on corpus coverage, not on the design: transform features halve the error, the tail is two under-represented classes" + state superseded + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality_prediction.rs", + path "JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs", + path "JPXL/tools/quality_oracle_labels.py", + path "JPXL/tools/quality_predictor_v2.py" + ] + statement """ + On the expanded 56-image / 38-family corpus with production-endpoint oracle labels (fresh Balanced pixel plans over the complete effective ladder; zero censored rows even at target 95, so the old predictor's 81-94% high-target saturation was an artifact of its HfMul=1 ladder cap), the memo's pre-PR5 falsification screen fails: best leave-one-family-out median |ln(predicted/oracle crossing)| is 0.220 with p90 0.805 (bounds: quick p90<=0.45; production median<=0.10, p90<=0.30). Three findings shape the path forward. (1) Transform-domain features materially help, as the memo predicted: source-only p90 1.28 vs source+transform 0.81, median 0.42 vs 0.22, first-or-one-correction success 1.00. (2) The failure is class coverage, not model form: photo, photo-scene, text-screenshot, noise-lowlight, line-art and grayscale classes sit at held-out p90 0.36-0.67 (near or past the quick bound), while saturated (p90 3.58) and sky-noise gradient (p90 4.07) dominate the tail - both effectively class-held-out because their families carry near-unique content. (3) The fallback discipline works: with honest intervals the simulated controller routes 99% of requests to the exact path, keeping bytes at 1.0016x but eliminating the wall win (expected reconstructions 3.98 vs the current median 4). Per the memo, production controller work stops here until the corpus grows (>=25 independent families per critical class; saturated and banding-stress content first); the PR5 mechanism exists behind the off-by-default `one-shot-controller` cargo feature and remains inert without a gate-passing model. + """ + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + method command + watches [ + "JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs", + "JPXL/tools/quality_predictor_v2.py", + "test-set/quality-corpus.json" + ] + derived_from [ @jpegxl-rs.work.pqc-one-shot-controller ] + verified_by [ @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24 ] + author "GitHub Uploader" +} + +record jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 : observation { + title "The one-shot falsification screen fails on corpus coverage, not on the design: transform features halve the error, the tail is two under-represented classes" + state verified + scope [ path "JPXL/crates/jpxl-encode-policy/src/**", path "JPXL/tools/**" ] + statement """ + On the expanded 56-image / 38-family corpus with production-endpoint oracle labels (fresh Balanced pixel plans over the complete effective ladder; zero censored rows even at target 95, so the old predictor's 81-94% high-target saturation was an artifact of its HfMul=1 ladder cap), the memo's pre-PR5 falsification screen fails: best leave-one-family-out median |ln(predicted/oracle crossing)| is 0.220 with p90 0.805 (bounds: quick p90<=0.45; production median<=0.10, p90<=0.30). Three findings shape the path forward. (1) Transform-domain features materially help, as the memo predicted: source-only p90 1.28 vs source+transform 0.81, median 0.42 vs 0.22, first-or-one-correction success 1.00. (2) The failure is class coverage, not model form: photo, photo-scene, text-screenshot, noise-lowlight, line-art and grayscale classes sit at held-out p90 0.36-0.67 (near or past the quick bound), while saturated (p90 3.58) and sky-noise gradient (p90 4.07) dominate the tail - both effectively class-held-out because their families carry near-unique content. (3) The fallback discipline works: with honest intervals the simulated controller routes 99% of requests to the exact path, keeping bytes at 1.0016x but eliminating the wall win (expected reconstructions 3.98 vs the current median 4). Per the memo, production controller work stops here until the corpus grows (>=25 independent families per critical class; saturated and banding-stress content first); the PR5 mechanism exists behind the off-by-default `one-shot-controller` cargo feature and remains inert without a gate-passing model. + """ + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + method command + watches [ "JPXL/crates/jpxl-encode-policy/src/quality_predictor*.rs", "JPXL/tools/*.py" ] + derived_from [ @jpegxl-rs.work.pqc-one-shot-controller ] + supersedes [ + @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 + ] + verified_by [ @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24 ] + author "GitHub Uploader" +} + record jpegxl-rs.observation.pqc-pr4-development-split-2026-08-22/1 : observation { title "PR 4 on the development split: the score floor holds in 150/150 encodes, photographs beat cjxl -e7 at matched score, synthetic text and line art trail badly, and the quality path costs ~3.5x the rate path's wall time" state verified @@ -1837,6 +1879,36 @@ record jpegxl-rs.observation.pqc-pr4-rate-curve-axis-audit-2026-08-23/1 : observ ] } +record jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1 : observation { + title "Full PR7 holdout closes the OOM tail and rejects Quality promotion" + state verified + scope [ + path "JPXL/crates/jpxl-encode-policy/src/policy_bank.rs", + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-encode-policy/src/reducer.rs", + path "JPXL/crates/jpxl-perceptual/src/**" + ] + statement """ + The current low-memory implementation completed the 27 formerly missing Quality cells and 26 companion Balanced rows, closing all 91 cells per effort on the 13-image locked holdout. The terminal reducer is safe and bounded (zero floor/decoder failures, at most six evaluations, final/before bytes geomean 0.991552), but public Quality promotion is rejected: matched-score bytes pass overall at 0.958292, while Contract B fails at Butteraugli mean ratio 1.047881 and worst pnorm3 ratio 1.868818. The added cells make the diagnosis sharper rather than reversing it: photos are nearly byte-neutral at 0.991367 and pass the BA/pnorm3 guards; non-photo byte wins coexist with saturated/tiny/text guard losses. Keep Quality feature-gated and keep its policy bank/reducer out of Fast and Balanced; a future promotion attempt needs a different quality policy or metric guidance, not relaxation of the gate. + """ + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + method command + watches [ + "JPXL/crates/jpxl-encode-policy/src/quality.rs", + "JPXL/crates/jpxl-encode-policy/src/policy_bank.rs", + "JPXL/crates/jpxl-encode-policy/src/reducer.rs", + "JPXL/crates/jpxl-perceptual/src/**" + ] + derived_from [ + @jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr7-reducer-holdout-partial-2026-08-22/1 + ] + verified_by [ + @jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1, + @jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1 + ] +} + record jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/1 : observation { title "Pre-optimization encode wall-time ladder (release jpxl bench)" state superseded diff --git a/.akr/records/jpegxl-rs/papercuts.akr b/.akr/records/jpegxl-rs/papercuts.akr index 2fda5ff5..123beb49 100644 --- a/.akr/records/jpegxl-rs/papercuts.akr +++ b/.akr/records/jpegxl-rs/papercuts.akr @@ -291,6 +291,18 @@ record jpegxl-rs.papercut.knowledge-propose-exposes-topic-for-every/1 : papercut created_at 2026-08-23 } +record jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1 : papercut { + title "knowledge.propose's generic `slots` schema does not expose that an…" + state verified + statement """ + knowledge.propose's generic `slots` schema does not expose that an observation's `method` must be one of manual/command/instrumented/observation. V-022 said to add `method`; supplying explanatory prose then produced dozens of parser diagnostics before the enum expectation appeared. + """ + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + about "akr" + author "codex" + created_at 2026-08-24 +} + record jpegxl-rs.papercut.knowledge-propose-says-observation-requires/1 : papercut { title "knowledge.propose says observation requires statement and observed_at,…" state verified @@ -384,6 +396,17 @@ record jpegxl-rs.papercut.the-frozen-pqc-holdout-reducer-scripts-and/1 : papercu created_at 2026-08-22 } +record jpegxl-rs.papercut.the-frozen-pr7-holdout-parser-assumed/1 : papercut { + title "The frozen PR7 holdout parser assumed `ssimulacra2` immediately…" + state verified + statement """ + The frozen PR7 holdout parser assumed `ssimulacra2` immediately followed `psnr_db`; the current compare line inserts `ssimulacra2_jpxl` and its version first, so an otherwise successful 12 MP resumed cell was needlessly rerun. Parse named fields independently or share the checked-in compare parser. + """ + observed_at git:41a2e3f20631e000954b9ea76612c70f1847e745 + author "codex" + created_at 2026-08-24 +} + record jpegxl-rs.papercut.the-installed-akr-0-3-1-rejected-four-existing/1 : papercut { title "The installed akr 0.3.1 rejected four existing observation records…" state verified diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 2bb91876..6f8c8cdf 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -1028,7 +1028,9 @@ record jpegxl-rs.work.arch-phase24-hfmul-overlay/3 : work { depends_on [ @jpegxl-rs.work.arch-phase23-cow-geometry ] part_of [ @jpegxl-rs.track.encoder-optimization ] supersedes [ @jpegxl-rs.work.arch-phase24-hfmul-overlay/2 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase25-multi-quantizer-workspace/1 : work { @@ -1094,7 +1096,9 @@ record jpegxl-rs.work.arch-phase25-multi-quantizer-workspace/1 : work { } depends_on [ @jpegxl-rs.work.arch-phase24-hfmul-overlay/3 ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase27-finalist-only-entropy/1 : work { @@ -1155,7 +1159,9 @@ record jpegxl-rs.work.arch-phase27-finalist-only-entropy/1 : work { depends_on [ @jpegxl-rs.evidence.phase26-rate-multiplicity-2026-08-16/1 ] implements [ @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase28-cfl-lf-scratch-reuse/1 : work { @@ -1538,7 +1544,9 @@ record jpegxl-rs.work.arch-phase31-lane4-vector-select/1 : work { verified_by [ @jpegxl-rs.evidence.phase31-profile-2026-08-16/2 ] } } - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase32-dirty-frontier-screen/1 : work { @@ -2778,7 +2786,9 @@ record jpegxl-rs.work.arch-phase5a-aq-policy-gate/1 : work { @jpegxl-rs.policy.research-out-of-core ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5b-restoration-screen/1 : work { @@ -2829,7 +2839,9 @@ record jpegxl-rs.work.arch-phase5b-restoration-screen/1 : work { @jpegxl-rs.policy.clean-room-boundary ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5c-lf-hf-balance-screen/1 : work { @@ -2925,7 +2937,9 @@ record jpegxl-rs.work.arch-phase5c-lf-hf-balance-screen/2 : work { ] part_of [ @jpegxl-rs.track.encoder-optimization ] supersedes [ @jpegxl-rs.work.arch-phase5c-lf-hf-balance-screen/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5d-cover-quality-screen/1 : work { @@ -2975,7 +2989,9 @@ record jpegxl-rs.work.arch-phase5d-cover-quality-screen/1 : work { @jpegxl-rs.policy.clean-room-boundary ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5e-epf-screen/1 : work { @@ -3026,7 +3042,9 @@ record jpegxl-rs.work.arch-phase5e-epf-screen/1 : work { @jpegxl-rs.policy.clean-room-boundary ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5f-aqoff-oracle-compat/1 : work { @@ -3144,7 +3162,9 @@ record jpegxl-rs.work.arch-phase5f-aqoff-oracle-compat/2 : work { ] part_of [ @jpegxl-rs.track.encoder-optimization ] supersedes [ @jpegxl-rs.work.arch-phase5f-aqoff-oracle-compat/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5g-aqoff-broader-corpus-gate/1 : work { @@ -3189,7 +3209,9 @@ record jpegxl-rs.work.arch-phase5g-aqoff-broader-corpus-gate/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5h-quant-lf-tail-gate/1 : work { @@ -3225,7 +3247,9 @@ record jpegxl-rs.work.arch-phase5h-quant-lf-tail-gate/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5i-active-epf-screen/1 : work { @@ -3270,7 +3294,9 @@ record jpegxl-rs.work.arch-phase5i-active-epf-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5j-two-pass-error-aq-screen/1 : work { @@ -3319,7 +3345,9 @@ record jpegxl-rs.work.arch-phase5j-two-pass-error-aq-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5k-active-epf-depth-screen/1 : work { @@ -3392,7 +3420,9 @@ record jpegxl-rs.work.arch-phase5k-active-epf-depth-screen/2 : work { ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] supersedes [ @jpegxl-rs.work.arch-phase5k-active-epf-depth-screen/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5l-chroma-qm-allocation-screen/1 : work { @@ -3441,7 +3471,9 @@ record jpegxl-rs.work.arch-phase5l-chroma-qm-allocation-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5m-spatial-epf-sharpness-screen/1 : work { @@ -3486,7 +3518,9 @@ record jpegxl-rs.work.arch-phase5m-spatial-epf-sharpness-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5n-fine-aq-lattice-screen/1 : work { @@ -3533,7 +3567,9 @@ record jpegxl-rs.work.arch-phase5n-fine-aq-lattice-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5o-special8-transform-screen/1 : work { @@ -3588,7 +3624,9 @@ record jpegxl-rs.work.arch-phase5o-special8-transform-screen/1 : work { @jpegxl-rs.policy.optimization-correctness-gates/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase5p-special8-exact-distortion-screen/1 : work { @@ -3683,7 +3721,9 @@ record jpegxl-rs.work.arch-phase6-0-distortion-currency/1 : work { @jpegxl-rs.policy.optimization-correctness-gates ] part_of [ @jpegxl-rs.track.encoder-optimization ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase6-1-quantizer-normalised-residual/1 : work { @@ -4622,7 +4662,9 @@ record jpegxl-rs.work.arch-phase8-4-finalist-token-tape/2 : work { depends_on [ @jpegxl-rs.work.arch-phase8-3-anchor-sketch ] part_of [ @jpegxl-rs.track.encoder-optimization ] supersedes [ @jpegxl-rs.work.arch-phase8-4-finalist-token-tape/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase8-5-leaf-finishing/1 : work { @@ -4969,7 +5011,9 @@ record jpegxl-rs.work.arch-phase8-6-butteraugli-localization/2 : work { ] part_of [ @jpegxl-rs.track.encoder-optimization ] supersedes [ @jpegxl-rs.work.arch-phase8-6-butteraugli-localization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.arch-phase8-6-finalist-measured-hf-allocation/1 : work { @@ -6152,7 +6196,9 @@ record jpegxl-rs.work.gap-g2-selective-coefficient-refinement/1 : work { Adopts the staged entropy-cost, context-aware trailing-cost, bounded finalist beam, exact-walk mismatch, fresh-chroma, and promotion-gate shape as an experiment; the report remains non-authoritative. """ } - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.gap-g3-bounded-truthful-rate-controller/1 : work { @@ -6250,7 +6296,9 @@ record jpegxl-rs.work.gap-g3-bounded-truthful-rate-controller/1 : work { Implements the G3 milestone boundary after completed G2. """ } - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.gap-g4-selective-cover-refresh/1 : work { @@ -6734,6 +6782,24 @@ record jpegxl-rs.work.general-use-api-cli/2 : work { author "GitHub Uploader" } +record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { + title "Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode" + state proposed + scope [ + path "JPXL/crates/jpxl-cli/**", + path "JPXL/crates/jpxl-encode/**", + path "JPXL/crates/jpxl/**" + ] + intent """ + Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients and reconstruction metadata into a JPEG XL stream directly (bit-exact reconstructible), instead of the current decode-to-pixels-and-reencode route. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. + """ + note """ + User-requested 2026-08-24 while supplying the painting corpus: 'if jpxl doesn't support the direct conversion that is technically possible, it should'. Today `jpxl encode in.jpg` decodes the JPEG to pixels and encodes those (lossy or lossless on decoded pixels). Direct recompression is a substantial clean-room feature: JPEG1 entropy decode to coefficients, the VarDCT coefficient carriage and reconstruction-metadata boxes per 18181, and a bit-exact JPEG reconstruction check as the acceptance test. Scoping and standard-clause mapping needed before implementation planning. + """ + part_of [ @jpegxl-rs.track.vardct-encoder/1 ] + author "GitHub Uploader" +} + record jpegxl-rs.work.native-windows-quality-tooling/1 : work { title "Native Windows equivalents for the last bash-only quality/conformance tooling (cjxl-match, fetch-conformance)" state completed @@ -7033,6 +7099,85 @@ record jpegxl-rs.work.optional-semantic-guidance-consumer/1 : work { part_of [ @jpegxl-rs.track.encoder-optimization/1 ] } +record jpegxl-rs.work.pqc-one-shot-controller/1 : work { + title "One-shot program: shadow-first common-case one-shot quality controller" + state superseded + scope [ + path "JPXL/crates/jpxl-encode-policy/**", + path "JPXL/crates/jpxl/**", + path "JPXL/tools/**", + path "test-set/**" + ] + intent """ + Adopt the 2026-08-24 advisor memo's common-case one-shot design in shadow-first stages to close the open Balanced wall requirement (<= 2.0x matched-rate on the 4.3 MP and 12 MP anchors). Staged: (PR 1) jpxl.quality-trace/2 with prediction/decision-path fields plus family_id/variant_id in the corpus manifest; (PR 2) a production-endpoint label generator over the full effective-scale ladder - fresh-structure Balanced crossings, saturation represented as censoring, local loss slopes; (PR 3) a source-only shadow model QualityPredictionV2 (log-only, no bitstream effect); (PR 4) a request-scoped transform-summary shadow model reading CandidateForwardCache; (PR 5, only after the gate) the feature-gated common-case path: one predicted fresh plan, canonical verification before entropy, one slope-based correction, then continuation of the existing navigator under the same total caps. + """ + target 2026-09-30 + note """ + 2026-08-24 status: PR 1-4 are implemented and measured; the PR 5 mechanism exists behind the off-by-default `one-shot-controller` cargo feature (model candidate as first fresh plan, model beta as the single-probe slope prior, fallback discipline routing to the exact controller) but stays inert per the memo's stopping rule: the falsification screen FAILED on the 38-family corpus (best LOFO median 0.220 / p90 0.805 vs quick p90<=0.45 and production 0.10/0.30; see evidence pqc-one-shot-falsification-screen-2026-08-24). The verdict is corpus coverage, not design: transform features halve the error and the tail is the saturated and sky-noise classes (effectively class-held-out). Reopening the gate requires corpus growth per the memo's section 10 (>= 25 independent families per critical class; saturated and banding-stress content first), then rerunning quality_oracle_labels.py sweep/labels and quality_predictor_v2.py train. Deferred: in-search transform-summary wall measurement (standalone is 21.9% of matched-rate on the 12 MP anchor, over the 5% bolt-on gate; the shared-cache in-search cost is the number that matters for PR 5 promotion). + """ + depends_on [ @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + source { + kind external + role origin + document "jpxl-one-shot-quality-controller-memo-2026-08-24" + use """ + Adopted the memo's verdict (common-case one shot, canonical verification as the correctness gate), its staged PR 1-5 sequence, its falsification and promotion gates, and its explicit non-goals. + """ + } +} + +record jpegxl-rs.work.pqc-one-shot-controller/2 : work { + title "One-shot program: shadow-first common-case one-shot quality controller" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/**", + path "JPXL/crates/jpxl/**", + path "JPXL/tools/**", + path "test-set/**" + ] + intent """ + Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the open Balanced wall requirement. PR 1-4 (trace/2, production-endpoint oracle labels, source-only and transform-summary shadow models) are implemented and measured; PR 5 (model-seeded first plan, one slope correction, exact-navigator continuation under unchanged caps with canonical verification before every emission) is implemented and, per the user's 2026-08-24 directive and the 2026-08-25 promotion A/B, is now the DEFAULT controller seed (`one-shot-controller` in the policy crate's default features; building without default features restores the legacy table seed). + """ + target 2026-09-30 + note """ + 2026-08-25 status: promoted to default on measured evidence - zero floor violations on 441 never-tuned holdout cells, byte geomean 0.997 (locked holdout) / 0.990 (painting ext-holdout), reconstructions -18% and wall -16% on paintings, 12 MP anchor 8.02x -> 6.31x vs matched-rate. Model qpv2-st-1: pooled quantile regression over 9 source + 10 DCT8-summary features, 130 training families (user's painting collection added via quality_corpus_extend.py); routing: candidate rung when confident, median seed when uncertain-in-distribution, legacy seed when OOD (tiny frames, out-of-envelope features). OPEN ITEMS: (1) the 12 MP wall anchor remains above the 2.0x matched-rate requirement - the residual is render/metric cost per the memo's diagnosis, a separate optimization; (2) worst-cell byte regressions concentrate in sub-kilobyte saturated fixtures (max +131 bytes) and target-95 cells (<=1.12x, higher achieved) - saturated and banding-stress family coverage is still the corpus gap (memo section 10: >= 25 families per critical class); (3) achieved-score dips above the floor exist in both arms (pre-existing bounded-search artifact); (4) 108 unswept ext-polart images remain for future 2-hour label runs (resumable). Reproduce: quality_oracle_labels.py sweep/labels, quality_predictor_v2.py train, one_shot_promotion_ab.py; artifacts kept in .agent/scratch/one-shot-qpv2-20260824/. + """ + depends_on [ @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-one-shot-controller/1 ] + author "GitHub Uploader" + source { + kind external + role origin + document "jpxl-one-shot-quality-controller-memo-2026-08-24" + use """ + Adopted the memo's verdict (common-case one shot, canonical verification as the correctness gate), its staged PR 1-5 sequence, its falsification and promotion gates, and its explicit non-goals. + """ + } +} + +record jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 : work { + title "PR 0 of the one-shot program: enforce the hard score floor at the facade and CLI" + state completed + scope [ + path "JPXL/crates/jpxl-cli/**", + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl/**" + ] + intent """ + Implement decision quality-miss-fallback-semantics: refuse under-target perceptual results by default (Error::TargetNotMet with a structured QualityMiss), add the explicit Lossless and BestEffort fallbacks to the facade and --quality-fallback to the CLI, keep the CLI from creating an output file on refusal, preserve the failed search's quality trace for calibration, correct the module and budget documentation to state the bounded-finalist selection and the pixel_probes + 1 rescue accounting honestly, and pin the previously untested miss path with facade, policy-layer and CLI tests. All met-path codestreams must remain byte-identical. + """ + note """ + First slice of the 2026-08-24 one-shot advisor memo's plan; deliberately excludes any search-policy or predictor change. The wall/prediction program is tracked separately under pqc-one-shot-controller. + """ + implements [ @jpegxl-rs.decision.quality-miss-fallback-semantics ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + author "GitHub Uploader" +} + record jpegxl-rs.work.pqc-pr0-provenance-corpus-calibration/1 : work { title "PQC PR 0: register sources, quality-guard corpus with splits, initial-rung calibration data" state completed @@ -7309,6 +7454,58 @@ record jpegxl-rs.work.pqc-pr5-policy-bank/1 : work { part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] } +record jpegxl-rs.work.pqc-pr7-holdout-closure/1 : work { + title "Close the OOM-interrupted PR7 Quality/reducer holdout sweep" + state completed + scope [ + path ".agent/scratch/pr7-holdout-closure-20260824/**", + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-encode-policy/src/reducer.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl/src/lib.rs", + path "JPXL/tools/**" + ] + intent """ + Resume only the 27 Quality cells left by the 2026-08-22 locked-holdout run, using the current low-memory implementation and serialized process caps. Recompute the complete 91-cell Quality/reducer and matched-score Contract B report. Keep Quality feature-gated unless the standing promotion contract passes; implement only fixes justified by the completed evidence. + """ + note """ + The prior run has 64/91 Quality cells plus 65 Balanced cells. The common-axis production quality/rate curve is already complete on 13/13 and is not rerun here. + """ + acceptance { + check full-holdout { + statement """ + The resumable locked-holdout runner covers all 91 Quality cells and matching Balanced curves, with zero canonical-floor or decoder failures and reducer work within its configured bound. + """ + method command + verified_by [ @jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1 ] + } + check promotion-contract { + statement """ + The complete common-score byte ratio and Contract B guard metrics are reported, and Quality remains feature-gated unless every standing promotion condition passes. + """ + method observation + verified_by [ @jpegxl-rs.evidence.pqc-pr7-promotion-disposition-2026-08-24/1 ] + } + check resulting-disposition { + statement """ + Any defect exposed by the completed cells is fixed and tested; if no defect is exposed, the evidence records that no source-policy change is warranted. + """ + method manual + verified_by [ @jpegxl-rs.evidence.pqc-pr7-policy-gating-test-2026-08-24/1 ] + } + check workspace-gates { + statement """ + The required workspace build, release-test, strict-clippy, and formatting gates pass after any resulting changes. + """ + method command + command "cd JPXL && cargo build --workspace && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + verified_by [ @jpegxl-rs.evidence.pqc-pr7-closure-workspace-gates-2026-08-24/1 ] + } + } + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] +} + record jpegxl-rs.work.pqc-pr7-terminal-reducer/1 : work { title "PQC PR 7: finalist terminal-coefficient reducer exchanging measured score reserve for exact bytes, and the Quality-effort promotion gate" state abandoned @@ -8068,7 +8265,9 @@ record jpegxl-rs.work.quality-q2-chroma-hf/1 : work { } depends_on [ @jpegxl-rs.work.quality-q1-quantizer/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.quality-q3-ladder-ceiling-and-allocation-screens/1 : work { @@ -8139,7 +8338,9 @@ record jpegxl-rs.work.quality-q3-ladder-ceiling-and-allocation-screens/1 : work } depends_on [ @jpegxl-rs.work.quality-q2-chroma-hf/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.quality-q4-cover-rate-model/1 : work { @@ -8203,7 +8404,9 @@ record jpegxl-rs.work.quality-q4-cover-rate-model/1 : work { } depends_on [ @jpegxl-rs.work.quality-q3-ladder-ceiling-and-allocation-screens/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.quality-q5-anchored-controller-above-ceiling/1 : work { @@ -8239,7 +8442,9 @@ record jpegxl-rs.work.quality-q5-anchored-controller-above-ceiling/1 : work { } depends_on [ @jpegxl-rs.work.quality-q4-cover-rate-model/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.quality-q6-cheaper-exhaustive-search/1 : work { @@ -8282,7 +8487,9 @@ record jpegxl-rs.work.quality-q6-cheaper-exhaustive-search/1 : work { } depends_on [ @jpegxl-rs.work.quality-q5-anchored-controller-above-ceiling/1 ] part_of [ @jpegxl-rs.track.encoder-optimization/1 ] - source { kind legacy } + source { + kind legacy + } } record jpegxl-rs.work.quality-q7-effective-scale-controller/1 : work { diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 15fc4998..7e051c5f 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ @@ -41,6 +41,12 @@ Define and screen an independently authored JPXL-side input for normalized seman ## [Perceptual quality controller: SSIMULACRA2 score target for Fast and Balanced, gated Quality effort](ROADMAP.md#perceptual-quality-controller-ssimulacra2-score-target-for-fast-and-balanced-gated-quality-effort) `@jpegxl-rs.track.perceptual-quality-controller/1` +### One-shot program: shadow-first common-case one-shot quality controller + +`active` · `@jpegxl-rs.work.pqc-one-shot-controller/2` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` + +Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the open Balanced wall requirement. PR 1-4 (trace/2, production-endpoint oracle labels, source-only and transform-summary shadow models) are implemented and measured; PR 5 (model-seeded first plan, one slope correction, exact-navigator continuation under unchanged caps with canonical verification before every emission) is implemented and, per the user's 2026-08-24 directive and the 2026-08-25 promotion A/B, is now the DEFAULT controller seed (`one-shot-controller` in the policy crate's default features; building without default features restores the legacy table seed). + ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory `active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/5` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` @@ -56,6 +62,14 @@ Profile and reduce the production Fast and Balanced perceptual path's full-frame | `wall-anchors` | observation | not satisfied — no evidence | | `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` | +## [VarDCT encoder (M1-M8)](ROADMAP.md#vardct-encoder-m1-m8) `@jpegxl-rs.track.vardct-encoder/1` + +### Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode + +`proposed` · `@jpegxl-rs.work.jpeg-bitstream-recompression/1` · part of `@jpegxl-rs.track.vardct-encoder/1` + +Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients and reconstruction metadata into a JPEG XL stream directly (bit-exact reconstructible), instead of the current decode-to-pixels-and-reencode route. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. + ## Unparented ### Encoder optimization plan of record diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index b67169c6..14afee13 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ @@ -768,6 +768,14 @@ The kernel OOM-killed jpxl twice on 2026-08-22 (12:54 anon-rss 9.9 GB; 13:28 ano > **Stale** — `watches "JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs"` was matched by `2425c0b2`, which touched `JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#perceptual-encodes-of-50-mp-sources-reach-12-gb-rss-two-concurrent-measurement-runs-oom-killed-a-31-gb-host-twice). +### The one-shot falsification screen fails on corpus coverage, not on the design: transform features halve the error, the tail is two under-represented classes + +`verified` · `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2` · scope `path "JPXL/crates/jpxl-encode-policy/src/**"`, `path "JPXL/tools/**"` + +On the expanded 56-image / 38-family corpus with production-endpoint oracle labels (fresh Balanced pixel plans over the complete effective ladder; zero censored rows even at target 95, so the old predictor's 81-94% high-target saturation was an artifact of its HfMul=1 ladder cap), the memo's pre-PR5 falsification screen fails: best leave-one-family-out median |ln(predicted/oracle crossing)| is 0.220 with p90 0.805 (bounds: quick p90<=0.45; production median<=0.10, p90<=0.30). Three findings shape the path forward. (1) Transform-domain features materially help, as the memo predicted: source-only p90 1.28 vs source+transform 0.81, median 0.42 vs 0.22, first-or-one-correction success 1.00. (2) The failure is class coverage, not model form: photo, photo-scene, text-screenshot, noise-lowlight, line-art and grayscale classes sit at held-out p90 0.36-0.67 (near or past the quick bound), while saturated (p90 3.58) and sky-noise gradient (p90 4.07) dominate the tail - both effectively class-held-out because their families carry near-unique content. (3) The fallback discipline works: with honest intervals the simulated controller routes 99% of requests to the exact path, keeping bytes at 1.0016x but eliminating the wall win (expected reconstructions 3.98 vs the current median 4). Per the memo, production controller work stops here until the corpus grows (>=25 independent families per critical class; saturated and banding-stress content first); the PR5 mechanism exists behind the off-by-default `one-shot-controller` cargo feature and remains inert without a gate-passing model. + +**supersedes** `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1` · **derived_from** `@jpegxl-rs.work.pqc-one-shot-controller/2` · **verified_by** `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` + ### PR 4 on the development split: the score floor holds in 150/150 encodes, photographs beat cjxl -e7 at matched score, synthetic text and line art trail badly, and the quality path costs ~3.5x the rate path's wall time `verified` · `@jpegxl-rs.observation.pqc-pr4-development-split-2026-08-22/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/**"`, `path "JPXL/crates/jpxl-plan-render/**"`, `path "JPXL/tools/codec_compare.py"` @@ -788,6 +796,14 @@ The earlier @jpegxl-rs.evidence.pqc-pr4-holdout-byte-neutral-ss2-2026-08-22/1 in > **Stale** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `2425c0b2`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pr4s-byte-neutral-rate-curve-conclusion-mixed-ssimulacra2-implementations-and-is-not-a-like-for-like-baseline). +### Full PR7 holdout closes the OOM tail and rejects Quality promotion + +`verified` · `@jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/policy_bank.rs"`, `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-encode-policy/src/reducer.rs"`, `path "JPXL/crates/jpxl-perceptual/src/**"` + +The current low-memory implementation completed the 27 formerly missing Quality cells and 26 companion Balanced rows, closing all 91 cells per effort on the 13-image locked holdout. The terminal reducer is safe and bounded (zero floor/decoder failures, at most six evaluations, final/before bytes geomean 0.991552), but public Quality promotion is rejected: matched-score bytes pass overall at 0.958292, while Contract B fails at Butteraugli mean ratio 1.047881 and worst pnorm3 ratio 1.868818. The added cells make the diagnosis sharper rather than reversing it: photos are nearly byte-neutral at 0.991367 and pass the BA/pnorm3 guards; non-photo byte wins coexist with saturated/tiny/text guard losses. Keep Quality feature-gated and keep its policy bank/reducer out of Fast and Balanced; a future promotion attempt needs a different quality policy or metric guidance, not relaxation of the gate. + +**derived_from** `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1`, `@jpegxl-rs.evidence.pqc-pr7-reducer-holdout-partial-2026-08-22/1` · **verified_by** `@jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1`, `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1` + ### Pre-optimization encode wall-time ladder (release jpxl bench) `verified` · `@jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/2` · scope `path "JPXL/crates/jpxl-cli/**"`, `path "JPXL/crates/jpxl-encode-policy/**"`, `path "JPXL/crates/jpxl-encode/**"` @@ -4897,6 +4913,24 @@ The exact chained final-state gate passed after the quality-rate harness and low Kept identity audit: .agent/scratch/quality-rate-curve-xlarge-exact-20260823/xlarge-identity-audit.json. Balanced q85 on 8160x6120 completed under a 9 GiB virtual-memory cap at 7,532,608 KiB RSS, replacing the former approximately 12.5 GiB/SIGKILL behavior. Across the three 50 MP images, seven targets, and both production efforts, all 42 current codestream SHA-256 values, byte counts, and controller scores exactly match the historical pre-low-memory rows. +### One-shot falsification screen: fails on corpus coverage, transform features validated + +`verified` · `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` + +The memo's pre-PR5 falsification screen fails on the expanded 38-family corpus: best model (pooled quantile regression, source+transform features, ridge 3.0) reaches leave-one-family-out median |ln err| 0.220 and p90 0.805 vs the quick-screen p90<=0.45 and production median<=0.10/p90<=0.30 bounds. First-plan success 0.88 and simulated common-case bytes 1.0016 pass their checks, and first-or-one-correction success is 1.00, but the honest uncertainty intervals route 99% of requests to the exact controller (expected reconstructions 3.98, no wall win). Tail is concentrated in saturated (p90 3.58) and sky-noise gradient (p90 4.07) classes - single-content-family coverage - while photo/scene/text/noise/line-art/grayscale sit at p90 0.36-0.67. Standalone transform-summary cost on the 12 MP anchor is 21.9% of a matched-rate encode (fails the 5% bolt-on gate; in-search shared-cache marginal cost unmeasured). Report kept in .agent/scratch/one-shot-qpv2-20260824/qpv2-report.json. + +### One-shot PR 1-5 implementation: all workspace gates pass + +`verified` · `@jpegxl-rs.evidence.pqc-one-shot-pr1-5-gates-2026-08-24/1` + +The one-shot program's PR 1-5 implementation passes every workspace gate: full test suite green (71 suites) in the default build and 155 policy tests under the one-shot-controller feature (including the PR 5 routing test and the trace/2 assertions), clippy clean in both configurations, formatting clean, 75 Python tool tests green (trainer, oracle-label, fixture-generator suites), and the fixture generator's check mode reproduces all 56 corpus PPMs byte-identically with the family split-hygiene guard passing. Default-build behavior is unchanged: the shadow predictor only logs, and the one-shot start is compiled out. + +### One-shot promotion A/B: floor held, bytes and work strictly better in aggregate + +`verified` · `@jpegxl-rs.evidence.pqc-one-shot-promotion-ab-2026-08-25/1` + +Promotion A/B of the one-shot-controller build against the default controller, interleaved on the same machine. Locked 13-image holdout (91 cells): zero floor violations both arms, byte geomean 0.99744 (bound 1.007), wall geomean 0.989, reconstructions 348 vs 339; worst cells are three sub-kilobyte saturated fixtures (max ratio 1.51 = +131 bytes absolute). Never-tuned ext-holdout paintings (50 families, 350 cells): zero floor violations, byte geomean 0.99018, reconstructions 1287 to 1056 (-18%), wall geomean 0.838, worst cell 1.117 at target 95 with a higher achieved score. Achieved-score dips above the floor exist in both arms (1 default, 2 one-shot) - a pre-existing bounded-search artifact, no hard inversion below target anywhere. Wall anchors at q85 vs matched-rate: 4.3 MP 1.36x both arms; 12 MP 8.02x default vs 6.31x one-shot (metric/render remains the dominant residual, as the memo predicted). Raw rows kept in .agent/scratch/one-shot-qpv2-20260824/ (ab-locked-holdout.json, ab-ext-holdout.json). + ### The generator reproduces 47 fixtures (47 PPM + 29 PNG + 47 JSON provenance sidecars, idempotent sha256) and the jpxl.codec-corpus/1 manifest test-set/quality-corpus.json (gitignored) validates; splits are calibration 19, development 15, holdout 13 with no source family in two splits; classes text-screenshot 5, line-art 5, gradient 6, saturated 4, tiny 6, noise-lowlight 1, grayscale 2, photo-scene 7, photo 11. 14 generator unit tests pass. `verified` · `@jpegxl-rs.evidence.pqc-pr0-corpus-manifest-2026-08-22/1` @@ -4907,6 +4941,12 @@ The generator reproduces 47 fixtures (47 PPM + 29 PNG + 47 JSON provenance sidec - `completed` `@jpegxl-rs.work.pqc-pr0-provenance-corpus-calibration/1` — check `corpus-manifest` +### PR0 hard floor: targeted miss-path tests pass + +`verified` · `@jpegxl-rs.evidence.pqc-pr0-hard-floor-tests-2026-08-24/1` + +New miss-path tests all pass: policy-layer UnderTargetWorkCap with rescue accounting (pixel_probes 1 + 1 rescue), facade refuse/lossless/best-effort on the deterministic 64x64 gradient miss at 99.5 Fast (TargetNotMet with LadderSaturated, FallbackLossless bytes equal to the lossless encoder's, best-effort re-scored independently), and 8/8 CLI quality tests including exit-1-with-no-output-file refusal and --quality-fallback validation. + ### 256 fixed-quantizer points (16 calibration images x 16 global_scale rungs 400..73728) scored with the reference SSIMULACRA2 in 604 s produced quality_predictor.rs: 56 table cells over 5 luma-variance x 3 flat-fraction buckets plus a fallback log fit [15.62, -1.60, 0.0099, -2.57]. Leave-one-image-out |ln(pred/actual)| median 0.30, p90 2.68 (cells hold 1-4 images); loss-vs-scale slope median -2.34; saturation at 73728 is 0 up to target 70 and 0.06/0.19/0.81/0.94 at 80/85/90/95. `verified` · `@jpegxl-rs.evidence.pqc-pr0-predictor-table-2026-08-22/1` @@ -4927,6 +4967,12 @@ Both advice documents are registered under sources/external with content hashes - `completed` `@jpegxl-rs.work.pqc-pr0-provenance-corpus-calibration/1` — check `sources-registered` +### PR0 hard floor: workspace and tools gates pass + +`verified` · `@jpegxl-rs.evidence.pqc-pr0-workspace-gates-2026-08-24/1` + +Workspace gates pass after the hard-floor change: full test suite green in both debug and fast-debug profiles (zero failures), clippy emits no warnings, formatting is clean, and the 46 Python tool tests pass (codec_compare now opts into --quality-fallback best-effort so curve points on an unmet target stay measurable). + ### 6/6 facade target tests pass: score 100 is byte-identical to the lossless path, scores outside 0..=100 are rejected, conflicting targets (quality+bpp, bpp+global_scale) are rejected, a perceptual score below 100 runs the quality controller, --global-scale encodes and decodes, and the rate report matches the bytes. `verified` · `@jpegxl-rs.evidence.pqc-pr1-api-semantics-2026-08-22/1` @@ -5126,12 +5172,58 @@ With one shared CandidateSearchContext and the baseline anchor reused by quantiz The single-evaluation Balanced reducer (first batch = half the ranked candidates, max 16384 edits) saves 0.4% bytes geomean at targets 70 and 85 (photos 0.4-0.5%, best 2.9%) with zero floor violations, at +21% / +16% wall geomean but up to +62% on one small image. Average inside the +25% Balanced wall budget, worst case outside, for a small gain: Balanced keeps the reducer off until the locked-holdout gate (matched-score bytes, Butteraugli/PSNR guards) is run and the evaluation made cheaper; the feature-gated Quality effort runs the full reducer. +### PR7 holdout closure workspace and feature gates pass + +`verified` · `@jpegxl-rs.evidence.pqc-pr7-closure-workspace-gates-2026-08-24/1` + +Workspace build, full release suite, strict Clippy, formatting, and the quality-effort+perceptual feature-enabled CLI release tests all pass after the policy note and regression-test update. + +**Verifies** + +- `completed` `@jpegxl-rs.work.pqc-pr7-holdout-closure/1` — check `workspace-gates` + ### Development split, Balanced with the reducer on versus off: bytes geomean 0.9825 at target 70 (photos 0.9903, min 0.888) and 0.9912 at 85 (photos 0.9886, min 0.9835); zero floor violations in 30 cells (the reduced stream is kept only when its exact size is smaller and every accepted batch is re-scored canonically); at most 6 evaluations; wall geomean 1.53x / 1.63x. Bytes down at matched-or-better score with bounded work, but the wall exceeds Balanced's +25% budget, so the reducer ships default-off on Balanced (BALANCED_DEFAULT_REDUCER = None) and on for the feature-gated Quality effort; the locked-holdout gate is still to be measured. `verified` · `@jpegxl-rs.evidence.pqc-pr7-dev-split-2026-08-22/1` Development split, Balanced with the reducer on versus off: bytes geomean 0.9825 at target 70 (photos 0.9903, min 0.888) and 0.9912 at 85 (photos 0.9886, min 0.9835); zero floor violations in 30 cells (the reduced stream is kept only when its exact size is smaller and every accepted batch is re-scored canonically); at most 6 evaluations; wall geomean 1.53x / 1.63x. Bytes down at matched-or-better score with bounded work, but the wall exceeds Balanced's +25% budget, so the reducer ships default-off on Balanced (BALANCED_DEFAULT_REDUCER = None) and on for the feature-gated Quality effort; the locked-holdout gate is still to be measured. +### Full PR7 locked-holdout Quality/reducer sweep completes after low-memory changes + +`verified` · `@jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1` + +Combined historical checkpoints with 53 resumed rows to close 91/91 Quality and 91/91 Balanced cells on all 13 locked holdout images. All 182 successful rows had zero canonical-floor and decoder failures; reducer work stayed at <=6 evaluations, saved 263,266 exact bytes total, and produced 0.991552 final/before geomean. The formerly blocked 50 MP Quality rows completed serially under a 12 GiB cap; worst measured peak was 11,163,372 KiB. Kept report: .agent/scratch/pr7-holdout-closure-20260824/report.json (SHA-256 fd78df93...f282e). + +**Verifies** + +- `completed` `@jpegxl-rs.work.pqc-pr7-holdout-closure/1` — check `full-holdout` + +### Production budgets keep rejected Quality-only work disabled + +`verified` · `@jpegxl-rs.evidence.pqc-pr7-policy-gating-test-2026-08-24/1` + +The new policy regression test passes and proves Fast/Balanced default budgets retain zero policy trials and no reducer while the feature-gated Quality budget retains its bank and terminal reducer. + +**Verifies** + +- `completed` `@jpegxl-rs.work.pqc-pr7-holdout-closure/1` — check `resulting-disposition` + +### Full Contract B result is dispositioned without weakening the public gate + +`verified` · `@jpegxl-rs.evidence.pqc-pr7-promotion-disposition-2026-08-24/1` + +The full 79-cell common-score report was evaluated against the standing Quality promotion conditions. Overall matched bytes pass at 0.958292, but Butteraugli mean 1.047881 and worst pnorm3 1.868818 fail their 1.02/1.05 limits, so the existing quality-effort feature gate remains in force. No search-policy change is justified; the source note now records the completed gate and a regression test pins Quality-only work out of Fast/Balanced. + +**Verifies** + +- `completed` `@jpegxl-rs.work.pqc-pr7-holdout-closure/1` — check `promotion-contract` + +### Full locked holdout rejects public Quality promotion under Contract B + +`verified` · `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1` + +Across 79 common-score cells, Quality/Balanced bytes pass at 0.958292 geomean, but Butteraugli mean ratio is 1.047881 (>1.02) and worst pnorm3 ratio is 1.868818 (>1.05), so Contract B fails. Photos are nearly byte-neutral at 0.991367 with BA mean 1.000790 and worst pnorm3 1.042495; non-photo savings drive the aggregate while saturated, tiny, and text cells drive the guard failure. Quality remains feature-gated. + ### Partial locked holdout definitively rejects public Quality promotion `verified` · `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1` @@ -5144,6 +5236,12 @@ Stopped by user after 64/91 Quality cells (10/13 images; 55 decoded-score-matche Across 64 completed Quality cells the reducer had zero canonical floor violations, at most 6 evaluations, saved 147845 exact bytes total, and produced a 0.98925 final/before geomean (0.98626 on 50 eligible cells). The reducer-off decoded guard baseline and 27 remaining Quality cells were not run; reducer-gate remains open and is deferred with no more Quality testing requested. +### Expanded-corpus qpv2 model: blind holdout hits production p90; transform features win + +`verified` · `@jpegxl-rs.evidence.pqc-qpv2-expanded-corpus-gate-2026-08-25/1` + +Corpus expanded with 211 independent painting families from the user's collection (104 calibration / 57 development / 50 never-tuned ext-holdout, deterministic split by family hash, originals untouched); 103 swept within the 2-hour budget for 994 total label rows (zero censored). Retrained pooled model, 10-fold family-grouped CV: source+transform wins (CV median 0.121 / p90 0.461 vs source-only 0.176/0.595); blind never-tuned ext-holdout: median 0.113, p90 0.258 (production p90 bound met), p99 0.478, first-plan 0.925, first-or-one-correction 1.00. Remaining CV tail is entirely the saturated (p90 3.58) and sky-noise gradient classes, still effectively class-held-out. Emitted runtime model qpv2-st-1 (19 features: 9 source + 10 DCT8-summary); the in-search transform summary measured -3.9% wall at 4.3 MP (shares the cover's cache; standalone 7.8% after parallelizing the fill from 21.9% serial). Artifacts in .agent/scratch/one-shot-qpv2-20260824/. + ### Corrected common-axis rate curve is exact on 10/13 holdout images; xlarge quality remains memory-blocked `verified` · `@jpegxl-rs.evidence.pqc-quality-rate-curve-exact-10of13-2026-08-23/1` diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 56070d25..3b907edf 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ @@ -242,6 +242,18 @@ Normal lossy encoding is specified by a minimum perceptual score, not a byte bud **supersedes** `@jpegxl-rs.decision.lossy-production-presets/1` +## jpegxl-rs.decision.quality-miss-fallback-semantics + +### Revision 1 — An under-target perceptual result is refused at the facade by default; lossless and best-effort are explicit opt-in fallbacks + +`proposed` · `@jpegxl-rs.decision.quality-miss-fallback-semantics/1` · scope `path "JPXL/crates/jpxl-cli/**"`, `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl/**"` + +An under-target perceptual result is never an ordinary success at the public facade. When the bounded controller stops at SaturatedTop or UnderTargetWorkCap, the default is to refuse: Encoder returns Error::TargetNotMet carrying a QualityMiss (kind LadderSaturated | WorkBudgetExhausted, requested and best verified scores, probe/price counts, metric version, quality trace), and the CLI exits 1 without creating the output file. Two explicit fallbacks exist via Encoder::with_quality_fallback / --quality-fallback: Lossless emits a mathematically lossless stream reported as PerceptualStatus::FallbackLossless with achieved score 100, and BestEffort emits the finest canonically verified under-target stream under its true status and score. SaturatedFloor remains a success. The policy layer (search_frame_perceptual) still returns every terminal status with bytes; the facade owns the refusal. The rescue probe stays one probe beyond the navigation cap and is documented as an observable per-solve maximum of pixel_probes + 1; folding it inside the cap is a deliberate search-policy change that would require the standing Contract B screen. + +**Context.** The 2026-08-24 one-shot advisor memo's first code finding, verified against quality.rs, jpxl/src/lib.rs and jpxl-cli/src/main.rs: the facade wrapped SaturatedTop and UnderTargetWorkCap outcomes as Ok and the CLI wrote the output file and exited 0, so a scripted caller checking only the exit code received a silently under-target stream — incompatible with the hard-floor reading of the perceptual-quality contract. The completed 91-cell locked holdout contains no under-target cells, so refusing changes no canonical-sweep behavior. + +**Consequences.** Met, MetAdjacentRungs, MetWorkCap, SaturatedFloor and RescuedFreshStructure outputs stay byte-identical. The public API adds Error::TargetNotMet, QualityMiss, QualityMissKind, QualityFallback, PerceptualStatus::FallbackLossless and Encoder::with_quality_fallback; the CLI adds --quality-fallback lossless|best-effort. A refused encode still appends its jpxl.quality-trace/1 record to JPXL_QUALITY_TRACE so failed searches remain calibration input. Facade, policy-layer and CLI tests now pin the miss path, which previously had no coverage anywhere. + ## jpegxl-rs.decision.quant-bias-defaults ### Revision 1 — quant_bias defaults are 1 minus x diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index c1f038b8..22221a23 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 05265cb9..fa83fa64 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ @@ -7,6 +7,7 @@ Small frictions hit while working, logged in the moment (D-027). None of these blocked anything; together they show where the project needs sanding down. Newest first. +- 2026-08-24 [codex] The frozen PR7 holdout parser assumed `ssimulacra2` immediately followed `psnr_db`; the current compare line inserts `ssimulacra2_jpxl` and its version first, so an otherwise successful 12 MP resumed cell was needlessly rerun. Parse named fields independently or share the checked-in compare parser. `@jpegxl-rs.papercut.the-frozen-pr7-holdout-parser-assumed/1` - 2026-08-21 [Codex] The Phase-32 corpus harness resolves JPXL_PHASE32_PPM paths from the package test working directory, so repo-relative paths that exist from the cargo invocation directory fail with an unlabelled Os NotFound. The harness should report the failing path or document that paths must be absolute. `@jpegxl-rs.papercut.the-phase-32-corpus-harness-resolves-jpxl/1` - 2026-08-19 [fugu-ultra] The native Windows workspace test gate treated the conformance corpus's 166-byte `IntxLNK` symlink placeholder as a downloaded `reference_image.npy` and failed with `BadMagic`; the test's documented missing-reference skip handled absent/empty files but not Windows symlink placeholders. `@jpegxl-rs.papercut.the-native-windows-workspace-test-gate-treated/1` - 2026-08-19 [fugu-ultra] The checked-in quality-track README points ladder.ps1 at `.agent/scratch/quality-track/inputs`, but the current `mid-photo.ppm` there is not a valid P5/P6 image and the run fails immediately; prior handoff outputs exist, so the fixture path or scratch retention is stale and needs a regeneration note/check. `@jpegxl-rs.papercut.the-checked-in-quality-track-readme-points/1` @@ -27,6 +28,7 @@ Small frictions hit while working, logged in the moment (D-027). None of these b Frictions with something else — a tool, a harness — hit while working here. They are logged where they were hit; `akr papercut collate --about ` is how the project that owns the subject gathers them. +- 2026-08-24 [codex] (akr) knowledge.propose's generic `slots` schema does not expose that an observation's `method` must be one of manual/command/instrumented/observation. V-022 said to add `method`; supplying explanatory prose then produced dozens of parser diagnostics before the enum expectation appeared. `@jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1` - 2026-08-23 [codex-gpt-5] (akr) knowledge.propose exposes `topic` for every record kind, but a work proposal containing it fails only after full validation with AKR-T034 because topic is normative-only. The tool schema or preflight should state/reject this earlier. `@jpegxl-rs.papercut.knowledge-propose-exposes-topic-for-every/1` - 2026-08-23 [codex] (akr) knowledge.propose exposes scope as Array; passing intuitive {path: ...} objects fails only with 'unknown scope form'. The accepted MCP shape is {form: 'path', glob: ...}, which should be expressed in the tool schema or error. `@jpegxl-rs.papercut.knowledge-propose-exposes-scope-as-array/1` - 2026-08-23 [codex] (akr) After `akr scratch keep` visibly added a new entry, knowledge.evidence_add still rejected that kept artifact from its cached workspace. The CLI fallback revalidated ~230 unrelated historical scratch citations lacking current keep markers and refused an otherwise valid atomic write; omitting the typed artifact was the only scoped path forward. `@jpegxl-rs.papercut.after-akr-scratch-keep-visibly-added-a-new/1` diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 704d5ff6..e9823dc6 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index ff6fc9b0..508ea4b1 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ @@ -359,6 +359,7 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality **Work items** +- `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/2` - `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/5` ### VarDCT encoder (M1-M8) @@ -368,7 +369,9 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality The VarDCT encoder, built as milestones M1-M8 (PLAN.md slices 11-18) and backfilled from git history as legacy-sourced completed records (D-028). -**Work items** — _(none)_ +**Work items** + +- `proposed` [Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode](ACTIVE-WORK.md#lossless-jpeg-bitstream-recompression-carry-jpeg1-coefficients-into-jpeg-xl-directly-instead-of-decode-and-reencode) `@jpegxl-rs.work.jpeg-bitstream-recompression/1` ## Summary From dd8c5ed6226611a3bd2f2cf0cabb2a7510ca06f3 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 01:09:02 +0800 Subject: [PATCH 07/27] docs(JPXL): JPEG bitstream recompression implementation plan Five gated phases from ISO/IEC 18181-2 s9.11 + Annex A and 18181-1: a new clean-room jpxl-jpeg coefficient codec (self-gated byte-exact JPEG round-trip), YCbCr 8x8-only VarDCT coefficient carriage with RAW dequant matrices, the jbrd box + Annex A reconstructor, CLI/facade surface, and black-box cjxl/djxl interop. Records the verified encoder prerequisites (no do_YCbCr / jpeg_upsampling / RAW-quant writing today) and the open questions (LF/DC bijectivity proof is the highest risk; 10918-1 source acquisition; OCR-suspect jbrd tables to check against the original scan; Brotli dependency decision). The work record jpegxl-rs.work.jpeg-bitstream-recompression now carries the plan as origin source and the four phase gates as acceptance checks. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- .akr/akr.lock | 4 +- .akr/records/jpegxl-rs/work.akr | 45 ++++++- JPXL/docs/jpeg-recompression-plan.md | 193 +++++++++++++++++++++++++++ docs/generated/ACTIVE-WORK.md | 13 +- docs/generated/CURRENT-STATE.md | 10 +- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 40 ++++-- docs/generated/ROADMAP.md | 2 +- 10 files changed, 284 insertions(+), 29 deletions(-) create mode 100644 JPXL/docs/jpeg-recompression-plan.md diff --git a/.akr/akr.lock b/.akr/akr.lock index a0b24333..9db5e2c6 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:c236729fcb7dc6ee76cdcbed13105424fee3cdac625e963fb4c4d230dd4b1dd9" + source_graph "sha256:f0707696eea1a0e184fe9f14440cb56b9712b5992210ce100ccdbd259e27cb3e" } source ".akr/project.akr" { @@ -69,7 +69,7 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:28ac87edba6875e61db20985b8b69fa30a2e7dcf898b6e876267e34f09397993" + hash "sha256:de6c9253fad79f03244e3cec35910b7ab48202eb858abb6cc8385770a32f23c9" records 182 } diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 6f8c8cdf..e423722e 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -6786,18 +6786,55 @@ record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { title "Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode" state proposed scope [ + path "JPXL/crates/jpxl-bitstream/**", path "JPXL/crates/jpxl-cli/**", + path "JPXL/crates/jpxl-decode/**", path "JPXL/crates/jpxl-encode/**", - path "JPXL/crates/jpxl/**" + path "JPXL/crates/jpxl-jpeg/**", + path "JPXL/crates/jpxl/**", + path "JPXL/docs/jpeg-recompression-plan.md" ] intent """ - Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients and reconstruction metadata into a JPEG XL stream directly (bit-exact reconstructible), instead of the current decode-to-pixels-and-reencode route. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. + Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients in a YCbCr 8x8-only VarDCT frame plus the jbrd JPEG Bitstream Reconstruction Data box (18181-2 s9.11 + Annex A), so the byte-exact original JPEG is reconstructible from the .jxl alone. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. """ note """ - User-requested 2026-08-24 while supplying the painting corpus: 'if jpxl doesn't support the direct conversion that is technically possible, it should'. Today `jpxl encode in.jpg` decodes the JPEG to pixels and encodes those (lossy or lossless on decoded pixels). Direct recompression is a substantial clean-room feature: JPEG1 entropy decode to coefficients, the VarDCT coefficient carriage and reconstruction-metadata boxes per 18181, and a bit-exact JPEG reconstruction check as the acceptance test. Scoping and standard-clause mapping needed before implementation planning. + Plan written 2026-08-25: JPXL/docs/jpeg-recompression-plan.md. Five phases, each separately gated: A) new clean-room `jpxl-jpeg` crate, a 10918-1 coefficient-level codec self-gated by serialize(parse(x))==x over >=500 archive JPEGs plus typed refusal of arithmetic/hierarchical/12-bit/lossless modes; B) coefficient carriage into a YCbCr+jpeg_upsampling VarDCT frame with RAW dequant matrices, gated by exact coefficient round-trip through our own decoder's new coefficient-export surface; C) jbrd writer + Annex A reconstructor, gated by byte-exact original JPEG from the .jxl alone and geomean size reduction reported; D) CLI/facade surface (encode auto-detects compatible JPEGs, decode reconstructs when jbrd present); E) black-box interop with cjxl/djxl both directions. Encoder prerequisites verified: jpxl-encode currently writes only xyb_encoded frames (no do_YCbCr, no jpeg_upsampling, no RAW quant signalling); jpxl-decode already decodes YCbCr+jpeg_upsampling. Open questions in the plan s5: LF/DC bijectivity proof (highest risk), CfL/filter signalling exactness, OCR-scrambled Table 11-18 field distributions to verify against the original scan, 10918-1 source acquisition before Phase A entropy work, Brotli dependency decision, level-5 check. """ + acceptance { + check phase-a-jpeg-codec-roundtrip { + statement """ + jpxl-jpeg serialize(parse(x)) is byte-identical to x over >=500 deterministically sampled archive JPEGs covering baseline, progressive, 4:4:4/4:2:0/4:2:2, greyscale, restart intervals, Exif/ICC/XMP and trailing garbage; arithmetic-coded, hierarchical, 12-bit and lossless JPEG inputs are refused with a typed error. + """ + method command + } + check phase-b-coefficient-carriage { + statement """ + Every JPEG quantized DCT coefficient round-trips exactly through jpxl encode to a YCbCr VarDCT frame and jpxl-decode's coefficient export, on the Phase A corpus, at multiple worker counts. + """ + method command + } + check phase-c-bit-exact-reconstruction { + statement """ + The original JPEG is reconstructed byte-exactly from the .jxl file alone (codestream + jbrd, 18181-2 Annex A) on the Phase A corpus, and the .jxl is smaller than the source JPEG on >=95% of it. + """ + method command + } + check phase-e-oracle-interop { + statement """ + djxl reconstructs our recompressed .jxl to the byte-exact original, and we reconstruct cjxl --lossless_jpeg=1 output byte-exactly, on a 50-image sample, treating both oracles as black boxes. + """ + method command + } + } part_of [ @jpegxl-rs.track.vardct-encoder/1 ] - author "GitHub Uploader" + source { + kind internal + role origin + path "JPXL/docs/jpeg-recompression-plan.md" + use """ + The implementation plan this record tracks: architecture (new jpxl-jpeg crate, container work in jpxl-bitstream, no policy involvement), phase gates, and open questions. + """ + } } record jpegxl-rs.work.native-windows-quality-tooling/1 : work { diff --git a/JPXL/docs/jpeg-recompression-plan.md b/JPXL/docs/jpeg-recompression-plan.md new file mode 100644 index 00000000..980c5ef8 --- /dev/null +++ b/JPXL/docs/jpeg-recompression-plan.md @@ -0,0 +1,193 @@ +# JPEG bitstream recompression — implementation plan + +Status: **plan** (no code yet). AKR work record: +`jpegxl-rs.work.jpeg-bitstream-recompression`. Written 2026-08-25, from +ISO/IEC 18181-2:2024 §9.11 + Annex A (`sources/markdowns/standard-markdowns/part2.md`) +and ISO/IEC 18181-1:2024 (`sources/latex/part1.tex`). Clean-room: everything +below derives from the standards and this workspace; libjxl remains a +black-box oracle (`cjxl --lossless_jpeg=1`, `djxl --pixels_to_jpeg` / +default JPEG reconstruction) for interop testing only. + +## 1. What the feature is + +An existing JPEG (ISO/IEC 10918-1) is represented as a JPEG XL file +**without decoding to pixels**: its quantized DCT coefficients are carried +in a kVarDCT frame using only 8×8 blocks (Part 1 §C, note in the frame +header clause: "existing JPEG images can be represented using the kVarDCT +encoding using only 8×8 DCTs"), and everything the coefficients do not +determine — marker order, Huffman tables, scan script, padding bits, +restart markers, APPn/COM payloads, trailing garbage — goes into the +**JPEG Bitstream Reconstruction Data box** (`jbrd`, Part 2 §9.11). Decoding +the frame yields the image; running Part 2 Annex A over the coefficients + +`jbrd` yields the **byte-exact original JPEG**. Typical size win on +Huffman-coded JPEGs is ~20 %; the transcode is exact by construction, so +there is no quality question and none of the perceptual controller is +involved. + +Why we want it: the user's archives are JPEG-heavy (thousands of scanned +paintings); pixel-decoding them into a fresh lossy encode either loses +generation quality or (lossless) inflates the file. Recompression is the +only path that is simultaneously smaller and exactly reversible. + +## 2. What the standard requires (digest) + +### 2.1 `jbrd` box (Part 2 §9.11, Tables 11–18) + +A bundle-coded header (same `Bits()`/`U32()`/`Bool()` conventions as +Part 1) followed by one Brotli stream: + +- `is_grey`; `marker[]` — one byte per segment, `0xC0 + Bits(6)` each, + terminated by `0xD9` (EOI). Counts of specific values determine array + sizes downstream: `0xE0..=0xEF` → `num_app_markers`, `0xFE` → + `num_com_markers`, `0xDA` → `num_scans`, `0xFF` → `num_intermarker`, + `0xDD` present → `has_dri`. +- `AppMarker { type, length }` per APPn; `com_length[]`; + `num_quant_tables` (1 + Bits(2)) and `QuantTable { precision, index, + is_last }`; `comp_type` (2 bits; `== 3` carries explicit `num_comp` and + 8-bit `component_id[]`; `comp_type == 2` gates a conditional row); + `component_q_idx[]`. +- `num_huff` (U32) and `HuffmanCode { is_ac, id, is_last, counts[16], + values[sum(counts)] }`. +- `ScanInfo { num_comps, Ss, Se, Al, Ah, ScanComponentInfo{comp_idx, + ac_tbl_idx, dc_tbl_idx}[], last_needed_pass }` per scan; + `restart_interval` if `has_dri`; `ScanMoreInfo { reset_point[], + ExtraZeroRun{num_runs, run_length}[] }` per scan (progressive/EOBRUN + quirks and non-optimal encoders). +- `intermarker_length[]`, `tail_data_length`, `has_padding` + padding + bit-bank (`nbit`, `bbit[]`). +- Brotli payload: APPn payloads of type 0, COM payloads, unrecognized + inter-marker data, tail data — in that order. + +**OCR caveat**: the Table 11–18 transcription in `part2.md` is visibly +scrambled in places (mis-merged rows, mangled U32 distributions). Before +implementing the bundle reader/writer, spot-check every table against the +original scan pages 12–14 (`sources/original-pdfs-do-not-read-first-if-markdown-exists/original/`, +Part 2 = "2024 jun"), per the STANDARDS_INDEX caveat. Treat the exact U32 +distributions as **unverified** until then. + +### 2.2 Reconstruction procedure (Part 2 Annex A) + +Deterministic serializer over the marker array with iterator semantics +(every stored element consumed exactly once, in order): SOF (types +0xC0/C1/C2/C9/CA; component order note — `jpeg_upsampling` is Cb,Y,Cr +order while the JPEG SOF is Y,Cb,Cr), DHT (10918-1 B.2.4.2 with the +last-nonzero `L_i` decremented-by-1 convention), RSTn, EOI (+ tail data), +SOS (10918-1 B.2.3; entropy coding per 10918-1 F.1.2/G.1.2 with the +`extra_zero_run`/`reset_point`/padding-bit amendments for bit-exactness), +DQT (factors come **from the codestream's dequant matrices**, Part 1 +I.2.4; an unused table repeats the previous one), DRI, APPn (type 0 raw; +type 1 ICC re-chunked as `ICC_PROFILE` APP2 parts from the decoded ICC +profile; types 2/3 Exif/XMP re-wrapped from the Exif/XML boxes), COM, +unrecognized (0xFF) raw copy. + +### 2.3 Part 1 carriage requirements + +The recompressed frame must decode to exactly the JPEG's dequantized-able +coefficients, which constrains the encoder to a fixed shape: + +- `metadata.xyb_encoded = false`, frame `do_YCbCr = true` (or grey); + `jpeg_upsampling[3]` for 4:2:0/4:2:2/4:4:0 (Part 1: subsampled channels + are coded at reduced resolution and upsampled per J.2; group division + unaffected). +- All varblocks 8×8 DCT8; no Gaborish, no EPF, no patches/splines/noise; + CfL zeroed (or the exact signalled defaults that make the coefficient + passthrough exact — to be pinned from Part 1 during implementation). +- Dequantization matrices in **RAW encoding mode** (Part 1 I.2.4: "for + encoding mode RAW, the dequantization matrices are equal to the params + matrices multiplied by params…") so each JPEG quant table maps to an + exact JXL dequant matrix and Annex A's DQT can read the factors back. +- LF/DC: JPEG DC coefficients land in the LF image; the LF quantization + must be chosen so quantized-LF ↔ JPEG-DC is bijective (this is the one + place where "exact" needs a worked proof against Part 1 §H/I rather + than an assumption — flagged as an open question below). +- HF: JPEG's zigzag AC coefficients map into JXL's coefficient order with + the standard's natural-order permutation; values pass through the + entropy coder unchanged (quantized integers). + +## 3. Architecture + +New crate **`jpxl-jpeg`** (parallel to `jpxl-entropy`): a clean-room +ISO/IEC 10918-1 *coefficient-level* codec. No pixel path, no IDCT: + +- `parse`: markers → segments; DQT/DHT/SOF/SOS/DRI/APPn/COM structures; + baseline + progressive Huffman entropy decode to per-component + coefficient planes; capture everything Annex A needs (padding bits, + extra zero runs, reset points, inter-marker garbage, tail data). +- `serialize`: the exact inverse — Annex A is its specification. +- Self-gate: `serialize(parse(jpeg)) == jpeg` byte-for-byte, with no JXL + involvement. This isolates all 10918-1 subtleties before any codestream + work. + +Boundary rules (mirrors `jpxl-entropy`): `jpxl-encode` may depend on +`jpxl-jpeg`; `jpxl-jpeg` depends on nothing in the workspace except +possibly `jpxl-core`. The policy crate is **not involved** — recompression +is a deterministic transcode, not a search. Container work (reading and +writing the `jbrd` box, Brotli) lands in `jpxl-bitstream`; a Brotli codec +is a new dependency decision (the workspace is near-zero-dependency — +either a vetted `brotli` crate behind the CLI/container feature, or +scope-limited vendoring; decision to be made at Phase C, recorded in AKR). + +Prerequisites in existing crates (verified 2026-08-25): + +- `jpxl-encode` writes only `xyb_encoded` frames today + (`vardct/headers.rs` hard-codes the assumption; `frame.rs` writes + `do_YCbCr = false`): needs the YCbCr + `jpeg_upsampling` header path + and RAW dequant-matrix signalling. +- `jpxl-decode` already decodes YCbCr + `jpeg_upsampling` + (`frame/upsampling.rs`, `e2e_ycbcr.rs`), so our own decoder can oracle + the carriage phase. Its dequant RAW path exists + (`vardct/dequant_matrix.rs`). +- The decoder needs a coefficient-export surface (decode to quantized + coefficients, not pixels) for reconstruction — today it renders pixels. + +## 4. Phases and gates + +Each phase is a separately committable, gated unit; the phase gates are +the completion metrics for the work record. + +- **Phase A — `jpxl-jpeg` codec.** Gate: byte-exact + `serialize(parse(x)) == x` over ≥500 JPEGs sampled deterministically + from the Pol Art archive (read-only; decoded copies only, originals + never touched) covering baseline/progressive, 4:4:4/4:2:0/4:2:2, + grey, restart intervals, Exif/ICC/XMP, and trailing garbage; plus + graceful *refusal* (typed error) of arithmetic-coded, hierarchical, + 12-bit, and lossless JPEG modes. +- **Phase B — coefficient carriage.** Encode coefficient planes into a + YCbCr VarDCT frame; decode with `jpxl-decode`'s new coefficient + export. Gate: coefficients round-trip exactly (all components, all + subsamplings) on the Phase A corpus; existing decoder conformance and + encoder tests untouched. +- **Phase C — `jbrd` + Annex A.** Writer populates the box during Phase B + encode; reconstructor = `jpxl-jpeg::serialize` fed from the decoded + coefficients + box. Gate: **byte-exact original JPEG from the .jxl + file alone**, same corpus; total .jxl ≤ original JPEG bytes on ≥95 % + of the corpus with the geomean reduction reported (expect ≈ −15–20 %). +- **Phase D — surface.** CLI: `jpxl encode in.jpg out.jxl` defaults to + recompression when the input is a compatible JPEG (explicit + `--jpeg=pixels|recompress|auto` to override; incompatible modes fall + back to the pixel path with a notice); `jpxl decode out.jxl out.jpg` + reconstructs the original when `jbrd` is present. Facade: + `Encoder::recompress_jpeg(&[u8])`, `Decoder`-side reconstruction. + Gate: CLI round-trip tests, CHANGELOG, README. +- **Phase E — interop (black-box only).** Our .jxl reconstructed by + `djxl` equals the original; `cjxl --lossless_jpeg=1` output is + reconstructed by us. Gate: both directions byte-exact on a 50-image + sample. Any mismatch is debugged against the *standard*, never by + reading libjxl source. + +## 5. Open questions (to resolve during Phase B, recorded in AKR as they close) + +1. **DC bijectivity**: the exact Part 1 LF quantization arithmetic that + makes JPEG DC carriage lossless (worked derivation from §H/I needed; + this is the highest technical risk). +2. **CfL/filter signalling**: the precise header settings that make the + HF passthrough exact (zero CfL vs. signalled defaults). +3. **Table 11–18 field distributions**: verify against the original scan + (OCR caveat above) before freezing the bundle reader. +4. **10918-1 source**: the workspace has no copy of the JPEG spec; Annex + A references B.2.3/B.2.4/F.1.2/G.1.2 normatively. Acquire and + register it under `sources/` before Phase A entropy work (the + marker-structure layer can proceed from Part 2 alone). +5. **Brotli dependency** policy (see §3). +6. **Levels/profile**: whether recompressed streams stay within level 5 + for the archive's dimensions (Part 1 §M check). diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 7e051c5f..2512fd21 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ @@ -68,7 +68,16 @@ Profile and reduce the production Fast and Balanced perceptual path's full-frame `proposed` · `@jpegxl-rs.work.jpeg-bitstream-recompression/1` · part of `@jpegxl-rs.track.vardct-encoder/1` -Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients and reconstruction metadata into a JPEG XL stream directly (bit-exact reconstructible), instead of the current decode-to-pixels-and-reencode route. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. +Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients in a YCbCr 8x8-only VarDCT frame plus the jbrd JPEG Bitstream Reconstruction Data box (18181-2 s9.11 + Annex A), so the byte-exact original JPEG is reconstructible from the .jxl alone. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. + +**Acceptance** — 0 of 4 satisfied + +| Check | Method | Verdict | +| --- | --- | --- | +| `phase-a-jpeg-codec-roundtrip` | command | not satisfied — no evidence | +| `phase-b-coefficient-carriage` | command | not satisfied — no evidence | +| `phase-c-bit-exact-reconstruction` | command | not satisfied — no evidence | +| `phase-e-oracle-interop` | command | not satisfied — no evidence | ## Unparented diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 14afee13..a8de9250 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ @@ -736,7 +736,7 @@ At 95c0217, phase/rung tracing showed that the previously reported 24 Fast price **derived_from** `@jpegxl-rs.work.arch-phase4m-fast-full-handoff/1` -> **Stale** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `23635f68`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#phase-4m-trace-localises-high-rate-undershoot-to-lf-fill-ordering). +> **Stale** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `0fd5b3b0`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#phase-4m-trace-localises-high-rate-undershoot-to-lf-fill-ordering). ### jxl-oxide 0.12.6 narrows LfQuant at the signed-16-bit boundary @@ -776,6 +776,8 @@ On the expanded 56-image / 38-family corpus with production-endpoint oracle labe **supersedes** `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1` · **derived_from** `@jpegxl-rs.work.pqc-one-shot-controller/2` · **verified_by** `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` +> **Stale** — `watches "JPXL/crates/jpxl-encode-policy/src/quality_predictor*.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#the-one-shot-falsification-screen-fails-on-corpus-coverage-not-on-the-design-transform-features-halve-the-error-the-tail-is-two-under-represented-classes). + ### PR 4 on the development split: the score floor holds in 150/150 encodes, photographs beat cjxl -e7 at matched score, synthetic text and line art trail badly, and the quality path costs ~3.5x the rate path's wall time `verified` · `@jpegxl-rs.observation.pqc-pr4-development-split-2026-08-22/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/**"`, `path "JPXL/crates/jpxl-plan-render/**"`, `path "JPXL/tools/codec_compare.py"` @@ -794,7 +796,7 @@ The earlier @jpegxl-rs.evidence.pqc-pr4-holdout-byte-neutral-ss2-2026-08-22/1 in **derived_from** `@jpegxl-rs.evidence.pqc-pr4-holdout-byte-neutral-ss2-2026-08-22/1`, `@jpegxl-rs.evidence.pqc-quality-rate-curve-exact-10of13-2026-08-23/1`, `@jpegxl-rs.observation.ssimulacra2-f32-recursion-ripple-2026-08-22/1` -> **Stale** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `2425c0b2`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pr4s-byte-neutral-rate-curve-conclusion-mixed-ssimulacra2-implementations-and-is-not-a-like-for-like-baseline). +> **Stale** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `0fd5b3b0`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pr4s-byte-neutral-rate-curve-conclusion-mixed-ssimulacra2-implementations-and-is-not-a-like-for-like-baseline). ### Full PR7 holdout closes the OOM tail and rejects Quality promotion @@ -804,6 +806,8 @@ The current low-memory implementation completed the 27 formerly missing Quality **derived_from** `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-rejected-2026-08-22/1`, `@jpegxl-rs.evidence.pqc-pr7-reducer-holdout-partial-2026-08-22/1` · **verified_by** `@jpegxl-rs.evidence.pqc-pr7-holdout-complete-2026-08-24/1`, `@jpegxl-rs.evidence.pqc-pr7-quality-promotion-full-2026-08-24/1` +> **Stale** — `watches "JPXL/crates/jpxl-encode-policy/src/quality.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#full-pr7-holdout-closes-the-oom-tail-and-rejects-quality-promotion). + ### Pre-optimization encode wall-time ladder (release jpxl bench) `verified` · `@jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/2` · scope `path "JPXL/crates/jpxl-cli/**"`, `path "JPXL/crates/jpxl-encode-policy/**"`, `path "JPXL/crates/jpxl-encode/**"` diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 3b907edf..d80755ce 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index 22221a23..0a3d3dbe 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index fa83fa64..fe6e3989 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index e9823dc6..a9c5b4f4 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ @@ -7,7 +7,7 @@ What should not be trusted without re-checking: records the build flagged `stale` or `at_risk`. Neither flag means a record is wrong (D-003); both mean look at it. This view is generated on every successful build, including one that exits 0 with a long queue (D-024). An empty file on an active project is more often a sign the `watches` globs are wrong than a sign the knowledge is perfect. -## Stale (46) +## Stale (48) ### The cover/CfL objective misprices Y-channel error by 2.65x across DCT8x8 frequency; the mispricing is in the ruler, not the lever @@ -93,6 +93,18 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/rate.rs"` was matched by `08f6c4a0`, which touched `JPXL/crates/jpxl-encode-policy/src/rate.rs`. +### Phase 4M trace localises high-rate undershoot to LF-fill ordering + +`verified` · `@jpegxl-rs.observation.phase4m-lf-fill-direction-2026-08-11/1` · observation · **stale** · [Phase 4M trace localises high-rate undershoot to LF-fill ordering](CURRENT-STATE.md#phase-4m-trace-localises-high-rate-undershoot-to-lf-fill-ordering) + +**Cause** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `0fd5b3b0`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. + +### PR4's byte-neutral rate-curve conclusion mixed SSIMULACRA2 implementations and is not a like-for-like baseline + +`verified` · `@jpegxl-rs.observation.pqc-pr4-rate-curve-axis-audit-2026-08-23/1` · observation · **stale** · [PR4's byte-neutral rate-curve conclusion mixed SSIMULACRA2 implementations and is not a like-for-like baseline](CURRENT-STATE.md#pr4s-byte-neutral-rate-curve-conclusion-mixed-ssimulacra2-implementations-and-is-not-a-like-for-like-baseline) + +**Cause** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `0fd5b3b0`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. + ### I.4 context model proved by ANS exhaustion `verified` · `@jpegxl-rs.observation.i4-context-model/1` · observation · **stale** · [I.4 context model proved by ANS exhaustion](CURRENT-STATE.md#i4-context-model-proved-by-ans-exhaustion) @@ -105,12 +117,6 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/rate.rs"` was matched by `23635f68`, which touched `JPXL/crates/jpxl-encode-policy/src/rate.rs`. -### Phase 4M trace localises high-rate undershoot to LF-fill ordering - -`verified` · `@jpegxl-rs.observation.phase4m-lf-fill-direction-2026-08-11/1` · observation · **stale** · [Phase 4M trace localises high-rate undershoot to LF-fill ordering](CURRENT-STATE.md#phase-4m-trace-localises-high-rate-undershoot-to-lf-fill-ordering) - -**Cause** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `23635f68`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. - ### Q3: at matched bytes JPXL leads cjxl -e7 on SSIMULACRA2 in every cell and trails on Butteraugli and PSNR; the deficit sits in low-to-mid activity blocks, worst where an edge meets flat content `verified` · `@jpegxl-rs.observation.q3-butteraugli-deficit-localisation-2026-08-18/3` · observation · **stale** · [Q3: at matched bytes JPXL leads cjxl -e7 on SSIMULACRA2 in every cell and trails on Butteraugli and PSNR; the deficit sits in low-to-mid activity blocks, worst where an edge meets flat content](CURRENT-STATE.md#q3-at-matched-bytes-jpxl-leads-cjxl--e7-on-ssimulacra2-in-every-cell-and-trails-on-butteraugli-and-psnr-the-deficit-sits-in-low-to-mid-activity-blocks-worst-where-an-edge-meets-flat-content) @@ -141,12 +147,6 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/quality.rs"` was matched by `2425c0b2`, which touched `JPXL/crates/jpxl-encode-policy/src/quality.rs`. -### PR4's byte-neutral rate-curve conclusion mixed SSIMULACRA2 implementations and is not a like-for-like baseline - -`verified` · `@jpegxl-rs.observation.pqc-pr4-rate-curve-axis-audit-2026-08-23/1` · observation · **stale** · [PR4's byte-neutral rate-curve conclusion mixed SSIMULACRA2 implementations and is not a like-for-like baseline](CURRENT-STATE.md#pr4s-byte-neutral-rate-curve-conclusion-mixed-ssimulacra2-implementations-and-is-not-a-like-for-like-baseline) - -**Cause** — `watches "JPXL/crates/jpxl-cli/src/main.rs"` was matched by `2425c0b2`, which touched `JPXL/crates/jpxl-cli/src/main.rs`. - ### The reference SSIMULACRA2 implementations' f32 recursive Gaussian leaves a ripple that inflates near-lossless scores' error on flat content, growing with image size; the in-tree metric runs the recursion in f64 `verified` · `@jpegxl-rs.observation.ssimulacra2-f32-recursion-ripple-2026-08-22/1` · observation · **stale** · [The reference SSIMULACRA2 implementations' f32 recursive Gaussian leaves a ripple that inflates near-lossless scores' error on flat content, growing with image size; the in-tree metric runs the recursion in f64](CURRENT-STATE.md#the-reference-ssimulacra2-implementations-f32-recursive-gaussian-leaves-a-ripple-that-inflates-near-lossless-scores-error-on-flat-content-growing-with-image-size-the-in-tree-metric-runs-the-recursion-in-f64) @@ -279,6 +279,18 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/field.rs"` was matched by `b25beda2`, which touched `JPXL/crates/jpxl-encode-policy/src/field.rs`. +### The one-shot falsification screen fails on corpus coverage, not on the design: transform features halve the error, the tail is two under-represented classes + +`verified` · `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2` · observation · **stale** · [The one-shot falsification screen fails on corpus coverage, not on the design: transform features halve the error, the tail is two under-represented classes](CURRENT-STATE.md#the-one-shot-falsification-screen-fails-on-corpus-coverage-not-on-the-design-transform-features-halve-the-error-the-tail-is-two-under-represented-classes) + +**Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/quality_predictor*.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs`. + +### Full PR7 holdout closes the OOM tail and rejects Quality promotion + +`verified` · `@jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1` · observation · **stale** · [Full PR7 holdout closes the OOM tail and rejects Quality promotion](CURRENT-STATE.md#full-pr7-holdout-closes-the-oom-tail-and-rejects-quality-promotion) + +**Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/quality.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality.rs`. + ### Phase Q9 fixes Windows AVX2/fallback cube-root determinism without moving AVX2 production hashes `verified` · `@jpegxl-rs.observation.windows-msvc-avx2-fallback-not-bit-identical-2026-08-18/3` · observation · **stale** · [Phase Q9 fixes Windows AVX2/fallback cube-root determinism without moving AVX2 production hashes](CURRENT-STATE.md#phase-q9-fixes-windows-avx2fallback-cube-root-determinism-without-moving-avx2-production-hashes) diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 508ea4b1..1cd4dbca 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ From 9c6e64a2f146e2805af9124a9bd061d7bebce763 Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 01:23:24 +0800 Subject: [PATCH 08/27] perf(jpxl-plan-render, jpxl-perceptual): parallelize per-probe render on large frames The 12 MP quality-mode wall was dominated by two serial per-probe stages inside render_metric: the varblock reconstruction (a 12 MP frame is a single LF group, so group-level parallelism gave it nothing) and the transfer-curve linearization. Both are independent per-element maps, so both now run banded over the EncodeExecutor with byte-for-byte identical output at any worker count: - varblocks: per-varblock compute (I.5.3/I.6/I.8/I.9 + J.4.3 sigma) moved into map_ordered chunks of 2048; each chunk's samples and sigma writes scatter serially into disjoint frame regions, and the dequant-matrix cache is warmed up front so the closure reads it through a shared borrow. Chunking bounds transient memory to a few MB; the locked 12 MP peak-memory anchor is unchanged (1,899 MB). - linearization: into_linear_rgb_at_depth_with bands the per-sample LUT lookup; the large-frame scoring path in jpxl-perceptual uses it. Verified byte-identical streams before/after on six image/target cells, identical canonical scores, cross-thread determinism (threads 1/4/8, fast-debug and release), and green fmt/clippy/tests on the touched crates. Release wall on the 12 MP anchor at 8 threads: 4.92 s -> 3.91 s (-21 %); matched-rate ratio 3.11x -> 2.46x on the measuring host. The remaining per-probe residual is split between the varblock scatter and the already-parallel metric. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- JPXL/crates/jpxl-perceptual/src/evaluator.rs | 2 +- JPXL/crates/jpxl-plan-render/src/lib.rs | 211 +++++++++++++++---- JPXL/docs/CHANGELOG.md | 6 + 3 files changed, 180 insertions(+), 39 deletions(-) diff --git a/JPXL/crates/jpxl-perceptual/src/evaluator.rs b/JPXL/crates/jpxl-perceptual/src/evaluator.rs index 42a194bb..7bb24f57 100644 --- a/JPXL/crates/jpxl-perceptual/src/evaluator.rs +++ b/JPXL/crates/jpxl-perceptual/src/evaluator.rs @@ -202,7 +202,7 @@ impl PerceptualEvaluator for PlanRenderEvaluator<'_> { // needed for this score. Exact finalists are rebuilt deterministically // by the policy if this rung survives navigation. drop(candidate); - let linear = frame.into_linear_rgb_at_depth(self.bits_per_sample); + let linear = frame.into_linear_rgb_at_depth_with(self.bits_per_sample, self.executor); let result = self .metric .score_owned(&self.reference, width, height, linear, self.executor) diff --git a/JPXL/crates/jpxl-plan-render/src/lib.rs b/JPXL/crates/jpxl-plan-render/src/lib.rs index b20c3116..d2096ea7 100644 --- a/JPXL/crates/jpxl-plan-render/src/lib.rs +++ b/JPXL/crates/jpxl-plan-render/src/lib.rs @@ -267,6 +267,36 @@ impl RenderedFrame { } self.planes } + + /// [`Self::into_linear_rgb_at_depth`] with the per-sample transfer-curve + /// lookup banded over `executor`'s workers. + /// + /// The lookup is an independent per-sample map, so each fixed row band is + /// converted on its own and the output is identical to the serial form at + /// any worker count. This is the large-frame scoring path, where the + /// 36-million-sample round trip is otherwise a serial tail on every probe. + #[must_use] + pub fn into_linear_rgb_at_depth_with( + mut self, + bits: u32, + executor: &EncodeExecutor, + ) -> [Vec; NUM_CHANNELS] { + let max = Self::full_scale(bits); + let lut = Self::linear_lut(bits, max); + let width = usize::try_from(self.width).unwrap_or(usize::MAX).max(1); + let bands = row_bands(&mut self.planes, width); + run_items(Some(executor), bands.len(), &|index| { + let Some((_, mut slices)) = bands.take(index) else { + return; + }; + for plane in slices.iter_mut() { + for value in plane.iter_mut() { + *value = Self::linear_sample(*value, max, &lut); + } + } + }); + self.planes + } } /// Wall time of one render's stages, in milliseconds. @@ -428,23 +458,51 @@ impl PlanRenderer { // I.5.2 + I.6 (LF): dequantize the three planes, then borrow // chroma from luma with the frame-wide LF factors. let lf = lf_planes(ir, &lf_mul, spatial.lf.extra_precision, (k_x_lf, k_b_lf))?; - let lf_at = |c: usize, bx: u32, by: u32| -> f32 { - if bx >= blocks.width || by >= blocks.height { - return 0.0; - } - let idx = usize::try_from(u64::from(by) * u64::from(blocks.width) + u64::from(bx)) - .unwrap_or(usize::MAX); - lf.get(c).and_then(|p| p.get(idx)).copied().unwrap_or(0.0) - }; - for (vb, coeffs) in group.blocks.iter().zip(ir.coefficients.iter()) { + // Warm the dequantization-matrix cache for every transform this + // group uses, so the per-varblock render below reads it through a + // shared immutable borrow and needs no `&mut self`. + for vb in group.blocks.iter() { + self.matrices_for(vb.transform)?; + } + let matrices_cache = &self.cache; + let epf_params = &self.epf_params; + + // I.5.3, I.6, I.8, I.9 and J.4.3 for one varblock, producing its + // placed samples and sigma writes without touching shared frame + // state. A varblock is a pure function of its own inputs, so this + // is byte-for-byte the serial render regardless of worker count. + let render_one = |i: usize| -> Result { + let vb = group + .blocks + .get(i) + .ok_or(RenderError::Unsupported("a varblock index past the group"))?; + let coeffs = ir + .coefficients + .get(i) + .ok_or(RenderError::Unsupported("a varblock with no coefficients"))?; + let lf_at = |c: usize, bx: u32, by: u32| -> f32 { + if bx >= blocks.width || by >= blocks.height { + return 0.0; + } + let idx = + usize::try_from(u64::from(by) * u64::from(blocks.width) + u64::from(bx)) + .unwrap_or(usize::MAX); + lf.get(c).and_then(|p| p.get(idx)).copied().unwrap_or(0.0) + }; + let transform = vb.transform; let (bx, by) = (vb.origin.bx(), vb.origin.by()); let hf_mul = vb.hf_mul.get(); let mul = hf_multiplier(global_scale, hf_mul); // I.5.3: dequantize all three channels. - let matrices = self.matrices_for(transform)?; + let matrices = matrices_cache + .get(transform.dequant_matrix_index()) + .and_then(Option::as_ref) + .ok_or(RenderError::Unsupported( + "a dequantization matrix that was not warmed", + ))?; let (rows, cols) = (transform.coeff_rows(), transform.coeff_cols()); let mut coeff: [CoeffMatrix; NUM_CHANNELS] = core::array::from_fn(|_| CoeffMatrix::zeros(rows, cols)); @@ -503,37 +561,21 @@ impl PlanRenderer { matrix.write_llf(&llf_from_lf(transform, &lf_rect)); } - // I.9: samples, placed at the varblock's frame position. + // I.9: samples, at the varblock's frame position (placed later). let x0 = rect.x0 + bx * 8; let y0 = rect.y0 + by * 8; - for (c, matrix) in coeff.iter().enumerate() { - let block = transform.samples_from_coefficients(matrix); - let Some(plane) = planes.get_mut(c) else { - continue; - }; - for row in 0..block.rows() { - let fy = y0.saturating_add(narrow(row)); - if fy >= height { - continue; - } - for col in 0..block.cols() { - let fx = x0.saturating_add(narrow(col)); - if fx >= width { - continue; - } - let idx = - usize::try_from(u64::from(fy) * u64::from(width) + u64::from(fx)) - .unwrap_or(usize::MAX); - if let Some(slot) = plane.get_mut(idx) { - *slot = block.at(col, row); - } - } - } - } + let samples: [SampleBlock; NUM_CHANNELS] = core::array::from_fn(|c| { + coeff.get(c).map_or_else( + || SampleBlock::zeros(transform.sample_rows(), transform.sample_cols()), + |matrix| transform.samples_from_coefficients(matrix), + ) + }); // J.4.3: sigma per 8x8 block of the varblock, from `mul` and - // the block's own `Sharpness`. + // the block's own `Sharpness`, as (frame-block index, value). let sharpness = group.sharpness.values(); + let mut sigma_writes: Vec<(usize, f32)> = + Vec::with_capacity(block_rows * block_cols); for dy in 0..block_rows { for dx in 0..block_cols { let (sbx, sby) = @@ -551,11 +593,63 @@ impl PlanRenderer { if fbx >= blocks_x || fby >= blocks_y { continue; } - if let Some(slot) = sigma.get_mut(fby * blocks_x + fbx) { - *slot = vardct_sigma(mul, s, &self.epf_params); + sigma_writes.push((fby * blocks_x + fbx, vardct_sigma(mul, s, epf_params))); + } + } + + Ok(RenderedVarblock { + x0, + y0, + samples, + sigma: sigma_writes, + }) + }; + + // Render varblocks in bounded, order-preserving chunks: each chunk's + // per-varblock compute (dequant, CfL, LLF, inverse transform) runs + // across the executor, then its results are scattered into the frame + // serially. Every varblock owns a disjoint frame region, so the + // placed pixels are identical to a serial render; chunking keeps the + // transient per-chunk buffers small rather than holding one buffer + // per varblock at once. + let n = group.blocks.len().min(ir.coefficients.len()); + let mut start = 0usize; + while start < n { + let end = (start + VARBLOCK_CHUNK).min(n); + let rendered = + render_varblock_chunk(executor, end - start, &|k| render_one(start + k))?; + for rv in &rendered { + for (c, block) in rv.samples.iter().enumerate() { + let Some(plane) = planes.get_mut(c) else { + continue; + }; + for row in 0..block.rows() { + let fy = rv.y0.saturating_add(narrow(row)); + if fy >= height { + continue; + } + for col in 0..block.cols() { + let fx = rv.x0.saturating_add(narrow(col)); + if fx >= width { + continue; + } + let idx = usize::try_from( + u64::from(fy) * u64::from(width) + u64::from(fx), + ) + .unwrap_or(usize::MAX); + if let Some(slot) = plane.get_mut(idx) { + *slot = block.at(col, row); + } + } + } + } + for &(idx, value) in &rv.sigma { + if let Some(slot) = sigma.get_mut(idx) { + *slot = value; } } } + start = end; } } @@ -731,6 +825,47 @@ fn row_bands(planes: &mut [Vec; NUM_CHANNELS], width: usize) -> RowBands<'_ RowBands { items } } +/// One varblock's reconstruction result, produced off to the side so the +/// per-varblock compute can run across the executor and be scattered into the +/// frame afterwards. +struct RenderedVarblock { + /// Frame x of the varblock's top-left sample. + x0: u32, + /// Frame y of the varblock's top-left sample. + y0: u32, + /// The placed samples, one block per channel. + samples: [SampleBlock; NUM_CHANNELS], + /// J.4.3 sigma writes as `(frame-block index, value)`. + sigma: Vec<(usize, f32)>, +} + +/// Varblocks rendered per parallel chunk before their results are scattered. +/// +/// A chunk holds at most this many [`RenderedVarblock`] results at once, so the +/// transient sample storage stays a few megabytes rather than a whole frame's +/// worth, while still giving the executor enough work per chunk to keep every +/// worker busy on a large frame. +const VARBLOCK_CHUNK: usize = 2048; + +/// Renders `n` varblocks through `f`, across `executor` when present (results +/// in index order, so worker count cannot change them), serially otherwise. +fn render_varblock_chunk( + executor: Option<&EncodeExecutor>, + n: usize, + f: &(dyn Fn(usize) -> Result + Sync), +) -> Result> { + match executor { + Some(executor) => executor.map_ordered(n, f), + None => { + let mut out = Vec::with_capacity(n); + for i in 0..n { + out.push(f(i)?); + } + Ok(out) + } + } +} + /// Runs `items` independent closures on `executor` (serially without one). fn run_items(executor: Option<&EncodeExecutor>, items: usize, f: &(dyn Fn(usize) + Sync)) { match executor { diff --git a/JPXL/docs/CHANGELOG.md b/JPXL/docs/CHANGELOG.md index a6f9bc78..5e5ef6f9 100644 --- a/JPXL/docs/CHANGELOG.md +++ b/JPXL/docs/CHANGELOG.md @@ -13,6 +13,12 @@ Format: [Keep a Changelog](https://keepachangelog.com/), semantic versioning. ## Unreleased +- 2026-08-25 — Large-frame `--quality` probes are faster: the varblock + reconstruction and the transfer-curve linearization — previously serial + on every probe — now run banded across the worker pool with byte-identical + output at any thread count. On the locked 12 MP anchor at 8 threads the + quality-mode wall drops 21 % (release), with peak memory and every emitted + stream unchanged. - 2026-08-25 — The one-shot crossing predictor is now the default `--quality` controller seed: a generated transparent model (`qpv2-st-1`, source + DCT8-summary features, trained on 130 image families) picks the first From 101aa7cc3c52ddda8acacb2df66eca81aa6a2faa Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 01:27:04 +0800 Subject: [PATCH 09/27] chore(.akr): record the large-frame render parallelization measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New observation pqc-large-frame-render-parallel-2026-08-25 (phase attribution, exact wins, -21% release wall, ratio 3.11x -> 2.46x on the measuring host, unchanged peak memory) and pqc-usable-efforts-cost rev 6: the wall check remains the sole open item, with the ranked residual directions — exact scatter overlap, blur fusion (risky), and the score-changing reduced-pyramid navigation that would need the Contract B promotion screen. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- .akr/akr.lock | 28 +++++++++--- .akr/records/jpegxl-rs/observations.akr | 20 ++++++++ .akr/records/jpegxl-rs/work.akr | 61 ++++++++++++++++++++++++- docs/generated/ACTIVE-WORK.md | 4 +- docs/generated/CURRENT-STATE.md | 17 +++++-- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 4 +- 10 files changed, 123 insertions(+), 19 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 9db5e2c6..c4309548 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:f0707696eea1a0e184fe9f14440cb56b9712b5992210ce100ccdbd259e27cb3e" + source_graph "sha256:377fb8449ddf3ac504a763abea14fcf7f25536d7dc0bdb6602bfc741e7e6ecf2" } source ".akr/project.akr" { @@ -39,8 +39,8 @@ source ".akr/records/jpegxl-rs/milestones.akr" { } source ".akr/records/jpegxl-rs/observations.akr" { - hash "sha256:9cc252028877e6d8d60957f5b5d8b05e5ca6f176bbd864023fabfecdb251cdb0" - records 98 + hash "sha256:e1d09082bfc799c75177ae7c27aa11fb799aa9e84833d5c85b71443549d5330d" + records 99 } source ".akr/records/jpegxl-rs/papercuts.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:de6c9253fad79f03244e3cec35910b7ab48202eb858abb6cc8385770a32f23c9" - records 182 + hash "sha256:d600b7004dc0e391df7ff439352cb971f3092bd7a7da12e74a1904d70179fe70" + records 183 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -3709,6 +3709,12 @@ resolution @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 { hash "sha256:52c2c39f382fd60234500876d6de823679e2b836bff13d4a5b2ec3a1c67bec12" } +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/6 { + slot supported_by + to @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 + hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" +} + resolution @jpegxl-rs.work.quality-q0-harness-attribution/1 { slot part_of to @jpegxl-rs.track.encoder-optimization/1 @@ -6967,6 +6973,11 @@ seal @jpegxl-rs.observation.phase5-quality-policy-screen-2026-08-11/11 { hash "sha256:c311117e5b24ac418aadfdabdf0ad660b0adc23ceb0b008014d0fb3583e681dc" } +seal @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 { + state verified + hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" +} + seal @jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1 { state verified hash "sha256:cad2fc5acdba36cfe38becc4b019e94721e78eaafbab1bb3c2cfe4ac46af0295" @@ -8213,8 +8224,13 @@ seal @jpegxl-rs.work.pqc-usable-efforts-cost/4 { } seal @jpegxl-rs.work.pqc-usable-efforts-cost/5 { + state superseded + hash "sha256:6b5fa483e0774275d95fb55701356b4b712a1e8e3bbc8e3ba65387ab3ed15799" +} + +seal @jpegxl-rs.work.pqc-usable-efforts-cost/6 { state active - hash "sha256:7f9bd01e4a9b9cfed55fd58d5e5b6c561a87856ad1ec256ed96c7aad6a08dc86" + hash "sha256:084f5df3c370d5557c91622769f0392688b3ff2abd694f293a5e1243655ab386" } seal @jpegxl-rs.work.quality-q0-harness-attribution/1 { diff --git a/.akr/records/jpegxl-rs/observations.akr b/.akr/records/jpegxl-rs/observations.akr index 8c2dca38..830e40a0 100644 --- a/.akr/records/jpegxl-rs/observations.akr +++ b/.akr/records/jpegxl-rs/observations.akr @@ -1759,6 +1759,26 @@ record jpegxl-rs.observation.phase5-quality-policy-screen-2026-08-11/11 : observ ] } +record jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 : observation { + title "Parallelizing the serial varblock render and linearization cut 12 MP quality wall 21%; ratio 3.11x -> 2.46x on the measuring host" + state verified + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/evaluator.rs", + path "JPXL/crates/jpxl-plan-render/**" + ] + statement """ + Per-probe phase attribution on the locked 12 MP anchor (fast-debug, comparing 1-thread vs 8-thread walls to identify serial stages) showed the reference side of SSIMULACRA2 already precomputed once per search (PrecomputedReference with ReferenceRetention::Moments at <=24 MP); the serial per-probe stages were varblock reconstruction (500-730 ms; a 12 MP frame is one LF group, so group-level parallelism contributed nothing) and transfer-curve linearization (105-180 ms). After banding both over the EncodeExecutor (map_ordered varblock chunks of 2048 with serial disjoint scatter; banded LUT lookup), varblocks measure ~235 ms and linearize ~18 ms; per-probe render+metric fell ~1.3 s to ~0.77 s. Release wall on the anchor at 8 threads: 4.923 s -> 3.906 s (-21%), matched-rate ratio 3.11x -> 2.46x on this host (the recorded 6.31x/8.02x baselines were another host/state; the relative move is the reliable signal). Byte-identical streams on six image/target cells, identical canonical scores, cross-thread determinism at threads 1/4/8 in both profiles, peak working set unchanged at 1,899 MB, all touched-crate gates green. Remaining per-probe residual is co-dominated by the still-serial varblock sample scatter (~part of 235 ms) and the already-parallel metric (~380 ms); the largest further headroom is score-changing (navigate on a reduced pyramid or downscaled reconstruction, full-resolution only for final verification), which requires a Contract B promotion A/B. + """ + observed_at git:9c6e64a2f146e2805af9124a9bd061d7bebce763 + method command + watches [ + "JPXL/crates/jpxl-plan-render/src/lib.rs", + "JPXL/crates/jpxl-perceptual/src/evaluator.rs" + ] + author "GitHub Uploader" +} + record jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1 : observation { title "Perceptual encodes of 50 MP sources reach ~12 GB RSS; two concurrent measurement runs OOM-killed a 31 GB host twice" state verified diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index e423722e..1cffd02b 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -8062,7 +8062,7 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/4 : work { record jpegxl-rs.work.pqc-usable-efforts-cost/5 : work { title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" - state active + state superseded scope [ path "JPXL/crates/jpxl-encode-policy/src/quality.rs", path "JPXL/crates/jpxl-perceptual/src/**", @@ -8118,6 +8118,65 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/5 : work { ] } +record jpegxl-rs.work.pqc-usable-efforts-cost/6 : work { + title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl-plan-render/src/**", + path "JPXL/crates/jpxl/src/lib.rs" + ] + intent """ + Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. + """ + note """ + Memory, production identity, and workspace gates pass; wall remains the sole open check but moved twice in 2026-08-24/25. First, the one-shot controller promotion (see @jpegxl-rs.work.pqc-one-shot-controller) cut probe COUNT: 12 MP anchor matched-rate ratio 8.02x -> 6.31x on the sweep host. Second, per-probe cost fell at commit 9c6e64a: the serial varblock reconstruction and transfer-curve linearization now band over the executor with byte-identical output (see @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25) — release quality wall on the anchor -21%, ratio 3.11x -> 2.46x on the measuring host, peak memory unchanged. Still short of the <=2x target. Ranked remaining directions: (1) exact — parallel or overlapped scatter of varblock samples (~50-100 ms/probe); (2) exact but risky — fusing the metric's sequential blurs in low_memory mode (score-identity risk, not attempted); (3) score-changing, largest headroom — navigation on a reduced pyramid or downscaled reconstruction with full-resolution final verification only; both render and metric scale with pixels, but it changes navigation inputs and therefore selected streams, so it requires the standing Contract B promotion screen (interleaved A/B, floor violations, byte geomean, achieved-score inversions) before any default flip. + """ + acceptance { + check memory-12mp { + statement """ + Balanced q85 on the locked 12 MP anchor peaks at no more than 2.0 GB RSS, with the measurement serialized and process-capped. + """ + method observation + verified_by [ @jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1 ] + } + check production-identity { + statement """ + Pure scorer/renderer/lifetime changes leave Fast and Balanced codestreams byte-identical across the locked identity cells, threads 1/4, and AVX2 on/off; any search-policy change instead passes the standing Contract B gate. + """ + method command + command "python3 .agent/scratch/pqc-cost-20260823/identity_current.py" + verified_by [ @jpegxl-rs.evidence.pqc-low-memory-production-identity-2026-08-23/1 ] + } + check wall-anchors { + statement """ + At matched achieved SSIMULACRA2, Balanced end-to-end wall is no more than 2.0x the rate path on both the 4.3 MP and 12 MP anchors; phase timings identify any remaining gap. + """ + method observation + } + check workspace-gates { + statement """ + The workspace release tests, clippy, and formatting gates pass. + """ + method command + command "cd JPXL && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + verified_by [ @jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1 ] + } + } + depends_on [ @jpegxl-rs.work.pqc-pr4-quality-navigator/1 ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-usable-efforts-cost/5 ] + supported_by [ + @jpegxl-rs.evidence.pqc-low-memory-wall-anchors-2026-08-23/1, + @jpegxl-rs.evidence.pqc-pr4-holdout-wall-reported-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-memory-12mp-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-probe-speed-2026-08-22/1, + @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25 + ] +} + record jpegxl-rs.work.publish-readme-benchmark-mit/1 : work { title "Publish-ready README, reproducible libjxl comparison, and MIT-only licensing" state proposed diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 2512fd21..9074ee2f 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ @@ -49,7 +49,7 @@ Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the ope ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/5` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/6` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index a8de9250..496c637e 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ @@ -758,6 +758,12 @@ Phase 5G promoted AQ Off, quant_lf 8, and no LF-fill after improving Butteraugli > **Stale** — `watches "JPXL/crates/jpxl-encode-policy/src/field.rs"` was matched by `b25beda2`, which touched `JPXL/crates/jpxl-encode-policy/src/field.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#active-epf-remains-production-special-transforms-need-a-better-selector). +### Parallelizing the serial varblock render and linearization cut 12 MP quality wall 21%; ratio 3.11x -> 2.46x on the measuring host + +`verified` · `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/src/evaluator.rs"`, `path "JPXL/crates/jpxl-plan-render/**"` + +Per-probe phase attribution on the locked 12 MP anchor (fast-debug, comparing 1-thread vs 8-thread walls to identify serial stages) showed the reference side of SSIMULACRA2 already precomputed once per search (PrecomputedReference with ReferenceRetention::Moments at <=24 MP); the serial per-probe stages were varblock reconstruction (500-730 ms; a 12 MP frame is one LF group, so group-level parallelism contributed nothing) and transfer-curve linearization (105-180 ms). After banding both over the EncodeExecutor (map_ordered varblock chunks of 2048 with serial disjoint scatter; banded LUT lookup), varblocks measure ~235 ms and linearize ~18 ms; per-probe render+metric fell ~1.3 s to ~0.77 s. Release wall on the anchor at 8 threads: 4.923 s -> 3.906 s (-21%), matched-rate ratio 3.11x -> 2.46x on this host (the recorded 6.31x/8.02x baselines were another host/state; the relative move is the reliable signal). Byte-identical streams on six image/target cells, identical canonical scores, cross-thread determinism at threads 1/4/8 in both profiles, peak working set unchanged at 1,899 MB, all touched-crate gates green. Remaining per-probe residual is co-dominated by the still-serial varblock sample scatter (~part of 235 ms) and the already-parallel metric (~380 ms); the largest further headroom is score-changing (navigate on a reduced pyramid or downscaled reconstruction, full-resolution only for final verification), which requires a Contract B promotion A/B. + ### Perceptual encodes of 50 MP sources reach ~12 GB RSS; two concurrent measurement runs OOM-killed a 31 GB host twice `verified` · `@jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/**"`, `path "JPXL/tools/codec_compare.py"` @@ -4879,7 +4885,8 @@ Kept /usr/bin/time evidence: .agent/scratch/pqc-cost-20260823/large-q85-v5.time. - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/3` — check `memory-12mp` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `memory-12mp` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `memory-12mp` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `memory-12mp` +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `memory-12mp` ### Low-memory quality path passes the full locked production-identity matrix @@ -4891,7 +4898,8 @@ Kept raw rows and report: .agent/scratch/pqc-cost-20260823/README.md. The frozen - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/3` — check `production-identity` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `production-identity` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `production-identity` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `production-identity` +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `production-identity` ### Controlled matched-score wall anchors after low-memory PQC changes @@ -4909,7 +4917,8 @@ The exact chained final-state gate passed after the quality-rate harness and low - `completed` `@jpegxl-rs.work.pqc-quality-rate-curve-closure/5` — check `workspace-gates` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `workspace-gates` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `workspace-gates` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `workspace-gates` +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `workspace-gates` ### Low-memory quality path makes 50 MP usable and preserves all xlarge quality streams diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index d80755ce..22118553 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index 0a3d3dbe..ecd2220d 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index fe6e3989..d5c0e3b2 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index a9c5b4f4..650b1182 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 1cd4dbca..ff6f8af5 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ @@ -360,7 +360,7 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality **Work items** - `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/2` -- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/5` +- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/6` ### VarDCT encoder (M1-M8) From 1fc3ca7df95e4dc7349d63f90f3aa7c7b24c60ed Mon Sep 17 00:00:00 2001 From: GitHub Uploader Date: Tue, 25 Aug 2026 01:30:36 +0800 Subject: [PATCH 10/27] chore(.akr): scope the recompression record to existing paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jpxl-jpeg crate glob matched no tracked path (V-102) — the crate does not exist yet; the plan document carries the intended layout. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Tct9rZempR3FTEksR9t3VX --- .akr/akr.lock | 4 ++-- .akr/records/jpegxl-rs/work.akr | 1 - docs/generated/ACTIVE-WORK.md | 10 +++++----- docs/generated/CURRENT-STATE.md | 2 +- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 2 +- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index c4309548..a0da948e 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:377fb8449ddf3ac504a763abea14fcf7f25536d7dc0bdb6602bfc741e7e6ecf2" + source_graph "sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd" } source ".akr/project.akr" { @@ -69,7 +69,7 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:d600b7004dc0e391df7ff439352cb971f3092bd7a7da12e74a1904d70179fe70" + hash "sha256:3127d3f9ad02fca3ff0a3eb6a82a805c839de5489cfc1fb7706d29eb4b8ad4ae" records 183 } diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 1cffd02b..15fd2b02 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -6790,7 +6790,6 @@ record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { path "JPXL/crates/jpxl-cli/**", path "JPXL/crates/jpxl-decode/**", path "JPXL/crates/jpxl-encode/**", - path "JPXL/crates/jpxl-jpeg/**", path "JPXL/crates/jpxl/**", path "JPXL/docs/jpeg-recompression-plan.md" ] diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 9074ee2f..e535eb3c 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ @@ -53,14 +53,14 @@ Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the ope Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. -**Acceptance** — 3 of 4 satisfied +**Acceptance** — 0 of 4 satisfied | Check | Method | Verdict | | --- | --- | --- | -| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1` | -| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-production-identity-2026-08-23/1` | +| `memory-12mp` | observation | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1` predates the last change | +| `production-identity` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-production-identity-2026-08-23/1` predates the last change | | `wall-anchors` | observation | not satisfied — no evidence | -| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` | +| `workspace-gates` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` predates the last change | ## [VarDCT encoder (M1-M8)](ROADMAP.md#vardct-encoder-m1-m8) `@jpegxl-rs.track.vardct-encoder/1` diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 496c637e..f4e1060c 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 22118553..e2c72672 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index ecd2220d..003f37fe 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index d5c0e3b2..8d415c72 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 650b1182..d162b00a 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index ff6f8af5..6600d212 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ From b0c128c785d7524c6002eb34ca7ebd990b24bf1e Mon Sep 17 00:00:00 2001 From: dk Date: Tue, 25 Aug 2026 15:35:27 +0800 Subject: [PATCH 11/27] refactor(jpxl-plan-render): band-parallel varblock sample scatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-chunk varblock results were scattered into the frame serially after the parallel per-varblock compute. Each chunk's samples are now bucketed by the 64-row bands its varblocks intersect (at most two per varblock) and written by the executor's band workers; each sample belongs to exactly one varblock and one band, so the placed pixels are byte-for-byte the serial render at any worker count. Sigma writes are a few hundredths of the sample volume and stay serial. Measured honestly: wall-neutral at the 12 MP anchor (render_metric 2043/2168 ms before vs 2107/2057 ms after at 8 threads; 4 MP varblock stage 45 ms at 4 threads in both trees) — the ~50-100 ms/probe scatter estimate in the work note was high; the serial residual was ~30-45 ms and inside run noise. Kept because it removes the last serial per-sample stage, which grows with frame area (~150 ms/probe at 50 MP), at no measured cost. Byte-identity verified against the pre-change binary on six image/target cells and across threads 1/4/8 and AVX2 on/off; the full 52-cell locked production-identity matrix passes (52/52 identical). AKR-Change: chg-fff37cc35072bbe3 AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd AKR-Tree: d43a52088e7cca5de9f78b8834586e88d1862eff Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/crates/jpxl-plan-render/src/lib.rs | 79 ++++++++++++++++++------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/JPXL/crates/jpxl-plan-render/src/lib.rs b/JPXL/crates/jpxl-plan-render/src/lib.rs index d2096ea7..f8a583c4 100644 --- a/JPXL/crates/jpxl-plan-render/src/lib.rs +++ b/JPXL/crates/jpxl-plan-render/src/lib.rs @@ -607,42 +607,79 @@ impl PlanRenderer { // Render varblocks in bounded, order-preserving chunks: each chunk's // per-varblock compute (dequant, CfL, LLF, inverse transform) runs - // across the executor, then its results are scattered into the frame - // serially. Every varblock owns a disjoint frame region, so the - // placed pixels are identical to a serial render; chunking keeps the - // transient per-chunk buffers small rather than holding one buffer - // per varblock at once. + // across the executor, then its samples are scattered band-parallel: + // every worker owns a disjoint [`BAND_ROWS`]-row band and writes + // only the varblock rows that fall inside it. Each sample belongs + // to exactly one varblock and exactly one band, so the placed + // pixels are identical to a serial render at any worker count; + // chunking keeps the transient per-chunk buffers small rather than + // holding one buffer per varblock at once. Sigma writes are a few + // hundredths of the sample volume and stay serial. let n = group.blocks.len().min(ir.coefficients.len()); let mut start = 0usize; while start < n { let end = (start + VARBLOCK_CHUNK).min(n); let rendered = render_varblock_chunk(executor, end - start, &|k| render_one(start + k))?; - for rv in &rendered { - for (c, block) in rv.samples.iter().enumerate() { - let Some(plane) = planes.get_mut(c) else { + // Bucket each varblock into the (at most two, since a varblock + // is at most 32 rows tall) bands its rows intersect, preserving + // chunk order within a bucket. + let bands = row_bands(&mut planes, dims.width); + let mut buckets: Vec> = vec![Vec::new(); bands.len()]; + for (k, rv) in rendered.iter().enumerate() { + let rows = rv.samples.iter().map(|b| b.rows()).max().unwrap_or(0); + if rows == 0 { + continue; + } + let top = usize::try_from(rv.y0).unwrap_or(usize::MAX); + let bottom = top.saturating_add(rows - 1); + let last_band = buckets.len().saturating_sub(1); + for band in + (top / BAND_ROWS).min(last_band)..=(bottom / BAND_ROWS).min(last_band) + { + if let Some(bucket) = buckets.get_mut(band) { + bucket.push(k); + } + } + } + run_items(executor, bands.len(), &|index| { + let Some((row0, mut slices)) = bands.take(index) else { + return; + }; + let Some(bucket) = buckets.get(index) else { + return; + }; + let band_rows = slices.first().map_or(0, |s| s.len()) / dims.width.max(1); + for &k in bucket { + let Some(rv) = rendered.get(k) else { continue; }; - for row in 0..block.rows() { - let fy = rv.y0.saturating_add(narrow(row)); - if fy >= height { + let x0 = usize::try_from(rv.x0).unwrap_or(usize::MAX); + let y0 = usize::try_from(rv.y0).unwrap_or(usize::MAX); + for (c, block) in rv.samples.iter().enumerate() { + let Some(plane) = slices.get_mut(c) else { continue; - } - for col in 0..block.cols() { - let fx = rv.x0.saturating_add(narrow(col)); - if fx >= width { + }; + for row in 0..block.rows() { + let fy = y0.saturating_add(row); + if fy < row0 || fy >= row0.saturating_add(band_rows) { continue; } - let idx = usize::try_from( - u64::from(fy) * u64::from(width) + u64::from(fx), - ) - .unwrap_or(usize::MAX); - if let Some(slot) = plane.get_mut(idx) { - *slot = block.at(col, row); + let base = (fy - row0).saturating_mul(dims.width); + for col in 0..block.cols() { + let fx = x0.saturating_add(col); + if fx >= dims.width { + continue; + } + if let Some(slot) = plane.get_mut(base.saturating_add(fx)) { + *slot = block.at(col, row); + } } } } } + }); + for rv in &rendered { for &(idx, value) in &rv.sigma { if let Some(slot) = sigma.get_mut(idx) { *slot = value; From 6e13305b647024c4fe4a1eabcef36560cd586a26 Mon Sep 17 00:00:00 2001 From: dk Date: Tue, 25 Aug 2026 15:37:39 +0800 Subject: [PATCH 12/27] tools(quality-corpus): 26 saturated and 25 gradient source families MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the one-shot memo's section-10 generation gap for the two critical classes where worst-cell byte regressions concentrated: saturated (4 -> 26 families) and gradient/banding-stress (8 -> 25). Every new family is a distinct deterministic (seeded) content process — hue wheels, angled saturated bands, near-clipping chroma ramps, complementary checkerboards, saturated noise, sub-kilobyte-encodable solid tiles, concentric rings, out-of-gamut clip plateaus, Voronoi; shallow luma/chroma ramps, dithered ramps, radial sky glows, sunset stops, bilinear fields, conic sweeps, diamond gradients — not one recipe with 25 seeds. Corpus grows 56 -> 95 fixtures (calibration 43, development 32, holdout 20); the split audit shows no family in two splits, regeneration is sha256-idempotent, the manifest validates, and all 13 previously locked holdout fixtures are byte-unchanged, so existing evidence stays comparable. 16 generator unit tests pass. Label sweeps over the new families are deliberately not run here. AKR-Change: chg-c88603675047fe63 AKR-Work: jpegxl-rs.work.pqc-one-shot-controller AKR-Graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd AKR-Tree: 92aa67ca024e8aa20a6d9f6fe87986a65bf49c72 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/tools/make-quality-guard-fixtures.py | 467 ++++++++++++++++++ .../tests/test_make_quality_guard_fixtures.py | 25 +- 2 files changed, 487 insertions(+), 5 deletions(-) diff --git a/JPXL/tools/make-quality-guard-fixtures.py b/JPXL/tools/make-quality-guard-fixtures.py index 6a88f113..e7cea413 100644 --- a/JPXL/tools/make-quality-guard-fixtures.py +++ b/JPXL/tools/make-quality-guard-fixtures.py @@ -175,6 +175,29 @@ def to_u8(x: np.ndarray) -> np.ndarray: return np.clip(np.rint(x), 0, 255).astype(np.uint8) +def hsv_to_rgb(h: np.ndarray, s: np.ndarray, v: np.ndarray) -> np.ndarray: + """Vectorised HSV->RGB (all inputs/outputs in [0, 1]); deterministic. + + Broadcasts scalar ``s``/``v`` against an array ``h``; returns an ``...x3`` + float array. Hand-rolled (no colorsys loop, no third-party colour dep) so + the raster stays bit-identical across environments. + """ + h = np.mod(np.asarray(h, dtype=np.float64), 1.0) + s = np.asarray(s, dtype=np.float64) * np.ones_like(h) + v = np.asarray(v, dtype=np.float64) * np.ones_like(h) + i = np.floor(h * 6.0).astype(np.int64) + f = h * 6.0 - i + p = v * (1.0 - s) + q = v * (1.0 - f * s) + t = v * (1.0 - (1.0 - f) * s) + i = np.mod(i, 6) + cond = [i == k for k in range(6)] + r = np.select(cond, [v, q, p, p, t, v]) + g = np.select(cond, [t, v, v, q, p, p]) + b = np.select(cond, [p, p, t, v, v, q]) + return np.stack([r, g, b], axis=-1) + + def load_rgb(rel_path: str) -> np.ndarray: with Image.open(REPO_ROOT / rel_path) as im: return np.asarray(im.convert("RGB"), dtype=np.uint8).copy() @@ -424,6 +447,289 @@ def build_saturated(width: int, height: int, seed: int, soft: bool) -> np.ndarra return arr +# --------------------------------------------------------------------------- # +# Saturated content builders (banding/clipping-stress coverage; memo §10). Each +# function is a distinct content *process* — not one generator re-seeded — so a +# fresh fixture id is a genuinely independent source family. +# --------------------------------------------------------------------------- # + + +def build_sat_hue_wheel(width: int, height: int, seed: int, sectors: int) -> np.ndarray: + """Fully-saturated angular pie: hue quantised into ``sectors`` hard slices.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + cx, cy = (width - 1) / 2.0, (height - 1) / 2.0 + ang = (np.arctan2(yy - cy, xx - cx) / (2 * np.pi)) % 1.0 + hue = np.floor(ang * sectors) / sectors + rgb = hsv_to_rgb(hue, np.ones_like(hue), np.ones_like(hue)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_sat_hue_bands(width: int, height: int, seed: int, angle_deg: float, bands: int) -> np.ndarray: + """Angled stripes cycling through fully-saturated hues (sharp band edges).""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + hue = np.floor(t * bands) / bands + rgb = hsv_to_rgb(hue, np.ones_like(hue), np.ones_like(hue)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_sat_chroma_ramp(width: int, height: int, seed: int, hue: float, axis: str) -> np.ndarray: + """Neutral-to-fully-saturated chroma ramp along one axis (near-clipping end).""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + if axis == "h": + t = xx / max(1, width - 1) + elif axis == "v": + t = yy / max(1, height - 1) + else: # diagonal + t = (xx + yy) / max(1, (width - 1) + (height - 1)) + rgb = hsv_to_rgb(np.full_like(t, hue), t, np.ones_like(t)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_sat_texture_edges(width: int, height: int, seed: int, cell: int) -> np.ndarray: + """High-frequency saturated checkerboard of complementary hues + hard blocks.""" + rng = np.random.default_rng(seed) + yy, xx = np.mgrid[0:height, 0:width] + checker = ((xx // cell) + (yy // cell)) % 2 + h0 = float(rng.random()) + h1 = (h0 + 0.5) % 1.0 + ones = np.ones((height, width)) + a = hsv_to_rgb(np.full((height, width), h0), ones, ones) + b = hsv_to_rgb(np.full((height, width), h1), ones, ones) + rgb = np.where(checker[..., None] == 0, a, b) + for _ in range(6): + x0 = int(rng.integers(0, max(1, width - cell))) + y0 = int(rng.integers(0, max(1, height - cell))) + w = int(rng.integers(cell, cell * 3)) + hh = int(rng.integers(cell, cell * 3)) + col = hsv_to_rgb(np.array(float(rng.random())), np.array(1.0), np.array(1.0)) + rgb[y0 : y0 + hh, x0 : x0 + w] = col + return to_u8(rgb * 255.0) + + +def build_sat_noise(width: int, height: int, seed: int) -> np.ndarray: + """Per-pixel random fully-saturated hue (saturated high-entropy texture).""" + rng = np.random.default_rng(seed) + hue = rng.random((height, width)) + ones = np.ones((height, width)) + rgb = hsv_to_rgb(hue, ones, ones) + return to_u8(rgb * 255.0) + + +def build_sat_solid_tile(width: int, height: int, color: tuple[int, int, int]) -> np.ndarray: + """A single saturated colour — trivially compresses to a sub-kilobyte stream.""" + arr = np.empty((height, width, 3), dtype=np.uint8) + arr[:, :] = color + return arr + + +def build_sat_block_tile(width: int, height: int, colors: list[tuple[int, int, int]]) -> np.ndarray: + """A few saturated colour blocks (still near-trivially / sub-kilobyte codeable).""" + arr = np.zeros((height, width, 3), dtype=np.uint8) + n = len(colors) + cols = 2 + rows = (n + 1) // 2 + cw, ch = width // cols, height // rows + for idx, color in enumerate(colors): + r, c = divmod(idx, cols) + y1 = (r + 1) * ch if r < rows - 1 else height + x1 = (c + 1) * cw if c < cols - 1 else width + arr[r * ch : y1, c * cw : x1] = color + return arr + + +def build_sat_rings(width: int, height: int, seed: int, ring_count: int) -> np.ndarray: + """Concentric fully-saturated rings, hue stepped by radius (hard ring edges).""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + cx, cy = (width - 1) / 2.0, (height - 1) / 2.0 + d = np.sqrt((xx - cx) ** 2 + (yy - cy) ** 2) + ring = np.floor((d / max(1e-9, d.max())) * ring_count).astype(np.int64) + hue = (ring / max(1, ring_count)) % 1.0 + rgb = hsv_to_rgb(hue, np.ones_like(d), np.ones_like(d)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_sat_out_of_gamut(width: int, height: int, seed: int) -> np.ndarray: + """Channels swung well beyond [0,1] then hard-clipped: broad clipped plateaus.""" + rng = np.random.default_rng(seed) + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + tx = xx / max(1, width - 1) + ty = yy / max(1, height - 1) + phase = float(rng.random()) + r = 2.2 * np.sin(2 * np.pi * (tx + 0.05 * phase)) + g = 2.2 * np.cos(2 * np.pi * ty) + b = 2.2 * np.sin(2 * np.pi * (tx + ty)) + rgb = np.clip(np.stack([r, g, b], axis=-1), 0.0, 1.0) + return to_u8(rgb * 255.0) + + +def build_sat_voronoi(width: int, height: int, seed: int, cells: int) -> np.ndarray: + """Voronoi partition, each cell a random fully-saturated hue with hard seams.""" + rng = np.random.default_rng(seed) + pts = rng.random((cells, 2)) * np.array([width, height]) + hues = rng.random(cells) + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + best = np.zeros((height, width), dtype=np.int64) + bestd = np.full((height, width), np.inf) + for i in range(cells): + d = (xx - pts[i, 0]) ** 2 + (yy - pts[i, 1]) ** 2 + m = d < bestd + best[m] = i + bestd[m] = d[m] + hue = hues[best] + rgb = hsv_to_rgb(hue, np.ones_like(hue), np.ones_like(hue)) + return to_u8(rgb * 255.0) + + +# --------------------------------------------------------------------------- # +# Gradient / banding-stress builders (memo §10). Distinct processes: angled +# linear ramps, shallow luma/chroma sweeps, dithered ramps, radial sky glows, +# multi-stop, bilinear, conic and diamond fields. +# --------------------------------------------------------------------------- # + + +def build_grad_angled( + width: int, height: int, seed: int, angle_deg: float, + c0: tuple[float, float, float], c1: tuple[float, float, float], +) -> np.ndarray: + """Two-colour linear ramp at an arbitrary angle.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + a = np.asarray(c0, dtype=np.float64) + b = np.asarray(c1, dtype=np.float64) + rgb = (1 - t)[..., None] * a + t[..., None] * b + _ = seed + return to_u8(rgb * 255.0) + + +def build_grad_luma_shallow( + width: int, height: int, seed: int, base: float, span: float, angle_deg: float +) -> np.ndarray: + """Grayscale ramp over a narrow code-value span (severe banding stress).""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + g = to_u8(base + span * t) + _ = seed + return np.repeat(g[..., None], 3, axis=2) + + +def build_grad_chroma_shallow( + width: int, height: int, seed: int, hue: float, angle_deg: float +) -> np.ndarray: + """Constant-luma, shallow saturation sweep — chroma banding stress.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + sat = 0.06 + 0.10 * t + rgb = hsv_to_rgb(np.full_like(t, hue), sat, np.full_like(t, 0.65)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_grad_dither(width: int, height: int, seed: int, angle_deg: float, amp: float) -> np.ndarray: + """Smooth ramp plus seeded additive dither noise.""" + rng = np.random.default_rng(seed) + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + base = 0.30 + 0.30 * t + noise = rng.normal(0.0, amp / 255.0, size=(height, width)) + v = base + noise + rgb = np.clip(np.stack([v, v, v * 0.98], axis=-1), 0.0, 1.0) + return to_u8(rgb * 255.0) + + +def build_grad_radial_sky( + width: int, height: int, seed: int, cx_frac: float, cy_frac: float +) -> np.ndarray: + """Off-centre warm-to-cool radial sky glow with faint seeded noise.""" + rng = np.random.default_rng(seed) + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + cx, cy = cx_frac * width, cy_frac * height + d = np.sqrt((xx - cx) ** 2 + (yy - cy) ** 2) + t = d / max(1e-9, d.max()) + r = 0.95 - 0.55 * t + g = 0.85 - 0.30 * t + b = 0.60 + 0.35 * t + noise = rng.normal(0.0, 1.0 / 255.0, size=(height, width)) + rgb = np.clip(np.stack([r + noise, g + noise, b + noise], axis=-1), 0.0, 1.0) + return to_u8(rgb * 255.0) + + +def build_grad_sunset_multistop(width: int, height: int, seed: int, angle_deg: float) -> np.ndarray: + """Five-stop sunset gradient along an angle (piecewise-linear colour stops).""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + theta = np.deg2rad(angle_deg) + proj = xx * np.cos(theta) + yy * np.sin(theta) + t = (proj - proj.min()) / max(1e-9, proj.max() - proj.min()) + stops_t = np.array([0.0, 0.3, 0.55, 0.75, 1.0]) + stops_c = np.array([ + [0.05, 0.05, 0.20], + [0.35, 0.10, 0.30], + [0.85, 0.35, 0.25], + [0.98, 0.70, 0.35], + [1.00, 0.92, 0.70], + ]) + r = np.interp(t, stops_t, stops_c[:, 0]) + g = np.interp(t, stops_t, stops_c[:, 1]) + b = np.interp(t, stops_t, stops_c[:, 2]) + rgb = np.stack([r, g, b], axis=-1) + _ = seed + return to_u8(rgb * 255.0) + + +def build_grad_bilinear( + width: int, height: int, seed: int, + corners: list[tuple[float, float, float]], +) -> np.ndarray: + """Four-corner bilinear colour interpolation.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + u = xx / max(1, width - 1) + v = yy / max(1, height - 1) + tl, tr, bl, br = (np.asarray(c, dtype=np.float64) for c in corners) + top = (1 - u)[..., None] * tl + u[..., None] * tr + bot = (1 - u)[..., None] * bl + u[..., None] * br + rgb = (1 - v)[..., None] * top + v[..., None] * bot + _ = seed + return to_u8(rgb * 255.0) + + +def build_grad_conic(width: int, height: int, seed: int) -> np.ndarray: + """Low-saturation conic (angular) hue sweep — a gentle pastel chroma gradient.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + cx, cy = (width - 1) / 2.0, (height - 1) / 2.0 + ang = (np.arctan2(yy - cy, xx - cx) / (2 * np.pi)) % 1.0 + rgb = hsv_to_rgb(ang, np.full_like(ang, 0.25), np.full_like(ang, 0.85)) + _ = seed + return to_u8(rgb * 255.0) + + +def build_grad_diamond(width: int, height: int, seed: int) -> np.ndarray: + """L1 (diamond) distance gradient from centre.""" + yy, xx = np.mgrid[0:height, 0:width].astype(np.float64) + cx, cy = (width - 1) / 2.0, (height - 1) / 2.0 + d = np.abs(xx - cx) + np.abs(yy - cy) + t = d / max(1e-9, d.max()) + r = 0.20 + 0.70 * t + g = 0.60 - 0.20 * t + b = 0.80 - 0.55 * t + rgb = np.clip(np.stack([r, g, b], axis=-1), 0.0, 1.0) + _ = seed + return to_u8(rgb * 255.0) + + def crop(arr: np.ndarray, w: int, h: int, ox: int, oy: int) -> np.ndarray: H, W, _ = arr.shape ox = max(0, min(ox, W - w)) @@ -555,6 +861,89 @@ def registry() -> list[Fixture]: "synthetic/gradient", lambda: build_gradient(800, 600, "sky", 3007), 3007, "Third sky-like noisy gradient, fresh seed")) + # ---- gradient/banding-stress coverage expansion (memo §10): distinct + # generating processes, each a fresh family. ------------------------------ # + # Angled two-colour linear ramps. + fx.append(_synth("gradient-angled-30-1024x512", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_angled(1024, 512, 3100, 30.0, (0.05, 0.10, 0.35), (0.95, 0.80, 0.30)), + 3100, "Two-colour linear ramp at 30 degrees")) + fx.append(_synth("gradient-angled-75-800x600", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_angled(800, 600, 3101, 75.0, (0.10, 0.30, 0.15), (0.90, 0.40, 0.70)), + 3101, "Two-colour linear ramp at 75 degrees")) + fx.append(_synth("gradient-angled-150-holdout-640x640", "gradient", "holdout", + "synthetic/gradient", + lambda: build_grad_angled(640, 640, 39100, 150.0, (0.20, 0.20, 0.60), (0.85, 0.85, 0.20)), + 39100, "Holdout two-colour linear ramp at 150 degrees, fresh seed")) + # Shallow luma ramps over a narrow code-value span (banding stress). + fx.append(_synth("gradient-luma-shallow-h-1024x512", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_luma_shallow(1024, 512, 3102, 96.0, 16.0, 0.0), + 3102, "Horizontal grayscale ramp over 16 code values (banding stress)")) + fx.append(_synth("gradient-luma-shallow-v-800x600", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_luma_shallow(800, 600, 3103, 40.0, 24.0, 90.0), + 3103, "Vertical grayscale ramp over 24 code values (banding stress)")) + # Shallow chroma sweeps (constant luma). + fx.append(_synth("gradient-chroma-shallow-red-1024x512", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_chroma_shallow(1024, 512, 3104, 0.02, 0.0), + 3104, "Shallow horizontal red-chroma sweep at constant luma")) + fx.append(_synth("gradient-chroma-shallow-blue-800x600", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_chroma_shallow(800, 600, 3105, 0.62, 90.0), + 3105, "Shallow vertical blue-chroma sweep at constant luma")) + fx.append(_synth("gradient-chroma-shallow-green-holdout-640x640", "gradient", "holdout", + "synthetic/gradient", + lambda: build_grad_chroma_shallow(640, 640, 39101, 0.33, 45.0), + 39101, "Holdout shallow diagonal green-chroma sweep, fresh seed")) + # Dithered ramps. + fx.append(_synth("gradient-dither-lowamp-1024x512", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_dither(1024, 512, 3106, 0.0, 1.0), + 3106, "Smooth horizontal ramp plus low-amplitude seeded dither")) + fx.append(_synth("gradient-dither-highamp-800x600", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_dither(800, 600, 3107, 60.0, 4.0), + 3107, "Angled ramp plus higher-amplitude seeded dither")) + # Radial sky glows. + fx.append(_synth("gradient-radial-sky-glow-1024x768", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_radial_sky(1024, 768, 3108, 0.5, 0.15), + 3108, "Centred warm-to-cool radial sky glow with faint noise")) + fx.append(_synth("gradient-radial-sky-offcenter-800x600", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_radial_sky(800, 600, 3109, 0.2, 0.8), + 3109, "Off-centre radial sky glow with faint noise")) + # Multi-stop sunset. + fx.append(_synth("gradient-sunset-multistop-1024x512", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_sunset_multistop(1024, 512, 3110, 90.0), + 3110, "Five-stop vertical sunset gradient")) + # Bilinear four-corner fields. + fx.append(_synth("gradient-bilinear-768x768", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_bilinear(768, 768, 3111, [ + (0.10, 0.20, 0.60), (0.80, 0.30, 0.20), + (0.20, 0.70, 0.30), (0.90, 0.85, 0.40)]), + 3111, "Four-corner bilinear colour field")) + fx.append(_synth("gradient-bilinear-holdout-640x640", "gradient", "holdout", + "synthetic/gradient", + lambda: build_grad_bilinear(640, 640, 39102, [ + (0.60, 0.10, 0.40), (0.20, 0.60, 0.70), + (0.80, 0.80, 0.20), (0.10, 0.30, 0.55)]), + 39102, "Holdout four-corner bilinear colour field, fresh seed")) + # Conic and diamond fields. + fx.append(_synth("gradient-conic-720x720", "gradient", "calibration", + "synthetic/gradient", + lambda: build_grad_conic(720, 720, 3112), + 3112, "Low-saturation conic (angular) pastel hue sweep")) + fx.append(_synth("gradient-diamond-768x768", "gradient", "development", + "synthetic/gradient", + lambda: build_grad_diamond(768, 768, 3113), + 3113, "L1 diamond-distance colour gradient from centre")) + # ---- saturated (synthetic + one photo crop) ---------------------------- # fx.append(_synth("saturated-primaries-hard-512x512", "saturated", "calibration", "synthetic/saturated", lambda: build_saturated(512, 512, 4001, soft=False), @@ -573,6 +962,84 @@ def registry() -> list[Fixture]: "test-set/test-set-4mp/20260606_203230_4mp.png", "centre-ish 512x512 crop at (x=900,y=700), sRGB 8-bit, no resampling")) + # ---- saturated coverage expansion (memo §10): distinct saturated/clipping + # content processes, each a fresh family. --------------------------------- # + # Quantised hue wheels. + fx.append(_synth("saturated-hue-wheel-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_hue_wheel(512, 512, 4100, 8), + 4100, "Fully-saturated 8-sector angular hue wheel (hard slice edges)")) + fx.append(_synth("saturated-hue-wheel-fine-384x384", "saturated", "development", + "synthetic/saturated", lambda: build_sat_hue_wheel(384, 384, 4101, 16), + 4101, "Fully-saturated 16-sector angular hue wheel")) + fx.append(_synth("saturated-hue-wheel-holdout-320x320", "saturated", "holdout", + "synthetic/saturated", lambda: build_sat_hue_wheel(320, 320, 49100, 12), + 49100, "Holdout 12-sector fully-saturated hue wheel, fresh seed")) + # Angled saturated hue bands. + fx.append(_synth("saturated-hue-bands-0deg-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_hue_bands(512, 512, 4102, 0.0, 12), + 4102, "Vertical fully-saturated hue bands (12 hues, hard edges)")) + fx.append(_synth("saturated-hue-bands-45deg-512x512", "saturated", "development", + "synthetic/saturated", lambda: build_sat_hue_bands(512, 512, 4103, 45.0, 12), + 4103, "Diagonal fully-saturated hue bands (12 hues)")) + fx.append(_synth("saturated-hue-bands-90deg-448x448", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_hue_bands(448, 448, 4104, 90.0, 16), + 4104, "Horizontal fully-saturated hue bands (16 hues)")) + # Neutral-to-saturated chroma ramps (near-clipping end). + fx.append(_synth("saturated-chroma-ramp-red-h-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_chroma_ramp(512, 512, 4105, 0.0, "h"), + 4105, "Horizontal gray-to-full-red chroma ramp (near-clipping)")) + fx.append(_synth("saturated-chroma-ramp-blue-v-512x512", "saturated", "development", + "synthetic/saturated", lambda: build_sat_chroma_ramp(512, 512, 4106, 0.62, "v"), + 4106, "Vertical gray-to-full-blue chroma ramp (near-clipping)")) + fx.append(_synth("saturated-chroma-ramp-green-diag-holdout-320x320", "saturated", "holdout", + "synthetic/saturated", lambda: build_sat_chroma_ramp(320, 320, 49101, 0.33, "d"), + 49101, "Holdout diagonal gray-to-full-green chroma ramp, fresh seed")) + # Saturated high-frequency texture and per-pixel noise. + fx.append(_synth("saturated-texture-checker-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_texture_edges(512, 512, 4107, 16), + 4107, "Complementary saturated checkerboard (16px cells) plus hard blocks")) + fx.append(_synth("saturated-texture-fine-384x384", "saturated", "development", + "synthetic/saturated", lambda: build_sat_texture_edges(384, 384, 4108, 6), + 4108, "Fine complementary saturated checkerboard (6px cells)")) + fx.append(_synth("saturated-noise-hue-384x384", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_noise(384, 384, 4109), + 4109, "Per-pixel random fully-saturated hue (high-entropy texture)")) + fx.append(_synth("saturated-noise-hue-holdout-320x320", "saturated", "holdout", + "synthetic/saturated", lambda: build_sat_noise(320, 320, 49102), + 49102, "Holdout per-pixel random saturated hue, fresh seed")) + # Sub-kilobyte-encodable saturated tiles (solid / few-block). + fx.append(_synth("saturated-solid-red-256x256", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_solid_tile(256, 256, (255, 0, 0)), + 4110, "Solid pure red tile (sub-kilobyte-encodable saturated fixture)")) + fx.append(_synth("saturated-solid-cyan-256x256", "saturated", "development", + "synthetic/saturated", lambda: build_sat_solid_tile(256, 256, (0, 255, 255)), + 4111, "Solid pure cyan tile (sub-kilobyte-encodable saturated fixture)")) + fx.append(_synth("saturated-solid-magenta-288x288", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_solid_tile(288, 288, (255, 0, 255)), + 4112, "Solid pure magenta tile (sub-kilobyte-encodable saturated fixture)")) + fx.append(_synth("saturated-block-holdout-256x256", "saturated", "holdout", + "synthetic/saturated", lambda: build_sat_block_tile(256, 256, [ + (255, 255, 0), (0, 0, 255), (0, 255, 0), (255, 0, 0)]), + 4113, "Four-block saturated tile (near-trivially codeable), holdout")) + # Concentric saturated rings. + fx.append(_synth("saturated-rings-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_rings(512, 512, 4114, 10), + 4114, "Concentric fully-saturated rings (hue stepped by radius)")) + fx.append(_synth("saturated-rings-400x400", "saturated", "development", + "synthetic/saturated", lambda: build_sat_rings(400, 400, 4115, 16), + 4115, "Concentric fully-saturated rings, finer ring spacing")) + # Out-of-gamut hard-clip plateaus. + fx.append(_synth("saturated-out-of-gamut-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_out_of_gamut(512, 512, 4116), + 4116, "Channels swung beyond [0,1] then hard-clipped (broad clipped plateaus)")) + fx.append(_synth("saturated-out-of-gamut-448x448", "saturated", "development", + "synthetic/saturated", lambda: build_sat_out_of_gamut(448, 448, 4117), + 4117, "Second out-of-gamut hard-clip field, fresh seed")) + # Saturated Voronoi partition. + fx.append(_synth("saturated-voronoi-512x512", "saturated", "calibration", + "synthetic/saturated", lambda: build_sat_voronoi(512, 512, 4118, 28), + 4118, "28-cell saturated Voronoi partition (hard cell seams)")) + # ---- gradient/line-art done; tiny crops (exempt from 256x256) ---------- # tiny_sizes = [(7, 7), (8, 8), (16, 16), (33, 20), (64, 64)] # Calibration tiny crops from calibration scene 20240501_110934. diff --git a/JPXL/tools/tests/test_make_quality_guard_fixtures.py b/JPXL/tools/tests/test_make_quality_guard_fixtures.py index 14ea0c41..1d9a1f00 100644 --- a/JPXL/tools/tests/test_make_quality_guard_fixtures.py +++ b/JPXL/tools/tests/test_make_quality_guard_fixtures.py @@ -165,12 +165,27 @@ def test_each_synthetic_class_has_all_three_splits(self): def test_split_and_class_counts_are_stable(self): # 2026-08-24: the one-shot program's coverage expansion added nine # calibration/development fixtures (noise-lowlight and grayscale from - # more captures; second text/UI/hatch/sky families). The locked - # holdout is frozen at 13. + # more captures; second text/UI/hatch/sky families). + # 2026-08-25: the saturated/gradient corpus-gap closure (memo §10, >=25 + # source families per critical class) added 22 saturated and 17 + # gradient synthetic families across all three splits: +15 calibration, + # +13 development, +7 holdout. by_split = Counter(f.split for f in self.fixtures) - self.assertEqual(by_split["calibration"], 24) - self.assertEqual(by_split["development"], 19) - self.assertEqual(by_split["holdout"], 13) + self.assertEqual(by_split["calibration"], 43) + self.assertEqual(by_split["development"], 32) + self.assertEqual(by_split["holdout"], 20) + + def test_critical_classes_have_25_families(self): + # memo §10: saturated and gradient/banding-stress each need >= 25 + # distinct source families. Synthetic families key on fixture id. + by_class_families: dict[str, set] = defaultdict(set) + for fx in self.fixtures: + by_class_families[fx.klass].add(mqgf.family_fields(fx)["family_id"]) + for klass in ("saturated", "gradient"): + self.assertGreaterEqual( + len(by_class_families[klass]), 25, + f"{klass} has only {len(by_class_families[klass])} families", + ) def test_families_never_cross_splits(self): by_family = defaultdict(set) From a523d0157ac223678b9d02342d0a8a7fbc34d642 Mon Sep 17 00:00:00 2001 From: dk Date: Tue, 25 Aug 2026 15:39:14 +0800 Subject: [PATCH 13/27] docs(publish): README pass, MIT licensing audit, libjxl bench harness - README: verified against CONFORMANCE and the CLI; the quality-search section now describes the promoted one-shot seed (qpv2-st-1 pooled quantile regression behind the default `one-shot-controller` feature, with the calibrated-table fallback for out-of-envelope inputs) and the full-resolution canonical verification before every emission. - JPXL/docs/LICENSING-AUDIT.md: MIT-only audit of all 12 workspace crates and the full dependency graph; no copyleft anywhere; the only non-MIT arms are permissive (moxcms/pxfm BSD-3/Apache-2.0 on the default path via the image adapter; BSD/CC0 metric crates behind off-by-default measurement features). - JPXL/tools/bench_vs_libjxl.sh: dependency-light reproducible size/SSIMULACRA2/wall comparison (jpxl vs cjxl/djxl) with provenance headers (versions, sha256, flags), JSONL output, and clearly marked JPXL-only degradation when no runnable native oracle is present. Smoke-tested; native Linux cjxl/djxl (setup-oracles.sh) is still needed to exercise the comparison columns on this host. AKR-Change: chg-3c1cf97d55c892ef AKR-Work: jpegxl-rs.work.publish-readme-benchmark-mit AKR-Graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd AKR-Tree: cdc6873fecf6eb9083ad447e284d4590e2f3ef96 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/docs/LICENSING-AUDIT.md | 203 ++++++++++++++++++++++ JPXL/tools/bench_vs_libjxl.sh | 315 ++++++++++++++++++++++++++++++++++ README.md | 51 +++++- 3 files changed, 560 insertions(+), 9 deletions(-) create mode 100644 JPXL/docs/LICENSING-AUDIT.md create mode 100644 JPXL/tools/bench_vs_libjxl.sh diff --git a/JPXL/docs/LICENSING-AUDIT.md b/JPXL/docs/LICENSING-AUDIT.md new file mode 100644 index 00000000..e728898c --- /dev/null +++ b/JPXL/docs/LICENSING-AUDIT.md @@ -0,0 +1,203 @@ +# JPXL licensing audit (MIT-only) + +Scope: the JPXL Rust workspace under `JPXL/` and the repository's licensing +surface. This audit answers one question: **is JPXL cleanly MIT-licensed, with +no copyleft and nothing that blocks redistribution under MIT?** + +Answer: **yes.** Every JPXL crate declares MIT; every third-party dependency is +permissive and MIT-compatible; no dependency is copyleft (no GPL / LGPL / AGPL / +MPL / CDDL / EUPL / SSPL / OSL / CeCILL anywhere in the graph). + +- Method: `cargo tree --workspace [--all-features] -e normal,build,dev + --format "{p}|{l}"`, plus manual inspection of `LICENSE`, `JPXL/LICENSE-MIT`, + and every crate's `Cargo.toml`. +- Host / date: recorded per run; the dependency set below reflects + `JPXL/Cargo.lock` as committed. + +--- + +## 1. JPXL's own packages — MIT + +- Workspace default: `JPXL/Cargo.toml` sets `[workspace.package] license = "MIT"`. +- Every member crate (`jpxl`, `jpxl-bitstream`, `jpxl-core`, `jpxl-entropy`, + `jpxl-decode`, `jpxl-encode`, `jpxl-encode-policy`, `jpxl-cli`, + `jpxl-conformance`, `jpxl-perceptual`, `jpxl-plan-render`, and the WIP + `jpxl-jpeg`) declares `license.workspace = true`, i.e. inherits MIT. No crate + overrides it. No crate uses a dual or `OR`-ed expression of its own. +- Repository license files: + - `LICENSE` (repo root) — MIT, `SPDX-License-Identifier: MIT`, + "Copyright (c) 2026 JPXL contributors", plus a trailing note that the + *gitignored* working material (a BSD-3-Clause libjxl checkout used only as a + black-box oracle, copyrighted ISO/IEC standards documents, and local test + images) is **not** covered and **not** distributed. + - `JPXL/LICENSE-MIT` — the identical MIT grant, same copyright line. + +**Consistency check:** the two files carry the same MIT terms and the same +copyright holder and year. They are consistent; the only difference is the root +`LICENSE`'s explanatory note about gitignored non-distributed material, which is +appropriate to keep at the repo root. **No edit required.** + +--- + +## 2. Copyleft scan — none + +``` +cargo tree --workspace --all-features -e normal,build,dev --format "{p}|{l}" \ + | grep -iE 'GPL|LGPL|AGPL|MPL|CDDL|EUPL|CPAL|SSPL|OSL|CeCILL' +``` + +Result: **no matches.** The old AGPL `jxl-encoder` project (AGPL-3.0-only OR a +commercial license) is **not present** in this repository and is not a +dependency; per AGENTS.md §2 its only permitted channel of inheritance is the +`JPEG_XL_CLEAN_IMPLEMENTATION_LESSONS.md` lessons file, which carries no code. + +--- + +## 3. Third-party dependencies + +Every dependency below is permissive and compatible with distributing the +aggregate under MIT. Licenses shown are the crates' own SPDX expressions; where +an expression is an `OR`, the project may elect the MIT arm when one is offered. + +### 3.1 Default build (`cargo build --workspace`, no optional features) + +These are the crates a normal build and the shipped CLI actually pull. All but +two offer MIT directly; the two that do not (`moxcms`, `pxfm`) offer +BSD-3-Clause, which is permissive and MIT-compatible. + +| Crate | License (SPDX) | MIT arm? | +|---|---|---| +| adler2 | 0BSD OR MIT OR Apache-2.0 | yes | +| autocfg | Apache-2.0 OR MIT | yes | +| bitflags | MIT OR Apache-2.0 | yes | +| bytemuck | Zlib OR Apache-2.0 OR MIT | yes | +| byteorder-lite | Unlicense OR MIT | yes | +| cfg-if | MIT OR Apache-2.0 | yes | +| color_quant | MIT | yes | +| crc32fast | MIT OR Apache-2.0 | yes | +| crossbeam-deque | MIT OR Apache-2.0 | yes | +| crossbeam-epoch | MIT OR Apache-2.0 | yes | +| crossbeam-utils | MIT OR Apache-2.0 | yes | +| either | MIT OR Apache-2.0 | yes | +| fax | MIT | yes | +| fdeflate | MIT OR Apache-2.0 | yes | +| flate2 | MIT OR Apache-2.0 | yes | +| gif | MIT OR Apache-2.0 | yes | +| half | MIT OR Apache-2.0 | yes | +| image | MIT OR Apache-2.0 | yes | +| image-webp | MIT OR Apache-2.0 | yes | +| miniz_oxide | MIT OR Zlib OR Apache-2.0 | yes | +| **moxcms** | **BSD-3-Clause OR Apache-2.0** | **no (BSD-3-Clause)** | +| num-traits | MIT OR Apache-2.0 | yes | +| png | MIT OR Apache-2.0 | yes | +| proc-macro2 | MIT OR Apache-2.0 | yes | +| **pxfm** | **BSD-3-Clause OR Apache-2.0** | **no (BSD-3-Clause)** | +| qoi | MIT OR Apache-2.0 | yes | +| quick-error | MIT OR Apache-2.0 | yes | +| quote | MIT OR Apache-2.0 | yes | +| rayon | MIT OR Apache-2.0 | yes | +| rayon-core | MIT OR Apache-2.0 | yes | +| safe_arch | Zlib OR Apache-2.0 OR MIT | yes | +| simd-adler32 | MIT | yes | +| syn | MIT OR Apache-2.0 | yes | +| tiff | MIT | yes | +| unicode-ident | (MIT OR Apache-2.0) AND Unicode-3.0 | yes (code); Unicode-3.0 covers data tables | +| weezl | MIT OR Apache-2.0 | yes | +| wide | Zlib OR Apache-2.0 OR MIT | yes | +| zerocopy | BSD-2-Clause OR Apache-2.0 OR MIT | yes | +| zerocopy-derive | BSD-2-Clause OR Apache-2.0 OR MIT | yes | +| zune-core | MIT OR Apache-2.0 OR Zlib | yes | +| zune-jpeg | MIT OR Apache-2.0 OR Zlib | yes | + +`moxcms`, `pxfm`, `image`, `image-webp`, `png`, `gif`, `tiff`, `qoi`, +`zune-*`, `weezl`, `color_quant`, `fax`, `byteorder-lite` reach the graph only +through the `image` raster adapter used by `jpxl-cli`; the public `jpxl` facade +and all normative codec crates are pixel-buffer based and do not pull the file +adapters (see `JPXL/Cargo.toml` notes). + +### 3.2 Optional, measurement-only (feature-gated, off by default) + +These enter **only** with the `jpxl-conformance` metric features +(`ssimulacra2` / `butteraugli`), used to *grade* lossy encodes against the +oracle. A default `cargo build --workspace` does not fetch them, and no +normative crate depends on them (see the extended note in `JPXL/Cargo.toml`). + +| Crate | License (SPDX) | MIT arm? | +|---|---|---| +| aligned-vec | MIT | yes | +| archmage | MIT OR Apache-2.0 | yes | +| archmage-macros | MIT OR Apache-2.0 | yes | +| av-data | MIT | yes | +| byte-slice-cast | MIT | yes | +| **butteraugli** | **BSD-3-Clause** | **no (BSD-3-Clause)** | +| bytes | MIT | yes | +| equator | MIT | yes | +| equator-macro | MIT | yes | +| **imgref** | **CC0-1.0 OR Apache-2.0** | **no (CC0 / Apache-2.0)** | +| log | MIT OR Apache-2.0 | yes | +| magetypes | MIT OR Apache-2.0 | yes | +| num-bigint | MIT OR Apache-2.0 | yes | +| num-derive | MIT OR Apache-2.0 | yes | +| num-integer | MIT OR Apache-2.0 | yes | +| num-rational | MIT OR Apache-2.0 | yes | +| rgb | MIT | yes | +| rustversion | MIT OR Apache-2.0 | yes | +| safe_unaligned_simd | MIT OR Apache-2.0 | yes | +| **ssimulacra2** | **BSD-2-Clause** | **no (BSD-2-Clause)** | +| syn (3.x) | MIT OR Apache-2.0 | yes | +| thiserror | MIT OR Apache-2.0 | yes | +| thiserror-impl | MIT OR Apache-2.0 | yes | +| **v_frame** | **BSD-2-Clause** | **no (BSD-2-Clause)** | +| yuvxyb | MIT | yes | +| yuvxyb-math | MIT | yes | + +--- + +## 4. Items worth a human's eye (none blocking) + +1. **`moxcms` and `pxfm` on the default path** are `BSD-3-Clause OR Apache-2.0` + — MIT is not one of their options. This is fully compatible with shipping the + aggregate under MIT (permissive dependencies inside an MIT project are + normal), but the BSD-3-Clause copyright/attribution notices for these two + crates should be retained in any distributed `NOTICE`/third-party-licenses + bundle. They enter only through the `image` raster adapter in `jpxl-cli`. + +2. **`butteraugli` (measurement-only) is a Rust port of libjxl's butteraugli.** + BSD-3-Clause, permissive. It is off by default and never linked into a normal + build. AGENTS.md §2 and the `JPXL/Cargo.toml` clean-room note already record + that using it as a *metric* is the same category as running `cjxl`/`djxl` as + oracles, whereas wiring it into the encoder's own rate control would make the + perceptual model a derivative of libjxl and needs a recorded decision first. + No such wiring exists today. + +3. **`unicode-ident`** carries `Unicode-3.0` for its data tables (in addition to + MIT/Apache for code). Unicode-3.0 is permissive and only asks that its notice + be retained. `imgref` (measurement path) offers CC0-1.0 (public-domain + equivalent) or Apache-2.0. Neither is copyleft. + +4. **Attribution hygiene for release:** because the graph mixes MIT with + Apache-2.0, BSD-2/3-Clause, Zlib, 0BSD, Unlicense, CC0 and Unicode-3.0 + (all permissive), a shipped binary should carry a generated third-party + license notice (e.g. `cargo about` / `cargo-bundle-licenses`). This is an + attribution convenience, not a licensing conflict. + +## 5. ISO / standards text + +No ISO/IEC 18181 text is referenced or embedded in any shipped crate. The +standards markdown/PDF sources live in gitignored directories +(`markdowns/`, `latex/`, `original-pdfs-.../`) and never enter the build or git +history, per AGENTS.md §3 and §9. Clause-number citations in comments are fine; +quoted normative passages are not, and none were found in the workspace crates. + +## Reproduce + +```sh +cd JPXL +# Full dependency + license graph (default features): +cargo tree --workspace -e normal,build,dev --format "{p} {l}" +# Superset including the optional measurement metrics: +cargo tree --workspace --all-features -e normal,build,dev --format "{p} {l}" +# Copyleft check (expect no output): +cargo tree --workspace --all-features -e normal,build,dev --format "{l}" \ + | grep -iE 'GPL|LGPL|AGPL|MPL|CDDL|EUPL|SSPL' +``` diff --git a/JPXL/tools/bench_vs_libjxl.sh b/JPXL/tools/bench_vs_libjxl.sh new file mode 100644 index 00000000..3f4c8a71 --- /dev/null +++ b/JPXL/tools/bench_vs_libjxl.sh @@ -0,0 +1,315 @@ +#!/usr/bin/env bash +# bench_vs_libjxl.sh — reproducible size / SSIMULACRA2 / wall comparison of the +# JPXL encoder against the libjxl oracle (cjxl / djxl), with full provenance. +# +# WHAT IT DOES +# For every input image and every requested setting it emits one table row: +# encoder setting bytes bpp ssimulacra2 wall_s status +# JPXL rows come from `jpxl encode --quality Q`; cjxl rows from `cjxl -d D`. +# Every stream is decoded back to pixels and scored with the SAME metric — +# the in-tree production SSIMULACRA2 exposed by `jpxl compare` — so the two +# encoders are graded on one identical yardstick. Sizes are the real encoded +# byte counts; wall is the best of N encode runs. +# +# This is deliberately an HONEST, not a matched, comparison: JPXL targets an +# SSIMULACRA2 floor while cjxl targets a Butteraugli distance, so the reader +# compares the (bytes, ssimulacra2) points, not same-named settings. See the +# project README section "Quality, density, and speed versus libjxl". +# +# PROVENANCE +# A header block records: UTC date, host, every binary's path + version + +# sha256, the exact flags, the SSIMULACRA2 metric version, and each input's +# sha256 and dimensions. With --jsonl the same data is written as one JSON +# object per row (schema "jpxl.bench-vs-libjxl/1") for machine consumption. +# +# ORACLES +# cjxl / djxl are looked up in tools/oracle-bin/ first (where +# setup-oracles.sh installs them), then on $PATH, then via --cjxl/--djxl. +# If they cannot run on this host (e.g. only the Windows .exe oracle is +# present, or none was set up) the script still runs and emits JPXL-only +# rows, clearly marked — the table stays reproducible and simply fills the +# cjxl columns when a working oracle is available. +# +# CLEAN ROOM (AGENTS.md §2) +# cjxl/djxl are used strictly as black boxes: this script only *runs* them. +# No libjxl source is read. +# +# INPUTS +# Any raster JPXL can read (PNG, PPM, JPEG, ...). Each input is first +# normalised to a canonical P6 PPM via a *lossless* JPXL round-trip +# (encode → decode), so both encoders and the scorer see identical source +# pixels with no external image tool required. RGB (3-channel) inputs only; +# images with alpha are skipped with a note (the lossy encoder has no alpha). +# +# USAGE +# tools/bench_vs_libjxl.sh [options] [ ...] +# +# OPTIONS +# --quality "Q ..." SSIMULACRA2 target(s) for JPXL (default: "70 85 90") +# --distance "D ..." Butteraugli distance(s) for cjxl (default: "3.0 1.5 1.0") +# --effort E JPXL lossy effort: fast|balanced (default: balanced) +# --cjxl-effort N cjxl -e effort 1..9 (default: 7) +# --threads N thread cap for both encoders (default: 4) +# --runs N encode timing runs, best is kept (default: 1) +# --jpxl PATH jpxl binary (default: target/fast-debug or release) +# --cjxl PATH cjxl binary override +# --djxl PATH djxl binary override +# --work-dir DIR scratch dir (default: a mktemp under $TMPDIR) +# --jsonl PATH also write one JSON object per row to PATH +# -h, --help this help +# +# EXIT: 0 if at least one row was produced; non-zero on setup error. +set -uo pipefail + +# ---------------------------------------------------------------- locate self +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +jpxl_root="$(cd -- "${script_dir}/.." && pwd)" +oracle_bin="${script_dir}/oracle-bin" + +# ------------------------------------------------------------------- defaults +qualities="70 85 90" +distances="3.0 1.5 1.0" +effort="balanced" +cjxl_effort="7" +threads="4" +runs="1" +jpxl_bin="" +cjxl_bin="" +djxl_bin="" +work_dir="" +jsonl_path="" +inputs=() + +die() { printf 'error: %s\n' "$*" >&2; exit 2; } +note() { printf '%s\n' "# $*"; } + +# --------------------------------------------------------------- parse args +while [[ $# -gt 0 ]]; do + case "$1" in + --quality) qualities="$2"; shift 2 ;; + --distance) distances="$2"; shift 2 ;; + --effort) effort="$2"; shift 2 ;; + --cjxl-effort) cjxl_effort="$2"; shift 2 ;; + --threads) threads="$2"; shift 2 ;; + --runs) runs="$2"; shift 2 ;; + --jpxl) jpxl_bin="$2"; shift 2 ;; + --cjxl) cjxl_bin="$2"; shift 2 ;; + --djxl) djxl_bin="$2"; shift 2 ;; + --work-dir) work_dir="$2"; shift 2 ;; + --jsonl) jsonl_path="$2"; shift 2 ;; + -h|--help) sed -n '2,72p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; + --*) die "unknown option: $1" ;; + *) inputs+=("$1"); shift ;; + esac +done + +[[ ${#inputs[@]} -gt 0 ]] || die "no input files or directories given (see --help)" + +# --------------------------------------------------------------- find jpxl +if [[ -z "${jpxl_bin}" ]]; then + for cand in "${jpxl_root}/target/release/jpxl" "${jpxl_root}/target/fast-debug/jpxl"; do + [[ -x "${cand}" ]] && { jpxl_bin="${cand}"; break; } + done +fi +[[ -n "${jpxl_bin}" && -x "${jpxl_bin}" ]] \ + || die "jpxl binary not found; build it (cargo build -p jpxl-cli --release) or pass --jpxl" + +# ---------------------------------------------- find + validate the oracle +# Returns 0 and echoes a usable path, or non-zero if the candidate cannot run +# on this host (wrong architecture, missing, non-executable). +runnable() { "$1" --version >/dev/null 2>&1; } + +find_oracle() { + local name="$1" override="$2" cand + if [[ -n "${override}" ]]; then + runnable "${override}" && { echo "${override}"; return 0; } + return 1 + fi + for cand in "${oracle_bin}/${name}" "$(command -v "${name}" 2>/dev/null || true)"; do + [[ -n "${cand}" && -x "${cand}" ]] || continue + runnable "${cand}" && { echo "${cand}"; return 0; } + done + return 1 +} + +cjxl_path="$(find_oracle cjxl "${cjxl_bin}" || true)" +djxl_path="$(find_oracle djxl "${djxl_bin}" || true)" +have_oracle=0 +[[ -n "${cjxl_path}" && -n "${djxl_path}" ]] && have_oracle=1 + +# --------------------------------------------------------------- work dir +if [[ -z "${work_dir}" ]]; then + work_dir="$(mktemp -d "${TMPDIR:-/tmp}/jpxl-bench.XXXXXX")" || die "mktemp failed" + trap 'rm -rf "${work_dir}"' EXIT +else + mkdir -p "${work_dir}" || die "cannot create work dir ${work_dir}" +fi + +# --------------------------------------------------------------- helpers +sha() { sha256sum "$1" 2>/dev/null | cut -d' ' -f1; } +size() { stat -c '%s' "$1" 2>/dev/null || wc -c < "$1"; } +now() { date +%s.%N; } +elapsed() { awk "BEGIN{printf \"%.3f\", $2 - $1}"; } + +# Parse a P6 PPM header for "W H" (skips the maxval line). Whitespace-robust. +ppm_dims() { + head -c 64 "$1" | tr '\n\t' ' ' | awk '{print $2, $3}' +} + +# in-tree SSIMULACRA2 of two PPMs, or "n/a". +score_ssimulacra2() { + local ref="$1" cand="$2" out + out="$("${jpxl_bin}" compare "${ref}" "${cand}" 2>/dev/null)" || { echo "n/a"; return; } + echo "${out}" | grep -oE 'ssimulacra2_jpxl=[0-9.]+' | head -1 | cut -d= -f2 +} + +metric_version() { + "${jpxl_bin}" compare "$1" "$1" 2>/dev/null \ + | grep -oE 'ssimulacra2_jpxl_version=[^ ]+' | head -1 | cut -d= -f2 +} + +# Best-of-N wall time for a command (args after the first). Prints seconds. +best_wall() { + local n="$1"; shift + local best="" t0 t1 d i + for ((i=0; i/dev/null 2>&1; t1="$(now)" + d="$(elapsed "${t0}" "${t1}")" + if [[ -z "${best}" ]] || awk "BEGIN{exit !(${d} < ${best})}"; then best="${d}"; fi + done + echo "${best}" +} + +json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; } + +emit_jsonl() { + [[ -n "${jsonl_path}" ]] || return 0 + printf '{"schema":"jpxl.bench-vs-libjxl/1","image":"%s","encoder":"%s","setting":"%s","bytes":%s,"bpp":%s,"ssimulacra2":"%s","wall_s":%s,"status":"%s"}\n' \ + "$(json_escape "$1")" "$2" "$3" "$4" "$5" "$6" "$7" "$8" >> "${jsonl_path}" +} + +# --------------------------------------------------------------- gather inputs +files=() +for arg in "${inputs[@]}"; do + if [[ -d "${arg}" ]]; then + while IFS= read -r -d '' f; do files+=("${f}"); done \ + < <(find "${arg}" -type f \( -iname '*.ppm' -o -iname '*.png' -o -iname '*.jpg' \ + -o -iname '*.jpeg' -o -iname '*.bmp' -o -iname '*.tif' -o -iname '*.tiff' \) -print0 | sort -z) + elif [[ -f "${arg}" ]]; then + files+=("${arg}") + else + printf 'warning: skipping %s (not a file or directory)\n' "${arg}" >&2 + fi +done +[[ ${#files[@]} -gt 0 ]] || die "no readable input images found" + +# --------------------------------------------------------------- provenance header +[[ -n "${jsonl_path}" ]] && : > "${jsonl_path}" +metric_ver="?" + +note "jpxl.bench-vs-libjxl provenance" +note "date_utc: $(date -u +%Y-%m-%dT%H:%M:%SZ)" +note "host: $(uname -srm)" +note "jpxl: ${jpxl_bin}" +note "jpxl_version: $("${jpxl_bin}" --version 2>&1 | head -1)" +note "jpxl_sha256: $(sha "${jpxl_bin}")" +if [[ ${have_oracle} -eq 1 ]]; then + note "cjxl: ${cjxl_path}" + note "cjxl_version: $("${cjxl_path}" --version 2>&1 | head -1)" + note "cjxl_sha256: $(sha "${cjxl_path}")" + note "djxl: ${djxl_path}" + note "djxl_version: $("${djxl_path}" --version 2>&1 | head -1)" + note "djxl_sha256: $(sha "${djxl_path}")" +else + note "cjxl/djxl: NOT AVAILABLE on this host — emitting JPXL-only rows." + note " (Run tools/setup-oracles.sh on this platform to enable them.)" +fi +note "jpxl_effort: ${effort} cjxl_effort: -e ${cjxl_effort} threads: ${threads} runs: ${runs}" +note "jpxl_qualities: ${qualities}" +note "cjxl_distances: ${distances}" +note "" + +# --------------------------------------------------------------- table header +printf '%-28s %-8s %-10s %10s %7s %12s %8s %s\n' \ + image encoder setting bytes bpp ssimulacra2 wall_s status + +produced=0 + +for src in "${files[@]}"; do + base="$(basename "${src}")" + stem="${base%.*}" + ref_ppm="${work_dir}/${stem}.src.ppm" + + # Normalise to canonical P6 via a lossless JPXL round-trip. + if [[ "${src}" == *.ppm || "${src}" == *.PPM ]]; then + cp -f "${src}" "${ref_ppm}" + else + if ! "${jpxl_bin}" encode "${src}" "${work_dir}/${stem}.src.jxl" >/dev/null 2>&1 \ + || ! "${jpxl_bin}" decode "${work_dir}/${stem}.src.jxl" "${ref_ppm}" >/dev/null 2>&1; then + printf 'warning: cannot normalise %s to PPM (alpha or unsupported input?) — skipping\n' "${src}" >&2 + continue + fi + fi + read -r w h < <(ppm_dims "${ref_ppm}") + [[ -n "${w}" && -n "${h}" && "${w}" -gt 0 && "${h}" -gt 0 ]] \ + || { printf 'warning: bad PPM dims for %s — skipping\n' "${src}" >&2; continue; } + pixels=$(( w * h )) + [[ "${metric_ver}" == "?" ]] && metric_ver="$(metric_version "${ref_ppm}")" + + # ------------------------------------------------------------- JPXL rows + for q in ${qualities}; do + out_jxl="${work_dir}/${stem}.q${q}.jxl" + dec_ppm="${work_dir}/${stem}.q${q}.ppm" + enc_out="$("${jpxl_bin}" encode --quality "${q}" --effort "${effort}" \ + --quality-fallback best-effort --threads "${threads}" \ + "${ref_ppm}" "${out_jxl}" 2>&1)" + if [[ ! -s "${out_jxl}" ]]; then + printf '%-28s %-8s %-10s %10s %7s %12s %8s %s\n' \ + "${stem}" jpxl "q${q}" - - - - "encode-failed" + continue + fi + status="$(printf '%s\n' "${enc_out}" | grep -oE 'status=[a-z_]+' | head -1 | cut -d= -f2)" + [[ -n "${status}" ]] || status="ok" + wall="$(best_wall "${runs}" "${jpxl_bin}" encode --quality "${q}" --effort "${effort}" \ + --quality-fallback best-effort --threads "${threads}" "${ref_ppm}" "${out_jxl}")" + "${jpxl_bin}" decode "${out_jxl}" "${dec_ppm}" >/dev/null 2>&1 + s2="$(score_ssimulacra2 "${ref_ppm}" "${dec_ppm}")" + b="$(size "${out_jxl}")" + bpp="$(awk "BEGIN{printf \"%.4f\", ${b}*8/${pixels}}")" + printf '%-28s %-8s %-10s %10s %7s %12s %8s %s\n' \ + "${stem}" jpxl "q${q}" "${b}" "${bpp}" "${s2}" "${wall}" "${status}" + emit_jsonl "${base}" jpxl "q${q}" "${b}" "${bpp}" "${s2}" "${wall}" "${status}" + produced=1 + done + + # ------------------------------------------------------------- cjxl rows + if [[ ${have_oracle} -eq 1 ]]; then + for d in ${distances}; do + out_jxl="${work_dir}/${stem}.d${d}.jxl" + dec_ppm="${work_dir}/${stem}.d${d}.ppm" + if ! "${cjxl_path}" -d "${d}" -e "${cjxl_effort}" --num_threads="${threads}" \ + "${ref_ppm}" "${out_jxl}" >/dev/null 2>&1 || [[ ! -s "${out_jxl}" ]]; then + printf '%-28s %-8s %-10s %10s %7s %12s %8s %s\n' \ + "${stem}" cjxl "d${d}" - - - - "encode-failed" + continue + fi + wall="$(best_wall "${runs}" "${cjxl_path}" -d "${d}" -e "${cjxl_effort}" \ + --num_threads="${threads}" "${ref_ppm}" "${out_jxl}")" + "${djxl_path}" "${out_jxl}" "${dec_ppm}" >/dev/null 2>&1 + s2="$(score_ssimulacra2 "${ref_ppm}" "${dec_ppm}")" + b="$(size "${out_jxl}")" + bpp="$(awk "BEGIN{printf \"%.4f\", ${b}*8/${pixels}}")" + printf '%-28s %-8s %-10s %10s %7s %12s %8s %s\n' \ + "${stem}" cjxl "d${d}" "${b}" "${bpp}" "${s2}" "${wall}" "ok" + emit_jsonl "${base}" cjxl "d${d}" "${b}" "${bpp}" "${s2}" "${wall}" "ok" + produced=1 + done + fi +done + +note "" +note "ssimulacra2_metric: ${metric_ver} (in-tree production metric; higher is better, 100 = identical)" +[[ -n "${jsonl_path}" ]] && note "jsonl: ${jsonl_path}" + +[[ ${produced} -eq 1 ]] || die "no comparison rows were produced" +exit 0 diff --git a/README.md b/README.md index cb5ed2ca..04c5b722 100644 --- a/README.md +++ b/README.md @@ -121,13 +121,18 @@ reported is the score the written file has: the reconstruction is bit-exact with `jpxl-decode`, and the scorer quantizes to the frame's bit depth the way a viewer would see it. -- **Search.** A calibrated table (`jpxl features` source statistics → starting - quantizer) picks the first probe; the controller brackets the target by - extrapolating the measured loss slope, aims at the log-loss crossing, and - then attaches entropy coding to only the coarsest qualifying candidates and - keeps the smallest exact stream. Probes reuse the first candidate's cover and - chroma-from-luma; a finalist far from that anchor is re-planned fresh and - re-scored. +- **Search.** A trained one-shot model (pooled quantile regression over + `jpxl features` source statistics plus a DCT8 transform summary; the + `one-shot-controller` feature, on by default) picks the first probe when the + input is in its confidence envelope, and falls back to the calibrated + starting table when it is not (tiny frames, out-of-envelope features). From + that seed the controller brackets the target by extrapolating the measured + loss slope, aims at the log-loss crossing, and then attaches entropy coding + to only the coarsest qualifying candidates and keeps the smallest exact + stream. Probes reuse the first candidate's cover and chroma-from-luma; a + finalist far from that anchor is re-planned fresh and re-scored. Every + emission is verified at full resolution against the canonical scorer before + it is reported. - **Budgets.** Fast: at most 3 scored probes and 2 exact prices. Balanced: 5 and 3. These are hard caps; there is no hidden exhaustive fallback. - **Reporting.** Every perceptual encode prints one line, @@ -204,7 +209,24 @@ The project has two distinct comparison modes. They must not be conflated. a quality claim. The exhaustive JPXL `Quality` preset is deliberately much slower and is not the speed-parity path. -The tracked harness alternates the encoders on identical P6 PPM inputs, +Two reproduction entry points exist. The portable, dependency-light one is +`JPXL/tools/bench_vs_libjxl.sh`: given a corpus and the oracle binaries from +`tools/setup-oracles.sh`, it emits a `bytes / bpp / SSIMULACRA2 / wall` row per +image and setting for both encoders — every stream decoded and scored with the +*same* in-tree production SSIMULACRA2 — behind a provenance header (UTC date, +host, each binary's version and sha256, the exact flags, per-input hashes and +dimensions), with optional `--jsonl` output. It grades JPXL alone, clearly +marked, when no runnable oracle is present on the host. + +```sh +cd JPXL +# JPXL quality targets vs cjxl distances, one identical PPM corpus, 4 threads: +tools/bench_vs_libjxl.sh --quality "70 85 90" --distance "3.0 1.5 1.0" \ + --effort balanced --threads 4 --runs 3 --jsonl bench.jsonl +``` + +The fuller, tracked harness (`tools/codec_compare.py`, and the Windows-native +`tools/compare-libjxl.ps1`) alternates the encoders on identical P6 PPM inputs, uses an explicit equal thread count, decodes both streams with `djxl`, and records provenance, hashes, bitrate, quality metrics, and timing dispersion. @@ -250,7 +272,18 @@ committing test images or generated streams. ## License -JPXL is licensed under the [MIT License](LICENSE). +JPXL is licensed under the [MIT License](LICENSE) (mirrored at +[JPXL/LICENSE-MIT](JPXL/LICENSE-MIT)). Every workspace crate declares MIT. + +Every third-party dependency is permissive and MIT-compatible, and the +dependency graph contains no copyleft (no GPL/LGPL/AGPL/MPL/CDDL). The +full crate-by-crate audit — including the handful of dependencies that offer +BSD-2/3-Clause rather than MIT, and the measurement-only metrics that stay off +by default — is in +[JPXL/docs/LICENSING-AUDIT.md](JPXL/docs/LICENSING-AUDIT.md). The gitignored +libjxl oracle checkout (BSD-3-Clause), the ISO/IEC standards documents, and the +local test images are working material only: they are not part of the build and +are not distributed. JPEG XL may be subject to patent claims; this repository’s software license does not provide patent advice or a patent grant. From c1cdbf582689914c46af67c1b3e59cacc88b920b Mon Sep 17 00:00:00 2001 From: dk Date: Tue, 25 Aug 2026 16:25:52 +0800 Subject: [PATCH 14/27] feat(jpxl-jpeg): Phase A JPEG-1 coefficient codec, bit-exact re-emission New clean-room crate implementing ISO/IEC 10918-1 at the coefficient level: markers, quant and Huffman tables, frame/scan headers, and entropy-decoded DCT coefficients, re-emitted byte-for-byte. Baseline (SOF0), extended sequential (SOF1), and progressive (SOF2, all four procedures) are covered; restart intervals capture per-segment padding; APPn/COM/unknown markers and post-EOI tail data are preserved verbatim; arithmetic, hierarchical, lossless, and 12-bit inputs are refused with a typed error. No workspace or third-party dependencies, no unsafe, hand-rolled error enum, unit-bearing newtypes. The one non-derivable wire fact is recorded rather than re-derived: libjpeg splits long progressive AC end-of-band runs when its correction-bit buffer fills, and that split is invisible in the coefficients, so decode records the exact EOB-run lengths per scan and encode replays them flush-on-match. A regression fixture with split runs proves the recorded runs are load-bearing. Verified over the full private Pol Art archive via the deterministic roundtrip_sweep example: 5,217 of 5,218 JPEGs byte-identical, zero mismatches (coverage: 3,166 baseline, 2,049 progressive, 2 extended sequential, 4:4:4/4:2:0/4:2:2/4:4:0, grayscale, 684 restart-interval, Exif/ICC/XMP, 41 with trailing data); the one remaining file is a truncated zero-padded JPEG that libjpeg's djpeg also rejects, refused here with a typed error. 34 crate tests pass (17 bit-exact roundtrips on generated fixtures with provenance sidecars, 10 typed refusals, 2 EOB-run regressions, 4 unit, 1 doctest); workspace gates green. AKR-Change: chg-2089f6782d8c12a3 AKR-Work: jpegxl-rs.work.jpeg-bitstream-recompression AKR-Graph: sha256:6eb74db06baef609d12d358f1a48e5fe3a85a55f9db79ea04ef192b562d021f7 AKR-Tree: 6b253d1bb25b15d5996d40e19f502e590449cd65 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/Cargo.lock | 4 + JPXL/Cargo.toml | 1 + JPXL/crates/jpxl-jpeg/Cargo.toml | 15 + .../jpxl-jpeg/examples/roundtrip_sweep.rs | 151 +++++ JPXL/crates/jpxl-jpeg/src/bitio.rs | 292 ++++++++++ JPXL/crates/jpxl-jpeg/src/codec.rs | 448 +++++++++++++++ JPXL/crates/jpxl-jpeg/src/coeff.rs | 82 +++ JPXL/crates/jpxl-jpeg/src/error.rs | 69 +++ JPXL/crates/jpxl-jpeg/src/frame.rs | 104 ++++ JPXL/crates/jpxl-jpeg/src/huffman.rs | 225 ++++++++ JPXL/crates/jpxl-jpeg/src/lib.rs | 57 ++ JPXL/crates/jpxl-jpeg/src/limits.rs | 31 + JPXL/crates/jpxl-jpeg/src/marker.rs | 110 ++++ JPXL/crates/jpxl-jpeg/src/parse.rs | 497 ++++++++++++++++ JPXL/crates/jpxl-jpeg/src/progressive.rs | 544 ++++++++++++++++++ JPXL/crates/jpxl-jpeg/src/quant.rs | 20 + JPXL/crates/jpxl-jpeg/src/scan.rs | 40 ++ JPXL/crates/jpxl-jpeg/src/segment.rs | 123 ++++ JPXL/crates/jpxl-jpeg/src/serialize.rs | 179 ++++++ JPXL/crates/jpxl-jpeg/src/units.rs | 57 ++ .../jpxl-jpeg/tests/fixtures/base_420.jpg | Bin 0 -> 24881 bytes .../tests/fixtures/base_420.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_422.jpg | Bin 0 -> 31956 bytes .../tests/fixtures/base_422.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_440.jpg | Bin 0 -> 31512 bytes .../tests/fixtures/base_440.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_444.jpg | Bin 0 -> 46658 bytes .../tests/fixtures/base_444.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_gray.jpg | Bin 0 -> 18529 bytes .../tests/fixtures/base_gray.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_q20.jpg | Bin 0 -> 6686 bytes .../tests/fixtures/base_q20.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_q98.jpg | Bin 0 -> 106679 bytes .../tests/fixtures/base_q98.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/base_restart.jpg | Bin 0 -> 30457 bytes .../tests/fixtures/base_restart.jpg.prov | 5 + .../tests/fixtures/base_restart_rows.jpg | Bin 0 -> 60519 bytes .../tests/fixtures/base_restart_rows.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/generate.py | 235 ++++++++ .../tests/fixtures/meta_exif_icc.jpg | Bin 0 -> 53329 bytes .../tests/fixtures/meta_exif_icc.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/meta_xmp.jpg | Bin 0 -> 28336 bytes .../tests/fixtures/meta_xmp.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_420.jpg | Bin 0 -> 23810 bytes .../tests/fixtures/prog_420.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_444.jpg | Bin 0 -> 43764 bytes .../tests/fixtures/prog_444.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_gray.jpg | Bin 0 -> 18111 bytes .../tests/fixtures/prog_gray.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_large.jpg | Bin 0 -> 387767 bytes .../tests/fixtures/prog_large.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_q30.jpg | Bin 0 -> 7921 bytes .../tests/fixtures/prog_q30.jpg.prov | 5 + .../jpxl-jpeg/tests/fixtures/prog_restart.jpg | Bin 0 -> 29094 bytes .../tests/fixtures/prog_restart.jpg.prov | 5 + .../tests/fixtures/refuse_arithmetic.jpg | Bin 0 -> 22417 bytes .../tests/fixtures/refuse_arithmetic.jpg.prov | 5 + .../tests/fixtures/trailing_garbage.jpg | Bin 0 -> 46689 bytes .../tests/fixtures/trailing_garbage.jpg.prov | 5 + .../crates/jpxl-jpeg/tests/progressive_eob.rs | 74 +++ JPXL/crates/jpxl-jpeg/tests/refusal.rs | 137 +++++ JPXL/crates/jpxl-jpeg/tests/roundtrip.rs | 189 ++++++ JPXL/tools/oracle-bin/PINNED_REVISIONS.txt | 20 +- 63 files changed, 3789 insertions(+), 10 deletions(-) create mode 100644 JPXL/crates/jpxl-jpeg/Cargo.toml create mode 100644 JPXL/crates/jpxl-jpeg/examples/roundtrip_sweep.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/bitio.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/codec.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/coeff.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/error.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/frame.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/huffman.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/lib.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/limits.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/marker.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/parse.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/progressive.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/quant.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/scan.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/segment.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/serialize.rs create mode 100644 JPXL/crates/jpxl-jpeg/src/units.rs create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_420.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_420.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_422.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_422.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_444.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_444.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_gray.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_gray.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_q20.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_q20.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_q98.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_q98.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart_rows.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart_rows.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/generate.py create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg create mode 100644 JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg.prov create mode 100644 JPXL/crates/jpxl-jpeg/tests/progressive_eob.rs create mode 100644 JPXL/crates/jpxl-jpeg/tests/refusal.rs create mode 100644 JPXL/crates/jpxl-jpeg/tests/roundtrip.rs diff --git a/JPXL/Cargo.lock b/JPXL/Cargo.lock index b908f453..9a33910b 100644 --- a/JPXL/Cargo.lock +++ b/JPXL/Cargo.lock @@ -357,6 +357,10 @@ dependencies = [ "jpxl-core", ] +[[package]] +name = "jpxl-jpeg" +version = "0.3.0" + [[package]] name = "jpxl-perceptual" version = "0.3.0" diff --git a/JPXL/Cargo.toml b/JPXL/Cargo.toml index 2338cb30..1a946ce8 100644 --- a/JPXL/Cargo.toml +++ b/JPXL/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/jpxl-bitstream", "crates/jpxl-core", "crates/jpxl-entropy", + "crates/jpxl-jpeg", "crates/jpxl-decode", "crates/jpxl-encode", "crates/jpxl-encode-policy", diff --git a/JPXL/crates/jpxl-jpeg/Cargo.toml b/JPXL/crates/jpxl-jpeg/Cargo.toml new file mode 100644 index 00000000..c41b41db --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "jpxl-jpeg" +description = "Clean-room ISO/IEC 10918-1 (JPEG1) coefficient-level codec: parse a JPEG bitstream into typed structures and re-emit it byte-for-byte" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# Phase A is a self-contained 10918-1 codec: it deliberately depends on +# nothing in the workspace so all JPEG-1 subtleties are isolated before any +# JPEG XL codestream work (see JPXL/docs/jpeg-recompression-plan.md §3). +[dependencies] + +[lints] +workspace = true diff --git a/JPXL/crates/jpxl-jpeg/examples/roundtrip_sweep.rs b/JPXL/crates/jpxl-jpeg/examples/roundtrip_sweep.rs new file mode 100644 index 00000000..86460123 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/examples/roundtrip_sweep.rs @@ -0,0 +1,151 @@ +//! Archive roundtrip sweep: `serialize(parse(x)) == x` over a directory of +//! JPEG files, with deterministic ordering and a coverage/refusal summary. +//! +//! ```text +//! cargo run --release -p jpxl-jpeg --example roundtrip_sweep -- [stride] +//! ``` +//! +//! Files are collected recursively, sorted by path, and (optionally) sampled +//! by the given stride, so the sample is a pure function of the archive +//! contents. Every file is classified as byte-identical, refused (typed +//! `JpegError::Unsupported`), parse error, or mismatch; the exit code is +//! nonzero if any mismatch occurs. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +fn collect(dir: &Path, out: &mut Vec) -> std::io::Result<()> { + for entry in std::fs::read_dir(dir)? { + let path = entry?.path(); + if path.is_dir() { + collect(&path, out)?; + } else if path + .extension() + .and_then(|e| e.to_str()) + .is_some_and(|e| e.eq_ignore_ascii_case("jpg") || e.eq_ignore_ascii_case("jpeg")) + { + out.push(path); + } + } + Ok(()) +} + +/// Coverage attributes of one parsed JPEG, for the summary table. +fn classify(jpeg: &jpxl_jpeg::Jpeg, tally: &mut BTreeMap<&'static str, usize>) { + let mut bump = |key: &'static str| *tally.entry(key).or_insert(0) += 1; + let mut sof_code = None; + for segment in &jpeg.segments { + match segment { + jpxl_jpeg::Segment::Sof(frame) => { + sof_code = Some(frame.code); + match frame.components.len() { + 1 => bump("grayscale"), + 3 => { + let factors: Vec<(u8, u8)> = + frame.components.iter().map(|c| (c.h, c.v)).collect(); + match factors.first() { + Some(&(2, 2)) => bump("chroma-420"), + Some(&(2, 1)) => bump("chroma-422"), + Some(&(1, 2)) => bump("chroma-440"), + Some(&(1, 1)) => bump("chroma-444"), + _ => bump("chroma-other"), + } + } + _ => bump("components-other"), + } + } + jpxl_jpeg::Segment::Dri(interval) if *interval > 0 => bump("restart-interval"), + jpxl_jpeg::Segment::App(app) => { + if app.code == 0xE1 && app.payload.starts_with(b"Exif\0") { + bump("exif"); + } else if app.code == 0xE1 && app.payload.starts_with(b"http://ns.adobe.com/xap/") { + bump("xmp"); + } else if app.code == 0xE2 && app.payload.starts_with(b"ICC_PROFILE\0") { + bump("icc"); + } + } + _ => {} + } + } + match sof_code { + Some(0xC0) => bump("baseline"), + Some(0xC1) => bump("extended-sequential"), + Some(0xC2) => bump("progressive"), + Some(_) => bump("sof-other"), + None => bump("no-sof"), + } + if !jpeg.tail.is_empty() { + bump("trailing-data"); + } +} + +fn main() -> std::process::ExitCode { + let mut args = std::env::args().skip(1); + let Some(dir) = args.next() else { + eprintln!("usage: roundtrip_sweep [stride]"); + return std::process::ExitCode::FAILURE; + }; + let stride: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(1).max(1); + let mut files = Vec::new(); + if let Err(e) = collect(Path::new(&dir), &mut files) { + eprintln!("walk failed: {e}"); + return std::process::ExitCode::FAILURE; + } + files.sort(); + let sample: Vec<&PathBuf> = files.iter().step_by(stride).collect(); + let (mut identical, mut mismatches, mut parse_errors) = (0usize, 0usize, 0usize); + let mut refusals: BTreeMap = BTreeMap::new(); + let mut coverage: BTreeMap<&'static str, usize> = BTreeMap::new(); + for path in &sample { + let data = match std::fs::read(path) { + Ok(data) => data, + Err(e) => { + eprintln!("READ-ERROR {} {e}", path.display()); + parse_errors += 1; + continue; + } + }; + match jpxl_jpeg::parse(&data) { + Ok(jpeg) => match jpxl_jpeg::serialize(&jpeg) { + Ok(bytes) if bytes == data => { + identical += 1; + classify(&jpeg, &mut coverage); + } + Ok(_) => { + mismatches += 1; + eprintln!("MISMATCH {}", path.display()); + } + Err(e) => { + mismatches += 1; + eprintln!("SERIALIZE-ERROR {} {e}", path.display()); + } + }, + Err(jpxl_jpeg::JpegError::Unsupported(reason)) => { + *refusals.entry(reason).or_insert(0) += 1; + } + Err(e) => { + parse_errors += 1; + eprintln!("PARSE-ERROR {} {e}", path.display()); + } + } + } + println!( + "swept {} of {} files (stride {stride}): {identical} byte-identical, \ + {mismatches} mismatches, {parse_errors} parse errors, {} refused", + sample.len(), + files.len(), + refusals.values().sum::() + ); + for (reason, count) in &refusals { + println!(" refused[{count}]: {reason}"); + } + println!("coverage of the byte-identical set:"); + for (key, count) in &coverage { + println!(" {key}: {count}"); + } + if mismatches == 0 { + std::process::ExitCode::SUCCESS + } else { + std::process::ExitCode::FAILURE + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/bitio.rs b/JPXL/crates/jpxl-jpeg/src/bitio.rs new file mode 100644 index 00000000..0a1a8807 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/bitio.rs @@ -0,0 +1,292 @@ +//! Bit-level I/O for entropy-coded JPEG data (10918-1 F.1.2.1 / E.1.3). +//! +//! Entropy-coded segments are read most-significant-bit first. Two wire +//! conventions live here: +//! +//! * **Byte stuffing** — a `0xFF` data byte is written as `0xFF 0x00`, so a +//! `0xFF` in the entropy stream can only be the start of a marker. +//! * **Marker termination** — a `0xFF` followed by any non-zero code ends the +//! current entropy segment; the reader stops without consuming the marker. +//! +//! Both the reader and writer track just enough state to reproduce the +//! stream's padding bits exactly, which is what makes a byte-for-byte +//! round-trip possible. + +// Every `as u32`/`as u8` narrowing in this module is immediately masked to the +// bits actually requested (`read_bits`/`put_bits` take `n <= 32`), so the +// truncation is intentional and lossless within the requested width. +#![allow(clippy::cast_possible_truncation)] + +use crate::error::{JpegError, Result}; + +/// The padding written after the real bits of an entropy segment to reach a +/// byte boundary (10918-1 F.1.2.3): `nbits` bits with value `bits` +/// (right-aligned). Standard encoders pad with 1-bits; capturing the exact +/// value keeps the round-trip faithful to non-standard encoders too. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Padding { + /// Number of padding bits (`0..=7`). + pub nbits: u8, + /// The padding bit pattern, right-aligned in the low `nbits` bits. + pub bits: u8, +} + +/// Reads bits MSB-first from an entropy-coded region, unstuffing `0xFF 0x00` +/// and stopping at the first marker. +pub struct EntropyReader<'a> { + data: &'a [u8], + /// Index of the next byte to consider. + pos: usize, + /// Pending bits, right-aligned: the next bit to emit is bit `nbits - 1`. + acc: u64, + /// Count of valid pending bits in `acc`. + nbits: u32, + /// Set once a marker terminated the segment; holds the marker code byte. + marker: Option, +} + +impl<'a> EntropyReader<'a> { + /// Creates a reader over `data`, starting at byte `start`. + #[must_use] + pub fn new(data: &'a [u8], start: usize) -> Self { + Self { + data, + pos: start, + acc: 0, + nbits: 0, + marker: None, + } + } + + /// The marker code that terminated the segment, if one has been reached. + #[must_use] + pub fn marker(&self) -> Option { + self.marker + } + + /// The byte offset of the next unconsumed byte (points at the terminating + /// marker's `0xFF` once a marker has been reached). + #[must_use] + pub fn byte_pos(&self) -> usize { + self.pos + } + + /// Pulls one raw entropy byte, handling stuffing and marker detection. + /// + /// Returns `Ok(None)` when a marker is reached (recording its code); + /// otherwise the unstuffed data byte. + fn next_data_byte(&mut self) -> Result> { + if self.marker.is_some() { + return Ok(None); + } + let Some(&b) = self.data.get(self.pos) else { + return Err(JpegError::UnexpectedEof { + while_reading: "entropy-coded data", + }); + }; + if b != 0xFF { + self.pos += 1; + return Ok(Some(b)); + } + // b == 0xFF: skip any run of fill 0xFF bytes to find the code byte. + let mut j = self.pos + 1; + while self.data.get(j) == Some(&0xFF) { + j += 1; + } + let Some(&code) = self.data.get(j) else { + return Err(JpegError::UnexpectedEof { + while_reading: "marker code after 0xFF", + }); + }; + if code == 0x00 { + // Stuffed 0xFF: consume `FF 00`, yield a literal 0xFF byte. + self.pos = j + 1; + return Ok(Some(0xFF)); + } + // A real marker. Leave `pos` on the 0xFF immediately preceding the + // code so the caller sees a contiguous `FF code`, and record it. + self.pos = j - 1; + self.marker = Some(code); + Ok(None) + } + + /// Refills `acc` up to at least `need` valid bits, or until a marker. + fn fill(&mut self, need: u32) -> Result<()> { + while self.nbits < need { + match self.next_data_byte()? { + Some(byte) => { + self.acc = (self.acc << 8) | u64::from(byte); + self.nbits += 8; + } + None => break, + } + } + Ok(()) + } + + /// Reads `n` bits (`0..=32`) MSB-first as an unsigned value. + pub fn read_bits(&mut self, n: u32) -> Result { + if n == 0 { + return Ok(0); + } + self.fill(n)?; + if self.nbits < n { + return Err(JpegError::Malformed(format!( + "F.1.2.1: entropy data exhausted needing {n} bits (only {} available before marker)", + self.nbits + ))); + } + self.nbits -= n; + let mask = if n == 32 { u32::MAX } else { (1u32 << n) - 1 }; + Ok(((self.acc >> self.nbits) as u32) & mask) + } + + /// Reads a single bit. + pub fn read_bit(&mut self) -> Result { + self.read_bits(1) + } + + /// Consumes the remaining sub-byte bits as segment padding and returns + /// them (10918-1 F.1.2.3). Leaves the reader byte-aligned. + pub fn take_padding(&mut self) -> Result { + // Any whole bytes still buffered would mean the decoder under-read the + // segment — a bug worth surfacing rather than silently dropping. + if self.nbits >= 8 { + return Err(JpegError::Malformed(format!( + "F.1.2.3: {} bits buffered at a byte-alignment point (expected < 8)", + self.nbits + ))); + } + let nbits = self.nbits; + let bits = (self.acc & ((1u64 << nbits) - 1)) as u8; + self.nbits = 0; + self.acc = 0; + Ok(Padding { + nbits: nbits as u8, + bits, + }) + } + + /// Ensures the terminating marker is detected while byte-aligned, and + /// returns its code. Call only after [`Self::take_padding`] (i.e. no + /// pending bits): the next bytes must be a marker. + pub fn probe_marker(&mut self) -> Result { + if let Some(c) = self.marker { + return Ok(c); + } + let Some(&b) = self.data.get(self.pos) else { + return Err(JpegError::UnexpectedEof { + while_reading: "marker after entropy segment", + }); + }; + if b != crate::marker::MARKER_PREFIX { + return Err(JpegError::Malformed(format!( + "expected a marker prefix 0xFF after an entropy segment, found 0x{b:02X}" + ))); + } + let mut j = self.pos + 1; + while self.data.get(j) == Some(&0xFF) { + j += 1; + } + let Some(&code) = self.data.get(j) else { + return Err(JpegError::UnexpectedEof { + while_reading: "marker code after entropy segment", + }); + }; + self.pos = j - 1; + self.marker = Some(code); + Ok(code) + } + + /// After a restart marker, advances past its two bytes and resumes reading + /// a fresh (byte-aligned) entropy segment. + pub fn resume_after_restart(&mut self) -> Result<()> { + let code = self.marker.ok_or_else(|| { + JpegError::Malformed("resume_after_restart called with no marker pending".into()) + })?; + if !crate::marker::is_restart(code) { + return Err(JpegError::Malformed(format!( + "expected a restart marker to resume past, found 0xFF{code:02X}" + ))); + } + // `pos` is on the marker's 0xFF; skip the two marker bytes. + self.pos += 2; + self.marker = None; + self.acc = 0; + self.nbits = 0; + Ok(()) + } +} + +/// Writes bits MSB-first into a byte buffer, stuffing `0xFF` data bytes as +/// `0xFF 0x00`. +pub struct EntropyWriter<'a> { + out: &'a mut Vec, + /// Pending bits, right-aligned in `acc`. + acc: u64, + nbits: u32, +} + +impl<'a> EntropyWriter<'a> { + /// Creates a writer appending to `out`. + pub fn new(out: &'a mut Vec) -> Self { + Self { + out, + acc: 0, + nbits: 0, + } + } + + /// Writes the low `n` bits (`0..=32`) of `value`, MSB-first. + pub fn put_bits(&mut self, value: u32, n: u32) { + if n == 0 { + return; + } + let mask = if n == 32 { u32::MAX } else { (1u32 << n) - 1 }; + self.acc = (self.acc << n) | u64::from(value & mask); + self.nbits += n; + while self.nbits >= 8 { + self.nbits -= 8; + let byte = ((self.acc >> self.nbits) & 0xFF) as u8; + self.out.push(byte); + if byte == 0xFF { + self.out.push(0x00); + } + } + } + + /// Writes a single bit. + pub fn put_bit(&mut self, bit: u32) { + self.put_bits(bit & 1, 1); + } + + /// Writes raw bytes directly (e.g. a restart marker) while byte-aligned. + /// + /// Must be called only when no partial byte is pending — i.e. right after + /// [`Self::flush_padding`]. Errors otherwise rather than corrupting the + /// bit stream. + pub fn write_aligned_bytes(&mut self, bytes: &[u8]) -> Result<()> { + if self.nbits != 0 { + return Err(JpegError::Encode( + "write_aligned_bytes called with a partial byte pending".into(), + )); + } + self.out.extend_from_slice(bytes); + Ok(()) + } + + /// Emits the captured segment [`Padding`] to reach a byte boundary. + /// + /// Returns an error if the writer is not byte-aligned afterwards, which can + /// only happen if the re-encoded real bits diverged from the original. + pub fn flush_padding(&mut self, padding: Padding) -> Result<()> { + self.put_bits(u32::from(padding.bits), u32::from(padding.nbits)); + if self.nbits != 0 { + return Err(JpegError::Encode(format!( + "F.1.2.3: {} bits pending after padding — re-encoded bit count diverged", + self.nbits + ))); + } + Ok(()) + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/codec.rs b/JPXL/crates/jpxl-jpeg/src/codec.rs new file mode 100644 index 00000000..05d72399 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/codec.rs @@ -0,0 +1,448 @@ +//! Entropy decode and encode of scan data (10918-1 F.1.2 / F.2.2 baseline; +//! G.1.2 progressive). +//! +//! Decode and encode walk the *same* block enumeration ([`ScanGeom`]) in the +//! same order, so the encoder is a faithful inverse of the decoder rather than +//! a separate interpretation of the scan geometry. That shared layout is what +//! lets `serialize(parse(x)) == x` hold: the coefficients, the Huffman codes, +//! the restart cadence and the padding all line up bit-for-bit. The +//! progressive coder in [`crate::progressive`] reuses this same geometry. + +// Coefficient indices come from `ZIGZAG_TO_NATURAL[k]` with `k` bounded to +// `1..=63`, component/prediction indices are bounded by the component count +// asserted when the vectors were sized, and every `as u8` narrows a value the +// code has just checked to `0..=15` (magnitude categories, run lengths). Bulk +// plane/block access still goes through checked `.get()`. +#![allow(clippy::indexing_slicing, clippy::cast_possible_truncation)] + +use crate::bitio::{EntropyReader, EntropyWriter, Padding}; +use crate::coeff::{extend, magnitude_category, mantissa_bits, to_coeff}; +use crate::error::{JpegError, Result}; +use crate::frame::{FrameGeometry, FrameHeader}; +use crate::huffman::HuffmanTable; +use crate::marker::is_restart; +use crate::scan::ScanHeader; +use crate::segment::ComponentPlane; +use crate::units::ZIGZAG_TO_NATURAL; + +/// Per-scan-component geometry and table bindings. +pub(crate) struct CompGeom<'a> { + /// Index into `frame.components` / `planes`. + pub frame_idx: usize, + /// Horizontal sampling factor. + pub h: usize, + /// Vertical sampling factor. + pub v: usize, + /// DC Huffman table for this component (None for AC-only scans). + pub dc: Option<&'a HuffmanTable>, + /// AC Huffman table for this component (None for DC-only scans). + pub ac: Option<&'a HuffmanTable>, +} + +/// The block-visiting order for one scan. +pub(crate) struct ScanGeom<'a> { + pub interleaved: bool, + pub mcus_per_line: usize, + /// Number of units (MCUs if interleaved, else blocks). + pub num_units: usize, + pub comps: Vec>, + /// For non-interleaved scans, the single component's block-line width. + pub ni_blocks_per_line: usize, +} + +/// A single block reference: which plane and where in it. +pub(crate) struct BlockRef { + pub frame_idx: usize, + pub bx: usize, + pub by: usize, +} + +impl<'a> ScanGeom<'a> { + /// Resolves the scan geometry and Huffman-table bindings. + pub(crate) fn resolve( + frame: &FrameHeader, + geom: &FrameGeometry, + header: &ScanHeader, + dc_tables: &'a [Option; 4], + ac_tables: &'a [Option; 4], + ) -> Result { + let interleaved = header.is_interleaved(); + let need_dc = header.spectral_start == 0; + let need_ac = header.spectral_end != 0; + let mut comps = Vec::with_capacity(header.components.len()); + for sc in &header.components { + let frame_idx = frame + .components + .iter() + .position(|fc| fc.id == sc.id) + .ok_or_else(|| { + JpegError::Malformed(format!( + "B.2.3: scan component id {} not present in frame", + sc.id.0 + )) + })?; + let fc = &frame.components[frame_idx]; + let dc = if need_dc { + Some(pick_table(dc_tables, sc.dc_table, "DC")?) + } else { + None + }; + let ac = if need_ac { + Some(pick_table(ac_tables, sc.ac_table, "AC")?) + } else { + None + }; + comps.push(CompGeom { + frame_idx, + h: fc.h as usize, + v: fc.v as usize, + dc, + ac, + }); + } + + let (num_units, ni_blocks_per_line) = if interleaved { + (geom.mcus_per_line * geom.mcu_rows, 0) + } else { + let idx = comps + .first() + .ok_or_else(|| JpegError::Malformed("B.2.3: scan has no components".into()))? + .frame_idx; + let dims = frame.component_dims(idx, geom)?; + ( + dims.blocks_per_line_noninterleaved * dims.block_rows_noninterleaved, + dims.blocks_per_line_noninterleaved, + ) + }; + + Ok(Self { + interleaved, + mcus_per_line: geom.mcus_per_line, + num_units, + comps, + ni_blocks_per_line, + }) + } + + /// Appends the block references for unit `u` to `out` (in scan order). + pub(crate) fn unit_blocks(&self, u: usize, out: &mut Vec) { + out.clear(); + if self.interleaved { + let mx = u % self.mcus_per_line; + let my = u / self.mcus_per_line; + for c in &self.comps { + for vv in 0..c.v { + for hh in 0..c.h { + out.push(BlockRef { + frame_idx: c.frame_idx, + bx: mx * c.h + hh, + by: my * c.v + vv, + }); + } + } + } + } else if let Some(c) = self.comps.first() { + let bx = u % self.ni_blocks_per_line; + let by = u / self.ni_blocks_per_line; + out.push(BlockRef { + frame_idx: c.frame_idx, + bx, + by, + }); + } + } + + /// The `CompGeom` a block belongs to (by matching `frame_idx`). + pub(crate) fn comp_for(&self, frame_idx: usize) -> Result<&CompGeom<'a>> { + self.comps + .iter() + .find(|c| c.frame_idx == frame_idx) + .ok_or_else(|| JpegError::Malformed("no scan component for block".into())) + } +} + +fn pick_table<'a>( + tables: &'a [Option; 4], + id: u8, + which: &str, +) -> Result<&'a HuffmanTable> { + tables + .get(id as usize) + .and_then(|t| t.as_ref()) + .ok_or_else(|| { + JpegError::Malformed(format!( + "B.2.3: scan selects undefined {which} Huffman table {id}" + )) + }) +} + +pub(crate) fn block_mut( + planes: &mut [ComponentPlane], + frame_idx: usize, + bx: usize, + by: usize, +) -> Result<&mut [i16; 64]> { + let plane = planes + .get_mut(frame_idx) + .ok_or_else(|| JpegError::Malformed("plane index out of range".into()))?; + let stride = plane.blocks_per_line; + plane + .blocks + .get_mut(by * stride + bx) + .ok_or_else(|| JpegError::Malformed("block position out of range".into())) +} + +/// Whether this scan is baseline/sequential (a single full-band scan). +fn is_sequential(frame: &FrameHeader, header: &ScanHeader) -> bool { + !frame.kind.is_progressive() + && header.spectral_start == 0 + && header.spectral_end == 63 + && header.approx_high == 0 + && header.approx_low == 0 +} + +/// Decodes one scan's entropy data into `planes`, returning the per-segment +/// padding and (for a progressive AC scan) the observed EOB-run lengths. +#[allow(clippy::too_many_arguments)] +pub fn decode_scan( + frame: &FrameHeader, + geom: &FrameGeometry, + planes: &mut [ComponentPlane], + header: &ScanHeader, + dc_tables: &[Option; 4], + ac_tables: &[Option; 4], + restart_interval: u16, + reader: &mut EntropyReader<'_>, +) -> Result<(Vec, Vec)> { + if frame.kind.is_progressive() { + return crate::progressive::decode_progressive_scan( + frame, + geom, + planes, + header, + dc_tables, + ac_tables, + restart_interval, + reader, + ); + } + if !is_sequential(frame, header) { + return Err(JpegError::Malformed(format!( + "B.2.3: sequential frame with progressive scan parameters (Ss={}, Se={}, Ah={}, Al={})", + header.spectral_start, header.spectral_end, header.approx_high, header.approx_low + ))); + } + + let sg = ScanGeom::resolve(frame, geom, header, dc_tables, ac_tables)?; + let mut dc_pred = vec![0i32; frame.components.len()]; + let mut padding = Vec::new(); + let mut refs: Vec = Vec::new(); + let ri = restart_interval as usize; + + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + padding.push(finish_restart_segment(reader)?); + for p in dc_pred.iter_mut() { + *p = 0; + } + } + sg.unit_blocks(u, &mut refs); + for r in &refs { + let comp = sg.comp_for(r.frame_idx)?; + let dc_tbl = comp.dc.ok_or_else(|| miss("DC"))?; + let ac_tbl = comp.ac.ok_or_else(|| miss("AC"))?; + let block = block_mut(planes, r.frame_idx, r.bx, r.by)?; + decode_block_sequential(reader, dc_tbl, ac_tbl, &mut dc_pred[r.frame_idx], block)?; + } + } + padding.push(reader.take_padding()?); + Ok((padding, Vec::new())) +} + +pub(crate) fn miss(which: &str) -> JpegError { + JpegError::Malformed(format!("scan component lacks a {which} Huffman table")) +} + +/// Consumes segment padding and the restart marker, resuming a fresh segment. +pub(crate) fn finish_restart_segment(reader: &mut EntropyReader<'_>) -> Result { + let pad = reader.take_padding()?; + let code = reader.probe_marker()?; + if !is_restart(code) { + return Err(JpegError::Malformed(format!( + "expected a restart marker between intervals, found 0xFF{code:02X}" + ))); + } + reader.resume_after_restart()?; + Ok(pad) +} + +fn decode_block_sequential( + reader: &mut EntropyReader<'_>, + dc: &HuffmanTable, + ac: &HuffmanTable, + dc_pred: &mut i32, + block: &mut [i16; 64], +) -> Result<()> { + let t = u32::from(dc.decode(reader)?); + if t > 15 { + return Err(JpegError::Malformed(format!( + "F.1.2: DC magnitude category {t} > 15" + ))); + } + let diff = extend(reader.read_bits(t)?, t); + *dc_pred = dc_pred.wrapping_add(diff); + block[0] = to_coeff(*dc_pred)?; + + let mut k = 1usize; + while k <= 63 { + let rs = ac.decode(reader)?; + let r = (rs >> 4) as usize; + let s = u32::from(rs & 0x0F); + if s == 0 { + if r == 15 { + k += 16; + continue; + } + break; // EOB + } + k += r; + if k > 63 { + return Err(JpegError::Malformed( + "F.1.2: AC run overruns the block (k > 63)".into(), + )); + } + let val = extend(reader.read_bits(s)?, s); + block[ZIGZAG_TO_NATURAL[k] as usize] = to_coeff(val)?; + k += 1; + } + Ok(()) +} + +/// Encodes one scan's entropy data from `planes` into `out`, reproducing the +/// captured padding. `out` receives the raw entropy bytes and any restart +/// markers, but not the terminating marker. +#[allow(clippy::too_many_arguments)] +pub fn encode_scan( + out: &mut Vec, + frame: &FrameHeader, + geom: &FrameGeometry, + planes: &[ComponentPlane], + header: &ScanHeader, + dc_tables: &[Option; 4], + ac_tables: &[Option; 4], + restart_interval: u16, + padding: &[Padding], + eob_runs: &[u32], +) -> Result<()> { + if frame.kind.is_progressive() { + return crate::progressive::encode_progressive_scan( + out, + frame, + geom, + planes, + header, + dc_tables, + ac_tables, + restart_interval, + padding, + eob_runs, + ); + } + if !is_sequential(frame, header) { + return Err(JpegError::Encode( + "sequential frame with progressive scan parameters".into(), + )); + } + + let sg = ScanGeom::resolve(frame, geom, header, dc_tables, ac_tables)?; + let mut dc_pred = vec![0i32; frame.components.len()]; + let ri = restart_interval as usize; + let mut pad_iter = padding.iter(); + let mut refs: Vec = Vec::new(); + let mut restart_counter = 0u8; + + let mut writer = EntropyWriter::new(out); + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + let pad = *pad_iter + .next() + .ok_or_else(|| JpegError::Encode("missing padding for restart segment".into()))?; + writer.flush_padding(pad)?; + writer.write_aligned_bytes(&[0xFF, crate::marker::RST0 + (restart_counter & 7)])?; + restart_counter = restart_counter.wrapping_add(1); + for p in dc_pred.iter_mut() { + *p = 0; + } + } + sg.unit_blocks(u, &mut refs); + for r in &refs { + let comp = sg.comp_for(r.frame_idx)?; + let dc_tbl = comp.dc.ok_or_else(|| miss("DC"))?; + let ac_tbl = comp.ac.ok_or_else(|| miss("AC"))?; + let plane = planes + .get(r.frame_idx) + .ok_or_else(|| JpegError::Encode("plane index out of range".into()))?; + let block = plane + .block(r.bx, r.by) + .ok_or_else(|| JpegError::Encode("block position out of range".into()))?; + encode_block_sequential( + &mut writer, + dc_tbl, + ac_tbl, + &mut dc_pred[r.frame_idx], + block, + )?; + } + } + let pad = *pad_iter + .next() + .ok_or_else(|| JpegError::Encode("missing padding for final segment".into()))?; + writer.flush_padding(pad)?; + Ok(()) +} + +fn encode_block_sequential( + writer: &mut EntropyWriter<'_>, + dc: &HuffmanTable, + ac: &HuffmanTable, + dc_pred: &mut i32, + block: &[i16; 64], +) -> Result<()> { + let diff = i32::from(block[0]) - *dc_pred; + *dc_pred = i32::from(block[0]); + let s = magnitude_category(diff); + if s > 15 { + return Err(JpegError::Encode(format!( + "DC diff {diff} needs category {s} > 15" + ))); + } + dc.encode(writer, s as u8)?; + writer.put_bits(mantissa_bits(diff, s), s); + + let mut run = 0usize; + for k in 1..=63usize { + let coeff = i32::from(block[ZIGZAG_TO_NATURAL[k] as usize]); + if coeff == 0 { + run += 1; + continue; + } + while run >= 16 { + ac.encode(writer, 0xF0)?; // ZRL + run -= 16; + } + let s = magnitude_category(coeff); + if s == 0 || s > 15 { + return Err(JpegError::Encode(format!( + "AC coefficient {coeff} has category {s}" + ))); + } + let rs = ((run as u8) << 4) | (s as u8); + ac.encode(writer, rs)?; + writer.put_bits(mantissa_bits(coeff, s), s); + run = 0; + } + if run > 0 { + ac.encode(writer, 0x00)?; // EOB + } + Ok(()) +} diff --git a/JPXL/crates/jpxl-jpeg/src/coeff.rs b/JPXL/crates/jpxl-jpeg/src/coeff.rs new file mode 100644 index 00000000..458ebef9 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/coeff.rs @@ -0,0 +1,82 @@ +//! Coefficient magnitude coding helpers (10918-1 F.1.2.1, F.2.2.1). +//! +//! JPEG codes a signed coefficient (or DC difference) as a *magnitude +//! category* `S` — the number of significant bits — plus `S` mantissa bits. +//! These are the two directions of that mapping, shared by the baseline and +//! progressive scan coders. + +use crate::error::{JpegError, Result}; + +/// Rebuilds a signed value from `S` received bits (`EXTEND`, F.2.2.1). +#[must_use] +pub fn extend(received: u32, size: u32) -> i32 { + if size == 0 { + return 0; + } + let v = received as i32; + let threshold = 1i32 << (size - 1); + if v < threshold { + v + (-1i32 << size) + 1 + } else { + v + } +} + +/// The magnitude category `S` (number of significant bits) of a value. +#[must_use] +pub fn magnitude_category(value: i32) -> u32 { + let m = value.unsigned_abs(); + 32 - m.leading_zeros() +} + +/// The `S` mantissa bits JPEG appends for `value` (F.1.2.1): the low `size` +/// bits of `value` when non-negative, or of `value - 1` when negative. +#[must_use] +pub fn mantissa_bits(value: i32, size: u32) -> u32 { + if size == 0 { + return 0; + } + let mask = if size >= 32 { + u32::MAX + } else { + (1u32 << size) - 1 + }; + let raw = if value < 0 { value - 1 } else { value } as u32; + raw & mask +} + +/// Narrows a decoded coefficient to `i16`, rejecting values that do not fit — +/// which for an 8-bit frame signals corruption or an out-of-scope precision. +pub fn to_coeff(value: i32) -> Result { + i16::try_from(value).map_err(|_| { + JpegError::Malformed(format!( + "coefficient {value} exceeds the 16-bit range of an 8-bit-precision frame" + )) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extend_is_inverse_of_magnitude_coding() { + // For every value, its (category, mantissa) must decode back exactly. + for v in -4096i32..=4096 { + let s = magnitude_category(v); + let bits = mantissa_bits(v, s); + assert_eq!(extend(bits, s), v, "roundtrip failed for {v} (S={s})"); + } + } + + #[test] + fn magnitude_category_matches_spec_table() { + assert_eq!(magnitude_category(0), 0); + assert_eq!(magnitude_category(1), 1); + assert_eq!(magnitude_category(-1), 1); + assert_eq!(magnitude_category(2), 2); + assert_eq!(magnitude_category(-3), 2); + assert_eq!(magnitude_category(1023), 10); + assert_eq!(magnitude_category(-2047), 11); + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/error.rs b/JPXL/crates/jpxl-jpeg/src/error.rs new file mode 100644 index 00000000..6a3c2f0d --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/error.rs @@ -0,0 +1,69 @@ +//! Error type for the JPEG-1 codec. +//! +//! Follows the project error style (AGENTS.md §6, mirroring +//! `jpxl_core::error`): a hand-rolled enum, no `thiserror`/`anyhow`, with a +//! human-readable message that names the 10918-1 clause and the invariant that +//! failed. Message text is for humans and is not stable. + +use core::fmt; + +/// Anything that can go wrong parsing or re-emitting a JPEG-1 bitstream. +#[derive(Debug)] +#[non_exhaustive] +pub enum JpegError { + /// The stream is truncated: more bytes were required than are present. + UnexpectedEof { + /// What the parser was reading when the bytes ran out. + while_reading: &'static str, + }, + /// The stream violates a requirement of ISO/IEC 10918-1. + /// + /// The message names the clause and the invariant that failed. + Malformed(String), + /// The stream is a well-formed JPEG the codec deliberately refuses. + /// + /// Arithmetic-coded, hierarchical, 12-bit-sample and lossless JPEG modes + /// are out of Phase-A scope; each is rejected here rather than mis-parsed. + Unsupported(String), + /// A resource limit (allocation cap) was exceeded while parsing. + /// + /// Decode paths are attacker-facing (AGENTS.md §6): oversized dimensions or + /// table counts are rejected before any large allocation. + LimitExceeded(String), + /// The re-emitter was asked for something it cannot serialize. + /// + /// A caller/logic error, not a stream error: a coefficient outside the + /// range its magnitude category can express, a Huffman symbol absent from + /// the table it must be coded with. + Encode(String), +} + +impl fmt::Display for JpegError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedEof { while_reading } => { + write!( + f, + "unexpected end of JPEG data while reading {while_reading}" + ) + } + Self::Malformed(msg) => write!(f, "malformed JPEG stream: {msg}"), + Self::Unsupported(msg) => write!(f, "unsupported JPEG feature: {msg}"), + Self::LimitExceeded(msg) => write!(f, "JPEG resource limit exceeded: {msg}"), + Self::Encode(msg) => write!(f, "cannot re-emit JPEG stream: {msg}"), + } + } +} + +impl std::error::Error for JpegError {} + +/// Shorthand for a result carrying a [`JpegError`]. +pub type Result = core::result::Result; + +/// Builds a [`JpegError::Unsupported`] with a formatted message. +macro_rules! unsupported { + ($($arg:tt)*) => { + $crate::error::JpegError::Unsupported(format!($($arg)*)) + }; +} +pub(crate) use unsupported; diff --git a/JPXL/crates/jpxl-jpeg/src/frame.rs b/JPXL/crates/jpxl-jpeg/src/frame.rs new file mode 100644 index 00000000..dc04a124 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/frame.rs @@ -0,0 +1,104 @@ +//! Frame header (`SOF`, 10918-1 B.2.2) and the frame geometry derived from it. + +use crate::error::{JpegError, Result}; +use crate::marker::SofKind; +use crate::units::ComponentId; + +/// One component's parameters from the frame header. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FrameComponent { + /// Component identifier `Ci` (arbitrary label). + pub id: ComponentId, + /// Horizontal sampling factor `Hi`, `1..=4`. + pub h: u8, + /// Vertical sampling factor `Vi`, `1..=4`. + pub v: u8, + /// Quantization-table destination selector `Tqi`. + pub quant_id: u8, +} + +/// A parsed start-of-frame header. +#[derive(Clone, Debug)] +pub struct FrameHeader { + /// The `SOF` marker code byte (e.g. `0xC0`), preserved for re-emission. + pub code: u8, + /// The coding process / entropy class this code selects. + pub kind: SofKind, + /// Sample precision `P` in bits (Phase A supports 8 only). + pub precision: u8, + /// Number of image lines `Y`. + pub height: u16, + /// Number of samples per line `X`. + pub width: u16, + /// Per-component parameters, in header order. + pub components: Vec, +} + +/// The block/MCU geometry a frame implies (10918-1 A.2). +#[derive(Clone, Debug)] +pub struct FrameGeometry { + /// Maximum horizontal sampling factor `Hmax`. + pub hmax: u8, + /// Maximum vertical sampling factor `Vmax`. + pub vmax: u8, + /// Minimum-coded-units per line. + pub mcus_per_line: usize, + /// Minimum-coded-unit rows. + pub mcu_rows: usize, +} + +/// Per-component derived block dimensions. +#[derive(Clone, Copy, Debug)] +pub struct ComponentDims { + /// Blocks per line when the component is scanned interleaved (padded to + /// complete MCUs): `mcus_per_line * Hi`. + pub blocks_per_line_interleaved: usize, + /// Block rows when interleaved: `mcu_rows * Vi`. + pub block_rows_interleaved: usize, + /// Blocks per line when scanned non-interleaved (single-component scan): + /// `ceil(ceil(X * Hi / Hmax) / 8)`. + pub blocks_per_line_noninterleaved: usize, + /// Block rows when non-interleaved: `ceil(ceil(Y * Vi / Vmax) / 8)`. + pub block_rows_noninterleaved: usize, +} + +const fn div_ceil_usize(a: usize, b: usize) -> usize { + a.div_ceil(b) +} + +impl FrameHeader { + /// Computes the frame's MCU geometry. + pub fn geometry(&self) -> Result { + let hmax = self + .components + .iter() + .map(|c| c.h) + .max() + .ok_or_else(|| JpegError::Malformed("B.2.2: frame has no components".into()))?; + let vmax = self.components.iter().map(|c| c.v).max().unwrap_or(1); + let mcus_per_line = div_ceil_usize(self.width as usize, 8 * hmax as usize); + let mcu_rows = div_ceil_usize(self.height as usize, 8 * vmax as usize); + Ok(FrameGeometry { + hmax, + vmax, + mcus_per_line, + mcu_rows, + }) + } + + /// Computes block dimensions for the component at index `idx`. + pub fn component_dims(&self, idx: usize, geom: &FrameGeometry) -> Result { + let c = self + .components + .get(idx) + .ok_or_else(|| JpegError::Malformed(format!("component index {idx} out of range")))?; + let x_i = div_ceil_usize(self.width as usize * c.h as usize, geom.hmax as usize); + let y_i = div_ceil_usize(self.height as usize * c.v as usize, geom.vmax as usize); + Ok(ComponentDims { + blocks_per_line_interleaved: geom.mcus_per_line * c.h as usize, + block_rows_interleaved: geom.mcu_rows * c.v as usize, + blocks_per_line_noninterleaved: div_ceil_usize(x_i, 8), + block_rows_noninterleaved: div_ceil_usize(y_i, 8), + }) + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/huffman.rs b/JPXL/crates/jpxl-jpeg/src/huffman.rs new file mode 100644 index 00000000..2f03d6a1 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/huffman.rs @@ -0,0 +1,225 @@ +//! Huffman tables (10918-1 B.2.4.2 storage; Annex C code assignment; Annex F +//! decode). +//! +//! A `DHT` segment stores, per table, a class/id byte, sixteen count bytes +//! `L_i` (codes of each length 1..=16) and `sum(L_i)` value bytes `V`. The +//! canonical code assignment of Annex C is a pure function of the counts, so +//! the table is re-emitted byte-for-byte from `(class, id, counts, values)` +//! alone. This module derives both the decode tables (Annex F.2.2.1) and the +//! per-symbol encode table from that stored form. + +// Indexing here is into fixed `[_; 16]` / `[_; 17]` / `[_; 256]` tables and +// into `Vec`s whose length is established just above the access, always by an +// index the surrounding loop bounds to range (code length `1..=16`, symbol +// value `0..=255`). The `as` narrowings are range-checked first (a code of +// length `si <= 16` is `< 2^si`, and `l + 1 <= 16`). +#![allow(clippy::indexing_slicing, clippy::cast_possible_truncation)] + +use crate::error::{JpegError, Result}; + +/// A single Huffman table, as stored in a `DHT` segment plus the decode/encode +/// lookups derived from it. +#[derive(Clone, Debug)] +pub struct HuffmanTable { + /// Table class `Tc`: 0 = DC / lossless, 1 = AC. + pub class: u8, + /// Table destination identifier `Th`, `0..=3`. + pub id: u8, + /// `L_i`: the number of codes of each length `1..=16`. + pub counts: [u8; 16], + /// `V`: the symbol values, in canonical order. + pub values: Vec, + /// Per length `l` (indexed `1..=16`): smallest code of that length. + mincode: [i32; 17], + /// Per length `l`: largest code of that length, or `-1` if none. + maxcode: [i32; 17], + /// Per length `l`: index into `values` of the first symbol of that length. + valptr: [usize; 17], + /// Per symbol value: its code length (0 = value not present). + enc_size: [u8; 256], + /// Per symbol value: its code word, right-aligned. + enc_code: [u16; 256], +} + +impl HuffmanTable { + /// Builds a table from its stored form, deriving the lookups. + /// + /// Rejects tables whose counts describe more codes than fit in their length + /// (an over-subscribed / non-prefix code, 10918-1 C.2). + pub fn new(class: u8, id: u8, counts: [u8; 16], values: Vec) -> Result { + let total: usize = counts.iter().map(|&c| c as usize).sum(); + if total != values.len() { + return Err(JpegError::Malformed(format!( + "B.2.4.2: Huffman table sum(L_i)={total} but {} values present", + values.len() + ))); + } + if total == 0 { + return Err(JpegError::Malformed( + "B.2.4.2: empty Huffman table (sum(L_i)=0)".into(), + )); + } + if total > 256 { + return Err(JpegError::Malformed(format!( + "B.2.4.2: Huffman table has {total} codes (>256)" + ))); + } + + // Annex C: HUFFSIZE, then HUFFCODE (canonical, monotone by length). + let mut huffsize: Vec = Vec::with_capacity(total); + for (l, &n) in counts.iter().enumerate() { + for _ in 0..n { + huffsize.push((l + 1) as u8); + } + } + let mut huffcode = vec![0u16; total]; + let mut code: u32 = 0; + let mut si = huffsize[0]; + let mut k = 0usize; + loop { + while k < total && huffsize[k] == si { + if code >= (1u32 << si) { + return Err(JpegError::Malformed(format!( + "C.2: Huffman code of length {si} overflows (over-subscribed table)" + ))); + } + huffcode[k] = code as u16; + code += 1; + k += 1; + } + if k >= total { + break; + } + // Advance to the next used length, shifting the code left each step. + while k < total && huffsize[k] != si { + code <<= 1; + si += 1; + } + } + + // Annex F.2.2.1 decode tables. + let mut mincode = [0i32; 17]; + let mut maxcode = [-1i32; 17]; + let mut valptr = [0usize; 17]; + let mut p = 0usize; + for l in 1..=16usize { + let n = counts[l - 1] as usize; + if n == 0 { + maxcode[l] = -1; + continue; + } + valptr[l] = p; + mincode[l] = i32::from(huffcode[p]); + p += n; + maxcode[l] = i32::from(huffcode[p - 1]); + } + + // Encode lookup, keyed by symbol value. + let mut enc_size = [0u8; 256]; + let mut enc_code = [0u16; 256]; + for (idx, &v) in values.iter().enumerate() { + enc_size[v as usize] = huffsize[idx]; + enc_code[v as usize] = huffcode[idx]; + } + + Ok(Self { + class, + id, + counts, + values, + mincode, + maxcode, + valptr, + enc_size, + enc_code, + }) + } + + /// Decodes one symbol value from `reader` (Annex F.2.2.3, `DECODE`). + pub fn decode(&self, reader: &mut crate::bitio::EntropyReader<'_>) -> Result { + let mut code: i32 = reader.read_bit()? as i32; + let mut l = 1usize; + while code > self.maxcode[l] { + code = (code << 1) | (reader.read_bit()? as i32); + l += 1; + if l > 16 { + return Err(JpegError::Malformed( + "F.2.2.3: no Huffman code of length <= 16 matched".into(), + )); + } + } + let idx = self.valptr[l] + (code - self.mincode[l]) as usize; + self.values.get(idx).copied().ok_or_else(|| { + JpegError::Malformed(format!("F.2.2.3: decoded Huffman index {idx} out of range")) + }) + } + + /// The code length in bits for symbol `value`, or 0 if it is not in the + /// table. + #[must_use] + pub fn code_len(&self, value: u8) -> u8 { + self.enc_size[value as usize] + } + + /// Emits the code word for symbol `value` (Annex F.1.2.2, `ENCODE`). + pub fn encode(&self, writer: &mut crate::bitio::EntropyWriter<'_>, value: u8) -> Result<()> { + let size = self.enc_size[value as usize]; + if size == 0 { + return Err(JpegError::Encode(format!( + "symbol 0x{value:02X} is absent from Huffman table (class {}, id {})", + self.class, self.id + ))); + } + writer.put_bits(u32::from(self.enc_code[value as usize]), u32::from(size)); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bitio::{EntropyReader, EntropyWriter}; + + #[test] + fn rejects_over_subscribed_table() { + // Three codes of length 1 is impossible (only two 1-bit codes exist). + let mut counts = [0u8; 16]; + counts[0] = 3; + assert!(HuffmanTable::new(0, 0, counts, vec![0, 1, 2]).is_err()); + } + + #[test] + fn encode_decode_symbol_stream_roundtrips() { + // One code each of lengths 1..=4: canonical codes 0, 10, 110, 1110. + let mut counts = [0u8; 16]; + counts[0] = 1; + counts[1] = 1; + counts[2] = 1; + counts[3] = 1; + let table = + HuffmanTable::new(1, 0, counts, vec![0x05, 0x11, 0xF0, 0x00]).expect("valid table"); + let symbols = [0x05u8, 0x11, 0x00, 0xF0, 0x05, 0xF0, 0x11, 0x00]; + + let mut bytes = Vec::new(); + let mut total_bits = 0u32; + { + let mut w = EntropyWriter::new(&mut bytes); + for &s in &symbols { + table.encode(&mut w, s).expect("symbol in table"); + total_bits += u32::from(table.code_len(s)); + } + let pad = u8::try_from((8 - (total_bits % 8)) % 8).expect("pad < 8"); + let bits = if pad == 0 { 0 } else { (1u8 << pad) - 1 }; + w.flush_padding(crate::bitio::Padding { nbits: pad, bits }) + .expect("aligned"); + } + // Terminate the entropy region with a marker so the reader has a bound. + bytes.push(0xFF); + bytes.push(0xD9); + + let mut r = EntropyReader::new(&bytes, 0); + for &expected in &symbols { + assert_eq!(table.decode(&mut r).expect("decode"), expected); + } + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/lib.rs b/JPXL/crates/jpxl-jpeg/src/lib.rs new file mode 100644 index 00000000..760a71a9 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/lib.rs @@ -0,0 +1,57 @@ +//! Clean-room ISO/IEC 10918-1 (JPEG-1 / ITU-T T.81) coefficient-level codec. +//! +//! This crate parses a JPEG-1 bitstream into typed structures — markers, +//! quantization tables, Huffman tables, frame and scan headers, and the +//! entropy-decoded DCT coefficient planes — and re-emits a **byte-for-byte +//! identical** JPEG from those structures. It has no pixel path and no IDCT: it +//! stops at quantized coefficients, which is exactly the surface JPEG XL's +//! lossless recompression needs (see `JPXL/docs/jpeg-recompression-plan.md`, +//! Phase A). +//! +//! ```no_run +//! # fn demo(bytes: &[u8]) -> Result<(), jpxl_jpeg::JpegError> { +//! let jpeg = jpxl_jpeg::parse(bytes)?; // -> typed model + coefficients +//! let round = jpxl_jpeg::serialize(&jpeg)?; // exact inverse +//! assert_eq!(bytes, round.as_slice()); // bit-exact round-trip +//! # Ok(()) +//! # } +//! ``` +//! +//! # Scope +//! +//! Supported: baseline sequential (SOF0), extended sequential Huffman (SOF1) +//! and progressive Huffman (SOF2) JPEGs at 8-bit precision, with 1–4 +//! components, any 4:4:4 / 4:2:2 / 4:2:0 / 4:4:0 subsampling, restart +//! intervals, and arbitrary `APPn` / `COM` / trailing-garbage payloads. +//! +//! Refused with a typed [`JpegError::Unsupported`], never mis-decoded: +//! arithmetic coding (SOF9–15, DAC), hierarchical mode (SOF5–7, DHP/EXP), +//! lossless mode (SOF3), and 12-bit samples. +//! +//! # Clean-room +//! +//! Every field order, table layout and entropy-coding rule here derives from +//! ITU-T T.81 / ISO-IEC 10918-1 and this workspace's own reasoning. libjxl is +//! not consulted as a source. + +pub mod bitio; +pub mod codec; +pub mod coeff; +pub mod error; +pub mod frame; +pub mod huffman; +pub mod limits; +pub mod marker; +pub mod parse; +pub mod progressive; +pub mod quant; +pub mod scan; +pub mod segment; +pub mod serialize; +pub mod units; + +pub use error::{JpegError, Result}; +pub use limits::Limits; +pub use parse::{parse, parse_with_limits}; +pub use segment::{ComponentPlane, Jpeg, Segment}; +pub use serialize::serialize; diff --git a/JPXL/crates/jpxl-jpeg/src/limits.rs b/JPXL/crates/jpxl-jpeg/src/limits.rs new file mode 100644 index 00000000..c7547213 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/limits.rs @@ -0,0 +1,31 @@ +//! Resource limits for attacker-facing JPEG parsing (AGENTS.md §6). +//! +//! A JPEG header can claim enormous dimensions or table counts in a handful of +//! bytes. These caps bound every allocation the parser makes on the size of +//! *validated* fields, rejecting hostile inputs before they can exhaust memory. + +/// Caps applied while parsing. +#[derive(Clone, Copy, Debug)] +pub struct Limits { + /// Maximum width or height in samples. + pub max_dimension: u32, + /// Maximum total blocks across all component planes. + pub max_total_blocks: u64, + /// Maximum input length in bytes. + pub max_input_len: usize, +} + +impl Default for Limits { + fn default() -> Self { + Self { + // 65535 is the largest a 16-bit SOF field can express anyway. + max_dimension: 65_535, + // ~1.07e9 blocks ≈ 68 gigapixels of luma; far above any real + // photo, far below anything that could OOM a 64-bit host at + // 128 bytes/block. + max_total_blocks: 1 << 30, + // 512 MiB: larger than any single JPEG this codec targets. + max_input_len: 512 << 20, + } + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/marker.rs b/JPXL/crates/jpxl-jpeg/src/marker.rs new file mode 100644 index 00000000..cd39ab67 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/marker.rs @@ -0,0 +1,110 @@ +//! JPEG marker bytes and start-of-frame classification (10918-1 Table B.1). +//! +//! A marker is two bytes: `0xFF` then a code byte. This module names the codes +//! this codec cares about and, crucially, decides which start-of-frame codes +//! are *in scope* for Phase A and which are refused. + +/// The marker-prefix byte. Every marker is `MARKER_PREFIX` then a code byte. +pub const MARKER_PREFIX: u8 = 0xFF; + +// Standalone markers (no length, no payload). +/// Start of image. +pub const SOI: u8 = 0xD8; +/// End of image. +pub const EOI: u8 = 0xD9; +/// Temporary (arithmetic), standalone. +pub const TEM: u8 = 0x01; + +/// First restart marker `RST0`; restart markers are `RST0..=RST7`. +pub const RST0: u8 = 0xD0; +/// Last restart marker `RST7`. +pub const RST7: u8 = 0xD7; + +// Segment markers (a 2-byte big-endian length follows the code). +/// Define quantization table(s). +pub const DQT: u8 = 0xDB; +/// Define Huffman table(s). +pub const DHT: u8 = 0xC4; +/// Define restart interval. +pub const DRI: u8 = 0xDD; +/// Start of scan. +pub const SOS: u8 = 0xDA; +/// Comment. +pub const COM: u8 = 0xFE; +/// First application segment `APP0`. +pub const APP0: u8 = 0xE0; +/// Last application segment `APP15`. +pub const APP15: u8 = 0xEF; +/// Define arithmetic conditioning (arithmetic coding — refused). +pub const DAC: u8 = 0xCC; +/// Define hierarchical progression (hierarchical mode — refused). +pub const DHP: u8 = 0xDE; +/// Expand reference component (hierarchical mode — refused). +pub const EXP: u8 = 0xDF; +/// Define number of lines. +pub const DNL: u8 = 0xDC; + +/// Whether `code` is a restart marker `RST0..=RST7`. +#[must_use] +pub fn is_restart(code: u8) -> bool { + (RST0..=RST7).contains(&code) +} + +/// Whether `code` names a start-of-frame segment (`SOF0..=SOF15`, excluding the +/// codes `0xC4` DHT, `0xC8` JPG and `0xCC` DAC that fall in the same range). +#[must_use] +pub fn is_sof(code: u8) -> bool { + matches!(code, 0xC0..=0xC3 | 0xC5..=0xC7 | 0xC9..=0xCB | 0xCD..=0xCF) +} + +/// The entropy-coding and process class a start-of-frame code selects, used to +/// decide Phase-A support (10918-1 Table B.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SofKind { + /// Baseline sequential DCT, Huffman (SOF0). Supported. + BaselineSequential, + /// Extended sequential DCT, Huffman (SOF1). Supported (8-bit only). + ExtendedSequential, + /// Progressive DCT, Huffman (SOF2). Supported. + Progressive, + /// Lossless (sequential), Huffman (SOF3). Refused. + Lossless, + /// Differential/hierarchical Huffman (SOF5/6/7). Refused. + Hierarchical, + /// Arithmetic-coded (SOF9/10/11/13/14/15). Refused. + Arithmetic, +} + +impl SofKind { + /// Classifies a start-of-frame code byte. + /// + /// Returns `None` for codes that are not start-of-frame markers. + #[must_use] + pub fn from_code(code: u8) -> Option { + Some(match code { + 0xC0 => Self::BaselineSequential, + 0xC1 => Self::ExtendedSequential, + 0xC2 => Self::Progressive, + 0xC3 => Self::Lossless, + 0xC5..=0xC7 => Self::Hierarchical, + 0xC9..=0xCB | 0xCD..=0xCF => Self::Arithmetic, + _ => return None, + }) + } + + /// Whether this frame kind is decoded by Phase A (Huffman DCT modes only). + #[must_use] + pub fn is_supported(self) -> bool { + matches!( + self, + Self::BaselineSequential | Self::ExtendedSequential | Self::Progressive + ) + } + + /// Whether this scan is coded progressively (spectral selection / successive + /// approximation apply). + #[must_use] + pub fn is_progressive(self) -> bool { + matches!(self, Self::Progressive) + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/parse.rs b/JPXL/crates/jpxl-jpeg/src/parse.rs new file mode 100644 index 00000000..fb0b30bf --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/parse.rs @@ -0,0 +1,497 @@ +//! Parse a JPEG-1 bitstream into the typed [`Jpeg`] document model. +//! +//! The parser walks marker segments (B.1), builds the typed tables and +//! headers, and entropy-decodes every scan into coefficient planes. Everything +//! Annex-A reconstruction needs — segment order, table grouping, restart +//! cadence, padding bits, trailing bytes — is captured so the companion +//! [`crate::serialize`] can reproduce the input byte-for-byte. + +use crate::bitio::EntropyReader; +use crate::codec::decode_scan; +use crate::error::{JpegError, Result, unsupported}; +use crate::frame::{FrameComponent, FrameGeometry, FrameHeader}; +use crate::huffman::HuffmanTable; +use crate::limits::Limits; +use crate::marker::{self, SofKind}; +use crate::quant::QuantTable; +use crate::scan::{ScanComponent, ScanHeader}; +use crate::segment::{AppSegment, ComponentPlane, Jpeg, OtherSegment, ScanSegment, Segment}; +use crate::units::ComponentId; + +/// A forward-only cursor over the byte stream, for reading segment headers. +struct Cursor<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> Cursor<'a> { + fn u8(&mut self, ctx: &'static str) -> Result { + let b = self + .data + .get(self.pos) + .copied() + .ok_or(JpegError::UnexpectedEof { while_reading: ctx })?; + self.pos += 1; + Ok(b) + } + + fn u16(&mut self, ctx: &'static str) -> Result { + let hi = self.u8(ctx)?; + let lo = self.u8(ctx)?; + Ok((u16::from(hi) << 8) | u16::from(lo)) + } + + fn bytes(&mut self, n: usize, ctx: &'static str) -> Result<&'a [u8]> { + let end = self + .pos + .checked_add(n) + .ok_or(JpegError::UnexpectedEof { while_reading: ctx })?; + let slice = self + .data + .get(self.pos..end) + .ok_or(JpegError::UnexpectedEof { while_reading: ctx })?; + self.pos = end; + Ok(slice) + } +} + +/// Parses `data` with default [`Limits`]. +pub fn parse(data: &[u8]) -> Result { + parse_with_limits(data, &Limits::default()) +} + +/// Parses `data`, enforcing `limits` on all size-bearing fields. +pub fn parse_with_limits(data: &[u8], limits: &Limits) -> Result { + if data.len() > limits.max_input_len { + return Err(JpegError::LimitExceeded(format!( + "input is {} bytes (cap {})", + data.len(), + limits.max_input_len + ))); + } + let mut cur = Cursor { data, pos: 0 }; + // SOI. + if cur.u8("SOI prefix")? != marker::MARKER_PREFIX || cur.u8("SOI code")? != marker::SOI { + return Err(JpegError::Malformed( + "stream does not start with SOI (0xFFD8)".into(), + )); + } + + let mut segments: Vec = Vec::new(); + let mut frame: Option = None; + let mut geom: Option = None; + let mut planes: Vec = Vec::new(); + let mut dc_tables: [Option; 4] = Default::default(); + let mut ac_tables: [Option; 4] = Default::default(); + let mut restart_interval: u16 = 0; + let tail; + + loop { + let code = read_marker(&mut cur)?; + match code { + marker::EOI => { + tail = data.get(cur.pos..).unwrap_or(&[]).to_vec(); + break; + } + marker::SOI => return Err(JpegError::Malformed("unexpected second SOI".into())), + marker::TEM => { + // Standalone, no payload. + segments.push(Segment::Other(OtherSegment { + code, + payload: Vec::new(), + })); + } + c if marker::is_restart(c) => { + return Err(JpegError::Malformed( + "restart marker outside an entropy-coded segment".into(), + )); + } + marker::DAC => { + return Err(unsupported!( + "arithmetic coding (DAC, 0xFFCC) is out of scope" + )); + } + marker::DHP | marker::EXP => { + return Err(unsupported!("hierarchical mode (DHP/EXP) is out of scope")); + } + c if marker::is_sof(c) => { + if frame.is_some() { + return Err(JpegError::Malformed("more than one SOF in a frame".into())); + } + let fh = parse_sof(&mut cur, c)?; + let g = fh.geometry()?; + planes = allocate_planes(&fh, &g, limits)?; + geom = Some(g); + segments.push(Segment::Sof(fh.clone())); + frame = Some(fh); + } + marker::DHT => { + let tables = parse_dht(&mut cur)?; + for t in &tables { + let slot = if t.class == 0 { + &mut dc_tables + } else { + &mut ac_tables + }; + // `id` is validated to 0..=3 in `parse_dht`, so this slot + // always exists. + if let Some(dst) = slot.get_mut(t.id as usize) { + *dst = Some(t.clone()); + } + } + segments.push(Segment::Dht(tables)); + } + marker::DQT => { + let tables = parse_dqt(&mut cur)?; + segments.push(Segment::Dqt(tables)); + } + marker::DRI => { + let len = cur.u16("DRI length")?; + if len != 4 { + return Err(JpegError::Malformed(format!( + "B.2.4.4: DRI length {len} != 4" + ))); + } + restart_interval = cur.u16("DRI interval")?; + segments.push(Segment::Dri(restart_interval)); + } + marker::SOS => { + let header = parse_sos(&mut cur)?; + let fh = frame + .as_ref() + .ok_or_else(|| JpegError::Malformed("SOS before SOF".into()))?; + let g = geom + .as_ref() + .ok_or_else(|| JpegError::Malformed("SOS before SOF".into()))?; + let mut reader = EntropyReader::new(data, cur.pos); + let (padding, eob_runs) = decode_scan( + fh, + g, + &mut planes, + &header, + &dc_tables, + &ac_tables, + restart_interval, + &mut reader, + )?; + cur.pos = reader.byte_pos(); + segments.push(Segment::Sos(ScanSegment { + header, + padding, + eob_runs, + })); + } + c @ (marker::APP0..=marker::APP15) => { + let payload = read_length_payload(&mut cur, "APPn")?; + segments.push(Segment::App(AppSegment { code: c, payload })); + } + marker::COM => { + let payload = read_length_payload(&mut cur, "COM")?; + segments.push(Segment::Com(payload)); + } + other => { + // Any other length-bearing marker (e.g. DNL): preserve verbatim. + let payload = read_length_payload(&mut cur, "marker segment")?; + segments.push(Segment::Other(OtherSegment { + code: other, + payload, + })); + } + } + } + + if frame.is_none() { + return Err(JpegError::Malformed("no SOF segment before EOI".into())); + } + + Ok(Jpeg { + segments, + frame, + planes, + tail, + }) +} + +/// Reads the next marker code, skipping any leading `0xFF` fill bytes. +fn read_marker(cur: &mut Cursor<'_>) -> Result { + let mut saw_ff = false; + loop { + let b = cur.u8("marker")?; + if b == marker::MARKER_PREFIX { + saw_ff = true; + continue; + } + if !saw_ff { + return Err(JpegError::Malformed(format!( + "expected a marker prefix 0xFF, found 0x{b:02X}" + ))); + } + if b == 0x00 { + return Err(JpegError::Malformed( + "0xFF00 where a marker was expected".into(), + )); + } + return Ok(b); + } +} + +/// Reads a `[length][payload]` segment body, returning the payload. +fn read_length_payload(cur: &mut Cursor<'_>, ctx: &'static str) -> Result> { + let len = cur.u16(ctx)?; + if len < 2 { + return Err(JpegError::Malformed(format!( + "{ctx} segment length {len} < 2" + ))); + } + Ok(cur.bytes(len as usize - 2, ctx)?.to_vec()) +} + +fn parse_sof(cur: &mut Cursor<'_>, code: u8) -> Result { + let kind = SofKind::from_code(code) + .ok_or_else(|| JpegError::Malformed(format!("0xFF{code:02X} is not a SOF marker")))?; + match kind { + SofKind::Arithmetic => { + return Err(unsupported!("arithmetic-coded JPEG (SOF 0x{code:02X})")); + } + SofKind::Hierarchical => { + return Err(unsupported!("hierarchical JPEG (SOF 0x{code:02X})")); + } + SofKind::Lossless => { + return Err(unsupported!("lossless JPEG (SOF3)")); + } + _ => {} + } + let len = cur.u16("SOF length")?; + let precision = cur.u8("SOF precision")?; + if precision != 8 { + return Err(unsupported!( + "{precision}-bit samples (only 8-bit precision is in scope)" + )); + } + let height = cur.u16("SOF Y")?; + let width = cur.u16("SOF X")?; + let nf = cur.u8("SOF Nf")?; + if nf == 0 || nf > 4 { + return Err(JpegError::Malformed(format!( + "B.2.2: component count Nf={nf} not in 1..=4" + ))); + } + let expected_len = 8 + 3 * nf as u16; + if len != expected_len { + return Err(JpegError::Malformed(format!( + "B.2.2: SOF length {len} != {expected_len} for Nf={nf}" + ))); + } + if width == 0 { + return Err(JpegError::Malformed("B.2.2: SOF X (width) is 0".into())); + } + // Y == 0 is legal (lines defined later by DNL); reject it as out of scope + // rather than mis-parse, since DNL handling is not implemented. + if height == 0 { + return Err(unsupported!("SOF Y (height) = 0 with deferred DNL height")); + } + let mut components = Vec::with_capacity(nf as usize); + for _ in 0..nf { + let id = ComponentId(cur.u8("SOF component id")?); + let hv = cur.u8("SOF sampling")?; + let h = hv >> 4; + let v = hv & 0x0F; + if !(1..=4).contains(&h) || !(1..=4).contains(&v) { + return Err(JpegError::Malformed(format!( + "B.2.2: sampling factors H={h} V={v} not in 1..=4" + ))); + } + let quant_id = cur.u8("SOF Tq")?; + if quant_id > 3 { + return Err(JpegError::Malformed(format!( + "B.2.2: quant selector Tq={quant_id} > 3" + ))); + } + components.push(FrameComponent { id, h, v, quant_id }); + } + // Component ids must be distinct (they are matched by SOS). + let mut seen: Vec = Vec::with_capacity(components.len()); + for c in &components { + if seen.contains(&c.id.0) { + return Err(JpegError::Malformed(format!( + "B.2.2: duplicate component id {}", + c.id.0 + ))); + } + seen.push(c.id.0); + } + Ok(FrameHeader { + code, + kind, + precision, + height, + width, + components, + }) +} + +fn allocate_planes( + fh: &FrameHeader, + geom: &FrameGeometry, + limits: &Limits, +) -> Result> { + let mut planes = Vec::with_capacity(fh.components.len()); + let mut total: u64 = 0; + for (idx, fc) in fh.components.iter().enumerate() { + let dims = fh.component_dims(idx, geom)?; + let bpl = dims.blocks_per_line_interleaved; + let bh = dims.block_rows_interleaved; + let count = bpl + .checked_mul(bh) + .ok_or_else(|| JpegError::LimitExceeded("block count overflow".into()))?; + total = total.saturating_add(count as u64); + if total > limits.max_total_blocks { + return Err(JpegError::LimitExceeded(format!( + "total blocks {total} exceed cap {}", + limits.max_total_blocks + ))); + } + planes.push(ComponentPlane { + id: fc.id, + h: fc.h, + v: fc.v, + blocks_per_line: bpl, + block_rows: bh, + blocks: vec![[0i16; 64]; count], + }); + } + Ok(planes) +} + +fn parse_dht(cur: &mut Cursor<'_>) -> Result> { + let len = cur.u16("DHT length")?; + if len < 2 { + return Err(JpegError::Malformed("DHT length < 2".into())); + } + let end = cur.pos + len as usize - 2; + let mut tables = Vec::new(); + while cur.pos < end { + let tc_th = cur.u8("DHT Tc/Th")?; + let class = tc_th >> 4; + let id = tc_th & 0x0F; + if class > 1 { + return Err(JpegError::Malformed(format!( + "B.2.4.2: Huffman table class Tc={class} > 1" + ))); + } + if id > 3 { + return Err(JpegError::Malformed(format!( + "B.2.4.2: Huffman table id Th={id} > 3" + ))); + } + let mut counts = [0u8; 16]; + let count_bytes = cur.bytes(16, "DHT counts")?; + counts.copy_from_slice(count_bytes); + let total: usize = counts.iter().map(|&c| c as usize).sum(); + let values = cur.bytes(total, "DHT values")?.to_vec(); + tables.push(HuffmanTable::new(class, id, counts, values)?); + } + if cur.pos != end { + return Err(JpegError::Malformed( + "DHT segment length does not match its tables".into(), + )); + } + Ok(tables) +} + +fn parse_dqt(cur: &mut Cursor<'_>) -> Result> { + let len = cur.u16("DQT length")?; + if len < 2 { + return Err(JpegError::Malformed("DQT length < 2".into())); + } + let end = cur.pos + len as usize - 2; + let mut tables = Vec::new(); + while cur.pos < end { + let pq_tq = cur.u8("DQT Pq/Tq")?; + let precision = pq_tq >> 4; + let id = pq_tq & 0x0F; + if precision > 1 { + return Err(JpegError::Malformed(format!( + "B.2.4.1: quant precision Pq={precision} > 1" + ))); + } + if id > 3 { + return Err(JpegError::Malformed(format!( + "B.2.4.1: quant table id Tq={id} > 3" + ))); + } + let mut values = [0u16; 64]; + if precision == 0 { + let raw = cur.bytes(64, "DQT 8-bit values")?; + for (dst, &src) in values.iter_mut().zip(raw) { + *dst = u16::from(src); + } + } else { + let raw = cur.bytes(128, "DQT 16-bit values")?; + for (dst, pair) in values.iter_mut().zip(raw.chunks_exact(2)) { + *dst = u16::from_be_bytes(pair.try_into().expect("chunks_exact(2)")); + } + } + tables.push(QuantTable { + precision, + id, + values, + }); + } + if cur.pos != end { + return Err(JpegError::Malformed( + "DQT segment length does not match its tables".into(), + )); + } + Ok(tables) +} + +fn parse_sos(cur: &mut Cursor<'_>) -> Result { + let len = cur.u16("SOS length")?; + let ns = cur.u8("SOS Ns")?; + if ns == 0 || ns > 4 { + return Err(JpegError::Malformed(format!( + "B.2.3: scan component count Ns={ns} not in 1..=4" + ))); + } + let expected_len = 6 + 2 * ns as u16; + if len != expected_len { + return Err(JpegError::Malformed(format!( + "B.2.3: SOS length {len} != {expected_len} for Ns={ns}" + ))); + } + let mut components = Vec::with_capacity(ns as usize); + for _ in 0..ns { + let id = ComponentId(cur.u8("SOS Cs")?); + let td_ta = cur.u8("SOS Td/Ta")?; + let dc_table = td_ta >> 4; + let ac_table = td_ta & 0x0F; + if dc_table > 3 || ac_table > 3 { + return Err(JpegError::Malformed(format!( + "B.2.3: table selectors Td={dc_table} Ta={ac_table} > 3" + ))); + } + components.push(ScanComponent { + id, + dc_table, + ac_table, + }); + } + let spectral_start = cur.u8("SOS Ss")?; + let spectral_end = cur.u8("SOS Se")?; + let approx = cur.u8("SOS Ah/Al")?; + let approx_high = approx >> 4; + let approx_low = approx & 0x0F; + if spectral_start > 63 || spectral_end > 63 { + return Err(JpegError::Malformed(format!( + "B.2.3: spectral selection Ss={spectral_start} Se={spectral_end} out of 0..=63" + ))); + } + Ok(ScanHeader { + components, + spectral_start, + spectral_end, + approx_high, + approx_low, + }) +} diff --git a/JPXL/crates/jpxl-jpeg/src/progressive.rs b/JPXL/crates/jpxl-jpeg/src/progressive.rs new file mode 100644 index 00000000..f9a64bc1 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/progressive.rs @@ -0,0 +1,544 @@ +//! Progressive DCT scan coding (10918-1 G.1.2): DC and AC, first and +//! refinement scans, with spectral selection and successive approximation. +//! +//! Each coefficient is coded across several scans. The decoder accumulates the +//! full coefficient into the plane; the encoder re-derives every scan's symbols +//! from that final coefficient with the matching *point transform*: an +//! arithmetic (floor) shift for DC, a toward-zero shift for AC. Because the +//! plane holds the fully-refined value, `serialize(parse(x)) == x` reduces to +//! each scan's coder being an exact inverse of the other — no intermediate +//! per-scan state is stored. +//! +//! The EOB-run batching and the interleaving of correction bits with +//! zero-run-length (`ZRL`) codes follow the sequence the reference procedures +//! produce, so a stream from a standard encoder is reproduced bit-for-bit. + +// Coefficient indices come from `nat(k)` (masked to `0..=63`); `run`/`rem` are +// bounded to `0..=15` before the `as u8` run-length narrowings, and EOB-run and +// correction-bit buffers are indexed by cursors the loops keep in range. Bulk +// plane/block access goes through checked `.get()`. +#![allow(clippy::indexing_slicing, clippy::cast_possible_truncation)] + +use crate::bitio::{EntropyReader, EntropyWriter, Padding}; +use crate::codec::{ScanGeom, block_mut, finish_restart_segment, miss}; +use crate::coeff::{extend, magnitude_category, mantissa_bits, to_coeff}; +use crate::error::{JpegError, Result}; +use crate::frame::{FrameGeometry, FrameHeader}; +use crate::huffman::HuffmanTable; +use crate::scan::ScanHeader; +use crate::segment::ComponentPlane; +use crate::units::ZIGZAG_TO_NATURAL; + +#[inline] +fn nat(k: usize) -> usize { + ZIGZAG_TO_NATURAL[k & 63] as usize +} + +/// DC point transform (encoder side): arithmetic floor shift by `al`. +#[inline] +fn dc_point(v: i32, al: u8) -> i32 { + v >> al +} + +/// AC point transform (encoder side): division by `2^al` rounded toward zero. +#[inline] +fn ac_point(v: i32, al: u8) -> i32 { + if v >= 0 { v >> al } else { -((-v) >> al) } +} + +/// Decodes one progressive scan into `planes`, returning per-segment padding +/// and the observed EOB-run lengths (empty for DC scans). +#[allow(clippy::too_many_arguments)] +pub fn decode_progressive_scan( + frame: &FrameHeader, + geom: &FrameGeometry, + planes: &mut [ComponentPlane], + header: &ScanHeader, + dc_tables: &[Option; 4], + ac_tables: &[Option; 4], + restart_interval: u16, + reader: &mut EntropyReader<'_>, +) -> Result<(Vec, Vec)> { + let sg = ScanGeom::resolve(frame, geom, header, dc_tables, ac_tables)?; + let ss = header.spectral_start as usize; + let se = header.spectral_end as usize; + let ah = header.approx_high; + let al = header.approx_low; + let ri = restart_interval as usize; + let mut padding = Vec::new(); + let mut eob_runs: Vec = Vec::new(); + let mut refs = Vec::new(); + + if ss == 0 { + // DC scan (may be interleaved). + let mut dc_pred = vec![0i32; frame.components.len()]; + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + padding.push(finish_restart_segment(reader)?); + for p in dc_pred.iter_mut() { + *p = 0; + } + } + sg.unit_blocks(u, &mut refs); + for r in &refs { + let comp = sg.comp_for(r.frame_idx)?; + let dc_tbl = comp.dc.ok_or_else(|| miss("DC"))?; + let block = block_mut(planes, r.frame_idx, r.bx, r.by)?; + if ah == 0 { + let t = u32::from(dc_tbl.decode(reader)?); + if t > 15 { + return Err(JpegError::Malformed(format!( + "G.1.2.1: DC magnitude category {t} > 15" + ))); + } + let diff = extend(reader.read_bits(t)?, t); + let pred = &mut dc_pred[r.frame_idx]; + *pred = pred.wrapping_add(diff); + block[0] = to_coeff(*pred << al)?; + } else if reader.read_bit()? == 1 { + let v = i32::from(block[0]) | (1i32 << al); + block[0] = to_coeff(v)?; + } + } + } + } else { + // AC scan (single component, non-interleaved). + let mut eobrun: u32 = 0; + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + padding.push(finish_restart_segment(reader)?); + eobrun = 0; + } + sg.unit_blocks(u, &mut refs); + let r = &refs[0]; + let comp = sg.comp_for(r.frame_idx)?; + let ac_tbl = comp.ac.ok_or_else(|| miss("AC"))?; + let block = block_mut(planes, r.frame_idx, r.bx, r.by)?; + if ah == 0 { + decode_ac_first( + reader, + ac_tbl, + block, + ss, + se, + al, + &mut eobrun, + &mut eob_runs, + )?; + } else { + decode_ac_refine( + reader, + ac_tbl, + block, + ss, + se, + al, + &mut eobrun, + &mut eob_runs, + )?; + } + } + } + padding.push(reader.take_padding()?); + Ok((padding, eob_runs)) +} + +#[allow(clippy::too_many_arguments)] +fn decode_ac_first( + reader: &mut EntropyReader<'_>, + ac: &HuffmanTable, + block: &mut [i16; 64], + ss: usize, + se: usize, + al: u8, + eobrun: &mut u32, + eob_runs: &mut Vec, +) -> Result<()> { + if *eobrun > 0 { + *eobrun -= 1; + return Ok(()); + } + let mut k = ss; + while k <= se { + let rs = ac.decode(reader)?; + let r = (rs >> 4) as usize; + let s = u32::from(rs & 0x0F); + if s == 0 { + if r != 15 { + let mut run = 1u32 << r; + if r > 0 { + run += reader.read_bits(r as u32)?; + } + eob_runs.push(run); + *eobrun = run - 1; + break; + } + k += 16; // ZRL: 16 zero coefficients + } else { + k += r; + if k > se { + return Err(JpegError::Malformed( + "G.1.2.2: AC-first run overruns the band".into(), + )); + } + let val = extend(reader.read_bits(s)?, s); + block[nat(k)] = to_coeff(val << al)?; + k += 1; + } + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn decode_ac_refine( + reader: &mut EntropyReader<'_>, + ac: &HuffmanTable, + block: &mut [i16; 64], + ss: usize, + se: usize, + al: u8, + eobrun: &mut u32, + eob_runs: &mut Vec, +) -> Result<()> { + let p1: i32 = 1 << al; + let m1: i32 = -(1 << al); + let mut k = ss; + + if *eobrun == 0 { + while k <= se { + let rs = ac.decode(reader)?; + let mut r = (rs >> 4) as i32; + let s = u32::from(rs & 0x0F); + let mut newval: i32 = 0; + if s == 0 { + if r != 15 { + let mut run = 1u32 << r; + if r > 0 { + run += reader.read_bits(r as u32)?; + } + eob_runs.push(run); + *eobrun = run; + break; + } + // r == 15: ZRL, skip 16 zero-history coefficients. + } else { + if s != 1 { + return Err(JpegError::Malformed( + "G.1.2.3: refinement coefficient size != 1".into(), + )); + } + let bit = reader.read_bit()?; + newval = if bit == 1 { p1 } else { m1 }; + } + // Advance over zero-history coefficients, correcting nonzero ones. + loop { + let coef = i32::from(block[nat(k)]); + if coef != 0 { + let cb = reader.read_bit()?; + if cb == 1 && (coef & p1) == 0 { + block[nat(k)] = to_coeff(coef + if coef >= 0 { p1 } else { m1 })?; + } + } else { + r -= 1; + if r < 0 { + break; + } + } + k += 1; + if k > se { + break; + } + } + if s != 0 { + if k > se { + return Err(JpegError::Malformed( + "G.1.2.3: refinement coefficient position past band end".into(), + )); + } + block[nat(k)] = to_coeff(newval)?; + } + k += 1; + } + } + + if *eobrun > 0 { + while k <= se { + let coef = i32::from(block[nat(k)]); + if coef != 0 { + let cb = reader.read_bit()?; + if cb == 1 && (coef & p1) == 0 { + block[nat(k)] = to_coeff(coef + if coef >= 0 { p1 } else { m1 })?; + } + } + k += 1; + } + *eobrun -= 1; + } + Ok(()) +} + +/// Encodes one progressive scan from `planes` into `out`. +#[allow(clippy::too_many_arguments)] +pub fn encode_progressive_scan( + out: &mut Vec, + frame: &FrameHeader, + geom: &FrameGeometry, + planes: &[ComponentPlane], + header: &ScanHeader, + dc_tables: &[Option; 4], + ac_tables: &[Option; 4], + restart_interval: u16, + padding: &[Padding], + eob_runs: &[u32], +) -> Result<()> { + let sg = ScanGeom::resolve(frame, geom, header, dc_tables, ac_tables)?; + let ss = header.spectral_start as usize; + let se = header.spectral_end as usize; + let ah = header.approx_high; + let al = header.approx_low; + let ri = restart_interval as usize; + let mut pad_iter = padding.iter(); + let mut refs = Vec::new(); + let mut restart_counter = 0u8; + let mut writer = EntropyWriter::new(out); + + /// Closes the current entropy segment at a restart boundary: emits padding + /// and the cycling `RSTn` marker. + fn emit_restart( + writer: &mut EntropyWriter<'_>, + pad_iter: &mut std::slice::Iter<'_, Padding>, + restart_counter: &mut u8, + ) -> Result<()> { + let pad = *pad_iter + .next() + .ok_or_else(|| JpegError::Encode("missing padding for restart segment".into()))?; + writer.flush_padding(pad)?; + writer.write_aligned_bytes(&[0xFF, crate::marker::RST0 + (*restart_counter & 7)])?; + *restart_counter = restart_counter.wrapping_add(1); + Ok(()) + } + + if ss == 0 { + let mut dc_pred = vec![0i32; frame.components.len()]; + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + emit_restart(&mut writer, &mut pad_iter, &mut restart_counter)?; + for p in dc_pred.iter_mut() { + *p = 0; + } + } + sg.unit_blocks(u, &mut refs); + for r in &refs { + let comp = sg.comp_for(r.frame_idx)?; + let plane = planes + .get(r.frame_idx) + .ok_or_else(|| JpegError::Encode("plane index out of range".into()))?; + let block = plane + .block(r.bx, r.by) + .ok_or_else(|| JpegError::Encode("block position out of range".into()))?; + if ah == 0 { + let dc_tbl = comp.dc.ok_or_else(|| miss("DC"))?; + let v = dc_point(i32::from(block[0]), al); + let diff = v - dc_pred[r.frame_idx]; + dc_pred[r.frame_idx] = v; + let s = magnitude_category(diff); + if s > 15 { + return Err(JpegError::Encode(format!( + "DC diff {diff} needs category {s} > 15" + ))); + } + dc_tbl.encode(&mut writer, s as u8)?; + writer.put_bits(mantissa_bits(diff, s), s); + } else { + let bit = (i32::from(block[0]) >> al) & 1; + writer.put_bit(bit as u32); + } + } + } + } else { + let ac_tbl = sg + .comps + .first() + .and_then(|c| c.ac) + .ok_or_else(|| miss("AC"))?; + // EOB runs are replayed at exactly the lengths the original encoder + // used (`eob_runs`, recorded at decode), rather than re-derived: an + // encoder may split a run into several `EOBn` codes, and that split is + // not recoverable from the coefficients alone. + let mut eobrun: u32 = 0; + let mut be: Vec = Vec::new(); + let mut ei = 0usize; + for u in 0..sg.num_units { + if ri != 0 && u != 0 && u % ri == 0 { + // A restart resets the decoder's EOB run, so any pending run was + // flushed before the marker; `eobrun` is already 0 here after the + // match-flush below, but guard defensively. + if eobrun > 0 { + emit_eobrun(&mut writer, ac_tbl, &mut eobrun, &mut be)?; + ei += 1; + } + emit_restart(&mut writer, &mut pad_iter, &mut restart_counter)?; + } + sg.unit_blocks(u, &mut refs); + let r = &refs[0]; + let plane = planes + .get(r.frame_idx) + .ok_or_else(|| JpegError::Encode("plane index out of range".into()))?; + let block = plane + .block(r.bx, r.by) + .ok_or_else(|| JpegError::Encode("block position out of range".into()))?; + let contributes = if ah == 0 { + encode_ac_first(&mut writer, ac_tbl, block, ss, se, al)? + } else { + encode_ac_refine(&mut writer, ac_tbl, block, ss, se, al, &mut be)? + }; + if contributes { + eobrun += 1; + // Flush at exactly the recorded run length (handles both natural + // ends at the next significant block and mid-run encoder splits). + if ei < eob_runs.len() && eobrun == eob_runs[ei] { + emit_eobrun(&mut writer, ac_tbl, &mut eobrun, &mut be)?; + ei += 1; + } + } + } + // Flush any final run (its length is the last recorded value). + if eobrun > 0 { + emit_eobrun(&mut writer, ac_tbl, &mut eobrun, &mut be)?; + } + } + + let pad = *pad_iter + .next() + .ok_or_else(|| JpegError::Encode("missing padding for final segment".into()))?; + writer.flush_padding(pad)?; + Ok(()) +} + +/// Emits a pending EOB run code (if any) and any buffered correction bits. +fn emit_eobrun( + writer: &mut EntropyWriter<'_>, + ac: &HuffmanTable, + eobrun: &mut u32, + be: &mut Vec, +) -> Result<()> { + if *eobrun > 0 { + let e = *eobrun; + let n = 31 - e.leading_zeros(); // floor(log2(e)) + ac.encode(writer, (n as u8) << 4)?; + if n > 0 { + writer.put_bits(e - (1 << n), n); + } + *eobrun = 0; + } + for &b in be.iter() { + writer.put_bit(u32::from(b)); + } + be.clear(); + Ok(()) +} + +/// Encodes one AC-first block. Returns whether the band ends in a run of zeros +/// (so the caller extends the pending EOB run). EOB-run flushing is the +/// caller's job (it replays the recorded run lengths). +fn encode_ac_first( + writer: &mut EntropyWriter<'_>, + ac: &HuffmanTable, + block: &[i16; 64], + ss: usize, + se: usize, + al: u8, +) -> Result { + let mut run = 0usize; + for k in ss..=se { + let coef = ac_point(i32::from(block[nat(k)]), al); + if coef == 0 { + run += 1; + continue; + } + while run >= 16 { + ac.encode(writer, 0xF0)?; // ZRL + run -= 16; + } + let s = magnitude_category(coef); + if s == 0 || s > 15 { + return Err(JpegError::Encode(format!( + "AC-first coefficient {coef} has category {s}" + ))); + } + ac.encode(writer, ((run as u8) << 4) | (s as u8))?; + writer.put_bits(mantissa_bits(coef, s), s); + run = 0; + } + // Trailing zeros (or a wholly-zero band) begin/extend an EOB run. + Ok(run > 0) +} + +/// Encodes one AC-refinement block. Returns whether the band ends in a run +/// (trailing zeros or trailing already-significant corrections), so the caller +/// extends the pending EOB run; trailing correction bits are appended to `be` +/// for the caller to emit when it flushes that run. +/// +/// `run` counts consecutive zero-history (still-insignificant) coefficients. +/// Already-significant coefficients are transparent to the run; their correction +/// bits are buffered with the zero-count that preceded them, so that when a +/// newly-significant coefficient forces `ZRL` codes, the corrections replay in +/// the same 16-zero spans the decoder walks (10918-1 G.1.2.3). Only +/// newly-significant coefficients emit `ZRL`s; a block with none is folded into +/// the EOB run. +fn encode_ac_refine( + writer: &mut EntropyWriter<'_>, + ac: &HuffmanTable, + block: &[i16; 64], + ss: usize, + se: usize, + al: u8, + be: &mut Vec, +) -> Result { + let mut run = 0usize; + let mut br: Vec<(usize, u8)> = Vec::new(); // (zeros seen before this correction, bit) + for k in ss..=se { + let coef = i32::from(block[nat(k)]); + let mag = coef.abs() >> al; // magnitude at this scan + if mag == 0 { + run += 1; + continue; + } + if mag > 1 { + // Already significant: buffer its correction bit; the run continues. + br.push((run, (mag & 1) as u8)); + continue; + } + // Newly significant (mag == 1). + let num_zrl = run / 16; + let rem = run % 16; + let mut bi = 0usize; + for j in 0..num_zrl { + ac.encode(writer, 0xF0)?; // ZRL + while bi < br.len() && br[bi].0 / 16 == j { + writer.put_bit(u32::from(br[bi].1)); + bi += 1; + } + } + ac.encode(writer, ((rem as u8) << 4) | 1)?; + let sign = if coef < 0 { 0 } else { 1 }; + writer.put_bit(sign); + // Corrections in the final (remainder) span follow the coefficient code. + while bi < br.len() { + writer.put_bit(u32::from(br[bi].1)); + bi += 1; + } + br.clear(); + run = 0; + } + if run > 0 || !br.is_empty() { + // Trailing zeros / already-significant corrections extend an EOB run; + // their correction bits are emitted when that run is flushed. + for &(_, bit) in &br { + be.push(bit); + } + Ok(true) + } else { + Ok(false) + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/quant.rs b/JPXL/crates/jpxl-jpeg/src/quant.rs new file mode 100644 index 00000000..dba94d9a --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/quant.rs @@ -0,0 +1,20 @@ +//! Quantization tables (10918-1 B.2.4.1). +//! +//! A `DQT` segment stores, per table, a precision/id byte then 64 element +//! bytes in zig-zag order (`Pq = 0`: 8-bit elements; `Pq = 1`: 16-bit +//! big-endian elements). The elements are kept in the exact zig-zag order they +//! appear on the wire so re-emission is byte-identical; Phase B, which needs +//! them in natural order, can permute with [`ZigZag`]. +//! +//! [`ZigZag`]: crate::units::ZigZag + +/// One quantization table as stored in a `DQT` segment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct QuantTable { + /// Element precision `Pq`: 0 = 8-bit, 1 = 16-bit. + pub precision: u8, + /// Destination identifier `Tq`, `0..=3`. + pub id: u8, + /// The 64 quantization step sizes in zig-zag order. + pub values: [u16; 64], +} diff --git a/JPXL/crates/jpxl-jpeg/src/scan.rs b/JPXL/crates/jpxl-jpeg/src/scan.rs new file mode 100644 index 00000000..23623ae3 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/scan.rs @@ -0,0 +1,40 @@ +//! Scan header (`SOS`, 10918-1 B.2.3). + +use crate::units::ComponentId; + +/// One component's table selectors within a scan. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ScanComponent { + /// Component selector `Csj` — matches a [`FrameComponent`] identifier. + /// + /// [`FrameComponent`]: crate::frame::FrameComponent + pub id: ComponentId, + /// DC entropy-table destination selector `Tdj`, `0..=3`. + pub dc_table: u8, + /// AC entropy-table destination selector `Taj`, `0..=3`. + pub ac_table: u8, +} + +/// A parsed start-of-scan header. +#[derive(Clone, Debug)] +pub struct ScanHeader { + /// Components in this scan, in scan order (interleave order). + pub components: Vec, + /// Start of spectral selection `Ss`. + pub spectral_start: u8, + /// End of spectral selection `Se`. + pub spectral_end: u8, + /// Successive approximation high bit position `Ah`. + pub approx_high: u8, + /// Successive approximation low bit position `Al`. + pub approx_low: u8, +} + +impl ScanHeader { + /// Whether this scan interleaves more than one component (MCU geometry) or + /// is a single-component scan (non-interleaved block geometry). + #[must_use] + pub fn is_interleaved(&self) -> bool { + self.components.len() > 1 + } +} diff --git a/JPXL/crates/jpxl-jpeg/src/segment.rs b/JPXL/crates/jpxl-jpeg/src/segment.rs new file mode 100644 index 00000000..7a3c0806 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/segment.rs @@ -0,0 +1,123 @@ +//! The parsed JPEG document model. +//! +//! A JPEG is `SOI`, an ordered list of marker segments (some carrying entropy +//! data), `EOI`, then optional trailing bytes. Byte-exact re-emission depends +//! on preserving that order exactly, including how multiple tables were grouped +//! into a single `DQT`/`DHT` segment, so segments are kept as an ordered list +//! rather than folded into a normalized structure. + +use crate::bitio::Padding; +use crate::frame::FrameHeader; +use crate::huffman::HuffmanTable; +use crate::quant::QuantTable; +use crate::scan::ScanHeader; +use crate::units::ComponentId; + +/// An application segment `APPn` (`0xE0..=0xEF`). +#[derive(Clone, Debug)] +pub struct AppSegment { + /// The marker code byte (`0xE0` = APP0 … `0xEF` = APP15). + pub code: u8, + /// The raw payload (everything after the 2-byte length field). + pub payload: Vec, +} + +/// A start-of-scan segment: the header plus the artifacts needed to reproduce +/// its entropy-coded data bit-for-bit. +#[derive(Clone, Debug)] +pub struct ScanSegment { + /// The `SOS` header. + pub header: ScanHeader, + /// The end-of-segment padding bits for each entropy segment, in order: + /// one entry per restart interval plus one for the final segment + /// (10918-1 F.1.2.3). + pub padding: Vec, + /// For a progressive AC scan, the end-of-band run lengths exactly as the + /// original encoder emitted them, in decode order (10918-1 G.1.2.2). + /// + /// An encoder is free to split a long EOB run into several `EOBn` codes + /// (libjpeg flushes when its correction-bit buffer fills), and that split + /// is *not* recoverable from the coefficients alone. Recording the observed + /// run lengths and replaying them is what keeps the re-encode bit-exact. + /// Empty for DC and baseline scans. + pub eob_runs: Vec, +} + +/// A marker segment the codec does not model in detail but must preserve +/// verbatim (e.g. `DNL`). +#[derive(Clone, Debug)] +pub struct OtherSegment { + /// The marker code byte. + pub code: u8, + /// The raw payload (everything after the 2-byte length field). + pub payload: Vec, +} + +/// One element of the ordered segment list between `SOI` and `EOI`. +#[derive(Clone, Debug)] +pub enum Segment { + /// `APPn` application data. + App(AppSegment), + /// `COM` comment payload. + Com(Vec), + /// `DQT` — one or more quantization tables, in segment order. + Dqt(Vec), + /// `DHT` — one or more Huffman tables, in segment order. + Dht(Vec), + /// `DRI` — restart interval in MCUs. + Dri(u16), + /// `SOF` — the frame header. + Sof(FrameHeader), + /// `SOS` — a scan header and its entropy segment padding. + Sos(ScanSegment), + /// Any other length-bearing marker segment, preserved verbatim. + Other(OtherSegment), +} + +/// Decoded quantized coefficients for one frame component. +/// +/// Blocks are stored row-major at the *interleaved* (MCU-padded) dimensions, +/// so a single plane serves both interleaved and non-interleaved scans of the +/// same component. Each block holds 64 coefficients in **natural** (raster) +/// order; element 0 is DC. +#[derive(Clone, Debug)] +pub struct ComponentPlane { + /// Component identifier `Ci`. + pub id: ComponentId, + /// Horizontal sampling factor `Hi`. + pub h: u8, + /// Vertical sampling factor `Vi`. + pub v: u8, + /// Blocks per line (interleaved / MCU-padded). + pub blocks_per_line: usize, + /// Block rows (interleaved / MCU-padded). + pub block_rows: usize, + /// The blocks, row-major: block `(bx, by)` is at `by * blocks_per_line + bx`. + pub blocks: Vec<[i16; 64]>, +} + +impl ComponentPlane { + /// Immutable access to block `(bx, by)`. + #[must_use] + pub fn block(&self, bx: usize, by: usize) -> Option<&[i16; 64]> { + if bx >= self.blocks_per_line || by >= self.block_rows { + return None; + } + self.blocks.get(by * self.blocks_per_line + bx) + } +} + +/// A fully parsed JPEG: its segment structure, its decoded coefficients, and +/// its trailing bytes. +#[derive(Clone, Debug)] +pub struct Jpeg { + /// The ordered segments between `SOI` and `EOI`. + pub segments: Vec, + /// The frame header (`None` only for a malformed frame-less stream, which + /// parsing rejects before returning). + pub frame: Option, + /// Decoded coefficient planes, indexed like `frame.components`. + pub planes: Vec, + /// Bytes after `EOI` (the "tail data" Annex A preserves). + pub tail: Vec, +} diff --git a/JPXL/crates/jpxl-jpeg/src/serialize.rs b/JPXL/crates/jpxl-jpeg/src/serialize.rs new file mode 100644 index 00000000..9669a246 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/serialize.rs @@ -0,0 +1,179 @@ +//! Re-emit a [`Jpeg`] document model back to a byte stream. +//! +//! This is the exact inverse of [`crate::parse`]: it walks the ordered segment +//! list, re-emits every marker segment from its typed form, and re-encodes each +//! scan's coefficients through [`crate::codec::encode_scan`], reproducing the +//! captured padding and restart cadence. For a stream this codec parsed, +//! `serialize(parse(x)) == x` byte-for-byte. + +use crate::codec::encode_scan; +use crate::error::{JpegError, Result}; +use crate::huffman::HuffmanTable; +use crate::marker; +use crate::quant::QuantTable; +use crate::segment::{Jpeg, Segment}; + +/// Serializes `jpeg` to bytes. +pub fn serialize(jpeg: &Jpeg) -> Result> { + let frame = jpeg + .frame + .as_ref() + .ok_or_else(|| JpegError::Encode("cannot serialize a frame-less JPEG".into()))?; + let geom = frame.geometry()?; + + let mut out = Vec::new(); + write_marker(&mut out, marker::SOI); + + let mut dc_tables: [Option; 4] = Default::default(); + let mut ac_tables: [Option; 4] = Default::default(); + let mut restart_interval: u16 = 0; + + for seg in &jpeg.segments { + match seg { + Segment::App(a) => { + write_marker(&mut out, a.code); + write_length_payload(&mut out, &a.payload)?; + } + Segment::Com(payload) => { + write_marker(&mut out, marker::COM); + write_length_payload(&mut out, payload)?; + } + Segment::Dqt(tables) => { + write_marker(&mut out, marker::DQT); + write_dqt(&mut out, tables)?; + } + Segment::Dht(tables) => { + for t in tables { + let slot = if t.class == 0 { + &mut dc_tables + } else { + &mut ac_tables + }; + if let Some(dst) = slot.get_mut(t.id as usize) { + *dst = Some(t.clone()); + } + } + write_marker(&mut out, marker::DHT); + write_dht(&mut out, tables)?; + } + Segment::Dri(ri) => { + restart_interval = *ri; + write_marker(&mut out, marker::DRI); + write_u16(&mut out, 4); + write_u16(&mut out, *ri); + } + Segment::Sof(fh) => { + write_marker(&mut out, fh.code); + write_sof(&mut out, fh)?; + } + Segment::Sos(scan) => { + write_marker(&mut out, marker::SOS); + write_sos(&mut out, &scan.header)?; + encode_scan( + &mut out, + frame, + &geom, + &jpeg.planes, + &scan.header, + &dc_tables, + &ac_tables, + restart_interval, + &scan.padding, + &scan.eob_runs, + )?; + } + Segment::Other(o) => { + write_marker(&mut out, o.code); + if o.code != marker::TEM { + write_length_payload(&mut out, &o.payload)?; + } + } + } + } + + write_marker(&mut out, marker::EOI); + out.extend_from_slice(&jpeg.tail); + Ok(out) +} + +fn write_marker(out: &mut Vec, code: u8) { + out.push(marker::MARKER_PREFIX); + out.push(code); +} + +fn write_u16(out: &mut Vec, value: u16) { + out.extend_from_slice(&value.to_be_bytes()); +} + +fn segment_len(payload_bytes: usize, ctx: &'static str) -> Result { + u16::try_from(payload_bytes + 2) + .map_err(|_| JpegError::Encode(format!("{ctx} segment exceeds 65535 bytes"))) +} + +fn write_length_payload(out: &mut Vec, payload: &[u8]) -> Result<()> { + write_u16(out, segment_len(payload.len(), "segment")?); + out.extend_from_slice(payload); + Ok(()) +} + +fn write_dqt(out: &mut Vec, tables: &[QuantTable]) -> Result<()> { + let mut body = Vec::new(); + for t in tables { + body.push((t.precision << 4) | t.id); + if t.precision == 0 { + for &v in &t.values { + body.push(v.to_be_bytes()[1]); + } + } else { + for &v in &t.values { + body.extend_from_slice(&v.to_be_bytes()); + } + } + } + write_u16(out, segment_len(body.len(), "DQT")?); + out.extend_from_slice(&body); + Ok(()) +} + +fn write_dht(out: &mut Vec, tables: &[HuffmanTable]) -> Result<()> { + let mut body = Vec::new(); + for t in tables { + body.push((t.class << 4) | t.id); + body.extend_from_slice(&t.counts); + body.extend_from_slice(&t.values); + } + write_u16(out, segment_len(body.len(), "DHT")?); + out.extend_from_slice(&body); + Ok(()) +} + +fn write_sof(out: &mut Vec, fh: &crate::frame::FrameHeader) -> Result<()> { + let nf = u8::try_from(fh.components.len()) + .map_err(|_| JpegError::Encode("SOF component count > 255".into()))?; + write_u16(out, 8 + 3 * u16::from(nf)); + out.push(fh.precision); + write_u16(out, fh.height); + write_u16(out, fh.width); + out.push(nf); + for c in &fh.components { + out.push(c.id.0); + out.push((c.h << 4) | c.v); + out.push(c.quant_id); + } + Ok(()) +} + +fn write_sos(out: &mut Vec, header: &crate::scan::ScanHeader) -> Result<()> { + let ns = u8::try_from(header.components.len()) + .map_err(|_| JpegError::Encode("SOS component count > 255".into()))?; + write_u16(out, 6 + 2 * u16::from(ns)); + out.push(ns); + for c in &header.components { + out.push(c.id.0); + out.push((c.dc_table << 4) | c.ac_table); + } + out.push(header.spectral_start); + out.push(header.spectral_end); + out.push((header.approx_high << 4) | header.approx_low); + Ok(()) +} diff --git a/JPXL/crates/jpxl-jpeg/src/units.rs b/JPXL/crates/jpxl-jpeg/src/units.rs new file mode 100644 index 00000000..dbad266f --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/src/units.rs @@ -0,0 +1,57 @@ +//! Unit-bearing newtypes at the JPEG-1 coefficient/coordinate boundaries. +//! +//! AGENTS.md §6 bans bare `u8`/`usize` where a mix-up is a real bug. The traps +//! this codec must not fall into are the classic ones: a zig-zag stream index +//! read as a natural (raster) block position, a component's *identifier* (the +//! `Ci` byte from SOF, an arbitrary label) confused with its *index* in the +//! frame's component list, and a DC prediction difference confused with a DC +//! value. Each gets its own type. + +/// The natural (raster, row-major) position of a coefficient inside an 8×8 +/// block, `0..=63`. Element 0 is the DC term. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Natural(pub u8); + +/// A position `0..=63` along the zig-zag scan order in which a block's +/// coefficients travel on the wire (10918-1 Figure A.6 / A.3.6). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZigZag(pub u8); + +/// The zig-zag → natural permutation (10918-1 Annex A, Figure A.6). +/// +/// `ZIGZAG_TO_NATURAL[k]` is the natural index of the coefficient that is +/// `k`-th in zig-zag order. +pub const ZIGZAG_TO_NATURAL: [u8; 64] = [ + 0, 1, 8, 16, 9, 2, 3, 10, // + 17, 24, 32, 25, 18, 11, 4, 5, // + 12, 19, 26, 33, 40, 48, 41, 34, // + 27, 20, 13, 6, 7, 14, 21, 28, // + 35, 42, 49, 56, 57, 50, 43, 36, // + 29, 22, 15, 23, 30, 37, 44, 51, // + 58, 59, 52, 45, 38, 31, 39, 46, // + 53, 60, 61, 54, 47, 55, 62, 63, // +]; + +impl ZigZag { + /// Maps this zig-zag position to its natural (raster) position. + #[must_use] + // The index is masked to 0..=63 and `ZIGZAG_TO_NATURAL` has exactly 64 + // entries, so it is always in range. + #[allow(clippy::indexing_slicing)] + pub fn to_natural(self) -> Natural { + Natural(ZIGZAG_TO_NATURAL[self.0 as usize & 63]) + } +} + +/// A component's identifier — the arbitrary `Ci` label byte from the SOF +/// header (10918-1 B.2.2). Not an index into any array. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ComponentId(pub u8); + +/// A component's *index* in the frame's component list, `0..Nf`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ComponentIndex(pub usize); + +/// A restart interval in minimum-coded-units (10918-1 B.2.4.4). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RestartInterval(pub u16); diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_420.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_420.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e0ff249331f547ac43d55ad47100d0acf7d1771 GIT binary patch literal 24881 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_422.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_422.jpg.prov new file mode 100644 index 00000000..b92504ff --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_422.jpg.prov @@ -0,0 +1,5 @@ +fixture: base_422.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: ed4231510da7db2dbcbd4f28ffdffc36952134ea0530ca6fdfb9adf87d259da5 +regenerate: cjpeg -quality 85 -sample 2x1 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae8c9e15ee7aa473122877fc311d67861b04a610 GIT binary patch literal 31512 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%0AnOU?Tx;2y*qd6MAs#FS ztQ^6)5=O$q;7A1YcdE-@{9lxtOy=tT#> znkTdZglyqhwfoo6{+b)iqK%jgLtM&XjV5yKPit5}B4dr&(d3`o%YPUjd9GWQM<~Zv zw#=o*#OjJiD8h5LltOCR*?`$N4rZ!bDA>Zjj_kl&-SH_(Kytm3^|wegzOJ!d2f;>N;))Wlx4Gd$^0x;}ZuB#D&&io#ot;qJ5)tx)uy0P1bC8Gt%Mzfs8 zgSyrRH0*FJ%R|p;r&FfvbM{0+?8gUDrbM)zXyc)Wj6Xv9Q8BCXH7y-n#blEeBV69nLMOb^R>mRgg$RYJmz)$vL`E=bJ<-InPy+4y^7DYfmP@BXz`4AO#Vqjojk+rvdIxNLBaq2e~) z88~AX6Xo}>zQj!ruT0ZjYnXBoB|aFetm4H@EcZ)kF`rH3R)rPv5DeE2F}xH$cI~A! z8!}BZoz0Q#dxHWt?7)Whb4DKg@%0dQD^%iLMt?-)Vp;raTJx^a+ zR=xUul_%8abl{6|-r>INr?MuJw?$)dpVfbo92yrZ8#51htVz6(pRplr1Mh2dpxsOq zWS8b5X1B&A=2uhP9y&`b%w2K6QTM)VKvb_9Y_~RaGK`uirF3S0o|He~nL{#r~wgO&beM4|s>AyNK@Qs~18Kc)6E;wn??? zOE?Pu^mV!Ah5jKk*IpLpnuH`dBeuoIJp0xkWTHKg{u${4)$JG`0_`D-kVh4@fygr1 zRbkD48n#9Y7U{o2u|EaJ#I$&c~&ZjzMF{= zVbheV(Ywbc=-KN2m1^+Hr|}g*C8_)3r%{Qj$bQ^tdFgpH;6{m|Pcz{YkMek^^>qON z#6W_OgPHPk7w3up$~-?hxI8F|%CU`3_A@gwK?yf@z7)T#%eV)8wC?$`Xc|$=zp-6l zDw3$W6%N4~+!7jWm8Ch~_n#$uoJ?W;MD9B79ai4fGw-{?ps(_6^&SW0A7Z}_b{IxQ8(RfyNp z*=P+LQ3X>%IGn~b$||+Xr@C_(!xwU|pVN*CA0vTl$vBVX-%gCSt4jMbA7+QTu$siA zCUQeqUT=sx6<^>yRNNj_MC2(${RGcDTL zgGjdxOBhUN&Of2EgyxtcqiaY6H;{$C)!GryG=qO`d~YgyW7Xb5Q>1A+CvAJ26Zbr2 zO2eK+=oQftEK#AT%SzSON*izX9lmC~1TvsiVy(8Zl|r;$mlFZhq<_4#OJb5+h6C>> zbPD(9cEdIIo`LK%ELKBokPY{M!QIH`oni>)m3sh^g!!++A4mGHcBm#pidW`7Lf-%- z^l&oVSF(4P46&H$rf-PD;)tD$?fh7|S$kXi<*?~P$#-4x^z0z`>1@qJVc8Fzu?*U2X zd``salXU&Q|LuqwxJ?E9cB)`hJBEDPwSoU>4F8c#w6g;nU?o(>Iq&J%z<;GpI%OuB zrK>u76kIFsJpS%>lZBkIvV^1)#Eb~O)7GQm1llU(@Mq7}r(_o{-e6{6LG2{7w$YI@ zwx>z=fR{Q=I|aw~pMoa^GE0IlL}Hu#4A*_>W1jVI2o6ZHhst;FS0W3htC`LmW;U+??q1r8)TLz5vC1ikb$uwFweG zammIU<1@?{AK%rOY=c+=8?6boo~}Z%fkzAUFo9&A3ep|E&hN}VW_m;BPm$K2t`!*hp?Q7`pV!i(KIh1rt39sY^{{3&kpzN=0Poik z)>(*mW6s@*B7rd5{?oKgR~8Is@rDWQgF-)93-E4wMh(=^Dqesl=Q6KHYs?F)(ctKg-^a$nSpyl{sq0>MKs(Nq5iXMI-%=EV802 z$g;EL3b9?mTMFBkxnqHv=fr0<+DbJY_SL_?dt0Ob0DyEu8;gytR$Z68{{RhkJnlrz zPq#JaVrqLsvJs z%-4m9kF+{rQpYoU;6Dz;FJ!pWncC#a^QfK5w)Rr(w6@NhPESIsj)2y^%c#4D?N14L zU&<^Cua(z~9jrHV&N21IM$*G$YDWC>(%N0)1vvG&d)uSI%+e1^U?EFZ{FvO zrSDo-vW=>9wI|QZ1k!xmM4k-r`svFA{vWklXkwCBSjc?Y2v^8w-MEgVa?66a=qsy& z+UD}%qLL<8R3bgB?FzQgb~^!#2FC*+;DS06PYsBSx#c%~HBIZP>wOlRua{%U%kw(; zy3oQ?sW;4>qtDFcHm1{E7bLeIK;sX&oX zNXsf`?|h?fM&iH?+~szjMSXS}i>+w#?brPF-=^=+Q}~OT{{YwEI$J+1$YfM#S_U#4 zf33hm4^S0>V}QdLLBPiif?KPbWRf!^QZsHg<%$wFkjVK20Df`1xr}ZEoDW)_Smd?! z{cZEVri;DGQkCt0>-zq+JorQHS{_%mdUDHRmd8|Unf_KgRo9NZ+(s2Z-Ze@EaZ|{Z z^Vq7M%;|*gSLsT^yc+qFhf}5}cOBx#D%)O2*I+cSAZgq9WD2gi%7cyn0L9LJVoz7b zmsZjbHr4{Ne9@~Gk0)RbGEUsE3_%&ga>tWiBhAuHtzMem+g*IGzpb_!jCy}x@Z^^o zg|ueoJCqSM#EpRH9z}I1yOl;%C@F#Ku=T(N47Q-p_B$(yR5zBA$0NJ^fbJh5$y4%h zNK##R$R@fdW-*gZr+=58w%1Fyr>|zpRMwh*U(^1qaR|Ao;au0$;i~&XUTXqHnz;;q z)$d{O%3qok?K1AbkCNe(5EyJ^$iUBFdI62Yt{w$;-jwYQqi%k1rO8~vD>+kAgCn^8i5thml(WWo8epk!cT%omO}!6xbMo;E=q ze92qpVB3q50Qm^WIKjyS{M@dv$#p{;l|WuBCZ9y-zyMC?dG3=B_Vuz9W>< zD6FHK$-T`{G7Qt=73fgMxsLWEM9o-*E8fF?@Hy;v(;H(Pa4piL$b6}$G6g9ZA($08 zbF_S;a0hlyaz#4c)>zdgoo(%<3}Vb@d&J&Z$Q-C7^PiaTM;IHczYl<>+qS>euczU^ zcdg7+t$j!kPXorca-%?qsAZ3ET$UvQtY9`(l#RK-&jm6KYd4s6hk|RnMN5em-^`2; zGkIOfpP1mT-Z~SOAml4n)i9J}6)Kij`ER1N)t=iWdo)+HcE9!gerK8aRe7W0uD%IG z?2Tf9OjU^!72x8(c67pbD&rLQ74!IvbAi(qFd8e%o^K@gH;LaY*K^uMdYAC5@U}@; zE3~l4KykHKhdY4_%J6V8Ue#rWAv8kH7PVGo0$e*V1&T-m45`_SW92*?;O7WtNy#YN zUYcvK;I{t&HDM}gGe*`#f#gelHr>kYf?IhJGT~cu402EqG8ANdubi5RngH>LBY7oM zdA>!RQt-_V{czQ~qx3sYm z!|hk&ioI%id}Ov7tWyyCR)>}CRXnySY-t+id5ayY8`)m&1iJOoKn804h?rHH_3ZzqgH-ix^=Si(b}%56|j~#+9jEmak^Gj zLd3gB%*&E?1p#x8na3Ozy|q`eYg@UemSE$UV%ZZplVcsqKfR8jzU$y*p8{`Sw=}dYsI@a&eu_n^y+b?ut+TuGGkJ>0Dz#Xp63J&<0qHs zK*KFO({~UMmYoosGM1AKyF0dnoxx8+F|-5J0rZ$UEz@b+UcaB`_xy}8`0Dksaj6J6 zsX|YcNyCv!E0XJ$+pD{|bGgdvmzfvRA8NfEbu@4)NA3RDsv-8R0TpySwl>(cT%qM- zwOwfatA@S}K-x7*B{5bcOjm)5e$45F?s@+J{3M zWqA^%ab6xWyjjr+8;_4RWsr%af;3}!8z3>TLy{N`z-~qWkaLlazcq_bEEKbzDDD$; zw97f(?BM}mtH*uWBxA2WpBb3RIJa**_t&>=_3YQF=)y@W@9EcG#8(e0l3pWiW>wNb z7$zkOO7a0D4CLWJ+!vm>TQmUy|j~i4-S7!eJt#7^l{{Xz@!d0;swRx_L?ln-3B&$)!?Oph4a!jviNdi>x zrF~WfwYd&t@TV0tLcYHTUMS7T>a@{!6Gd?)lB|--&^t!>b0?S_eAvbVfV`ZY{)2+@ zK$f<5!s0}dCh~;KBEDNBlaYhFmN+11jmJ4!oJJB@x}U{%OJv@s$rt8H2Ddq=WJjaSQQKhyf{ zZl8g|%9S&W*Po86o$P5&MtrGO9V_E8$-R!)OrXilS~2s|zGA&8Hg!g!SreMHk^5IX zD9+}}q0`EZA zkQdBeqd4Fd&INl}dX!s~wOZTy^|qZ=y6>zTi*4+?chI+Nsj*~`U9%?)-fDo4y@8Md z2OePLwhkC%4k<0Ah}=RfktLi&WM+{am5C)=JTJ-@7y){&I`#Vu1Lf99_Sa6Hzo(aG z@|;_{TQA4@{*i|gRFPNJVX+ZN=jI+0ig?9(A6tcrQe;k*BUhP<#6_ccNTn)N@-A!S zar3?Gb<>%#sKb=Fxpiowm|{heVjW2>jHd;50j;?S{00(IpIpB_J86vcM>0&mL#_U;S--3|k&m8~-K+nsOwOAg6*N;3Tn&y(f z`FU&kb=c{o{nsb2pZR~l@1e}boYlDF_N^$=l4ACPz?EhcucX0U66Q?;RM3o9*kG!q zxtozRX;MdDM@JPDiRNch;l;(QmvXC!QgtN1EkCRI zbyoC3YG7K;6_8l&vI{gwzcP&Q#4Z7Ij9|CSz-N$Y=;Y!#Eg-Z3nn>73w}u^+zu6A0 zk^D+PBbw#RCrQhj{tElGx0e01Mv;%an*P79^4q5cDpc|&Yvl3on)W*B%+GYIO&_&< zmMWZ^I^j^L6PmQK`&XUpXLEH>WJy+HkJ`N)EjcDPQ0g@0mf?}$WkiipE+CZwXK|3h zQAXgoZ1n>yNx|l{ZNnY0+D`<}>HUAe+fT&aORI?zBC%Rr`Obh2ih~MCWzOBo!9R$cXBgzxTvFYOHV+C)5mjQ0 z0tQezn^ zLtNO5R9ZHSnNn1#oL9@^s;6_RoXq)BtU6c5W0PlWCM>^YnhSX@mN}*QB)O0UXyYHn z%K|`c!*|MlW^S1Pz`tX;xidM1!#jVYGf4jcC~P7Vj42z`f~9kTo=FGG#NlSI%$~hH zS4Xy=g`KW^hEY5{NXi&(ed#Mru4!pyIkx$nF5FX&vgb-kCnob{fuxP1As~^J3N(IL z4#7t~!33~5>zwo?)%j(*w7ZTuY>vc-e?sjf;ghU46C|DD!}~8a(9vs&Tu-4`aDeG zQMIMJ>u$+4=h5qZ*O&6ck0mM6Zu&wtdh5N`i}v>4SC8F)U2ePW`j`bXRuMB;zuuf`b}M;a z)qP^mYKNC|nyS_)2z{$eL+w-jkK|~ploA|vtq&%b_HQRcRa)laMC9$aoGg=V+vY;j z0LZLD?#40>K|MweKq_+_Rr zSL(a!`hUTF?$PM;sWmJ0{{V;o052~gmd|6hE4B*`TYKkmbYSFfEG3Jjif4}-> zTAE-r%l)}-rxHZDrm3X!u5PBO4kI-58L4>BXlSKkBOZHIw2w6ohqN_SENjR@WFh$hUQbcRW=psr@};B$_o#*X!$gkj?}$}vouvMZ(H4@7WWd&5_?s~)|BALSowniM>)noY;6)8f;g(F9qEk5f_SZ> zRZ|wiWO)=Fy;KpxE-*4aQ;xXN){Qq7+DiWbH~nn(w%S=9m$Rnc`+e_U>;49X_?XWm zW*ZxID*j^1@wBYTjl*flIm3q9q#UCWoZ_hJHw@EU%{(q*bPz`)zT`N4!N|wl8BxNJ zPZ`POEYfR4^?Gl8KdnAb=d5Yew*LUGo&Nw&BRCpjn#=og{Z2%Ok(yK)sdS&#Y z6wO*h%|l+$)l{)p$qn2H)G6>)< zeojrjD^I*weQMpS3!BDS7M$eUaC*ZvRW?dI&?sIdwP z%3AvW0K@nH0EgYhEwQ%(d7u%Ut+pb&!|g}QNZ16nKsauIliTH%d%0u#Y%<0?*&{z9 z@w??AzzjNLEEseFLF0e+(EhT6EYp%EaKSOS5Hk0nXHMai%@a768D#pxKU&1(4 z?llG}X@6;ZKBkLOMKr)^e{Nf;qC~l-tuO7KZl;L_qbo(H&TiwiA818*gmR%0E^<^I z=O9Q_h9m|ERBhl@_m(6`U~8D~;fQY=ilKpk$wl6!pRovc9k|>MRM(?V80yaLZMR4G z^|w`hG(4E$AzqBBSEHqgLvT1Ra}S(|N9t?&SzXg4?~_YM_)_x$w80lRV=2ENVabm1Ro zC1oF9?{@QAwQtb;CY3BjI+3oVrz(wgyl#{o+Nq@El}~nalWI=RF|i%$S=c{i@%W?Mv7f?GG547I@FvT1HV8eq4_~fJ2j>F^r6Y z06Y#cQryVa(#mWu9ZN~HnOuC&zyy$RPu)D@jocDNeV#I`psyV-^8UU@@+}#;?EK$P zo6+0PYdh%lAe6`;R(pv&vJ8?ms=!L2Pz}HzoZ}b(^U(Fp67RZ)A(l~YF$+S)M$)9? zV)22~0P%nc#%twkVj8IL*ZSM0ue0iP!lxvYX<6;xw_923u8+&1n!Rc2yw{l=WbS#1 zl0+(KR=u1iB29ylG%1>~0$0}HWz0jFBvI9K+eaO|(a#~cR~!|%C7S`xGu$p2 zSmU~mn8`sTk-`M*#Q@(X*w-6Z7-ZafjFJz{z~>|y^s7--VB1Y!r{U7-`gh;f#*u`i z|3Hv(4jq=}J(uu*^p1_N{303HT1*@aP>R)*m=hXENtXz`H4 z(;qVuJ^98DU#-jLmalK?RsDWvuLPkUX}06#Z8g>O-+r1}_USYhk1v*zOE`{PG2b9o ze0@VPB<)ZD>Z_cP2(bm6QQFM{`63e>iqgx~n3JBQh7nvU*@)tch`A<-wj#j*hSE(5$ z-PfO%+uKjavLcl}M;SS2{W@y5wwijsnk&XCNRyh^4T+Oyo6JuN=BI%x>aaCAw;{}y zDDzffO8V?16GS?KELCk2iuR#G76nwd1GC;kJoj<1^S2}Uukvh6oE||3fJi5*pGt<# zI48EY@@7kE)fz(*L&}$C0~sNQew^{t;F{#6R)#8#nGc`04BQpn&}D}KvOZpn3lNwBmKF^RMI?>ml@y;e zgv$Yt4nZxR26|^FlU_z+1q-O@{b~OI2dCy-xsv6J+fP3(i{|$H%r{z^RIh-?R&hC@ zBB7}nd9RwUi;`zlWHl;8UTX+rCv#NEA{8_#Ugi%F*f|=@aIBwWo<>-tM)P+v~ z_zlJ~c<6s1EZRsz#c?CAl@j7K-bUg^ae!F&><&OVEs4hd{2Uzvj8fcco=LWKaF9&yET z{dT4O*j-3zM?_<348r>lPU* zaNc7A3gv(*g^1vc5NTi9J*C~h_6#!Wa0utK7gttgV}pBOEU~&24huL66(p70lp(;E z)WlMXr8J(pYW>|iwWDn%(@k`_`CUvdAL|j6so6ejsI@HbBM@9l z%%JUGEaWnQy-w|3>b zf>u-a{r9c;qU_$bItCRhrG7z)#Yr>vh)9WP>YUe~^~v1bG4TqP6t7DQiIZdICxvQw z627woK5G!>cz^b9h=1qPQvU$J(fFs8BKU(yw_w)#25^N=)gEVv5DjO3+w+s|e-NPh(YB zf+*_ZMUK_z;qa~l_O}@tnVEMuC}sd}0e6AIl5ju-Xa4#*&Uu#AB#+Hw6cSEfW&*Ki zB#=)fdE+NJ$=lr5+hJ-dUl&#VefoU+A5BtiLrv{&nl7*Uwcgs7th~i83!_ORhVwU& zBw}zCxgAL(8T+H2wHv$=3vo14EY^jdHD~!rJ9=^nvI!QZE1K{zRehn;3D~0X z)x#ER=W#e^GpZFz5W?G{j7u!WNPM|j6}dZcx8*n>ZO${hC+kMZUmU(kQ%w>(Q9(zMKXws2@45>V`w}&<#v;dsm9=XoMA^G956{dw*eK_K=NRD zPk;f&2nqlIk%GgKxMb&P8dt@0Mcw}ZTK@pR`5$JKlvL*Q-K*)>{sql6v#=IGEqw zw)XV#JI5-9>F2`(=vRn=!ga<|3jX!v^QLIpmCB;BW}4u)MD-$q^+t2aI_RTL37= z2;?wOeSYaDn(3)m4_$ut3R+uL=XLof>%Pd+6uD!|H{Q!tz4Uh0_P2d^*%nBmyvn(x zWVb$gBvLI%Mr*{yVpPtEO2s)6tr+v#zH=2x%<6?p8Rjl}RsR4cyh^!zq|TVuE9t2q zof&F}ssKr&P+{WsC?>!aVT^+Z@~$KA&!B2J`i zxBkDcP5Pfz8g3AbK5OUs-7cxwX#C>6-EZbgGOX%hRuRh>QMGyZz|QZNzIuRpmN6^A zHOy0&*cMid#f{+S8Q`%UeYxGoTKT&889Q?=b<_v&0k1;t4eqYMeJX;BZVr`M~e1v)Z7EwTzo51AjN$K8x)`} zYn)cAYiFvhm4&6sT}*ttiNuo@k@K|4AD5Gyv~jtNqZHcwAC*pwqeqG;bcsCsTQh4S z%jKu~LIzdqDsD*Q0fBY-PUTi&;mL2%zqBY;gw&(W2JG&xtnRIEh1*>o-7Tg5TN6pu zlcQBdO(xRjY28_`dTq=3TeMSuYVE#9+O?gHt-D#pbt~9hZZg1#S#7~5$$6y=#iLfh z2g_iR0>J>0mbdndRyS7iTwXz>tP{s&6cPq+kcecMCg41^Q0}3&>|n|;pd(Q{T|QRf z3+U{v)2q{F+;{s<&9?pun6Nzd`}cWChSR&?24l*7Gj_bXCsmhDf7H5bk~y0VSO$#9LRi_Ro5XwKmzS) zIsmz1eF;B9gkH_5C2PO<8aHmsrJb*@OmR4>m-bEcOh@HtYS#cHy2ff*KqSEUE}YO z>Ujq^9W$I`BD7j2F&G|bagW`)01Gg1K;t7kt`2y~&#iq2_Ecl8pVIt4B)2}wmL*-Q zX!mON_QC~B%4d;{%1}ORjNnbPuF$Re*Sj!+SjwU6+5mg;c*o`nex${$SGKJ zUNvmZ!>Q2_29Q@9&R=s>iC3B<712vHbLv?}2NL2?L}E+_n2g2)axtFjLf{^9Pp)cW z9z>C?iIhljFPA9{-ztH$ZuBG`+>_65wxK_3r}h5;2m0LmWmYcM)79;(wXeOb`gOC@ zRbnXCMgA4}51G7nza^l`PPrnNC5C)8+t-gS)>##2(|E*QJ`&Np@?OYMc)98Gvu2B&CRr43M zWm7Ft=xZ9~4=)vM4-txV&X~&Pd{=SYT_nZaq?kkjaHT^@4 zr(1Q_)^AH_?dNuvk!F9hi*S<}6_aB$az;15BPE9gzVXS=C3;jaT@N%*``Io4P0ovz zZZJ6Lt<>X=fDWdjIkc9&e(&r0`P?Z~gt@im_q4vBeXhIemfm{muQd3Da8b-0&y>A~ zqGqf@nXh7p?*pF07A*Fw(Z%gvwJZXd{k4jMA8OGNdsj#HTzeF&nPFjwJZ*0ht19`e zf@0cmGClE;fsVX-)Dv2#?xcgvY#+V>kr8q7oPv6E$F@6U`gL-cq}qFazt_vL_H?Mi zcWzGUd9B;GSE}EwyA6&a%Qe*LvvPgnNJ6aRcgk`B`GNG$IPPX-w`+B|GMM)qMn?e~ z=HZTVK*1eBJoc{_5Y#s5(_ibi^v<7a%_R+0wYI&juTH6}FTFdKF^Y4F_Pvc8O7c7zSg_nr+pT-O2nccYS8kyuNGNaT@RRz za)*_QxbfGWUkadY8n-F!#Z2pp@bMMBnbR5EhJrb-9ZiY-D2ME11-Y0tZ2sE^>3*3KWsaI3H38ZppdicuGne%p)5d zfW%`YbmO@hAddJQw6i(3wEVaHjq0Y-aZ2{`e)CJ^Yizdb|+nR1UqSk^du1(M@f)+Jfq zTbnujltAqr&-$>-kizJ(K6ruo zO8)?=KyorquZpJ%bm1MXr<0ZZt){B>zME_DcGAPiR;;5G_7Gbo&AsFiO*9e+ZakZa)>~{!!O$z1wj4sHLg+Fim>U5@v#rtoe`bN_^CG6pY^6|%zXI?VI9DTg>^*RnH@uXydS&( zz`+^MLynmbf=$hD7;h#v$%-c)X72rR4>=rXr(U)F34naMzMcI0@A@Chu5w=Vwu9FC z-tVecOSbD&?zOYFOl+)-(8#R6IV2fhbhz+;nB-^SM^nQmPoF&z1?wOa%O(}R*h$oWAX#w*gL2IkzA zulk>Vf~yxM=M=e{R=0gq^H#r=uYFjpA_F5_0AgoQ`C!c40*%HF4hJ8^GI+%{WeqE= zQb}&46=F~lasfF5?tzT)>y_)zp@gL<+Unn*T~)uPZ-SmaniQ$wTKgOwSfe*F=$ur@IInh{cvv}z%5hSWSXZwb8pdZUaHc5Z zM~+96mXiuGCgwQ}o`WEIu1{X{`_}SiGc>FBbGLR|aRttJ~kBU%+9891fPn{6%A+k13+=+|cUW!74> zW9Gjy#70*>S~UvDwP|CnctUqIRRR^O5l7ET_V9ekiOiY=so+}s31<% z`Fxr8N@-8JO(vVs>8-DQv}>Zj8+uPDXot!VLBlcR6M{z_umQQ| z`Igeo!C!X>Ne<^Y+L+HkaoFS>jCHRo`xR2>r4*$~$;J|smZ?kmz8lG>bk(_4mE4)P zJpHs(x3;M?yWZ+4E}d1kvu5+9Ov1eERcN$)EGAKvDtQ*ZLmxZd$6Yy@(k)qd>*F!J z&7H8QQrlL1zH7j#8QkfOLz`IDCll{KyJbcjM@`Bzk=3%T$mHPl$f+TYJGsPZA{J!k zNWkRh9W#-_pMRMB2iH-AHhiJJ3TtI_1gacYpUv(M$H)3Rh4HD zqC*!~PD6o$K4Zz}Jo`N`t$B_ z+DeV-q~h&<{#{#FzPfC_{+p<3_2)Jt>W+{t4~nlWEVM!YOfFx{L~*sUs8g^P^J zk|j>-UIsR-S~_7fly5LNw}DFqA13I*0Y-TQ6Y~%Xo|z}H=9Qz7-zb6O5qV+80U=K8 zlC4Eg)x<_C7}a(F!89D&dp`7B%|EuY=^_x`@~ zxk?g(j4gGdct~BGndsNg%HBgi=jU?88HgN4Cj#DYlM$3QLJ!xIlZ zmR;9!VWW<6jN_4!j1hoF4o6}zP6xe90aAGA;#1{NYftVZv{q}C-8-l5^0oG^m)uis zr8&jxK38?3OSRssZu(s<8qw0bju_KW?(Vv(mfQWbX(pYO-!C!+M?@x2 zl`44>zCSRX?_;u@&y|0-uD|En&;I;>_H}E_KlAl3{r>=e{w}{x<^I2{J)f6tZu ze2&68D-1h7HD#2dy?Slf5c(?RCu2k-QC@B#vv{1PswuzTMpzccPI|?k=4h`+G$%kcJt(h)XGb2J756wP%2lMK@1gZPJzAw&lJ=5sPOjuPpdYp51Lef!ciuwFW(-DLZN-M{zkOB9uAku2uY4{{ZPI?W3Z+x-HoJ z%#d`Vx}2ShLYga%yJpcNU?{H(5_G`j;E5puShph^cvz6fxp03B0mnf_bmKL1zjl6u zIm&Wzle*`r_$W2^Jy1mieo0^$HmN5vms(AIS)?NYy2{{W=J z{NLXDohkw8MSR6HkBuxw1r_UIz_lF}?%~*r2}OF;+py7zENT6nB*Ad-nEbLh+sIx& z06!`#=v?LP7QG+peU%x@>y+bt8kPL?Z(jQA>Q?jt{r7k8ciiY$3M=zmN%624K=h)#D#;x&5CNW) zR~?b6WYK}?MRid{oW_|flC*+WE+mvAgV2rX+uL!+r*4#2>2OI_No{5MAE)LN;|^X` zYewtI@=sq~7u<_>*E6BJnI&N9EP+VuHnHjTRs4k&?$Sv(uAcAb5@$-Bod`IqIKxY6 z@k9LI-pQ?!CHK(SpPdDA3pBNNZ&QLcj#oCJZaN;bb73|V(z)C2thbLmu z^Ci9g!s+(0{^gWdG$1?(*j?ec$l&f~JGzYZqQ7FV2(;b4*XaKM$ovL%s(WgSnb}K~ zYqEUJuSWH2X3Tk~k)x3d0-`398%7HP#1rfI`cYmy)!Tk+^*wqt;H4Il_>%cG8?Q!v MS&A$9*!}GP+0}Y>{Qv*} literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg.prov new file mode 100644 index 00000000..77a5a627 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_440.jpg.prov @@ -0,0 +1,5 @@ +fixture: base_440.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 4c081c88785200558d5e3440d67bfc667434795ee01f6c46e86e974795fe02d0 +regenerate: cjpeg -quality 85 -sample 1x2 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_444.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_444.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d65a99d0ab6b2c4a52e5faa32cb9cc5c1a04e67 GIT binary patch literal 46658 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_gray.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_gray.jpg.prov new file mode 100644 index 00000000..c9186591 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_gray.jpg.prov @@ -0,0 +1,5 @@ +fixture: base_gray.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 9ef80258bd425cd773d5e62863b3cab6097491f00a9f60316f0ffff9e25d37c7 +regenerate: cjpeg -quality 85 -grayscale diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_q20.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_q20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..640eaf421241f6700e570747c9b28bf3a8e7ed29 GIT binary patch literal 6686 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

cE1D#+*vtuu`!Bjg>0x!ONs&k{Oby& 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

Sv0N_h!y0zvktetbLY;POz>_1bjsq+2ED6|Bf zcz;YTRKpK8eY3LrPS`hh>@pzuqHD=?pfr9s|daE zrtTv=?|zu>gD7m5!xgNlOjYLK7)k#0A+zTF-gyv-w^aoB;vZ?qjfNm>nWYU3eiRHYqz$wFSARl`)CzfTMU4dEeU}sc9Wq+Kms&PXAC@{ zYfrM}N<=D?2oE&V{2si<$#m(}Zp3Wayz;s++a1ocO}V?W389Qj&i`E>W0_7XlsT0= zGur(q8*iVrR05gA-|quKr={ikvPIYXm?7M8)kFEuNW8}+TMJ{9YuV|ya1&@BFedx0^m$(U#{$(HUbkCJUNRN6?pCUX0^*CQ2) z<{hRgB#CKzCA^>8c;<#}iz^x#`AvpamZewSU?+Fr!Il7L)5WoMW@X*;oUcHL&9NR)Yyhvw~Hm6Xp3uY}2zd@BPmGW&g_UiC4gz6tud!Bw2-w_eU zkEfB3++(K3t}@5U9f`8}A?8>??|(hp`qq!P!%@j5=Z(f>24j3EuEN_${ZPai%|lUH!EAO z)HNP!*OyNX{N}t2{(#kimyQL?CZw)|QTs)yi&5hAIh8A}7=e^qSuWB{)ohLW;GGsK zHxuT-)ft|~o+aPE+nDMkD`uZiJGUfar0}`=>oYzNQ)N7iJ2~Asw$Rg>BM6mY-PEXc zBG8@B>|B|~A=!MC4X!U(EcyU`A{y-z>M|aE9CxKOrrYDRXVcO(ehok@c75mrdqSY8(2feyGku!vw&Ea5J@&M804p30H2k%W_FG*$!W1Q~R zyPCud1fp$QRuB5-{G{5QlP?7S3n=N<{84lWVd*da=;PHcB+ex6oL^Sxe*SC*r&{^W z^Lfm9@=0`={=(Fp&tb#dMRdqL!4Q&`xKz_brOz1EN1-0KYF{M&MzXfCeth{=CEWPP zQhvFfXLb&0?CC%Jm;JjvuhadMprj{|F4w?vW?vZ!8Z_>UmC!Bu)QV%O9>JXXU-c&9atZqH#j$;D?kJH2+q&@bUtzx^z=^GfZF4)(^T zCu$%ovy~}EPf)=KIiC)2_82W#p@HLs`J7La`p7mGR_%3|XOTa+L+4zL2Q}Tn@RF0O zX7Qg@r@Q?5M49a0Y&l3tLKUdONwUIA*YqQr;{O%RgMB!D)QCA1w!-=yC1VK|zeNAe z>20j)-z`8v5zxRh%Px71S7V+G{@M9Wfc9sMEvSdX`5=T%$ftov!fMcWEV<rjeNUW<%u^Bg{k8YUVijxf2 z{44*Ir_*)`!2HFjzK%VQeNK^w;Ix?uyQS+xu!0gDNHPKJH1xha0tvu^K41lD$n%)L zFGdClHh=-u57c3fD^8kCyI%{GRD7U;Rj3;qh!R=Dcd=epFcOjif}Bk0VF2oob$#x( zapuFbPY8#3mzrnP-&yBnb~fA`eJ9#K7}~+#{CNU{k2hZyl20p#qH3a z+|gmX%F5c9{pU`ZWN}Xze0}J>Jjhpni1!=^;$6COIy12`ROg9o^@J37fZ+4L;(efn zBH}f{#Pg^ZSc9kYk`+Yjil$VQ5|mE+4+O-bdDkf+!r{JeDRN z4pqpFlx~ivf#C8kIl?1ZINK>Ia6U~YpJOtGi0UY}N7*wofW=KCb-gX)n(~KbKk$1h z%O4vGdseptlymmZ+e|aBbyo!Dn~F1U1`?f*XC>@)sLny4f<~gYG?yoDwtuo(t2S-p z3;7x>w?6fGoJv$Q@<OK4FtxM#1xrrkQX!-Rb*x0{57#7HNz|1kV+(!!pn zqp`ohJ5_I)A&WJMBcu#Oc2DiqC*p}pp!M|C0p*gdsL zr0yP}pggu7-B7GBES1KrsFWCGss;z}Dl|afpT|Y2$JtdNL=dR}u&vcvURa`^%9(Km zCIueC_&>_shTQEV`|aDP>->U~D?2Rq7!_;!H}Nj5+;G2$@Y~NGCJT!GzNE%95p?-4 zvzu7Pb`k)>7dvCim=Ga&D)CEjRxs*L8ae=0;)DaJhyX@Od>a$p0i2L5S`C+yr9f<4 z9zqW3I7*^uf zPi`|0tCkB+QvfiM^uY5d?r)M2{$__P4R{PjT!jeu93 zEsnJBPEsvnhtF;7#_Q^nB)Wx17}mH&m+HA*|H07NZDEBnzo!w}JAc(QPlM7Q6h3ch zpU_qLr!dF_qsw7Sr$a!kfmo+Q<# z%mJoFPMPO>nx_>!d&2t>K<89Z!+%}z5l+k4oDYfSlPqrn_)6IH!uDfFd+sz8xZ}gd zY0c=gUzR;(OO&}}AMGnc^YaB$-{`ra3DHQVSis=|F01kWdE3>nhfPZ;Hq2$a#4? zE1HK{p7oO^w+LgijQ>hpaBpic_bkp;yww=Km};(;e-2dP%#dI>iPwv$7RnIn$ktZL z-D4;OLRK3AFV~R<@QF)m-U0^XNb=I>%Eqi<)c&6pZSe+4foDoi$|u{blp5PqO>O5I z+X6m8n<~qVy)<;=BRA2ftmWxaH)w3y5YnMHmFqrT74G+uc8U_f#2Ah4v>0AoV#qYm zGj4o4cDEd%v|skTVR;wpOR%W%+P*7xS1n+f8dS3Is77(#;Zu2r-;;g&hFWYuE+_5I z$yY^Qh0b)&G~gb|h{rK#uzz+OzG=yA4FLyK8Qvg4Imv&mvWC>$#6u!;*)U;=RJYgj z{EP(s?x$~}#I>6y$zW|@qhcs)othv(khQOr?d#r>zkgih6IVrGPT1j@xOm;(l+hJ2 zG2lYzyaC{x>)#-)B>#`PAZ$fgb4dJRBdY4z#qeU}?Evn$A;4=AI`r<0?cZW;-wcLZ zqs_k08G!M(=N>Jk*J>Di=uvA(wK4Ie)-9Q}!A7aBxC8gj=AQ`_er>8}TE`(JD(U*S z)=@XXq_SfP)7{qL7r}!zh%asZ8aXH3rDY-C4Cl@5XVn7AkVcw0uarxa5E(a^o5CmMc)kAq75FROEz3E_ z8?PHl?3MYvR?&%5G`SCxJi+RU>PIyk|GS&pX+5ci$w$=H$1imt@uPF=p(t;L6Xo_1 zDd1`eKhBl7u>ONrp2+;3X7;V>(%l-Ga(ZpjcXWbV6@F=TiDqTgKI-L3{^S7;jF(K6 zEVr%~R}b>bnxXq2d&7(L1gEHAEPx^skS@ytW51|P-CDd*J5hTQ&X39)p%q92r+ppv zJm_oXUxsQ~F=tAE#=Nfu9PIl%G3I=+gA764C9c=jeYHTr5bY<^@e# zlGC-4af+_Jon3#B?wwyYH$SDgsw;g;YWX(`DewCV z{!*Xy3+4PU1p}KN8mzoe!(pVLfDG2osUhC+g zJRBzu5QZV3U_}};3DN-0^e~u3m;<2#(hP#vLa-L*%xFStj@QWXtQu@Sqn&( zGEJ*#|LaEm_<>quiPx;!VwR&2n}unFnLh=XuA&xgK=3y?hke<^$k~?*$0a?VLi+u9 zH|n)!I7?{#wX}Q3Dr3gGHZc{xiwzCf#S!m$?J64!A06M94<%DRP28j{cvz!WhNKy$ zJa3lv9awm5`WV@2$K{mWuX)pP?Bb4CH+XGoeY}VmiaF9in7J9-NNU`1{$ zZs)*93ml0g)615pF|o&}*o|>2EWdaoJG?8P!=J|MqU#?GxYwJ1 zzMZ^?-zt2-GLX4~*}?t3^dUzASVDbCb^p5d)GjZmHM~zd4EQzGL^dy5&g!TCR|I5) z`lT3JhaM~WL`doa!#DLGU6r_<6Y+W?WILS*#j)SXQQ%)atbj`knDQM8oyy5uvR$)@ z(-})4nY^c2a>Ny~VhvQ<>rUrbnca>^NJTc&|9ocfzeZ>8JhGtB0QCFPy%v)$3pi60 z^+kQ-=fmE+%uDAp2C8qm4N%qKK1K5?O}lpw*7zjspvop80HN1nX*x@C_<2py*L_?? z!zZenEH$pQI2t0N;BhE%O)tw~7KOaU-CF*C26a0-$|ZgU`Q%&+wA4%b()pF$F-^jY z4(AOTX>V#dr1)-$^<6B;*J?Elq2TNZquwpAa=%~hUxn#_CH$ZS0d%4{X|+52B+F^) zOrKgbq6dEDU%AmcS11`2;xG7+rl2JtU^MOQoQzmA&A+^Mf{N83Yu%sHu!j}P!~~R} zXP&Pn=^Ca;&lAD}LPBe|UM*)0=1fRcbDMjykA85xzpR*A=-s|n6lXgZ3c%~S#66@t zJH@V#zCd&16ChR&iQMxjI+E^vjH!fsNRP! z)KYsn34p#FvF4XuE`X_Id?GQSIG$+9V}eKf!qV=AhPqysI+^;>q6u{BkuMsEwhfyb zzZ>HR{-!n<(NwVxVE@rc7n>}#Sbj^5Y}F(EitYkc!&m+8YzCg0d@Oi=NSOcc@XU+J z;094}%*y9mH|!5*O`HU*{rT0SQ8Ab`!z7>ZDa(nOh_s(OHZrcywP)xLcG?SS3#6!eA^p8h$GU58^F{V|8^4l@K2bl@T?cof+KkpL!k<{1>OfuFwGy zwlc%3(^J-ZJpV&mFoV!lRKRv~nC`}L#+}jN{`+xD4GvG+zo>AnvVd8#Y`BSE?PoYz z6;8}mu+sO1s5gEz)zZiY#*=jh*U}{HYIZXcw}bnuxaTl_=DRhgCRqWbdGy_!&)j(% zY+ql$bEsGPGQd%~uhDaT#UL4D(G6!f2*p#!Xpyp9k5w0t1k`ONaF-19QuU|0mKav@ zC}AAL)gIT_GVv_S8rF9)asBLyYZG|;Q?`5E0rh5uD<9|sf9*3@w6 z2pswY#X%BG_y9d(W~Y)X#Y*CZp#7_4);qgR`Wjd+BHW`en^&+mCJ0Z8CrGS(J_puAH@h9duDO6i?y|7Bc z7Ds79DF@R0={UZr-oDuDyS-VooK-E*Ad9!hW82McwcSqg^7;SmR;H`)z?#_di8$>h)9Deafycs@U`2ZZ9{W_tO;Karx<}u0a0Cyq zbr&A%+;2!E{L93+6$;nA-R!@vsK~fMctM3^lN!c61xDXb9SMKGYvp*nM730mlFeBd zs(W4^%D+~het9sQFt}_F`q0*$CkE7p&Y0;_{eNGYfe>C9Dfh2A@AtiyYY8zR87gCe z6C0z^4y3p^-A4dp1SzIC^{8%b3{#GI_Bz!<*GW9M=1%c9(U|K7q^G*~60n{$1=^~x zTEXJ7{mfM58vWL`lLSZNTf8-2^?}?Tjk%Fl4v}+~O)fDNQ=AfO*nhSU^yLxz?yRD@FD{A}DwJ%(wl zJi)YtuJHmlc88i*SjAznZ}Z1N4<i(mNm zFMYB;39Wa|>vkXxYlQZ_dT z$bi*I2A=uX%Wm@Q?B~h{s-uxtbco31mmxgI49STi}J! z_AgZ&<-M7>vkaMfDOgi&t)OUd)#i=_*1%b$Amzqfg9h@8&zcV3Y{5`wv4VJxz>OV> zft;=*?cOjeCZ=;?xA?%veTgN3e89yYd8^?#g&|9G?8&7WT;gzt+TZiy;>AQDMoULa z#>wy7rkr2&&5VJjbj?WU#W~NL_8X91dh`sY-=KeAwEa$o#FFRZ0Egp8-qo)iyq{O9 zS;e6b0gr1m)VKt6HBC*~wiU*^T>n3g&cl(-{r&qrr$cMjh*jg1Dq?Ta;9G5}STz#F zDrzM5s#tZ}+Vd1a)uRuqldC%@-U$bH}WjO)7Iub1Lm7;jdI zfg;xOhQO-m%C2!bcemCNvkv>}wo)pyZJcSH5bRnlCI$nul6t_F-f6~>$jEqzs3`?& zXY-~xy>-tWe1LP3?vr&>D}27lRyH0Y`WGFmHx;Rbw?8v|*TZ$Dlf2abGBTP-A92|0 zPxh%FGCi6#K?+Aanecv(-U&V$#UNZdYDkS26Lk}1A4!c(;|CqTE^l5o68(1>$l2}g zTWDmOrKHriqR1fadOekyEY`zTXEPUyVBi4+xXl)`8bjO;JyP=z-n2%8-4%W-{JrnX z((A7#)PE~y5?cU;D&xbhUA~o0)Q{6?aM)EoX_{B}uoM-lEma<@OfzTp;*gc+-~f*> z6<8LEv^)Px)@f`aX))qb7dHdSfFFq3W=Fp-JJ{arMTW9-tU6^r09em$jg*u)*lZqb zTcTH*)K0ChxF8w37P%nAbu9Ru)5p#;!YR31aJL0f=n#3IeturJ>~|u$aOKaX?lGFj zpG#@e=YRdVgy;7=5nR^Yo9>Q2d;8}SPX|p|gaJMnBT^U7)Ypp6`HDmxzCJ6|Wylji z;0GVJ2TB}0e%*CSGQ!2)%`*5?-?9`fDH)M6-KVU&#Wi z0fcTo!tmo0OW*H}CaqpH52}D{j5ALyuQF~%!HQ}^ZRk+7m{c1n7iWg{yw~6+CcbAM zv+KYAgrKMDD9*xI4Q8U9_L?UCiJ0)3V@b8Pof4afYiP-87^Np2{jl^wc)YmshS!Ub zSn0BVbwFR%=X2&@hE@1Q{g*m#_S??18>gdKkTUjjDUQ(!Z-&U1y8pbsuQ}8OmAp&P zQ%nI$MK*-IdluGi_-d&3Fv-SSPSzMPo+b>;g$Lh`$4N&-yQUmDS(lh5rAcN$+eryA zE7lwk?i7WGC8GaUnjPYEYUt^!c;(QCK|R;O&#TC&j|sR*hPyb5m-)HpdX-$LN17MC zuE!hR1I&&ZX&@73`;|hT8fscn#yetBKQDtO?~d!N|CMYg2Z2A&1Pztg+$9jVDPAHx z(d_hibBgTGXQ%dwWd^?4@>3a6g*M(pNyDRW$7z*S&+Fg(Y<@^@T@PE zK#O24L}Nl0Hq-*WV$p)fbs@-XU-Y0{ICD$g<{G+p$K8@#gUf<8ZU#yvK%Vn5k$i+i zO3X?^2JsQBBmpNlljn~U0+$>76_>8uV~*8p^Zl^@06Y6t#$O0ia5| zAPJ&aMNaG*E4X3gYEm<6#ko1Bc9QeglVAy!qAzMnh9#r*bN*im(9${CJ54e@@*eU%)fLc8OoJCj$Qwn-_4UGLl;d$KpDKgDIslJgR2^P z0WnnT#fo_=Hvuct`jT595~CBCXZ50nA<~j=Y`AZ*1->sLO~$WCuVxmC)ZccD!CKg) z*yDyp`qachGxTvJSFawxf3kFAtk)cufYV1q`$n)_rhG-~PHUljt5D$NyT@l9S5qLf z|2-8-uQ>%yo>@oZ?>yqN*StmIqs))g;Ajjs!FWIv-$~rm!8X*tF;hQ?0S$RZo$O z$0RW3++ZlYP*R~+B05Uh-igz-R`baxa37jkMpO?I=1fDSICw*yyiDtPZo^Zw<8JpA z{a9MtSl*CGi*8E|e*?NEd@cOkYqcpo5jUKbqjYv0lWC&D{$ueU+8p5Q%`&ftAg$o- zkyezK&FYVxKr?dxct;HQ{(BntJA-elbPP|50maZ2tut@E?{V`xNfK`I@Koro+8q)| z;fgKt=2l*P_>VWlisB&j9trifwJdCTu;%=G zZN92AE6iz-5HTUA6*w7y)-pHB`xQ`z+aKWB)anX|R|#HgmN-xB7qb4hcSRAZ38}{< z*(4n-w-NG>1A>9n&1KK&6y!vz=9J}X*Hd1xyx<8uP_xS8;f87|y9ecP7xK%Zl`*{c zjzy+T!)UViutGzkd&v1JakT~V+q3%XeCPL1+rq6m)82`fjgJWDmp>hRyW#9VS}aPe zL_t(19Bj)it&X>yw@vi(-=RT`S-|0{y4P9jf=I&mej1O8{aUPS(N)tzUl?tvo3`BefK{hTWC%j`v`lUOaQbqU*5^ zF{0pGip}M;fQ614P3weWNS3uy-B z0w4y)buzEsQ`N6Q)NOMcl+VoBI~ZZ~Db4i_6hGCsKH?vXo@6Tp!yWFsw1%RmmNglG z=;pttMC;_%sX|fK0PDYGVIqtn>xtI)Ta#HBfe29WibZe?EdA|8EtARF)) zytw51&%mD6D%`(oIt7R)eZcD>OQSF$04*sD#F8lJo8k%|!vAOTXjlcUJ5kFR(SGY_ z_vu&`8(TrsO!DH6BOH=o6!QD&%g`GY zKjT}5X3p+$q{7DXvcnOqn%dzjfS>*sMivD&4R1GOee+ZrD6%z;ubA4RVg78AdQX~1 zEP3+g59HKYudW;nMf>}Uc$Vw&MWus?UUf_9q9~`%4j$?r(@U`QG6Orez2Ba`@{{j= z6}>FVu1|1%Hq$hxr$zKkJXw1*%h3rH^uI~pvwI;WP7a$2SY*|AV?x;t-+hmDqfZ;XGIA>WE2#HKQP-AE{b6nH*mbQaGY)f7kmuAyXesDEke zQq1fhioS_LXYZv0VDZ;SSMu`B`gp`@G;^n*@K%d@3Qtgn=?mCvztxf zVfr3Mw>!-TF+zov&dFbDJx|qRgO>hWy1b#o|E>$An@L;LMJ9xfSspS-xupG2R0ks%eTQF4xII!3)ruz5Xa`fz+y8y*M`t z_2*5pELBKbBcXpX>{hA=Grzr>J@4fkW}>Ox;{&wgLTy(sXGhBifRyehSkPMr5;tyW zqoSV!ZH~)b4jifWeLBw}P_@6Fu99nwRegJr;C2Z2TvL#3^k8x7Dip&e95L>>KjnMi zv-t=>99jhn8}3~=q@1+dnlZ`t zt{_JV`ZnQyH((74e2|daPLjQDGvHrWjc%o5xAM}{!E)FwMbG~bc@FU};T!@ekM5&} z7M}2Toy6pFHM?_>X|u#<^(0CwLjIi>0x4H$8HOZ-tLL?=FOHXtd75OvY3yxkmfQ)w ziySbLp*8l52Mb9>PGT+8Xnq<-34WkcIt!WWM}+BMD-2~b9|XxJY7e~@5ZxhzQ)TSIwQ5C!`vdTf`hD;5 zY~yM&+hLL>!V;=gLN?JKZ4-Qwk6Yes8eb1C0Wb%xt^M}7hB=QfFo>@Mx5Qpw4yf*? zx|-GzB=yiB#o}GJ7-R!)wvv_+r(+p!QrD>_b%R3WGsES3v}Gz%4uR1#cG=RCRUkbnXdJ>+L7x5IV*~2<@q&V72FlVWGbt2 zA2g^OE%Z>rv`t7G4!ujYySe397Yi99-0Evj6mjCP+mRS4>hs0=lQo+o1u@-vq|q=QpCghQ=ZX>e=hlEo875; ztK+jn;p_%9>E2IRVjZ^09UY*l^i2feOf55BCFe^Imx%;KPBhPzzVj)~+A%f7dma3J!oLGdu-XrSIz(sKuA#CVDi>BWx_w$6WUFdEE-8 z2lh__d)I>TdLNQgfRS6g$?*H%-J?}GP!luaFF2iWtQ6wE8x&UFeY zk{K*miPbvU8a6%dAT9LHYVtwFZ|#)oOjP80V^^6m94Ov#omFTW1nQmoZjjVT4sp$d9FA;Uy zpYXZnjrtYi6`3quORM&;HW_ukYAT4uKDfDQDN8LeONY=mm4G%}`u}K=} z+*Dpn9aq(+8gWpX9{?^@UESHFf4ZjGSFx0;_a_smD4y%B>UlFFtSL`^U7Pw?oKdW*q@V~4{ zf#nh>4j@kevt{-*9^7*I>7fK%5tIDA`#{j=2QDPq1BdXtll}LJw#S2)k+&-rx5!sZ z1-;q48z}kxat+QIUu<>jmDY3^gr^Sh6?EJ+xI{i!CwSSDr^z%Y;|C)%LO(K} z1u~~CqUcc!?(-XygL6iKER95e9-RYr_3fuPD@C?F?n}@7ZK~b-0-EdZO)w;9G0d*c z4VV!YN91qb7ZMX9wjmA@!Cz!6RPAIghskx1L_~zBhCVNLls;?rDwO+;WMXMN=}&H2 zYTksqeR|nUw}AB$YGA9fjjpVXr<+#AppbGX#%Sxn&6-=f6N-~IFUMG*!YQ4#eX65} zY#Ln}f(NjvVn5k4-4H-qe@w-d{|vl;2lWT|eK$~>{^3)xk~VZ@Y9uRTW~jyOTtk66 z$i^HJlyZ%|z+3M~z9MYJqRtYhWm%e8U<(Wg%wGig6PO^r6`oz3FG_#0dhjd-2sx#b zpKa;73FiMQP7FrC&>kjM7`PfnNbSb34Z&5CRb64Gt5t~~WR8i$ZSZE#DfMZePNUzf zTwut!vlv2(9*eQf|4MjdTg4ZTaPNfyiEKAg z*zKqzns1sxE$j+SWi`jLPeB&KyiGCXP*gmdpVz$wYlsX zUnpNXJ|JiM8!{bfgz>7~V!?DHmLuADo!GVCP8GMD+S%Pgdm*asrf~X~w$!tGL{vMw z8OAT?n|1kPAaO>yORIvEqpVvc&|j2*92qMgIM37N5&^?UTDs3RM`js6k(T|I5)14|WxL1cb;GvG7Ckaf~Vo^pk$}-69POf~0+E#URu%c1b=OBf1Kj`3$fW6!~Ud+JLm1aGiKJ5v<2uBK98Kp}@e1^3!i^(uZV}Na8rCl32M= z8I*RRuUrKIln#`vJoHPqzBmJ+v5+^J*q2p8>Kc5>`5O$`XhktB#s3hwq^Rl-RYYcU zAB&52wF|g~wYx3J2PNY&{Kvaowf2&-#o8i;YB$_7uXBACW!DEc>nviUD>%G9wWZB?t9X^ z;x(P(Kbl0mzEYhlJ^8Bq8*);ZRn2z%h%Bm)0nw9!dZ9h8d;3QLXHf}D@>;mq?zLtE zb~n20RGPug_c|fJY5$8knL!%eTg^K}L#Xj;9FFe+(_3n}dDrs(Npo<(%`VC(x2E3T zzwVyVw#PdLy?-T6DH$#3HdJS7*v%H50xqP*@b8=_b6-mFdJVrryR&XH5ol+Cp;HwNl~9uZsJoGcCe}c1P0ftB<;b1)WxAhB*^{RkqAPDsW;p}nUs@tLuZytY^ON-}v8qh4q z3NtT|?Z1na`g6(InlTLu!=Bx>{@%aTmfUaE{e1>0BUciK(W3gBmab$i6PEYS9tObk zFh`kwl?y1c)uhg?Nd2ky!Qbk$y~B+%sLP$t;luAFdemV-o1?u~b18m@2d)4N@onJ= zf+7&NY?nUSqI!n-OrIkuKwYlB@I$_PA0%ojjV7|F8TTABq8;>0{B^$^MhFi zi3`HhdZ93knu_P9*0M`v(F9yOpPaPcVtZ7fEemsIwr#~N?bQ>UsqusyMuZLrzOrpa zR<3XqAqv*lXoAK+6)|SM(K{XJ=r?~Z&AsqA4?i^%Kkj^Ows@u;WdQ$k$#75eYHFS5 zpGzad{ET}^6>oYIM`pUQHDz>(B;@s88*O?*$g5%bVKH8aZJgKw$Y;8{~_?HGK zX+h?2`q=U=GZ?%Yx(r|UGmrEUZc>Z>xIWWCB}cdSU3P!WjsHBNtq+Dc1W?V2bmmk%AY^rkrIdR)1M?9{KZ0%ccOF{$mUhDFg>2&~>#V_BEj5I=%mV4@)JPLnUQvM1Gu%W8zrIyK62|*p zpAq+UXE%Lp=`Ejv8pc_-r@2sNOQWvl%q^~JXu`ZtX||n3xNn4YcKysOf<22QX=rAu ztHXv;p@~_q9>v$`<*7Qm%xFhMc{Q^2b8wBXYXcDy#p{C5t*w`8nuT)8YzS)I6xjTZ zoNla-+#w*%&Jx+kKKQJqwjxdQg{3KFxTfZQ#(%T4sRghfPU%{M2Jq+2JObq!Yt%Q$SL|_T$=Q8< zH0Iy`q$&XeC*IjOhi4v*g_K*x>OQ6%Y^brsr$b27XDY>B0rqwqM<ttw1Eb^4e}q|{5=5T*(BBTxA$H&18BB|Hr0k788&5sA)g0dzIOhu8c~t`y=7AAy4XFjZoq=2cw`1La19Hy-?|y|O^|s~7)WAhCZ)QfH_~L{>U222(9Ses zu>2EUr}n-wg+yQ`!H(@P(h4})JKbvR7Mtjo66A;yCb~VA``$$QAJ5y^Iuri7 zM7eK~8)sQ>qQ^P5Y&)*IZVaHMCvk_lpvqS%4xnlRFvJ05Uz2i-IEAE{6NVB(B=k(N zb)hfM19GgEMQ-;j{hCkL>K(9%lQzc=mgN2#L&NJgT>yC)eVYWk?I2u22{70zXrgOK zRwztHTiq^!^yd=5>v^h6oTa6%>4uL zofed9HBe&{S@l<9`bTra1!=bM|G zUnw7oQfJaeO?wrL;sydlgvNh*P)f%Sy$JUf*=_92N^NGMTJ1U!R~q!F#ND0mBtv0$ zJ1LjM(vLR+eJwo)(}903AuH6GQMt|~v8kX#R|v-NbhvaL%OFV*AST++@P-jrs9)49 zwv=>7+y7bCwCp)G6@I_q0kp!mkT#x_uftNqn^4QEj@e=u32WAEC=PbBb0V8vGyF$u zz7(ajjp=_h-*n6(D*UIx_yOS?*Ply9X;I-`&EAC<5uBcKa;r8w84K6%{JSu+ZWsA^ zEIW+rS~lzETe_MoS+Z|3Ta0M@@WXk&y+^b+Uc(&HhRn#HF90E#l>-BxU^)D*kL0Y@ z>2jdR+T^;FssyFUKbJ^SUmmbQlo$jetB?SG+-Oc?LHJA(6gT#v`f&Y*mWuva@cl<8Ou%qtp*Zb=okYnTie{AlNxaYc8np+ZljzkJH z52{9jjJvlqCVuZtHvcP(J?M?RdoRsV(1Zd1_!w_kk~R@(a1D!C66R%l(XD?m=%8yh z>1@uEXVR+M_A%r0XLFySiALTNWbVKb-r)h)wq6I&W-}_ad)j}zfm1^0_ z|Awq$W9F)Yab?j+TTf-E%}>8YJ_0*LaUQb7x1}4>cr$(O2oAy@3iiE0${&V>2pIpm zRZDe2Rk+k=+hm!kZnLspo8%f|Epq;M1>A0#LZf>mV3|Yi;_BNnF;b8EGS&jIz~LvU z^WuE4lF;wpQ>JG;_NZqP#&>lS{B0Vgowt_p&NTj{tc@s(C$Y>{9^p-iBZ}E~RXv#W zQZ*7P3v7cx!Q0()a=VQyt7kHMvK{=iDTBQ%nf*E>n+l4_&ku46W^_*AX4&g*U5-dgr>%xL478Hl)?Zs3=L-83$*cm=0EArb6&sYT%%cqU)ev7LR!_b zs%vl@f+%Z!UG2aI=X_VB13i<*vGhAWQk%LMvj|X?k_9VK7nzEN_Td`U#G1BCXT zKq0`;eDUjPP6qF`N1@`e1cu)s@M~A_))SPyv6}$-27>~SMNjWy?XnqRKeHH|1gk-G z9S6%@s{N0`vR&h%=bA8y*ldUm^JKMethYPjm2HTzM0jq#AjYCiC<`GyHmwv^Kk3s1wGlmzOjqOr z`Gt<7%I0`GMWN(y+sU%FP$ugl{6g+Njf46FNA%PcR!zU*^#V)}q56pT{9X{C*AaVH z7(&`x1*W@}_Ui5belA9aUnXlf%Nt2?=VRS=j51x}sR1Iagp?!}4U0IFgbVgiA>Wd(LrBX@W6 zM09;nzDgePQU~(fLqT$1l%SJkPn#Sk1Uk zY5&xJ82015?WJ-ncWo8ME!RWqV(Ry4h&;&?Ulf`L*F zW&p#M(HQCB*D>3Wcj*nmuGfbF(t3(m$5Q9dOUuo=EwvRP0`803A|~0e(p72Dfy@9A zd9!BLTtV!|VIbc7~^F z47LoM+rk3v^c*~T|nK8)>>Qf`uFh^a~!M%2?AGxvDyTJ6-G2Ns9HVn&$Wif^X19xpyl0&Ykq(_Fr-IGARC$mM{bc z(+V{rZ~K_~v8m1u!s`hfiD~i6wz46q|1-cM_WmbJ9NK>D;G7*{*BGp*FMsF1MD>5- z&mH!D@o5>cSt?|T{Cg+PwPZ9(Pc~;b&{8HR-{NL+V+ z602FYpodv;-<&+(dD@2A98k$B?+S=HmTp12US<9dYrT&1Xcf2Q$a(r}c07%pMl|D$ z>uEMeBUNI-rXu_{1m|6N)r8`tw4Og!4`Zw^SSZS2F7fjT;^|BDZXudBBO-tW8SWQ# z`}cqR!QjAj9$)HzU&WzK9SFu5?iUVo3(Y~+9ERfNX$POG)^tE!va0U8HHS0*XJ#Ir z@2oG>W<)nVgx9Lrq`wyV{7Z-sk*Mr+^T5p1&n}cimDxKkdOC_$PZY>k9K@1x=jZIZ zdRvw5_X(sasK@-?>k$?k}Mc@oo(wb$8oFd zyYB`8tWlak_M@C-RQp2ST(s9#``uS_Mt0v^4Qh7#t3g;oINe?62 z4rb9B{q%j<=1V_^9j7Im6Pu(?_+DzLAX=%}8_}yrM825fS%p=~@R3;o%x2%b#bm8q z8AZnL^alwN3HjV_&LlwD9}PmVwm;_j*PW)%n5U%g*JYH6XYG2|e+t+?xWt%eo@`Zl z)77T=QHa42pa9-3Ne&@)^q&&%fgoU%S^7YyUTv_Draxa*wV8-1g#KX453=6bi zIHTUB8a5B_;_K*cyTTF|W&Vp?uE#3gIVmE%c{tO8O;e2QHAzkwDk&LYqQ{V2S%@q! zYbIKM$5;@<84C~Ic@pKU7*6@R!6&GOO>~0gn0Pg3kKE7T^U)c3{1G|?>h}Q6RbrYxSrYk`Kz_&1NrYqx_mGfe&umXl}ioJjzV z-kii&DfAp<*32MAedqS44~nk1S8-8s`CDIo!`4#W@QbOEMiaruXBwq+_f0nkjo|4_ z;BX`7EgUA>2-Wv>S@9cN_BLy%N-GOev*L-aCBLTX(<-);F0V@XFc$uHCmvb4Wdd-{8+%d z{qz`{TqxflQXmU84)nLuw$|hQH4?INtHw*l9-)MZB{qBvIBHsL4Z?Q?c(Z!Y#LkYHgp3m{P!MJ{oJH9$*1Ie`f zWaBw_RJ8M+?_NZ3Z?yKBz0&n@zpM+HZr3?4i`2e?deNMzj=xrn8#Gbm2V07o0Bnl8`OJgH z6n`;YK5J*}E7?-p6mOil+nDToYd0_oZ`C}Lc_(P@O3G~O2dVBCXoJ++c{T%pd6XR| z=mjMM$(K&T4rGQRt)`W6JKrEG>*X~!yC;G?PWWCAqV8SI8!lClE#VX7ks#h|;aVlL~t`c%=xmnm!F08!V116f4CA&w`KQ>~^e6C5Kor$L`F1SXVD7 zT|qq|0E02ilM+x$PW@q>kg)fN0v7$0R=>T}mhb;03vbo4Bo4}jsNIHZh9ol9K1Cc{ z<%lOBMCu9dF#qBF-gxhd&VLkQC}8POWOCDAU3$4$KDvk`vtKZjmT&X7HKnmF+Q~o7 zQs|LpLz=Ct+{9+_c;D#rfXy_SBYOo_%V5h@dH&x~U!06CiDq5y_qHAkVB)aELL4`D zsY*j_0yIZVvF8h=B8zs4%XqME!V4imzP{5aK5NQf(`UB=g8yqz=SVtyl%-S4XSs)N z9c*zMDygL_7`@u9l$)}9m)^8ep|22Idt$|4x5@ZcUY@nASTQAO4w<&u3(M&78xF*Q zO2y3p5PT_ZCTYgaG{hMq+CD`U5v6e_E7?mIP)c&v_Y0Xgf&K$~|DnCXD~3cjY2r znUxQ)2lM+k@?H-~*w&e9fd*>}xml;auSQFP=#e@=6dcv`KoO@V=u1RRX4UZLHASl1 z=@X@2NcX?3bjtTn5rOv@TwlCAW#i@#~=iQ4I}TNS8IyKWJ>EJ$nhoc7y$JK^?%YiW7hPVnPj_V1%? zwv~|4vyJK}ux#u_My)YHV|n)WqXX=WGAy!#Hoo?Q^BBD93+`-bcxa~fq4hjP!(B#w>%Rd z=HhdlSmoV{8r8OTgx!tg;X)10xG#Ib0r~20mbAIMpI^xA{Z2kGxn(~MR;rvsY4nXy zqlM`arZjB#-8z~`u}|fu5~3xeGl<|A-SffF(d4qFel-Idk~88H7QlJVp)gdd%N3BW zxHqkZN$R3XTD%UxSOQ`RNoiau3tRp4p(Z9WxXU8 zH~w5Q%|!g`vAQH`hWvA>$W-==t6qMd!TJ5CHAE6FHU!c5e@39Feujx<#D}AfOcRK97Yp)kxj?vspLi z_+1{m#W)DOHAx+dGuY32{O8iPO4q!)7|B>RND5egjxm5Km*^qit;bHw`X741d>yB_ z&{E}AP~31;4dEX70)!k+lLT}hHYITr;s^=7_%F`>YFlG@z57!oLk0?r(K9?L+AF@t zh)oE@sGoCb&iZGIsS-}W88#z;$*gG|(MdT4)in@ivad)qPVHKI+H7#_6`5c_ar3)K zBUW=w{p|a9jP8VTlFx;EA|;(tYs0~E3{&8E)vkDI*Rq>h;wNfN^9v1@v`(-I&}L9x zz73)|8_);zC+fu_QG!`C%@*?>D!?p*tSGQ{i`zgE6W{M#qgum>W`0ry?q(8FB|OQ` zDyj(qcnj-5YKibR4U1G}u9C!T-UfD}0_H_e)8@~lGqQ53!qan(r5$yN`GJph{9yLC zRcn4X^8s3OgC{#3+;}aPQ^zKewyV*FFQl!#X6*(~&uZKIrGvZ!Q9mhwv#b4@U-~Kz ze?%QQIX%MtawG*TORE-ed3iZ;BnF@ED&I3o6*sJXHGZj~YgN*)6=%*V&ow*yFb%dS z1RJZ6l0=Ihf*ZF0^w+l-+(;xh^j)k6{9XQRMo!atk(#Y#7Uo8vyrI@`Q06LuCq9w-W$llVr2#Wdv{nXia3v47|g_l!a%d3u+$jb69;m#3ChszA8S@{;0sX?nW0Z&BuT( zvEZs8z;hG>NF)f6nFOYwxDjp$DsB=|gNy%UohqbKfLfN+JbzO<`>aaj^@Q~#-7O(7 z_{XW}T3h7(O^{peZPbqD|K%kAcczmiaoy?jK5|(!sHUOzmI2v`BJd0mam7u@sQvrC zEgP2Q+-n{6*23z}*zQxB!dr(T3FOf@;yK&_vm1wr!mHRjZ4><55W-any@%&x*$anQ zW|x0Wu3R2;*3SD9x<4?N@t|{AM};YOqIbD`KxfzxZ&5xkEMt)_=Lk3HB3VO1#F+pL zT^}w;?_EqYBimsrG$`$%)qt>*>~Y>$Dq_U%vbJ43tS`&zuy9;;Vqu2fsR1xTDUOc4 zeBCJumGUmH_T;+z2H^Y8Ozv4d zB>lEwH*6(WbMX?fzH* zA+_g}wV{W*Q|QE+zTT@jn^aHIP1*~Yd?VLZ^Lb^%YgPFy#K||)qmg;5->Gaize*Gn z{g@t|oR8Pr874*@YD*f{Q&E2|F*Dv0SP5T>Tl~E5wajsM?h44^a9-V~Y!ShtHaT9J z8tsLj>kRikR8N3>zf+;`-Y7xtS;M>-w}E8#=!fXKw>pn9)@_8WS!p56nynv24aW%j z82(dG{gy>-F;vGfQrjSu-{m%()BXUjx9{t;$!l(Ki*W-47b*Al1F~0yK?<`H%vsyk zo#$TH@PQJN56%ngt8?>Bgj`<}EZGv@6AZYuTy1S_Hy8LOg-1HTBuQ?1`B9?9cyQV-aBB*#BIL-B-Ad%8B1U`?+zvBFQ`1 z`>Z+Q({i~xs?j=r&nf`z=^@Z&Xi@06M;QsQG1hl7&={=I%-^1cW{Z`9+9GvabF6!; zPq22{!^`wJ46l4-{er$%a%MAyQNtD|*aeYySLf_5Y8CUqp^?3UC!9M@lxq?)5HucS zUZlpH`RWK7MX-u0fLk8fPr825Y&sQej8!-|Wh!*n8a6wRcVfBGYQjuU@?`+<^8DCR zd-Mt*YHsjg|0mrpdl^>>Z{3(naaB^xkN~7|v3?$K(O0 zg>owbGW2DjV6$mBKa9%mk%*O)4!kpNQHH${jknrZs+nP)_voF;?Okr0oo{Nvf2=Ye zTg5o0+RI6uTQb2gvfWG)mYjlCia(S>HX?vZD;Hxg)ZUTJI8hPy6jG`aj8ajtQ-6=Y zQtp5tW0$HYgYqk_4JoA7*63Y&I6rGrXCQqFGV(icXe zR%0iBv&B4P9qs4^%NK{05Uvh^l+QUhUJl^>k3^N0&gR$>$$PnxCJ<5$bX6af0!5-g zB&4%=Di*{aTm^oZQDyh_fl}`{F)qHbfYoetY)<%Ve>5gKU(^Y|{UiFycqM~97*%uU zSw!%rQ?)-#*|$P|NRpB4Hu*mo&b;;S&^YK^FoT6d+MYQmP#<)DVV=iWmNc(+j>nGN zEt4EP_UO-3Pj>T>PuEV-k((q5$RGGY|E~RPwUNPA|#-u0wVTPKOhd5 zLZ%)Xn~DqfHqGV^*t9emKAUOfyV(e9_&fbX;w3IWp#6T{K%8xC?~>y+hPJQ^S`EPK z-R$a@Eikm~?ep(lc6;4rX%o(#EeKWGFLC19oG6s9N|06cVbK@ir5u`Z(Eoa&xY*c3 zPUmw%A-g*jDz~h|E0eEuFnXeyyL_RwI(1h`=+$op8%ZY-Y8}eHwRNo*#e3#7M$#lK zjwY_VEI@l!+D?k;G4C|Epy{j^yPXj&XsP9Z@qQSjbz|9uaX#Yv zHL@!YSW01(b-U*QmMn~A8n^5Uq63@VE2f%bZowKmq2sU%G#jmH2A<9U0F#{el!-Aad8mO<TNk@1dHROT(HbvAZ(X`3L{ORAYyRymnYM!VuqL!L+6tSY8|b*-Xz_bxJ@@ge*O zsP>Wn(ad_Pc86TM7Vgk(BWM~+eMMzkN)IrbE$FrMh^vzm?RVNPCMc0k2Mb~6h~Y$-P4p)jotY;8BwoLviZ{93`DJZzb6ouCN3)5pm5{g0x1{& z`i+Sm^H}8D^rb_(A4_Jy0-|dyE$Gea#9qA=mDY3v$Vlmx18t1^ReP++DnJ`=ZLpfB zo6{V66Iczsoknmhyv&{Q$>^_hz8TvT1>riz0l2qIWN18fD|9s_x2^zUdSbuvlk>b=^|r&k6n!7m>bb=ons>{;?7p!Y6B^AKfxP~a>JeH{nQg| zwjN=S3U&knnM+sX4Gd=gUjXSB7U(#}GF0JC)oH@2PE+M~uH_18&kqT&#&J1Bviv@O zomZv(p_tKh;f=-6l(Oj1am9p2p8qwS7+T!6ns%CiqHhgV;QP-9#ZN5?h9dAZBTte@tPp`4=2;CS$`G zTsp(y@$@QTtMWq!S`bwzSBs2r_}Nv$;?j)YJaJU9P{hf8`jt#SEjd08ub1W-y*$5! za_CY~sXBG4P^k(um1yCyl@=M&`uyo;P^J5dvajtc&lcfURiz40!qb=6;Q?Q$^x#+X zrenm^-4?mJALnKoZ<$!mxr8u0es%os$63s3!f7+=uoW7$&R+J@cHxg+)%+piO-EhL z#QVIr5o>C5R~mUF@y2~R*YNil<+Uo*n@Ibd4lyK^&zF2%rp@9fx}Mv_x7v1(Cx%@m zUlMB4X?jibTV2I%atcDL8<}Ukwvb#}2eFP=6kA4`K{hr{EtN;r(gW1!>p96W2{Aqv=e z)}d!sQ>jigu=q;wmKz%A=GCaxbt%-NU+Wz0)a{n$`uYtoP}Mv!Z6q(I-(63o!+$)& zXszz<8sZq3Euuv6eWVCjZh54V(Oo3{{{ZV8C6MNMJZZx)Us|gb?IRw3Sg2B!SU5wP zH9e}WP4Y@LBGovoH5%$}oPD3)aGh%S9GeR~{{RU?HLnL6)M>0mYB7(qsa}N`(WzBb zc*>QbIN_VE2{=-tM-Q7$6)0N^_r8~Bjl6!C&2o5l-4_e_ALV^qPcuCaO@t^O-kn8% za_}~B3Q&dF=FY9ow%#$c4m~;PU(mUK64Q+p?+Oxku{@CiJ9GKhgT!1%PIP3sk81}v zbz|ed6kN8AsY|U-sai8Nf?U0$-Za{*cT>SGu_{i!Qpp%d!$+2iM2#L|i*4sdkauxz zJ14|t(#WSm6{-7Ll;brRB${;a`bP6rdFm%MtW@U+MqbhsmA}=#1jAvtpCG5gxEuue zJ+r48k);?=czP9SWwhr?O0=ogl}f2pvcyuWTC6BSrA%kAt5+39_QER&Al1BC7OA4^ zo)k8Sx}KY@OQ>sipJp;l&>f2EpJ_Uc(=2bamAjSLqQW7s^Xraj;%eh7(aRgFf~`iX zl{ZbrE3QPpc_%(vE}ybh94Y(o<))=6N}}{Ktj`mgVrp>jhq9rM%Cl98e#X8QG;q}Y zx{Vmr{p6|ClEJJM2+E~3C`PUxjXG+rTua2B;QDDT_kT+N03rC3DXBWrvOkS*c*K_E zkZGhJyZUioF@eddD$3~gspD0*I=w!?hF_OG&ls=h4hO)_jYw!Jn6&Sy=vrN_-Mn-7 zSN9i(c%?-`{zsi!zcJ!kc)5#9u#I$km|A@zO;cBkNz)1pd6>#%XoM%bJ zdq}yt$}y(V{*B_RW!au*jE*xir&5e@6tLKLiLE;Gg-ka+derehRE((7!aK^0@R6k& z)hVn!T9Tz44C5Q)RQ}P?HGOV$w7vUlh%}idn%7_P9OmTNC5D!;w1*Me+`KOy*d!BM z-Nz(x#P0Ghp`UnH;r=Hv&9mGV6Af2B%rJ7o)x=|Pv&PW+ty(ITOg(5;o*SDks?x8C z%PHdUSbF%Ch@DsUj8$sYeVSY;fXVo;FUYX2RO?Zu-Xq+^VX!cT6^VGfZ04!KUl)gs zRaOrfhx)!Z4_1YGxSBNSQJ>bc#0Wfpjeiz#j(rNWrSBi-Mh2rzHgk8Dxz8WczleND z#VYEhpL6aq+V-)_-B=bU<@(p~e;#0^TBPi|pLdtyl1o!J`!$R7=t$|se>i4YEm>B2 zA6bURt8RR~rfJq5E!AVNw7%2kyS&p#xz{ucTV!jQqm}H&>t>ZDi)F5tI-|(uVl{iG zRM^HCf4lN*s}sX+jc7(urmHV@txi``l2e?yrwAoel2zd6C2Dr1PEnNo$<_Y=Q!%-g zXW_pN{{U&`7%H@=QLlJmadj%mLX}7?Ou2Coqf(u2d|p0-sTy?F8b8_CN>wVl)Z>c8 z`fiUc&V}M%BT2V?6GYV(;Ukw$)MT_5>1P$>a8DJAMR;RnneCc3xQ^o~4X6bKkwGW) zUx&F?2Bu$2H_R~ettS|_8jz(Y8B}y++pAKoKWgONtq9P&sX2S<5}Nr;eh&e}{0|(% zJgfEG%M9qbLaiEgRT*Nc;c8WieaMgSQD`PcPUL&S99Mk|=XP2Y1!pvF7nt$JCn64R9y zxsO8)J#;fJpadSB>-npXbBI=_G@13d3bttY!hJga_UN^hlp2PY2ZtL&k5`02XLCGS zeCJX#M`w_h&v%#vk0f$j+0P2>ix8}me4mK)u@b3Y4~VmHtt?86qS~6MoF^GMCmu>u zo3%P|jVj4D#IeVt5HQ=TdqFW)*; z>DQxx%&@g)>sM05PK_R2v|HT{U$*#%!X(O{oF1o@c4gb)NoUOR+S2PHHDNQsX^7NPEQ9K^>ERnUlBSn z#A7hkY4d$c9-4P>zombhpV*asz3hLURI#etoehSbK^b100gC-sgfiLEs~2N=V(zVT zztZgRSLHoMGAsK>!@2th1BSLD=b-7(fESM6QD59V5uHMmA$EDws@|v0ULeu5tBBiP zp4(Sz&xjiDk$Iu$J~O+}wH<0Vj^friZQ9*!=Y^q;=G`rBJlI`hHnT?1&p3`h@2?Md zUoXfr{Lea}nqsR|mM8m0x7Mk~G%4bvLRdvQwCKe{5l8AU!C)tY#Qkds-cd&rI8Pl< z7nQ(l2Wr|p5Inl(? zuLml$a9>=n%y`ejSNcYeuXxwO-Wj^lej`P15z+0ucLbjiH2pqER`$o;MK+c$`(LO;E?y zr(U!$czXD1bugGZICxT{N-n+=0Y?`)&X0@5aLjnK3mV)tnPGEW(*a62j0~yKok~j) zhNpcooqDgPQ5G|!(hI}ytf1%J6HJ;;x1tt@tvP@ z`AaUXYaFHBiDGa)PfGp=@eYLRN_O);^Do6EV;S_{nEbW;m&6%GNmi4w_E=16vedC} zrjSR=j(Gg*$IEhRk*uAMVi?^wKNw*6r{L)a#BT+7-@+dTp!gB-1L0Pe@zca#5A-d1 zO(xsG+AX44Solf&Np@s{>r1|t%H!e84X0=qI?T%_g{KypM8j5rhx_Be91p~pYPn|> zaNau`E+fnFRqzzCRcJZW#aEoFRK`mWAAjpK-Fg|FUvG}1fug>*PI#Pjexoz_LySC8 z;k@RyFNLw0ULP^x%+~{g%`0JW*xW`6cw99((1u$)bsBFAPb^rh8_8(msO9f9lxW~1 zEGAWnmTMWVBgcLgz4(3MUln+F;(ojE??kout$7};tmrxf+SkONg&q#^MEa%Hk+16- zz5UJhv3(zj;k2{yZH(5?!4<2)pwD+>e|f9vP(c^^U*Ja(<+=5Izck@`6{}avAk=GD zjXV}Mz7iN}b#nSNVNVa2Ql4GUQw3IyS5ek=tI?rf2}1*psa{{oo-<(h{{V#iJ*kM| znBef3?810@)aXMKjm>bF9DX9LTno%Gn5;#7>lrNF(ZpuBI@Ic6{;P?pM!qKxj;oEz zA%~as1!E8acC>ef$o~LNoZB~&F^=4E(!W8i#MIpza~(8h?p3&lAn{*{ zai)1rttr|^(_ktz)y`(})Hd!uyjSq&i8z&C+9l?F&RbopT>dyd-2VXdH0xjZS?YiM zw6FG8>RA{42q*slfU!i>EYF+h$aIEj@hrsxStVT zP?t2B^jJ3Tk?}r-V`|7UfUAYe=PGb-3m3@TTVGq9J2@t#{l?a>chB+r$NJsH*`_{^hu_(ldY;aN>EYug z1fb-p=aa(1@`8+;jR;06P4jbeRll{I=PqIiO8E9400?algjZc%F|>BFh0<6pXR`Y| za!gmveV$TayOYNx>Z!%JHB;DBe9M)jtmh_|!kfBRNw3jr z;UQDPYQ8VZrBC&b>T<*^N~={k)~`bp>dq9@)f#;6q}x!Z7^d!$YBu{(VvG@l9c%pE zsfEM)M)?u`V^OI)Zfx2{oP49DeQraCqN*!J9jbD+=UHNkIswKp`d9QH3Hz=JK0?;t4=VfQO?S%l}SzgW--O4u`!Kh zgW`0KQ{!(kINWI79Mo>KT1PZUvu_Xh+D@Nl?XKeWFhJ(_hU z*8c$2&xxh+M!BcAjP%Vu^jKKfL1!JTHde6Od1!T8JvYlNh#8aYv7{RqmDsq&f2kZh z!n{38A*Yx_3m8Jc!yq!*Ty$-YdWNNHopij@9pJF z`BkF0w$x|0igC_7-InnNlkYiFSg|Zm?|Ci@34vKeu(YEeYgsufCm(+Y3i45lykQqt z-cW*!Vv>YXf>B>5#e5sWRk6G;msZOtN`+n{!qS{wC^afo@HA`GrL2F2?JHDpq^$}` zs8Wm}7)C##ww*`$?_ZK+IIKiz#?xo{Ef(C3gg+>+v7R>-?4Q2Q+9_@;at?mA@H|6` z#7Yvp+3#Tq4BMVXYO=hW!5$pGmRo&7 z;TA`{pIoz@3UArv7;T_chaOyJAjpc}HGOA@G^l1ZvmdU~gxxvfV;12Hr3kr2-7C4? zDLJ<1DBcp2gryh$r{~$$JBX^)$#ca{z8V>(bxJXv8ZD^dX=QktGE??^{@GToR#UId z?dvJZoobaSzCpGcE}#9GJnEM*Tz_KQ*?p$pP`I|a@~72xm}Orv95>G%n`VUPJ7Zy- zNhbdQ$@r#?3F9!-s?&s{JVaYkjAdr1!fTV6K2Ls5Pm`AJ&HOR9yO?n%ZT-7L2~pFX zAFWlZ$vQM=4{1v;%WBh?BwiqE@n;<11d37feU&mQy7Y`Y$ zMIYz1XEya`QDle+!hy)I+xS+TBPgh%r6*&svH%VSImLfU@V#oVjCo~kjHM^}ojtk0 z{4@Nk_UyA6iB#35%^-aBeWuwjgLQ3LAQ0+49J$exL%M<5-Sn_7t!z}n6o3lD3@RHL z4H7sZ)G+?9shws}&v5pd{pC8dpScfyPF3XGz2AAaDPH%we6I1({{TTae?84*;$B;j zRISZ}%xUIw#llK6Scd50F>l(rCgi1FrAbOR<&-K;jFY1|Q+$r6V{4~r)_R?UZD)O? z-D~=VjP}yJCOLGK@lt85>JHfjy6N`85;5(NnGFgtsUBGN zI^#JsuFsDA4XbK8th%&XmDAqAtN4vHZA#kZR$E)UW2|UVqC7-w%t?K6kvJRSR#6)u zjxfLLo>zyZh2mU3Ux&PDQl&B@4KHTj=B;-yhG)kk+VE@@x=fAI^4vTTF< zHl|BKQK^H$JXHz7$~7t1{>{y5N^z*4J*!m0P^pNf^2ssF#e@bMyY&{s@GPpr| z8B@YVrmrhhtA?j9eco%_tyVE=SAv|mIZrz^;LaAzXmFPcW3W)32NQtHDprHN=Q-lC z`Vy0=e`(F^v6#5Usiy~tuPAcPq~!kqLLuw;pXFc6xO{FcQ_^hz03|8zO#KJezJEE! zVx>*qZ>j9zbb1sg0F%dh{(j=>wIrv^&#J+6GA2dmeYl~qCN_@!r+#KY#XXnT5 zAK@PjY90>p2gUtIMzYktB76b(AL0*)zX?1;sYZ1X;XQCzB+IFNjzZSAQ!KVNa;d$F zV3J#-s0=q(^bY_^aN<0xfN=AbJfk_rWms%&8gi8{ZAN&ALU2>2<5H58F%_XFMs(!i zQgLsU*!`#J1Hk+!{5{2ZnLHFFoaB`=TA6)Gb4s2Xe^IMeGKLyW;biGmp+=<-X-0m+ zr8v%yvy!>vUMcu<;NJ`QFZTIw59tl0KZpDStyse?mZy0X`rp|85$W38HyY%gZtY&{ z?fx#;?;iJ6H#ZW^cN57gwXLax(BPgg;w*l7z%j>SJ*0lIgOpU^PEn)nbIO=$#}64r z#|;OE!cd~A;ngTcoE%{os+Bqa04U7(H-@QrQR41>g~F;>+8Bp|_OzusQ>}+t6`XN$ zr6@dgO0P4QwdlW3@vrC%j~9tl<7u*EzxCYMPN(T#Q&$^`_IBrIeH4El z`ah36MQ`B`25I>4{oR~j1~2?4Z*Qu2J4eLeC}JKT1&o$yiNr#L>(Z$@Ri#>_+%a&Jag<>v zMumEGe|C5_r>$yJ+3J>)O%#@qcz)+zYgV&_+g;t-U1~Dh%X1#hvxQ`gl$bg{l^*47 zi;u(eN;K=@YGUf$)s1yZGP_PNtxl)3vrSrZ_E3J+e)26jMrq1T{+GjiF_PhP92R4T zrzqkjjG=>76-uh5JU%B6gQr>)C(q4?a)hL#QiG>fpFKxTaCIi-{jO|ZPu9QXM$57P K08f1KKmXahHaTwq literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_q98.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_q98.jpg.prov new file mode 100644 index 00000000..2d675f77 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_q98.jpg.prov @@ -0,0 +1,5 @@ +fixture: base_q98.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 0f4769cf46a36d8e11bde1f4e4ce556784298ea3ec8e7fa4db19c7fc1cf9bb82 +regenerate: cjpeg -quality 98 -sample 1x1 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa39771ee36428fb6704e42a9904565952b3878b GIT binary patch literal 30457 zcmbTdWmFtd*Dlz2kdQ!tK!6~PyGyX(4#65}+}$;3(BSTt1gCN6#@#(QbOXT&gvKRk zhWGvM%&axP=H6XZ=TDugb)MSwRGq!gv;Qss+W@>)l2eoepr8N%D9;<<9|9l^Kto0S z&;D%a&pXBo3=DL13>+*h%olh#czC!txVSIz317d&C&0(WeNFP3;0+NmF)`k&x1=OQ zq=ZDoME|)73fl8E=or`-7}!KFabFVsZ`;2f0Kp3sG?XuBD6{}n0u(d?lz#&Ns^@WH zp#0YW|I1KNpT~%a^#U6Q_qhY~H2@U_4Gk3??LVV_?j87i9)M1OK}f?biTOsu42#x{ zi04c4_ZL8^`d(tqnNvDmbN5he9Fn)BWbf!17@3$^`1l0`g@ivy%gD;fD<~>yY3qP= z_4ExaEUm0YUtC^Y-~7G3`wuP@0NVe+dT##* z*#8F?!80yYbaXUytpDIbLG^h)&Sm^%|vi}Fz|BGu4fDQ=!2M6Gxp**_~jQ}74xGh*eUiYvm zKd2w{ac;h~;T_I&9PP$a${OLA!g8-qM|ZYg%CKBa(RW6&SMA+vDMbg;==2F`MG?Lx zEB2A<$C2Kr4k?R+rcA5(+TwWwvIXemIMg^G8!3i}nVYgNo2;xYr%CMNl03lf(gh1>2K&$;l!7_09k_Vx zE6{NBpJw-XhL=j)glwBN+s*CqgzSe$BRI}0VdNecFnkovL8YH_lhB$UR>~Ut4Q#Nw zl{#K9>+V7<^6W8_XVk52bD%{s_(D}aN=t%< zV^uawrJNu|hh!R3+$6(qc-p`xGi8)M+7i~**qlc#kU|nksBh^9r|$iNpj%0_#d(3J zhEg81Jnkle*lh>v?^Py!tr zI-SOd!;5kLA(@!mgEKJJEwDu%gu|W0uk27PDVR!J1&c1H@BIjPT#?Tlr>>(58Te3H zhlqenTGPN>u3)NC{{R{*yFGnkf5&JQsoe`KAY8vSGWT$*n@$ovobQh0s4%knax2$1 zPKw-oR1NMKYS#}ZAr4+!bIvczufzkAg(|fM4IFuOc4GNg3d|R5n-Z(~$A;MIa>7Q0 ztD|6}2P*fB&|rx@J;nKQxOhywWim37d2gP+Y2Ml7Xc0Ebo?kuLBcF&xD@FK`Q1;>z zK>R$cDJ+!l@;=Mycv)YPuWn1h2^fcA*I&Qfo77n08UkrRNzJp^Bbt3Ya+xWHCR&1R z!04#9`vFNxoLMO5GO_8p+GuD=xGuT+Uh&3bim9U=bF1|kq+}YTFH_|7>FL9`e?ejO ztJ;KmG3O0cY~_l~5c(VAwalQltIv5HM$?>v}?<>Ck(FIQ`Z z9k;&ae1H}(C$!eYO>;*mIWc%^q$heez`9#t-S-$_$^^bAPDNjSG%3t8XrQnuvPcO4 z;1hAmuNTt(TqFHz-YMrRjWN(FXKf|`re+dzsgbx5QD)Q-XEOIn)Rp-fvyMe1+jd>^ zTkbEE9XGFKYWG>oeS4U9v+*LPe7Hc?RNO43%+AMNP8N#VO=foJd&(e!^5_}Y%G(u& zSt2G17;*E{5r+Mf-ZWP_>y%c5uKM_XS97znSDUxoU+>x0iyu86y6l_OF-@?%O9&Ty zpGZ7Ow(sLbTtYsp#_*Wv5qs&5q_Olx+YMLrzb(Gb|kFDH&UpU$~%_RIKO(f7W8#BCj{WhOx>zr-M$EuqHi4`m0-pEJ1Mq2&a8qFBss z8a+@~R=0l$@+G@hE;wR^kQ)RJR!?1K&<@SYoe%I<*isPEin;1yMGlY8o8~pJPvtPS z&TlsR`w|i&`Hp*De|XiQ-7=n}l((JGp3zyJvUT8n%j`xm(ku`+F`jm@cVr9obs|NfG5%JA!a65v+a@|bZK@VH8~(&B^vTakWusWh|u%E4hT z_^q`NJpB~8wd!XvZJ!!$CP_EzygfEd?}-Y=1v$KF_U|S8Z=k_}x)wsO+9`ykSL|x%(@V<|BGY6zR6G zeaey%@1KlCZ-bJE39XTzh^plJ2qB<@mBOyRZ+x0n9T)b%o$A`e3Gw9;+s`^%km3tw z+6-;dd<28s3xxo@O+ip3-riE0CbB}J=e^bOY)pgBs2mE&W%Knv1%O(k2)a8c~DZ*H~& zzKHgiz(QseF)jz|-kDHs#ru5bi}!!{*R~JDE&q7zd^gs2bWBO5z^zp#IcygR+s})g&IsJurq3P9%jUtVU?89~3Lg$vSde=x zqsaf!r9_6vNXm%6v%5Q~?1Ffd<`bpQ&D}k9O!MQm6|i5x3vsM#`&|kau7o}p8i@(b z?=rC$_-n^nA;ARC{~sl`GkB-Vku3eBMI~wK&r6e+n2U>B4yM=M?i{0>NmR6ariYNG zaLpOvRRIB4rNLfNL=;KpX3`e4^sFKU%Qn9_+?1X#Rwxm%Isvid2e5QB!{25uBvFLB_)u*OAe~z(LqFbIDL$s8tCE;1fK5dP& z`(;uAxTY~UX&lM6c*#pu-EMdk*Q%~IQ!RU)>3QMj(MuE0QPk9cps2(}k8k)EH4&3L zM2hN`RHfyLnD!}GwRRif_3*hSayxiVmz?8K2EjQ{DcM( zX;j`LY{>4-miveMx`x%L-RYMaq|8ZR;a)C%>FVx19?Tc1Mc*X?YF^=n7dZrWLdpm- z*;qOg4f2D`Q)=MZYRpF58vB@C23^jlHA`X!L1%vq%5r_|-aXbH%}hk|B_a46Xb6g; z$Bs>3;Q2)CY(}Yua9md%5z>gd8yX-8@KjqKOOKdMiHd&g({KNZ+ZgzZl`}9$yc6qO zLI+YAQ1s<5tt3VzHWovzgR7*nUW@W!uQ!h+W z2T7&I3x&7MeYiL_xTu8Lq1YKaS(He+Ht*=;7=?Pj%6&H-Jjq_3_bGVgy(o%`oJZ9U zlDH*3jr#m{<}ujjItkljfTWod97Td7IW&m?;%%H6`?ojOnyYeu_J-TgT%FB)^nQul zQ25!mlh%*pQP6mkM+6A464q5NEmd}J-?Td{IMw8P&gQ2>MZXvngC=#ci1`8*9ljbq zzjXF5(ijeXVQcW>2wvUI=4tz@)APH7xp#S`sf`S2u+nu~B}7)9!HWg-+~p4U@erIZ zZhQ}AmJGD)&gH9y zSw(Z%zIW#BFgr$59cNHCnWdv(q(^c^@i|rl!M;BR^L#Ey_XRR_xjTGNd|0^5d4d;F zV^BlYBpGX$@Q;M zKP5KZWT3*4NG&$sYE0O$eZ=;#{of94@#0|hS;&N388IzX#7TxpGK zhCTY7b+tc{vYl%@T_e2bEDH-C(#i`B8{6Jp2IHy8k5MmZR3qVkl(P%g`acE-?Y(%q zP!O$cZ<1v=ZQU4pOI^BH;^7}i9N*a}@8I)tzVaWSt%X66YzJQAj_s1gj5k|OhcS&= zt1oR+e8_HFH)wi1Q-Xwt7Vqka%in_Cl7#?B7ReVY?h=Om+wTQ*Q`-aIv^AIBEUtSi z`~&bp`gr#tYA-LGCcAkXQ(ZuhzxKU#(~4>!b6wN2c7b^sCWViN{{Wvl>(7d3Hgd{G zCmp6=YWWJ+%8Ntj1uY(``X`WY1|;BuDG)`xbx&Fi*STA@5jwg-wvhY@4Pu_Zy`Qjd zat)@G$;DW9Bl@O4WLkrgid6}}KQ6wB#^H}RQ*I}|RkEpR+U}y_vb;xD-$N>!I zQP>rZ1}0Ue{OODQq^A5i1;W33Hn&!D;tcE}=zWSV^D`?luhO8i3X(T|fwe1r(M;dL z2GdNpA}xi@K4ml_T-5e|mV2zpyO{iW!kXB1GM{1vt^X0gnhqK1H~WX9LYZR8D~EtV66_(<`KK5t%(yl zy+J(qtB~)A<`8HtCq1l587$)aF-Fn_+w&I-QGE6uk7C3Kp3*+~7mqjrz*J5|{E=cX zn*hArXK=UQB^3v)Zc3XNp9gmnwvmoAs_o^_UQ~wY@%&;NHzae@4XcOrjZ-r z`{Z_iGBy8v7F(XkRdERBQ!Q86l-HK0(Z;iU{p}wBQ%K0Zy#z7r;vhCJcnVjwqksh& z^;ruQYkPXd`|vulH6&3GDq9B5R$u?k$;#TcP)hZ7gHOTNm`${gZvApJXNu>we2z3? zN_Xk2FYhxQp1?k$ZF<}}-}&?=omJl&d>7T94A>_erMp$@;Fo?UvzG;#G){SM7mFpv zdOJsHU|$n8tC>^(`x@ky8{Sqh23Gdh)8z95zQRjR{R+!p*OOo&oKiC}1F$6d`<*!R zm8Z2u|h=!33!*BB7xTge1Iisi~Z^G*P{qS43&HVbW!hfY5NEGG60s$)47Cgm(Vu_vOKt*nar-3084B9 zx@J~O7au$;8zp0wZU0uL4lZ^CT=i;si&2QFsuk%qs|vvPtP2)Y=b1gcJcw7LCgO{M zHyY_Z)@u=_q)d`|6oiOzNT7&ri-2t!ZZuIT4>sG=RNLXdY#Fz4z7<15&oK zE^pyW8)6Vb4V)V9uSelgxR=!YjaPrnSj=JjvDG%?ub@Hv1L^Nv(ym+=@Q`KvqRf*LBYRcSSiqQvAL-95 zy#exDKxdE>@1W;K!?7d47*=p$Bc#P9LdK0{l(s73QxPwhh<>IA#wUB_PQ}uN@69}q z)iw#g9^c!B(^wD+%+_qvVX#SaO|2veab%M?ZH0~x5Wv#+iw=3@`Kv~K_sH{q`C@ue zE2t<5C}6|V{}}pOc`jRL+^zc;;or9_600~-=UeD<6Jo~;kKpOQ#bVjCcnaIJ zLFTxxkBvS_uvXU=-4>!Grq!LsRxtc1p|{*hF`TU4nQco^V+Sj;8vJw{bQ)I5Ld5B0 zS&$MlC&oO2#HUxNTyPThwCgPRrF`qbsj~VP{U(WSY$@t#H?9k>gyzxBjv0Plj;}Z( zs}&vU&@ttu`gbqHopKu{o6*o;SE)IWh(yf0)SA7E?}UGRuUKYiH2!|KS1}-<^($+j&E*hc)SR z2fdJU`#CkW^~1eu4Nc=o`JCd`>Pc233C(7eWCK{e4KZjPU)JF{>hE}Z4`HPDxQFRr zoHIeXbfLs|-Fzxy-!Dwye3MdlMs!M?(uSEyJV*N6@0wDm6O;Uaan7qPjQbOB->KZF z#0(M7(^y6iw$Y9=nXD07dSrlbbn!|gQ!U;!?`DY6i^>Nzzgd&4QW#r2)35sN`*CwY z`SnNi6X^_dGG&7NzMAIheoN6wQL&~?Iy)NQbyWRqTm~&OM!c#Q<{#ryTf`lH*ZUdP zduWtY6+G7N|5=i0WBmSPu~{K78`cgkdt|4&oZ@E}t6U{nMjEhMB`mNQY(ssQ11f%| zAGr+^xQqDK$IOJAYBQ6-^Mz{LdMy?S^c!V4Eau(}%pGP*ZSr}wg^kPR>>3d=_2unP z_BsxYGp`D4Bo8U&40%T$+DQ%C&rJg(9Q?wt>^hdWFHq0UzXQIpsVQ@a2VO%(a=#)s zad85><{YdQfi2ou`3NdNL!_pnHT>JqH)3P^Jl%KB?LNSMGdF*G6pnW`**~l`fDJ!d zX?ED&p5PI?uiS>*(C5qcB3u}6|13ED+Nr&4O8NV(nmowBp%(lP(7!_AS#J$dbShXV z2d*ixdzj=~zrNc&l6}`vD!9V)S-aj~(JGkZ7hpBUOpkWu zBAa;-brLo(9Ex_9;y4*cT>K!jjF%m#_prrF*o}df>-4`XnG}R(qo| z4eOVdjbn<}Ldv*yi-L9UJ}Yyky(5a*vn^3bdRaNxmkv-EZ+WlC8qtf$w=vBLLb4sk zeU$C6i6PNcNS;;w2N3GPcq3X)kBR9gVUpRhSpad)pF201Lx6eLW3_@#)bdXbnoUM} zW6CI5+5}j1?C;VGhq!lJ_a(7u_bSKn{dIows=Yq0O`qU4F;oT4mxyc0A2GAX|D4w+ z*%?a2B=c$d;{7s?YihPdQ;%7*67#5hFjkDD@7lq-sS$S<+zyta&#>Kb?Vs?nV!>Q8 zYGse?dzJRu9+?o%_qF;;)m@@Nta#7@%!A8i!XuAP4ZI%@8RH@sg8OK;G_*C&Z8|7R zz(mdH6O_TibkPrk(z}iAZs{*t;Ck_`j*YrQOQXiL&Xac#MCHjp0G3Ji^?YT8<|r#Yqnw;V`#ONC1qRg<;uu3LTb`A_xwTkz*ETOoqy$=v zy+no+{FyvkpQfTf-8mHCLB>oMrg)iLSdHY`d0dT#`h-#ns1oiVVYL9oEor-_5SSV2 z)pD$w@{aPuQ<4dX9i@)&;A`hq*=WX1^(mNq0z;q-Of^2kt_m{FYV#iLHpgd4DAeJI zEZQaWe4s-vCx^1_ga^We>!p`4O9KIL$600Q*<x&(h`cxMeu9caJGZ0>Z z%ov;6a;R-o7X1V`16Cv``#$vOhgc*g4@5Bugt~gPW5-)=fF%Vp8b|SlM6BEJ=Bi?d zX9+RaWm0$&I~4bAWk;6%5%(jV3DE9Y_pWVSA*EmyY?D$ z+~*iKWP&-+t^NJrGwp7^n!;vkWne*`@g6Wcj)n`zc;e*9iwisfOlelY0Er~5VXmJ* zezUwPU6WGjTmU8;wSiebb^b9 z(Rjnx4e-u!Npx0S-LYzH;)@m-GFJ5;e@qM^*v&)r@&5tVMzqJWJxv2j5G^l@Nd`iY zb~zq?*9Z~pos_>nZrujG*%5m@tf7_JvZgU8Ll0D$Y00d{ETK`gd=KyW?^PwQqnEh~StuS4Qr1@Nh`C!A%S4P0RaB`N zfMC51ohi#cmYo$<&qON)St24p+Kj$)j_+c1OeR&Y`*=q+;WY=jh$6;U8u^LAY*Lc&qx7GSJ`xvGRe_!*6a`)YY zGb0D@9!kij+a3CW>8{4l(z%p5FDrUT!i^K>XGKo$rSzKGYXi;1rZ!FJ?2@urkS?+& zr>2Z$@h>F{i3+IWr>PEE(O{5r{`BEe{oc=t39TvS-}7-DvqK$AyTrWNl^Lzbx#gox zJ>&`cl9U6d$OI^xgPTSN01=?5hx@$BN|BsCBLJ~GbF$a4dk(~4%Oq5YW@%Bg%mj?L zY*^`lsR@2QwA{LFx8}({ekWM2|5`!^?r`h=^r^-0p;c`7qoVM(B8O;&3j?><0HLTq z!yDA%#viEQ34*XMjX;M@X}3t=CMCnJB_na&+dm~&OJI&k zvYF?lUp=t+2c6&S7cnqcKPBsBfVdHM-?g|V|IvhnL&5^NbPU`-*uIQUs5WhiiA@wN zUm;aJ?xs$bpx8B$`RYTuU4aL$#Mt+MiJed$Y#jhcpLYV+a#Jj*)#B^oiow@RdZ7R>aHz}&*ep{ikp_fum+GfXuD?ary@n2NJ_tAo+El!y6h1iYvuF0)Q zJRJ422gsMvUjwnBjCJNO`uuWJQ+X)1$9jU(T^4DoOx`3C$!i2V(el%~F1Wjjb zY#U=YN;p|a{ZVJsjA8Z8Cg>ppoo_{7$7jJ;)*`82W1B@FLf?J&DCxH_Yf!fPk-*uQ z5ebq@CR1}Q4^qfIq=sEhfy^Dr{;&s1z?h)PB z*~M11bMNSb(UY+bieLM9D<@Cys)$7iMsp8!m>t+;TIgq)H{m(1wrT5}3;ZAgwytTq z&-(z<-sOyHR93b>nL(81`@r&2q;9y9cSf z0BxwHpZ|4S_hK>FFK?39r$RXIw9Gv%@TKsaM3aS>x$0$*snp_aMpKGw^a#B%wxwE8 zYIaqo13M+m*>12f1DroiZI6>1=9jr$2#-u^Hkvdlnqi30LTy7wJ_hzmJwRIZ z1YI|}g{gJ^2yMswG^m=~f%SE%+!w{u>F8M7AQC=`HBRY%qiWspe9ypq^GiMR>O#D- zkdJL^p$+0vsdi6yIp=@8UOu5LVITaQ?uMoy;Cau~V*Lfl=+){>%qla{BB(xtf43ZT zP{n77o7w7Sm^utJxE32D7ETmABcA^}ROsckX{6y;!!gqjSM)xU7BU)QM(?smPwD|? z$=|aQq4d#hTjbzVmDc^5FQG6uyC-5q=qnpu1-n?AXvT6T_l*(Mn`22HD+Bs+ z2J5F^PO?lr2|KfO@YP`#!D2uTx%a*tv`I!o78=pU*bK?B>87stlHn?}9X|s&-TCQY z^HmF4YDQARUf|MrQ$-K@CUMXp2&Qk$$RY#j6v&D;mFMGal~dwTt=9Bz2g^br>;mAf z%-=!&(czF*_jx7F7$;+a{M(W$gYHiMam%~vMEM90-S1)i#&FRsDAH_-HIbqADx)T- zXWuN@zy_*E60{J68-zt{=hRZW@Y||BUu0RM);gfRe3E`W)2aem($%ncaJ&^GghD7+ zeojgkA5EjpnS~nq-TE+8b}jjRCVm`yafo5Np|s0e_$JVLPdz&>uD*OgP6tPi`8MIf z^#eI%9MZ`Tj9mDhs}WMET&a+%(Ar|nmaiPvrJlttGXJ4&)#svb3)jpQwqiHHjEkKi z*z6@0Ppgd0(w>%D?|+~a(yb4^r$)O|d2j zF$-_;Pnz0<+VzBC55gmW;~wH)1`f+|dkG5}o=imbIikckx)B!1jBA`d02@&fhT>*x zW`n&$TpmrP7&U#gP35Rjpsn?-qNyLF8%1I%1S6`qO{0zXsI>0Sg!nvznM)Ksbh>_ED7e?KTX#cp%RONSg=V?fOD_8bh4c4;a%jo5qb7&1#MMT`g2u3?rlSjJ<(Y-ESP}))L{wcv5lOeSw(k{0#q1xc(-N{WcfAne5|2@ zI_1cRvIuVs1uYkDm3i&RtwNpR+fa4F-u{Zq8IMXx_%>M*z8Zy=ni5>!uB5#=aLKZ&&HKQqWt+5&&w^;D?Dfk)ew9A_^qL@l)%G;Tn8y9sR4}1@ z4up-YcF;E_2!1Vg#CK%Qkm-DCY+O{m>Mq?fjBg~9bsK_nh>6yafR8OiK>)h^D$%%^ zGwY?#VB77pYgk#Oq|NaVYr`Qd2?x&~&TdW- z#HEDMb=SfNmzuVNX0;qvZ=H}TCCx4?^ww=>?{rk zRv)L$6{nMaVA8h9eHt$!V4mB`EZ>^J6qni76fqIGM|Q)OK$i7=1mzRzhLgEm&;bkT zE_nEZ-OLCjawbJM8%S4|XkO|QALceHiW+3X*e7Zd4#KMaDz zE+0YrheDsdh{&M}HElKR-BHb@hkri5k^bZ-y4aC%i_{2AEl7WWuwmzY#lHM{=bAC*u0|dRbR3d~v5dI{o)b8ZVyU?Ce&}Ia_8l8NN6?3QEj& z+>s&;h|_PD6gF|}U%iB0?l!>r2v?O^=oDXb$%*zpkbmj9O+Ur-dF|@Q|EQQTA6GXO zYxm1275w{|Lh!(+LLVg^>R&)r{-9|ehhMS?`jORqcvCuS zGcc1;s&b}7MpIp3HVN`~ysq6hyvwT`Z3=w*GkwbJ-5vB0t$+K|4TUhhpD$YQ&VgnlP81 zQjEQ((&&@A+CRXO^wrnV7UqNT;BhB%AH*cYHunD(v5DBa#ms%-j(L=(t|~9_xbRck z{klzB;yc8AFq_w-`y1Mb~BIUn506lGV@JZ#!nA?e=@V zZt&P!^mbJD7;mbKmiQnE;_8}gKl|ifH~O{}Fx-x#k~V|AkvZm5&0&;F#>`mD8dObg z$v9=*XZ}I4Mn4uD_HNYx+Q&D`tWSsKl4dLgq?JvIO2l#xas4Fn>CCXL3{KNL%V`!( z9RQb-8h*FR=FFfvVR&1qY|h=0!Q>ruD3`kJG0=Let?LsMUA#E0dUD|7ff$rEXZBZW zNcC~Op{qf1=o_=!-liEWDvl`{9G%gSz@7v`p<$qI!vt&i7|9?X4s1%LMl(k7e(GA$cyj%rTV|ZJ^3C8P)X1Hss@}Rg`{F?g z^48)W_)3Hz zZXspFxwR&vU3;ut8QdM>s-~@A+9TZ&sT)Y|D?lL?+W&T&?r=9-YI|BOJAVAsT|548 zm~$P&UBTLFFiBSpS%Ov{6tzQ}ey2C8q4DDg*bhkapN@-NKOv{5zke%GJH#mU*ZKW@ z5}Yt62B{%6sj>H#Yi1q+_wFCQ)ykCxXYDQTv@A=*@ZCa{j@{082;K55DfPCRVa-|; z=PJ%TG9D^p32Q*}O5V6|(j7p%I!z_nanS6Pr9Z?oqC_uxz=0FZR!0a()4s78vL9za zAnD}EhWvKH(P9`{bLuE$EPjDj?}~*6q}xHehVo!3GV|Nq`8@?kSzNWMdBp6uT?P^! zKRrtHJHT%=DWO)F@a*ry_VJm-w}Xbd^NC-|#LZ>xlPDHk7g#8kaJN{yzy;w1sdR6+ z6L^_&r=t}z7|2uiO*$}x+&X*EDC;i>S>_N`j`h?zN2yE%RO<>sx}U1VKVc7imBYtG z&3mtJA?k71WtKPX))7KI^w{E!hs#A3T&6L!0jMY#n-w7kmqbm&E2%82Y7)N5g}o5Q zE+EZ|v!E^r+9T8(mDT@)m_DA9393hu=h-Rytg!DC%56KnP5-5;{G4m6lx-NcUw(ej zm1Z_b6V}J*iPoy1U_8ZyjCOyWtSy9Y&Jty%K%$fDLfIAYXPIu+n4$~qJi4<1YqYMp z=-*5SE>rxE16FG8?ta4ij2vwpb}ORUV?}L~j4L&tFjcnMcr$@OvXQam)E?3k_e>OJ zWeNw{SkENo7CCJnm@0a}1baJ0B7X)z)r`usVSGfL} z4*uPO?nH`DbBu~QlBVIwkwU-_S3e<&m|P?e*bdUQtWr%|bnEQ1F+lvqqS=9X=adIq zcmq_7O?WFs0>u@!KF6(emx&2-$t6XjsF_tn6W+gkVf7E7e=~dk3}JTo=(TgKmPXs+ zQuF@o_!!)%{ZJ3gX`ju`4ToVALYbk@VvyFI^-&MKhjr4}?3sR874@LX&#fXL@$SOl zqjJUXxk7Haw^7;zYvkDk+u=iv11gS#C9i19TnE06Zl`?McEDUS`twGAc__0%Z}qmi z>sT*LwAWG=ZEtE-cz>5g+SDwjZTGPG`t#ttfP@9FJg}d2&@<9#Q|AHp$z+H6YgXzg zj;ruX4L$vJ`!+4yHR0#He}U`u^<8*!-#APLZBph|`gm2`XuO*Rje6yZd%AUYuUVCf z)9CZT;k7|Pw@P`s(C7y>!JDe<2BoJFC(yu}M-g2t{up zfX;IG)lG^!aVL0(ALtjZ^RAcRpu*r_Cqjhr55(%I>66vKrsmwy_D$tX*4OZmJ-tHf zTav=S$Lj0-81_#Fc5RMl)9ek|O(R=rb#3&>&5y^?M5d8?TWr0hGAN8mPvfG%aTB`C z&X>-|1&Q*dxohrg0{J-xRLP*h%H;gd?#{ez_UvGtD(AIXMP?5JC7wkdAMZOWd=@MF zuo7W|?gILO_mk(Vk9Rmb+c_qzUUUIlHPR%9<A1aE5>>D}xA);dTx&4#D!Zt0@ za?ZoMe2m|X`S)tUr3&R1Q8_ zL5Jp&@q(qxdP#_VM3{6x^=EukSGMO4ZyxUv1)x2G~BQwZ7kY+bc zXEFct9URek71QXRXD7)}(|q86v^1`}vmQ_R1AeBO@~qHTG~ddlGt}h}$RlQdHFBWi z!_5#yS(#Q51215i&UyFbx22Tu^1M(!vfK}0Eb6~FnmZQ5A5Mp(m%^nCKy!jm1(~jz zso`@O-*~JmI`Msx(F)jfvY=uukTeXHKE@I+^6S}YfIGM;nqdy|tVS#76=TecU(`k7 ze0+Kh==l>OJXza-&IN4RwQQlOK_aHsZ>L;)(s3YhHi=o5AuDGz(S^;1GmTFjof0EY zA>og4NKk%puI}e%%j4^If4+r>^o)FDli#4jvp~a5hu!P|EKu)RTedlRfyjC=_rj)u zT~~BXB0=9{V+zN%x*f~Z3LALibv-7h3o_|f$-{7=Ty5OV1VWm*!x!| z9iT|m0$1_@68n{2Z`QL-MD$<>C0hX{DbMN)eS!j3`UnNV_8H5yoAa+Fmp{|Z$`VisMZ~$t9{X6Pj*7k(sz9PW{IY}j zF7znS<}ry|SRPN2?z*k(0;vF`|gMhyQt9%30DK^!IB0pE>L}J%ALBxXseBl z=$cD^>3$Ub?bGBcFsS|F#V6kLH*|q=+>LaPtHF9|F4$pvrRnP@4QuW8NQYmlgf2^0 zBb6C$-cD=n28WC7G3wAo*viVG5BswWp_-4BxM4;|ajW*rG?27+`JwiemKMJ4nNGHY zrmuaG^kguvO;)sAQT^p#zdRQ>V^?Ro1uUN(*2>!Qic8>Ue-ciaFkP(_Zvu;ofnjW} zBqN-Thqpccgkev(XNNYV6`jJyZaaZlnW(OkyS0f+aFjNt!NTX=L4 z8m~Dw)fvl1A(udZfq(v%pdq;(h*qN}Ic*%&&0H&JASSW-Ful4>ug0AwutIm$dG~8; z@kY5Vu==-m-9B*z2JGY3xBsft2778}8}Ri94Ik8W6hH`g_J0Jh-(q7D^jc~m!*ij< zM@~wR);*ukNzjFkmUp&WS*|o%v^+bRR6gDwDSo}H*dv3wt+2?|Nw@uuxx8>0w91Du zO!?^$u8-7|9s9r!Z#511lt_n52WnJQYU?-@bw72y?8(4_dBj;guTH0^im@-J`Hh~# z@eQgvtkJX!VN0!S&*2 zIsur;%3XD%Ae?O@0}Dg*RPp^{0@FtD`a{+6CZSRJEN%f|f#2oV2UUMRyHfG*#nC5F zhkWX<>l^aBSv#DKyB6%J)bh(n^a$_W%KWHEC}ptD0`;8p<-)86?M2fGzMl(!XFs+8 zvBDx@B1Ij1x0saPlIDjx&xfbF0SnL@s;jp|yW*f?#ChB!!a$8{QM77GwXfz(4nTfcB?`D%B4!31ynPK4YV@@3OHRluO(EJ6ru8oEVuly zG0U`9t@uiex#HEclI?^nCI|BU(zERQX(WWc-4MJo(tJK@MHBj48Rth&rKIQb_Z%*I zzbPM7kN9%MsexvnVB2};OR$@Z1M3@J1H=cm`idSukf2qZUnGx?TFr(U`cYdY`h;EJE|%PZ4l5RJED}C=49>*?6{12n-Z|$iovuM@$Q{0g(~|< z;+S6WB4&IWQ{ZW&CIw~eoVQHyYM!vZwnlq*Q00jUz5m_geK48DCL4==S? z^v@37Q{_Hg?r9(KXxOo^g?9$|t%{k_uG}>D4S-f2OE_>FACnjBf%Vl3{pDZbb2<7X zSDUKUd~*tVgjs}KJ0Dacc|Hga9UY5Zr<=kW*(Y+WVyas8oQ}t`i)rQJYBP8My%KJ& z-zn?Rh#YDf^bayTES!o9z_`%?7j$$ZBxDqgiGmsFumXS%9e&gv*SuRA>&1r@`vXot z#GKEg=kn6MF1etcZY}~TUMQab3KbShS}pXJwDN2aR?SF!_hgiyr=4nJBgkzgPn5u` zP_6dIcrDnv>@uo!LvY8l-|Pp{2#!y*L(5Cgw2J2nVpoHmz8=SKYS+=F0M5_ z;s$kzFeYu|vV+}OjZM1O&=w*!6?REnQgmotzbHkF*Wm=fs5j1jD|zAab#|oaIRi&t ztI)(>yiQ+8YpXnFr+sY>^f-1jjkI5=pWE-81I}_d7p#L@RmPH(&fdyJr<=nSlQY?V zF~bF@&6*(WL6W&!Zdny=DG+A;e*nJjGM8bCnUb9C?;JkHKHV3}57SveV}UHT)vljG z<n$wh!eG(0NafkO0-0z5;HilR=;iBfblwGTCpduCBRjbsylIPk=;Y0)V@F&Wa zpT#W&eYlE9X?efhac*fQe7z_|QW)k?vQ!>we*(8k0LkffR8Z;G;+}{?sTTcewd-N! zOhEf~d>2So>(@zMz<7RVMp3b>Rmrjmwm~1<_Gyy6 zir$ePS!!w2(iPlwUx?bvOloIPES&Qym`&T?9?cP7H68h1+KgFrHj6Ps?2upy%h3b1 z=h^?mFhROf@okmA_8Ey7Sr^V<(6xe9s*JxXcbt7n?sy?r3N1T1tDsSXf>D}1}v1Y3B7JIf0W56gZne8S*t5AHbi_Z(umw1|O_4(fqw zmt9*YY=@RUEEdP{uyoY#mA%ZtvF>>2=hOSzl>J-N`$Kzo*n3=(rTeO5Mt?1dm5qD3 zzYHvI@wK}wOs>LAhg<=szh%Md;X&^LHL#Wo#-`M{1~@pZlC9!#xl{M)Xfc)*Ugc-8 zkVlG#!j?Wim5{b1C}(L|h6>HT$gy;qRhTAQwOk#4{pwx1QxsMOzv#!a-`7LqY7M^n zxg!~-CO#Pgp#ya~##TW}rD$=lu_WRwwqpn9HLl~k&jiw+P6ka*n@jMP$VH^QivIzS zd;b7h`z^@8%B#*U?m?qxZ`D)6Sn9GJ%axGt$7n}Xs=ewMS^=4f6>om_Ww!u>LNx>_Itz2)$(ni^+EHKyqj>x}E0j1n*zKW==DQ##t z{TrhQeH*&v0$nXVKxIv2B_mg#jEjCDhf$x-zmSf*3H4N{SxH_f7!0k7e=(f#n`mU_ zOT>CrnVCy_M<&F^gDd5z7q20n=NCt^Ov*%RNA}m^2wZfh3Wl902JfB`RLI#&d2AN` zoP#s|#U<}Rf#;^%{J@&@{^H*6*MsIsnel$)IEj7nb6(qE9dxMPun!I3uv6Va*xtS4 z_fs)xxYs`WkHk&mS>jgmEO8qeW=gY}v{;Cw{*4U6_w`U|&)BTmZ<=RhrJLSU%OD)Xw%sJ^hdGW^pS-k)YbK#$?xvKYb=Ewu0HYf$@GIPCrry#hG zWrpS|eCX9;J@=!t#m?Lx5}W@Moa;dwmQ8f4GGNMbYsp2{8@K6>{iOyDAKUx%Wcp&- ze&Yuu(h6s?gJXlIfA3GH5Snwa)aY4D2T;ez&%2xV9CTrTnEm5Wn6!PdD#zSGwP=5F zA~d9U4jCKuALGt3e#~>;*;vc{lnH!Y;Wj80Ma<;5v>fa_D4nr?bh?`GUlw0iqw*^5 zIM;ribfW^R4OhzrlocKZqjUi6+p3xkl*j2W43#}&y_j_`DDhHWtUPpVJ6Oh{y>q(% zw)9r8w-9IW$FP&vrZUqJ`$mqxk6o6blGJ#*pU;WaW|sS(&=yggtM-F*FI>rhQ@;Nz ziZyl0v&;5!>Sa%}{qK}3Y<1pq^J4?wXl2T^)2}s~$sw8sNm3?Qyta|;QT8bj%HS2k zupcPMIAB+hPZ!kj?(>~lTYcK@wAZKMbND|MpH{?T@boFvlw~ehH5$%Mw$;>?z3y$J zE6G|($*IOsH{iCx=~{Ns;}s-!nC(`hhi!cBU59v9n?KP%ZOk{z_M)fRMSXYIS8(P$ zr%jIH=1pSY+&#%lc9mi`%t~zxzFeucHVxEnXII7)7Q}+KZZv8+R5+PE!A#!UFtK1og!ITE+Pk)I~Qg~_Hdv+TXO6?sG`#D z`%@4&18~@Dwwc)Tou4>!VxW z^7(A7(CdsM?9%?fugLO9quo~Gwa;oxXtRpdx3|tXuh}xFtWsAxonBI!S^HF30#A#C%XS%tWHO%9`S!+wH3;ZN;AVL6#5PnYBWMd{wCU{iE8R9rDE|Po zC*Oev3rHCim0d(n9zj*kK2T4}1~-ZAb$Jw}tlO_O8De;r0DS4+=V2!Hkd*-WML|=y z3|OlCsy`mh+^|u6uJQR=-S67nPTDR{P0`_i;+~C6VS`SLb6*!zJCdI*&!(rbW+9rl z6g#RLbjht-Xmg7C{Hqf2O2<(`#2xaqlEHaEOL9-naHIk-3yr7qTg0;4Twhs79ptHJ=i7zz%&fcP-SZq2F`c~$ z%aFvXwSB!Tx`b%nR^{El*7j@DrTRS?&l-(s{eNH8kDva@ifM;!Z9TRJN~ttBt_;@> zu*tN0lq+)?#A1MINfQ+oFl+J*c5f;(vS+(XRC_|+U*75V+KOtH@!nfDS&~B-jhJLc z7_QV#Bs+l!cP2w%0e?@pj><{nw9+jmWZ7vwmBev|kyZD|3i4wGMnVn4AO3xHJQ9N~*;yW1>~EwoQP!bc^^@=&wQ5n_aS za|a-8^3LWs+KrVvdE~(6A6b%u+5Z4tI)9#ZA%RYX>>`ZqdSZ>P@JjWrG z$@2%AjWqatXmgMjQ}p@XX&A8WkLq^Saw>e7bh>w&$fr3Qq6( z{C}^+@+E4XK5DX#YTOHwE6>B{QmH#!_m+i-qRm^5*FC9iqRuN)-rqRlzfa1ktWsA8 zSCsZyH3>9pi&tkhGRtT!;F@OhWG)&*G>wP(7zq@%?3LWXP6Har*R5>Ucwu-J>O{I- z_c9_z&RR#A-QxtemJwAqZRid#GOeL~<15&88>gFdY7#32w%8ipFsB4E07*x8MJ!!# zSuhEY0*Crt?dfmRqk6#j~N88kkR!!*D+Pb=qx4W{po8B#+NNw@jqbZ8imJD{PQpbw;jN+%& zZ)5H#ROTQJPe!H@%{nn(o6kIwpDoX38y2tSmrwge*PjjTz02?WM&Z3xg0g_b;~Ci7 zNI5w9x!hM;w$xu;f;%xCrkflrVU3gp1PH-M&vF4(^AwUdNzj3Y!+9Q~Zf3rhb)~XC z>1DT-BPgx=znwyW3hiO@9PVOs@`YxeCT&L9MY)plD9dehXP*zhJN&S69aX;T3d zQ`(NxJ*r`4?OHHfXSG@uqtd>Hj#nAN_CKv-h_f-i$&S>+4%*q0-;R||XmMXDn^0I( z+C54&Zg{W!ERPj`(6M#@0DkWO0LF^vKjAim{(f!$0Pss?{{Y9Rudn1^^X5PJ4A1h@ z>i+=U{D0sd|Iy{`V(D72#l=;&Ok%WQz!mz=ONopiu749bU5Au0P)!#VdZLkYUY2Q# zgsHTSsxo&v`3N@`Ybh5~+Cv+iS_X+)7g(DlNEdp7(K>>`vKdIhIe9N;nli>nTE|Re zk~xGSn%z|x$MYy8g~%rg6gZ4$8?csi?3;T1|W8$u$@BP%*>8<}0XT#yDy+N-y9e>QOq7m2Ix>8DQCdivdM z)1md)WTj@GZ|Q4o*U0nLi<-G_7b2!xZhBRyE%DmE8w*Bit8O*>J6Xu~@@x` zl$PAgPFHC!%!g?JFu`-2soJx>-FtI!GaHRPhR?7petb~GSV`uRyl&vO@480pJCR>Y z92{YZc-pdcHFmtErq)V2rqjD`O&*)r?NuCX){9&H{m&kr8qc0CyjIkynx!l`?O&bo zB>koAdU$Gqt1ml4Ms$$1M% zw(?-ct{gE4GR&YU+&rW_;I(ep#}|~6%D2w(9j_so0E%BR!IUWjk2OeQ*0pwpvEo5g_6(p*O)qY})I zo)hE<7XT(Qg+R&Y^xhp#Dm>}CYkw}6+o$|7(>`fOm#^V(!_)LQ`*^q&abn`5fB{yQ z7_Z*&n8+$tX9APCT1a@RGsV`moC8%Rk2Uc8L0&Z??0VRF*_}?aXPB>VC0Dn%xl*#n zJ;@TI68m!>1-|TU^KHl(WjHRfUff>jvq7do6{W4bZyc_Y%M>!mpD+X&8)-XKDJVwM zyM`Rrz4gVep{Q6#9q^XYXuo$Xn3RG_se}R>bAIqX*JHGWVxTS?o|e|eU0UJ;VLTGM zG;?{lFW|{O$Al+&r7L{{XM}Z_MB! zi<;E6i={Fvd=bTH+e3_TUWQExLTcr@rMp=FP7$N{R6ETKix$t7*PJ8coq|4>c;=bJDHB zZ^+`lF9$|(t9E+Iu7{y$vCn@bGFv)D6e%26c2^OC@<_Xt#BreB5SK00K{7EoJdaO{ zQ@fr!a*$Zx{iaBi05Fj*)Fqxh#WzOcGCpER&fId<#n}g895y#L^J;}cpCO%*A#_dB zBOYU8yq)W~GZ08(z$TTw-SX*@ESBkVv8p6C;aOyXbCi&lS0ey|cSsZka5`7+*&9>j zb4hyseLp={p*8*aCm*}|{=GCupGOxIuNM_Ebf-2kU#M5cQ*^CzDaqW+o+{6tD%+Ja zRHcVKtMINQpR~P?pu<$Kv;B(l(#l)pitgtz&wDsn#t@AsPjDXy& z#gkk@@GwhBog!$S4<%Md2s!QbrVS0D}Ck_>a)-)UNm5lyE?%Cex98BDk<3q|FtZv|MWTq!wMd@jSB{%o+dVO~is zeEn~y`R!|UKO)DxHC(cnvXqrMUOGF=l2(lUrjkv#$@6pCP0n^rMKaZ_T{x|qNV-*9 zcrlJER@yvbzQc!Grq$2iCsO6OBI2zGhAEH$1zH^|_k1og3YD3PleywCT-<3IwZv_0 zJ736FW_GxR=NWJWdr%1X`AfidQi$!e@I`aF#jLt@j;Ai8WT~jz#~^sD(6EX?K|owF z11mGcl)*M*quxaxavAVQau5DnqgKDgip?Q#_hWXHjK4TC^nAluyTCo+z zr>I?BJhyi@%(0@h$-3RluNh)Ch9L*;$vb?%nF^e4CccwCnsAiiYkbxAwVu}LZjC=P z{Oje5wKsPqe)hCn+@&ikyI+|*D=Xh^eQ(P4qOW-)(u)2=w2$=*+8-uHn5!spO)0jJ z99Q&r_P^D1x&BPS^Bwh$u`^uWTt%$OCYKCR2M@KuGDz-@$_OBjmv;=zH-mz39dY(S zC9STUgHgP=CL3^(#G)0AvcO2nHZTQ>;1(PA65k=HtZb*$HHfCVw}$58Pz}^=7U%;U zG8QbPgO_Chg>?l+89^*+8uP_uZ{cK;b+}VyAu@ShOB|BkXUw4GLl8oLt6bwC=`8W$ zCZh>&_jXtBucnJl52&XG<;w;BURUq0;BjUg(@my(R)kRQt5Mo#wRzIQ{c}@}6}e1x zvi7N?w8v`LTQ6#N*)iI&c`vi~tarl^bFYrlM{Sn3K_|J{w9joM;5=yLF7mkC4naIJ z?KuFU-A=|`Gi9~=g|*B1vTpMlSlmcdN@O3rA)GcN2daWxIb6+erQ7OP2GK5%#pKIw zAhdQSJd$!o@TCs&+p&TpP3vRhTNNgARl{PXLz>}yMSphZBoN}e zg*4mewOENWR-?DiYUh;xx_3gSDbq?;Fu}EXrqtq=c$xlLBz?mQu@zIaksE&K6y)_B z15)owi%ywV+TQM4Xkb-iC+^IvgT#*EOnBbFvW#tIT&{W5k5;y^7W3W3Yk8?VLh*m6 zN(v({n4p;%fW(RP`IkFE!KrItsc~bjBvZ!%%uA$Bq>_x%7h{3EkgtFT@Zh*SRjh%x z&efO8UEfW6+qLx8$JW!9n(3!kzP?)e9F%sr?N;KCZCp0E?M*S_xV@Luk2)23f<7vB zYOm6sty1`d*?YG;pssfp(ameD8=Fl+-hEch8WSu9rj_JX3Zd13z;C*6La79nB<-sY zc?23-zM%|8XpwEa*DEKGRmde#jZm0^UyVDV|uIY`6|~ zviZzO0F*)U7Gtvwu$+rF@ZAYx`!X!EO{OzT5RA+&B#n|Yys{P`1{3S$}7JS6dzMugrFKdaQOfchlSm?=?H<0*{+< zP{26`F*9Tp;Mjr9?9iKiHq{EsCR>T2dxvO<-Zr-4+ltB`VlW`YkHd{}PnmMiQme1^yHLz=Irgl&eZ^|hAS$<*%BO`;gD9Y#%qecQdF}rIwXLUweWG}q_bHLTf<<2fwB(T+F1;!&)rwD@7?OPzpeiOE1y*g?rV}Vx?X?QpQme_WKiv^ag0;UJ83bB z;`-mzv^ZftVr6Q6b5+A)r9+yB#9ZF(&gdZ4uA0+Fyf@KV54J?IKp>hn6GyvoK*C1c zqGpYQu>qDcqi4zu`}+X0-Cx^WwB;>hyuXc!$uf}X5#2atEx0eumvXra_iD_yP{SRq zvtGrlT0p*F-cmNTkw#i5{KPLVcp@{sf`!};=-ADuT-<69Sl)Po8^*KqR#?8%CzlXV zh-5D)UXH$Q&>oi$1lsqOh2k1M*ngq74| zcuSvD%tr z+fm8&Bf1rNk22HzKyLMWi|8#AQI_m^j@ELfW_1X#25_O%<#7BKVTafqK--n6!>8y{ z+-Z?%iy>QYHdbr9k1b?T9FQn^7{;S)XXnYn{Dqe|Gs`RZg><85t-X%W1=ZD{l0Z%^ zWK>VR$n&zGK0aTVI1PqhUyHe4&9&8@>r%HNViwMA(d2BE5xjv|7EQf&90E3znSWP~ zez5XNH%iuO@1~Kzrna~FBly1+gsbe_XE^gF?Awb{buB3*=A6=cD7ijF+~s*nZJy26 zkj0+VvGZ1=i#7QKvk$ImbNjLq_%2AXHNM*?&U7zMx}NO89SADf}Zb5`$eA5EU* z>332gc}!&7$^dzIRV+cn5;l>$9B<&U7^gp(s#|4QZdTB3x02D!V`C#WJhZ?7ecS_) zv<4&=ud~l_5`>p!_+R>7y*5by05q`GER{!Qx7~gkTX&`1`r69N1qU^D-7|`UIJvD0 zXu4O(W!TBiqD`JpGDx>=4slv=TW7UHX%|Y@w}+*FLS!+GNUNOkt7CskM^md@E80a8 zhmu)VebB3%eBf{1tV14G3PA%PoC@|<@mO5Jb1Aw>N6OLOvpe8Oc9J@(g^^s~bwEfM zEODpRH6U(M`grBFk_Y=VisCzac-R0)i3ko8X5pL+IAUvHTuX0kqUGJ9RWi-x!kcy| zTuC1D)k6{v9N=tU6e|)232<9!q)T(;q_bQs7X&df$`Rz- z>yXS@n_`7k88N0oAy`(92YZPv?xOOo*oXB&ykI1b0SN8cB*p4irktWyjFbib+5{} ziqwb}T_}kSg?cxg9Ci(C{llIC!t< zT(*oSO*V7;R+*)1E6u4}T-@J9BK@O$a~b5!eq32>6OdK7Rw0`?L)40@mQd<5OLuX1 zDHmHdyqq+9vw^vR+8ZDg=*rAQcmUGrw)WSyHXmoWOAEz~=WA%K`HiG!RRb&-90uq! z^5-XMG`7+Qx3_5JidZL&3HwVddz>ypoxqmQ0I=h6C+0j?24d+~!YoBDsdx42lU=n* z>9O}uNlC)#?PjfOH?GZUz8gJ#S;)&CD!CXnr94$pqUOJt^Q=^<&7@~ScOr~c>vYa4 z2;%0nEn?(XnU-TGI*B%VJl01|9-xv$H2axaJ87en?Hk<*QfA&Hz9!cW!Z(t;w&6>j z`Ww`8_bqMRude?9J+*%&-57$`j+BpWfzq`EBONL^VdA}g z#Z!@RYi4+>K4`1?>xip5P1yQ8H10*M>3UV--%C8M9T6#>@(kCoIZ2eBj! zoc!X}pB4V0V*}ltKJw=3cehAqw1^WB`4@T!#_R@-&NGA1suHx_LK~Y{;D+Z^x3f`f z6^r2+K6g|K;C$>rK%rC?!+h8vR2uZMY1T8(D_>p0Tg_{0Rc|s_i>V0_RmtTECi;4H?q$p7h*SMGKu7oRFqFB z@7lWJgcV{)xu&FC*YQ>_9W_?hKd-4vLRc3jwC$qKDy6(#D_+trmG;>U zcT!5`lAVu{f8iVPXZ-kT-}o{g{9P5@eYW@hfi@rbHPpYxivH8~wZG@W{=e=a{%!qc zAM_JP?yLT1pG^i$R%>p0R?)ZIdQ*PKp4IqHX@iy`O{9Ef8rCur+n$wr-rpGFt|gzf zSc=yjtIEOU(xp4JdJ4VB;YPe@!^o`>xe5fT6U+;crI@J!S#mu-bDh~B@9APxX#tWq zo@qp$f0#g-*zHw*N8Vz?8-Un3$s)2WVVd0BIfB}IwvflPD;JQGLlPO2leZ^3i933e zS~5#(II&xZXO8PmFDy~KnMP*Vc;!jKZNrc|6UeXMa@=Ha@lbz1&3}>iwI?Y{k!;qx zZ_)0wx=XizhQ+lUQaaGBPA4@uoaQryu6#{B3R6Xzr7gaDR=Y=rL4A zXjQR~Z)m~o;uFtw(_0uPk|lGvh`jbHdi z>wTF*$MdW^^8q$}&$z+|a=e?6xSJzap zs$PR_pvEg&+B{;Sir*coY)hPT5g*AjtW)ouIMd zK~e_orvTCmi<_IpMRlEJmkKSU!l*dfz#@PFASq?!ZySzEk!DDtp62cco<+Kf;zA@v zEFj$5BSzq=lahC2jFv5&f_#kXuht{3uloEvy4@eEYX=uSUXM*}eYWX)>DNtmG2Yuf zsf%mRO4<7+dsCLr+P|N%Ib^C%_C9izb3O}vcCAZTaf*C)nC({KhZXN-cz1@9Z0Ppa zG<2Kiw2o6OZm=^+{*eCwDx>T;#^ay5c8mfDa&v{+Y+qX0+g*j83rQTbGRFs<5=R_M zwbi-)B_+8d8%YXq3Y<5z`#z}~aS)7oaea?0?m^p-tfVsl2qP+Tqq_5M+Ii)kIiMuP zyh{p;X<7EDLAPliFl9h^$;QG@Cck*2io@2W$yF&vOH}rLzc0M|xKfOosGXwMXWva# z`mUDhm7e+xxvH~AnzfwMSuc$276Uzw&$g7HNNiDpJUHz{9`l0 zOA#j0J^D4QQPCikE~gh(>LCgx;vLDkTsc-!SLW@#wvp43$hT)=&2M!ylRUQUh|$G1 z@Ef`XWe0e|u)sJ{&44mir_!dlk{iiO8Q>9=ND(&@OS2Hmf*S?GfC(Qd9ApMa6j?b^kRR-6mPR*F5fZaypS^1OP(MJCUJ=DA=2ifoCIO07zHuX7cg#-uHCrk;dh zVSC0CM6TvBleJN!Dj2yNxswb@IRJdC_eR~-H8R#7eD?~sh;7luz05)}v3d8}rM%CR zoaY$;^JmOtSGwICOspSwno%Hb6oqYtMo!W}$Q%+gv;>7Fn%3&_3!^BACBr~qE9NV1 z+q)`Qj=gv}Eyo7F4>+MLU%Zx|t=Db8rhcbFE|#ydUb|^@pD#^rufJQE-)8oy;kM?q z#hunT@o!Gpn(95x@bAE+d&stU+dUhT${|SS@r3e$%IEhQ?^;@*_WNj5pTP>{DOBUr*5-65b z541+g=(s?!J%2>J#}8IK(WiMeqic1275BF`wo1$5dflI%s47+Eim52VOWb_2cTatJ zrF|`X)S7bmR8^xB)8$OnDWT75(j#>+!MeYl^NRlfT29C7v?t19y|zzEMYZRpY<-hG zsmo{WUz1`o$xe5%?o+OOrxvQsYYemL`hC8ia!{6gw`knP(kURxC}i6b?JC5&?}eBx94G;w?(vH-W9Z&@ib-3Dz!|9HCbxRBQ^81v&mGSEzeFm81|D3tW2arvz1wHh|DUl zg1dJfsLG@U=W`RDL9L5>b-0j-65`6%aLW?KG>VgLQs2UP`A4C~;0Fo;q*h7enXJoP zEW`_sB66rNX9@c>$;@6`y$#bcMU2@AC~QJG)v}4G65_w3w`oS6U!$|@lHqWz#;pE`ThShvjz21ofoT&Or#>OeUApbxxF zdD70`HRH1WyJ_eBTlPwD_MEk8T_v(lYdsb1Yj1boRi)bK&}OX14(f^p&MQvZ9OAk1 z>`&I(x%t;WDGFL=wM@2N)uj!#dsU^hF3!Wsp1?TH?;Sb)=x{zWuvzhvsCBS4Vfzr=^?eBa3%xvmLH`Q|+S8D^3e+j+NV&Q&_aFer4@> zN(*J}PZoIXTM28&N^@G^bg!kUltwV-vC#}cK-KVJj-(>#-XO&eU>Hv^Wq?l!Cyw^!rNGqX<=BcwQFx9R#+yF_m=Fyd1B&B=5O9xgYq(;^k~CwT(fZ= zBEQnSNu%G(sCZ__?B?!On(qGY)mF}PIw6W_p-1wGwoS~~1wuxO?IQ*q*@R<8s%bk$ z+ccz|l1*O9+w8jN^*uVg*h*58ifiA!)SPASK4{5BwxpDledgOqrnyz0vjd8eTA?GF zd}Esab(Vc4K3ksT7KNl4s}aGwwMTB5txI@wiu#MW#d=sgSLf#j;T`tnjDCaTT5~pLV zF(@)XQb%~?a)G>@yEDqf{_Q|rDl0Dz>FH~$=mt$-+1$iZ&fedCR^7_H(1_MpL|fM- zhGg>uo?|Rg)@7!HZKlV4X{cL6b8~Al+TEL5$QFAdv$&Qpl5J^Q_hjZN2EcG!tFW)^ zdRa9J*mX*!uJl&2y0x2%ZS3y#ZQa{fqy9|9R;h)p&q+3;ckeBlk1|rfF1B2*%_}D; z^1&nPzp`Vs9!}oXuJ+jNRb_`2{G*v+))6+3?5Z`nid!hD{J&}|Fr&T z8_@v?Dn}mN%_L#|PnEIeC|nE?yFYcXPBJywqIG!L?dOft_m0RRZ!jsq#_mHLi~wL~tc|e%IJG$;tHwGL7_dw6ytM`$zT031_JMKVHB~tCQvW@xK zlqcpGzypkTHTq^&qdYsKC3~&9zLwjr&s{G5U&Su-sTfOlw^#hT+q_${c29QIlI^^a zih^i(tqr_XQl@L?GfYGsH)HbJGt|VMDx)M^*4)tNHBL)>n*M6yCn-ttpG}9TdHw8{ zZE0-Cmiwc4g0wN9QS#&va6kcqslnt07#oN;8s;EpYo!L+q(zf1uI_e&9EWZW4$;8n zhEEv9H&?c8MV*rJNNvrz2kkNxMj?Q1m~^kgB!a>7@UB| zAbr3H-N$lDAPA?oTX@!c^_JS^(_r&sl(dbEoN=6P z-rRq*PZ-5~UlU88UGMt-Yj5jA_v$qjp*btw`YyNKTSnE|y{~1XR*tCnKRkydn99FfgSjB#y!>z@xV4Xvj!lkUTEPhO| z?q1;XVglWiv_?rpF@#dBg#~wVTfXe$?+$lVqU-GzR~IZ2P3IAoDcSF$pzZn`GomSkvJ$m?&KSR7zOf=$iX{LP(^f5#X3(N%G*a3t-fm?bUu_4&S?wMiw)IPJejE7~pMLyVz=bU_)(fF5nDq zS9nms5THIeD~yKVFHC@cPRb!tH4=NR*1LaS=6=;$P1-lS;+~I9E%MiUJ2%}cGtezz z=~|G(#a6bD7_BH)zfs9+#x&K>#hmpRVdAUJ4QTD+p_V*~>&>wcblr~1Gt~2U6D*5q zY^B6w_j}<|p$2|sV8`ZQ3LXYab~!bl=1cb3BfGSo8;PWi6(B%DBC~}KQ02Q0gZ*0I z{GhIz+eo600;W{f5HBR!_Eyf+U9G^nZ5hpbgr!5$?McCT-D2Xxu>nS zJu2K5`0ZbWV53o0S3fpYbSzp$(zb1(>BUpEhZy3ut zmUmi}jgwny;YXCW;N2<_xnIhTM@7pltef(H?!z|Tzq94|X<@56l_hA-Q))MMSLLZ>0#!GEY~{uR@sN$(@Iy1(zZRnap=^ie*)1Y4#cxoc8YiWOG5W{b&l=*ZE{ERN4Bi-4qIbQ!Zq~O=JIAi4unQVebvpQT(e&F*3mx9 zf*u815b@HI1;7<(FBP5_5d}#7RZZNbEHy%St7b&aQkpF0zdYh9v6NkpV-GX)kNy+O z-}&;e{{X;W{@F!odFTFrr~d%JsXzGT75djFpXdHZ@jv?){R2nw{{SP@AQgH#D6X8n zwG@60)R=Upy%bkYHNHow*+}zy7!c~lLN_>PAh9PIfHCXtPx9$hqkL=YrEO2JdEQ@> zuyU$b0Gwpw1F52l;LcU+Nj{79XY^eUdaU`iwJWbCuV%d0MtV3wo|R%19+XjEh0#l# z{E{X30ITqdD6i78r(}lCf5lpDwwV-0+94QuMpq<`IT#0l>Bnw5aaDCS40SAke3&F; z9hfl zO4!o%8>EX+k!~dpx@3-|IpKql57z*5-@O!9^afu}5sKwWd_ST68CwrUVUti=xb$71 zdek*wbfSvFgr6qo^VC4}s9XvtubRypY25WF9(!wR9iptz?~X>awsKY5lEzgd8R>-_ zbQt22-pVhsvjEEeQl{m02L?$2W(A4({%)o=~+pj(`r_R)DnT zf$LN*pi6sr=7@Q6!q*Orq>LE+{hwTHTxZ|Z?6#T&8fNP!Ad^x9SdN0K@X>M)Jy`1O@NWjatO-Tbv zbNklcaWfW^a}vOkcCHF5(4jd+!YSEZuacA+{9Asl_dbs@p-Ez9)D`&~*>CLXEBe_b zee9QG=tvY~aas@#lu=(hmFa64GW=ZySYEVIU!+l!g@xb%%WFZ6jM8dR3b zvf9MqP{<_Xa-25l9EKk--NCM2=JscXc-euBLep$2FwU&x78v=t1A~#k^rDLXm&_v- lEw?|A@GaJp_K%v)+WOkfrEf%%`6Kjy0*WZF;8XjA|Je=hRxx2`h`=l)k&z}Np(*#D<4;Gb~j?D_NO&R_gn*O{{cz~?#k^Ne?8FI;z=n=2d)W=6?WQ=XCHMVBr#wn-bjkTiX9r_W!T2*Z)VA{Xd2MzjaX= z&NG~)lNnggodMp$bLRoT^XmI```jer(Akkv@3fVnaxV$+$td6J6K>cpJxE6Fnf#|C;#!;HKne zfAwTZ6@w(Oz0+$RE+)Lpgb?Z>d&tHlL$df1$r#)Sw%rrkK5YuOd%G>S1$Qq_2M-0U z#7LGYyks3A9{4$QH7hfH_owl5A5`Y^8R*5%-O&;lM9@$!gTys0KLYH4m#yZO-N;Ee zouT>kHl1|5RUK0p@vl?-$ivrQ1%P`$5O4z7smq*xwGqwSBV zxn(Vn^Iy%^!oH6$f;H%)`$FV9?yEA)MG3#G<60~t^q*V%*p;c*$L-!} zy8bOcZC`$rD>7}JD&S6hni(6d$L0U>!ld1>98CW@2#mSGDX4= z1#JJ_)`Q9)s89Cj!3E#a(yWTuh#)h~(4-PWy{ed`h1ld5V2(xM{$5c-+e*w87dYI{ zov?^KfWr?9$|C3k`=?K)o2x5uX6Z+~3g8^E<7T;()!^iU2>RfDyoEuS`SZ+|r!lB| zSN@rAk}Biv*L17FZ1@}uv3 zimzT5at{|W27yXILZroEoA%A_<~jeyL5lH@HUzPhlm!H&CPXB-J`^1K}uC2>uUCXvC3;{L1%&Ao?$_J8wSg2G*K*Sq zQSQ0)p#zl>sgx7()V^-M!Aj>W(e|*@JEaxb{q*63To=w0oK@wVM&WdqP<)fPY;lH5 zoSO7(ggt*l-%MVt?Rmbgwm+?tRRvjJE8CoZ6fKo;e z7@sR+vt|P{@Ix`F>mRj3_C>0Hjsrd6AbwCP<iSqfzNAn<&QK2RuF zb8PrUTldbb+5*S7)KB7Ko9Uv`t5Z(inkim2u4dkh;nWyZcKvGn+5E_RSu@H$Mib-! z#KSp5x41_}%N!?t5iUk!wf%=Jc>Z5MPsW2v)$e5w&d!E#yBYJCitO0Mz3T?6Kc#Fs6ZX2o1`B+)g zLSWGpnLJxkVgQo++*qg;AX4*4BbM{ z)`jf=P&db}HRo>(n>?j6=!jNEZn(%FIK#xpcSLo8W<)vVlAk~z|b z5A-j{={dgBr~Ccr&eL%0w_zNz-!RZ6b7i3KSwG`|bE=XsQr_`=#ogq8j8Yz7V>st! zm$vqGSXfyJO|V*a9)#OiiYtmQ4Vmr0gs~66$dKI-x1>|tad`xQ!Vi9h4Ff9^`dqKa zqv~q83>JFiLY(4IQlB}+ejnFO<$iqhPlTf}`lg?tn4kQxl#{!>b3VL`MCx{3a}H&N zdG{6_;|6^FYwR|4Vn=`W-^SF@o&dCV=+_(J=&+&IoukHMrRzg(x^Zi5d>ht9A3R=i zx8B8$$SQ?9`)cN`b(+*<7i@VTP}#}Q9>nK^GF!pc_fKPvs%jV5l4^ZZKHcYF616oG@{y=Pl~BrN?w{kF;2#7TA+sLsX%JGoFFK&m>p zqv~;4)~@LJNza|ClzxET^cXwD4=I;E%&F_A@vA|aS-thW6Y0bI?=TV0BOOIG>VI-5 zK|vBoi&TAgv}#bxIoMrSd%;wl>@Td`yp;x@c3*vd)%N@5@#J-p+HO#7H+q>+Ss6!j zSIBb-b1H+EDlck@?}Z(1<92D79S!=(dIJ9bxY6zbVq_#}RvW3=6Ukm2AJ+A)%i>i7 zYehdp2V?k$Pxe`ptH=e=!%S)o=MT)S;G+@>OyM+@8P@HNU7Ex$m;8Q9Qb)gS;;gCI z{#qOXAnTE&I(nHu5|&$18dVFgAUCc^-Z9?FrT^TooKSaHmic3xHqU~oUQ6&noaVk2 z8nV~EUo}Fp9jL4{5T>f%a@&i=a5Zga%e!_4oZ2+=DUL0AoVCwbX`3EOT^RTV5ZSJ7 zl^x-QOTW+Vp|`j?-bio%sm&ede$DFum+g11i^t1JxLxy(mF$f;!)^`tBk=!%xlde0z9RB&I8PPXnN`R4MVpoK$(ix|2H} zeA;cEdb|6$0{!72dsO~}Ev9YD`ZVv2IvQN}b_-fv72h&$-ErQ3Yy8o*=B)C(83t6U zPq;FNAeda$F|fX@ zURE7Tdm)m(1eYD_AXtZaaPxBgiK=_Wls>FqZ|N|zsq?4UJD`=kzGZzUWyyYhRpPX7 z_ig0W8O~h`4eyqD4f?=3H{@({NXvAP@kyG`#7!thVd`e!>H-O4xghQ(;r&2Vg2$2d zfE@mNbFj5W1bRmrr&3q_>xXqXO`R|H@)LA%30Ve6M6!_9}MXpX~FmX8PWiB z%mHxd`rfjmDo&%s-o_Oprx*PXM7?fz)hNXN_+iX^G^;2fV`d>L_Gp%~>*O4p=2Z~U za0IR{iPZ{E&=DYmjh>KTbcUP73zif1+SM;uRU!Y?Cvts9>?qjFb~43)=y7?mcp(3#q`;I(($!K? zFifGO|B8v>9dSMGc8&-i0Ipjyf!IGf3FZKYKX%}c9gX?WzQte8N*~=Uxz`((qhP(K zF&OQw_U`m58|3sAlFslu4Pd_M<>jXO=7oh*vl2Q(SMJ=Ek&=k}bcSz$RQ-9pW=gRm z={4Wo6dg}76r(d#D+g!t1Eg|bj$Q0bw=3Bic>IlO(Zka}G@-%=I3?t%iC^(zO@J5m zM(U*x2rzY=HNWzhusJKj@bT)*Z5K(q1`*KD62ln1K81OGgXUD?TH!=J%`qYOjhm!4 zS$=<0=ch%Nn$a_kVGppW$&59}Cf|p8GOC zfNnM%amQ>!COU{>GTvD>r>jj(HjwEEpat=~Y(yWP`K|UL4s2H4>Be;>Ew3Kvz(f0w z>^t2~-{^l_2G`w>!+mMr^i#0P1$@$C`5GT+OBk9fcrnfQ=dFU5Q4z9D-h{N^w`61d zjgK5JZ=VkA9C~6`7ON@y9>h`P;4ua*zNM2cazvc}`Fh?QH_=1gTpSb4lUw+7NL6s z3O{i5kn@+g{vb*LDp~Kn(fSC7tIhbOo}Cp_sXu$MD~?;Gly;Y++Ax4E?)B$&8-wr~ zuvpZU@tz6fXi`5lgt}TCN?fit;PYOdVAEPU@>?7=$5z!h9+!ch55R&aDqHr+)?@h~ zMnZ+d;`L(!kBlBu<*g>3E}o0LoPwgps9@Wb>;gJN8xF26{e!fGbqY}rzLh+4OT1_& zyg9sv(kq<{7^96q?D%P8&(Mk1HT2Id=?AX)0B)}O*wv^7D4Oi-BboLfYAD*}_ry** zX_M{dkR2?*NHIW|cK`_-6z`AXGd^k3UsEb2!r0&@5O^m2pTY<`#~ z{djNgS-Ru~8Dlc5U{VLLh#v$VUSDzh-8R^f=w)`2-!nAE51dMjNiU%~E_`&?b^b|) zE<8j2ZsOV>%qGHR`0R+{}y_x^xtDOfB z#{h6*wxw6-4E%>wGePK6|NNz_B4QqWpqnIAjdw!A6dv8n^sV`L{QE6~gmI)q-Byl* zu+Q1mb7!`4za|S!FL3(fn&I;E@B$jKAoT-Up{e`C6PeTiIS5^I8r-FL{EHtFf+C*> zR3jJ3!u6u}+UL>P?WVF9&vymu-p@bJC@5EA1vcoJFs=fTX-b$|i+=Ddn;Js{A7}yA z1v_&nO^Q(e>tj;fPQ;J&s7AE&A2_R?ZCyqbZRh|7njsUt%?w%4sF_|-gCc0wgN1#* z+WtWQ-+Je4APP5V_?c28t5nTi%B)xsIG4u2c~(J0hj>m&m<4olp~&)1faFe74PwL` znP4!g;=f2N-#)3jo<+=Ik8rVfv!~W6r|6o*x()bXzzNl8W(^lDlM*I9*&LpdzU(}X zEN@g@$h!>9Xlf97H!ioT+?BFQm zrn+ad8qKENscPoY*t?YhfL7haDPi$|*_AdqVSW-l)Lh-rJDmW$*DbyO<~|+9I(&dj zu@|Ibi8=xZK6P z)#nLoa&L%7gePE+OF69e-YoE!d2Sf(`YMtmj*>P(2=4aAk2LgIp?dGI{9?BEG%Yoq zO)6f^Snz%)VKVDoSukGrdp0R+>Ps5Gw-GS?NvIkfIC^j*u<%gb7m#hym~Nlr;x}~w z4{ZX|uaQpg#{Ts8vudLPK9?H%A^~_D(-|V2n;lxG>k_W+u?elt2Sgvbr@C6mi3>z> zByOzLfRH|tXK)dgqb-~H z(VS9G)~B0{xay<=G<5)TtiC!CmfwG_*DFB1rJByrU@>O= z-?MLY28@=(HyMe~9}k=kf|U#Az+!Bwm}`}L9+;6_A~lV$q3Zp|58RL(WM{xNncZ*# zF9Ia!$U()tv{Ye13_74jUr0vVDc%WoNt;-wYO(&|X7Ob8Oj0#ubZWzTGtON;NKo>o ze|6HSyBsAsCk;{$QQt;Z55gwbNAVy0Lf}h5VIc#2DbDNR;ii3$uf3Q^% z)wij$Ej~HZ6!?c0(K@=e+Q>7@)*i^Yk%jdc_zy5S8wO#Qe(>&TK%*tJdIO9*eAIid z0ASGLuChbhyYz)O_r4gz-71kJ&$`{m6~u{q;A<_UEOUgk1lG=pZB)O!-%Xr7S9^WU zX4(&_lD&I-ISFR@2DPYaaMeK3@N^DpImy6K zzX=;qc-(+o*&SR+3`^og7nQRMR!~{q0Tfk&+1 z9ak7oO71BzHK|}h|zqKgeQgb zni$WyVxfOcY2}h? z{&r5Ibn4j-;Yd8DKBa_UkS*Cv>=~^u)pe`WDIPn`8I^!(Fj%y z@4F92Gzz-^q0ZvylRnrrB)zs$SQ|Imo8Wul_jt={ zMN`14(&n(g?2A-xCCwE@mNG5Z5GCT$T4w4i$51VpYxnFzV-p|FSQ3Hh4J$txfGE)7U!k}HfR>m*oQV<;eg09Y&BWXzz41P zwzy+Ns}J}`UZmt0q&}PI^X9-mN!ZiBY9&K_^_tkq{W!NK9?CL=;xWPPBt=^_^?Vtt zo^uy>%Szmr;(Pd`Hq4}3#6139qiKq~tv5;zm=&6dseFt@t(xXt)@=$9F^iHbG=u?5 zgRNz8-H{2A3K!nv4&~m$;U6Bn-)xAn+_AYkufDwcob(a2e8LO4gwCjcT|W;(+m4Uc zKszDzAaVYk8!@e;#P!{(4)fhAi%_iy*V1xWN3xT0i!C`L)98v3*(8$Ur#w4Unvy}` zMgNwS3%at{^%UT+y#!wLMEelHZFleSw}%#ndWes&{s$NsocwP(gR6Vp>4&zhOPQ2E zWpG-CDzp|+2ss)4eqXm=XjuHCe&}~y=afhPNCnCtDO_vh=^aYA4^s!W7$mp?TrN*2 zWzxC%$C6=CoQkiwqUbP23MN7-F*hg{7c4YCS$qh2sD}&|6d~#W4XTuQOI_I5UF)s* zMgKTy#1N;jSjnaakrMmi+eH|dNxLWUDE_iZyOya@M)z3IT4{iRz@FPNSy|^vD8`!i zCN5h^Kamu^T?l>niT<4G)MeC%F^?dz-=iUbV+3`9WgUAA1)x1#hrvB(J>Id{jhnPul4o7-qb#uaA#`XCntZMh|2M`zh&Y*p4HAoCDPx{g^ud1})96QaDZ zlDdj3vT$*Kz0G(~X_ZRIZ z9(G$C_{mm6k05Vii;ynXLVs6L2KNUIxGa5R2&Of09Fn*mYkdxBamF9v4&|3NKKjqu zT&6ZKp3JDCZzfv2H{8U{-O{Q~IaGIOz`ERU34_j?1r?z5%_!Qma{Rz)ytKJ_LqjTg zK=LJR>Z~YZgCHri7_8v(saxpAKeFOk2*y5{UwkPq>~ENxlu~k5CrShhC-7CLke(`? zd_!}uM8Y+ z3n|xbFH`R1JdC%-zPQLw|G62%1TUAPd99S)oWB*6ZPoSNV@`COa5jIcRmS2w>jlr_ zx0e!@vRXOTlW&_jR@~|JJ|aHwMj~I&r^(%rTjSON|f0tpV)`@ukC^( z>w?T?hpxw>M^LvyVX%u`LzB3ax*0u5MU1t^FvZx40;L3?2MFzM*dp>)0Da&9VIiAw ziu!R6=|Uob>F3meNsZ>lA}d#l^s1~puh;7eh45tjb?ic?qiHMUoTR46x|>DJdrXK*|8@$)x^ z%o&-l^p6R4a~Ep|fIVz$@M!nO?aBc=C=M|ZLWvW*&YBuiNa?-Kl;aGEH9SA|@2ix! zYztY5PUY|2-Pmd(KMPGyI@8P->zf&0Gl08+s;{P6THb+dD2Dd0RyOz%^mYR%h9f$F zK^r8qPB$0Hu-EG>NjYufX9~mMDth0F{({fEW~MO0+5skzFp_t_?59Js`j3?IV7b`{ zz^1V|P~|A@=@}triq8srhkro4`R*%xOrUB*&Sd&?zIvW_!PC>IE*2vw(jhvb2$Dk@ zcm&xwx%lNLt!5|uDK*bDj)fwFrTx1pXsa=Q|Hc*UKuwzYp+DBMrCp%r5nJNs_mEc# zBCa2q^*x z^<*_>l|>(Dybt|0msiGj60Q#CHV z%MOmuK=ED=jq4z?Cm#$g`6zb?^#|eeq?{6k}Lz z<+~)JDV4k=Feg$+XXr`!vA|?v=EPEc*Gkb)pFTY80AM#l?^!{HM&&_wTx~nd3RzJN zG|`c1!8bhEUt3IL4~|}EMPF}^K1M&tE)T8-a6Ec&^o;#oZRTT=v{MxxCs_XNDi80a zD``It@Q5CTan`Gv70SgOkU2Nb(tBSo{iUT`J&&6`3|Z*z-^ajE>U5T+sc>LA0Wfy(r%#hw&PTk(I- ztvmG4qT^?%u^)r-1(~#JLAKq}QR)G3LVp+u1oO~%+bdg}HNoCmnr4hryaUs7v#zuz zrYMmbnaH_v4kZJgsPN$Uq^M@V^u_pgbFS};^;SoGiOaxOxNshtL&bk}chLvtkE2g6 zbkP|!4-#S)-Zqx?Lv!a+l==S0_5~_qjX1LV6CxA-y^8yhQ84;U3-06C#n|RC54w$# z74YT++XOx%GIx7`%>qS9fq^8?q|hFBOYc9c_$mW@rj;))DZD3HOk31;v#M%j+34+w zpTGYA3WEa%iU(Yq`-`ii0X6KCOn@GFLTdOrP+=~Dyx+`^J%4U9?|_1wgry94?D?); z>|N7a3qb3Umr=dP;;5U2AX4nTL8#)g^YHQT_A04z@RvfOFLvmDw^e5!pgD&24<{a` zoD{w;M(VWJ-QapC{ztv^D@g;;?Dur{u`~&|mUC#kGH<4e8<%2*;PF?ATi)c2G`6Ov zB9~j0pP4FkNx!AMHWe2Pho-lixQ~NV6fsB=wBV3H2{%pKD=!FReIe$(;MI@^#}Lzu z6xSy)*a1Q=ApC29tcFTh!$n=L_dgtsD*I+$mOVP?W;i&q^z6^^dIkD^Xl>4`SGKH& z8O<1IcQ4HS9x^ck63T@J59Ki%48_*Xrmj+g1P9ay^-Jc9F1t@JYmN5-X@=4LB5vjK zmufcsFFuiQ?@*kF7ohptz`pM1f%%9OV}y=VRi)0$f?)GHKdG90YHW9PN6pp~a%4P* z-xF`ne>)zrTv>nd<}KDo&f?b;rv#oQ-#b!1xfplZ7bVhn=VYbqu-usj?#nuPsb(ss z3Bn5ZG{x^W?nFe`8PNwed!;5+Lo8SGn%@q(>&{heu3ndIzn7n1=3bX+@09%NHxkOf zn&Rx)ht26F7$^0y$)XmN1qDIcOWxs};WqrxN4uPN^qWAjHb^B=R zK{ZBPV&v4V=iLPFCvOTUY5qPd;}rBv!sJ|_)sFoQD+QRP6$tuekm~C{H@pLZg*Wd6 zX^Z*YOQ{~*vr?}aF36b(fcVd0fLIZF4u} zGA_V=^uzSBU7fSf_(^nfe4_7>YS)U#`Dd4&oj)Hm7U=(v5QxuJ%=Dkovsq_9v-Ku) z`(z-&CHmQ0wseNe@oTP2NyD{Nl=~!9=oEP!tC#;exY3M;*~YD5wrzq{|6~HPA6lKV zYW4eji4N;#=lx5bP}%fPuQ)q~TSHst1c#HS!h`n@w+BOOVqmJMfnv*ks=&c}m4;UlO zA#t&Sz;0uc61dNK^59KzS;8M3{^lF{FVg~t@BKHjZP6}RDyF9ToHesyw4ZpjNLNRo z;^I8dewk#bzxbu-{7*h&rT%GEWHRmXj(dREkqr>zAZ-NM?_#gW7~mspO%g9A_J%s?60!%tm7y{jmcnai)Ip9r3%56^g5 z&9sUawaV3uY*D!bsAS#J7qIioH|ySidB9gb``7@-iS6phJ}ERFJ4(>*^QA#oa`?shc_E z>vQH)vCkodcco?FxjsNR>1ilryVtF>b61YX))hTf#Y27Y6U7fqDqP;;dwEwQ{K1KH z87W`&-4{Lh%!a^Y8N1In@{n3V0tSgGo=gm#|Cmdio#7Uat$2nowm?`+Qb8cJTxw-~ zaH9ixju3o6XW*OunjjZh#d_%H))JLf>Md|`INtCPy#|g>5leP&$uiRg)s>owc7VMB z9A=tjsB^-1bojvmRT-k3!Zz<;#2K`$&Z-hT-U=*#l=+OG!zwZxnw9EBHn#uBwLwfu zm}P;@lM7M3;t$4p-Sqk1Mc5^k(5}Y{pa+vp7hAH5#!);(GeL3H@Kr42k<7DM9 z|B1_5q0Y>511Ix9s~VWc+*`D6Z^{s_nHTX?Tm_3R|fL7{1T#y-p$*I9*lBhKcUk}?v#p*4O zC!xY21Xo%?&qaB1x;kb+I z+!mLHDX_3Ye7`BR&^P}F(#TFPat57g$OWG?#Ro$T5vz-hgOE|cby@;@+&?cO10IT+ zN`8mO53fy$Y*ql_iJ|?z2?>YhpzlV=&bGRnGO;X2FBK%Ut^u-tgJVPPu<&QC0Wae7 z=74h@erW|?#O26)n`1bu&UnbnzEXB{+;ZhpmhALA+-2kkxSQ+JqfwtO0m{M zHWcH^MR)Jb8t0u!TX!uxKp%(X3k$7@iY(TXM;dcOcK*~fHckQr%wvy!eZ-c-bnw|2YM?*3-5Jf5@YB{1Lu`q#y(=>G1eOj^x- z%2y2+Jj-o@SV8QQmzOSw+o!TMoIS#i{@GV~@MaGp1~#7$f%+!FC~!G*Shql3?oV$~ zL#doZBQ#;~bg7DB+9Z&g23qpa_wXxx46CFvc0nwyCjMw%vpg&NA%-AVd9C(V{N<_d z=Muj5pbV`?5o82l!-g#dIja4w#^im;%8ME>VQz|fP9wY*^96Iu_Q=xG&IIt7c(GGd zek=&_(on^GShgF}>+qK*TeNy39?!_Cb?yE`tZdC0URPQRU&#MUlj|N|3-F4MjUAy$ zH`TbECI%UQDaf>{>P>;37wQ$?s?5LB2k7N)6{)4e#lb@=)DXyfWKWDB=nN&=r6pY@ z;=Z`t{YJ?Z3X6JAZTC zeacI2dCV8eac&gBv^H`6&m=muFp@E&j(o4uk-bts^rN$}4$; z3r8xvR;R9B9vnF~4PWH1A;@(X1BmzQv@~SJKvt&-*`aDLz$xO|u|b)8eumx+4Xad& zk@+OqZDHl=v(g35aJ_)hAUBMAqwgqg5M-D32@NYdNi!dkN?l&@r0CpYG^ngnCiBk9 zie9@$sWkehOBw9lES*k@7m7ypgiQJl0&3hRRKdWtUeW|IzZ4~?;#oA~e@H<4fo!M( zO@`DO`sjL)W*3{+ebcTYb2!nbffe%MDQ3+|UJ}GM~Gj(Yx2g#J_>YZFa1Lknm>Xx!p z4KNT#g;du|DrZze<{*hD{GuWz)%a#fY`x8zZ%(ed0;n>?sj{jzV|it;pMyb7cj*Ok zLPLh@Irq%0*Igpp=d9bSEny?KI7Pw6;HvD^Jp&3Fq;6K0gCBNFQy5y(^Q=+8hWLfa z;W1-Exuunp^uev~gUxvp%21!f%lBluu-7?bz&h5z7=KF?NJgZpgzjmeN{H<)_XJ414!uGgyhqJc=c`P_LX< zWFjA}_T)e;(Ck6 zA=?Y1>vV>#&F1MKz9j_z>FXIfLllR`fqh?R0qXtQ$aF}t{0APZ332)CD@?alN*tIN zvj2Ghcr5Dhpf>PIP8KhBdn>fYrKsL^v}Us#9zxOQUzjqULMDHi;@ATWCML;XC5O03 zsQ^p@KXhcO@{V)P9kM4%)m$~LWW8(6Pc`#BZR!$BSQE+J8N^>}`YD^j z$58ne400xa?)c1U>t3E}uLml3*9?9+;|CvUDDVG52=K6!eE9m!ZoFYYm5uLS2v&9F z3l{C4oCpvcSv*eBhLAY*wYCw*kjD1l$G`)T#4%&*kDFYV_TIA8y*&OFexhchCyN6W zN~MAPyg*Gh`GhcxyQ^oZp9pv_!zKk+nUX@qTdm||mB{(rQy-r0Ln+U-{{qGxPpsyf z&2sp%HuAx(?P)k4HO50;_R^8zvdMHOmtSTIjt>k5U7SMA=SbZWYa*r?lKI2{LMiaPc36 zA<}#S0Id^L&y)6GE~XA=C1=#0RU)6nBRvi}7(~+N9*M|2=sx+P^g*6xidgG$N4ure z#2hK&NL1?BSQulzaS7@5pn*8e5!;w+SU(m#_MG3L!t$N8Nrx@~zILl-`=FG=?25;) zdVP#;^bh?3_Cg0kkL#O3xocN~?^lp4{yf%=o-`fhKjJ4pHJ7jYr;2e)z*F(*vzWNT_mtEg`S6UdvdOs*a7W#umWrj7 z!xMV}l>wENx+k`rB_R;zZUJfrT;~bxi!Ekiw^@{F1aPpM>gc$PE`M9I0ld^GbP$C_5#wylEdUA4v6YZt-w7+VK-%J z)m~s1!$(bc|C0@m6xGemj!`^1;0wg7vbmw5ybgb;d-TwvV|NZpb61zM-N951D7u$B z57I}sigCYCy5#wu9whm}?cLEI=WGo1$|7|SZY9aSx-4_ao8$RuK7Z&vOfJs{}5YB4$Skej(6a zEAnE9jW?khX|7RJa>TSf>>-K7<4Lx*G3c>ExUygHTu@s2oB%M`+WO{W=Oh8-uMQRN zy|00=F3>K20`gZwTN~~WXuxVw8Wv`q;+WaW_gK(T%gyjYFBh z{VM(tkTl>0Lv`TSORc{B^R*FXK5@z2BChfYaX{CGO|Q`8{VM(;AB1=%_8PqR9}Er!`DrefMLMN`~V;+>fk5kt9cSomrPprCj3fIT=ejvK!u)pwx8=xy*DLgm6{1b zOtS4YKlpImo#G(N-f46x?MSco5Mlh<@9sM=>Q11k;T>ApA}KRp)C6mok0F6xnbpoOjZxs{Wha`^R&kE&I5qkoIol<|Hu+5CDC?sYgP!Rdx9#uhft z5vqm-GGl@#sqtu+@&uaO^yZ;*_(it3JZ+4go@u^S>OE6R2~&sEGq;<1vZ#0O)WfxY zOiwpPd#gXSa>0c^lHij)f!urttOlUcZu5ul5>NVX7L>r^hutrAA%FurSp1QOj@fnD z%Sh=@ji`h-FO)~o8QzHJo~qgnOp-4h6w(=9wI26xUC*!^y8AaY3iz_a|H*r>swww( zDED}gU2q{o>!CdkDUSiktY&!x+2L3RNhWmhKhp*d$3_Kr_~UXwV+|kJ1|b~V#~(T( zpT5=(2}*SNB;i)-|LUnzO4g3Bdj-RmU84g(%Z*65Q&At0cIe~oA?*kE^|a5^GH5X| z1dRz55*ru7>^fasQBDEe9gDxr{-Z^T-qccCkCN@Md7UQpO2P9!h#}UG7Xrg3V?XIk zBo`73M=?kb$J0}?CG~-;QtRb8B?<&9om|+%fwRU}myb%77|3M+#KmI}#{a`_{D3K` zy}P{LV#1}a9Ei3YxN&dU6;GA=6r(s-8`0=prLqQIQb?Sr4q->X+kSjSHT!aq=JkzOivO2C z@4##G#O$W;QtwE>KuFCPRbM+5-#A0UwuiYaZ7*pDmQ1jSQ-=#0$HY~&X&w4$=9vTT zU($eK7SrEI2m*+N=#-XnarMWikk`d1H#kACDa=-cMEp}l_evxDLZggTD^az)ui8?)0keP-%+%en_(~ZL(!Vk8c5Yr+n~mA z8(B4H2MBbfgD*MiHFbxz1QxLyXb@3G5qJeLw zV21I$?9Nn$I#t7&d7s3L!*Mx5OK;+hfYEgIu(07E%YxJrXk8(zjTkA8r?9u*BY2NW zosf+IhmJ&h>8aHWtWAO^ouX@R{#e_p5#R@ddbXc zluX^{dJ5@w@Vfo?f*4e1Po#`L39d)gMOknpXjl9f_Ju`h^% ze69mKez+kQT7}*0?j0N8Ccb+l6gW#|73)`K?V9lx%dQfhh${TyUoWs8!)ys(AfzKM zk1K3&AS#N?_)`qE*KIw2)yqb?nWBPBldMVLg&H|nd#jDaEGfBcPNbStRTK5e!F@DJ zERg8m+L6UqRcNeo**J1p1f--&mYd>kos8vQ6UB*4(tox^`Pdq;8Y!Qod2h=&H)XQc zb@slX59neZkei69H`4jWO7osu9zmerVHIU=02Lj~7-#4=O7)ny>xVh@lBn$*o@WK( zjAvy)x2|ax+sQC8YW5DFk0BfeTgbmdtEqVyJ;~WK+_3I0NJ1QA2nr)v;~44tS34T+ z+_(jn$v}yMq%4RUZONsgB1%$v;r%(LGfW*_?TdO-( zmo)!^U&2k6Jw-E>CHzJ6?JHRw!`Yx_ch1!%E15?io6Sjgt!l65bOwr-aTK%O z)wn#d3S_Lk7!sz{;dWC=*w8cUOQA@eXlcX^I!OT%L+f7?&lQF~%7W|&+18asBq9E$ z7T(?pjZZm=dk1WLSr&u=$KW^grOcfNfYU)VH1u-HiB7_6P;NZwQxfc>o?zvN)WJCc zC$10wNR**_#2t@`Z{;o~OjC)-hvKs3i#-(62HXT$L+}fgibEqt29^)iS@ltzR&E=W zp{u+C!{yizAD^DEp&(y3X@KagXsI1!*lf`G!FLbMCO=*6jg)w;pVbtXTs9%-xA7#^Im}alIP5YyeoXuv5FBBx4swIz(r=?E?VIu?6 zeCC17$moIQ`Ch|ogqH^jz4{ha(I?lXa^+)zkjc-3B{J)O?GZY^|H;s4Q%+r7n>~8` z-Q81*Bv4@VRo9Ze?pj;{GJriP={vWQYU44&=UpB+$o%sEMbddVve|xbT>ExvtF6{5 z)nV^iwRt;?L{TfT6*VLFo^OW^vqq?0BUX?aK}2Y^v^I%IC1Qsfp<*=@zo*~dpCIn% zKIdHLb6vM-fjlv<#3V=O>*Iu%Hj4=fh|x62t_8Bc7}4>d^Kkh^u+u*7n_($hN+$$r zIwA|xHykhLuK>#4#@D2o$hh27^!NWZi}SUS*{5#F9(r~3^rEZKVnZCd~??KjvmLU z{v>qVg>K%I@%rAV;c_Ee!!2rPvs6sPeluK(b|d->xb)V^HGFt+x~(XsSt!>q8$6T) zWDdZt(w|SMqN_U1jcx!F03a-JHSsPvl4827ekOKnbzs7Hdp$)%?gNWJrbJO!%!_Po z+d?DbhmgA34O#{H@IG9gmG?);Yjc=p-BQ-5B4n=}*fvz=?O4qV}$Qe5{9?|3V^ zry&AY^88jB3j&Q*tN?`lR*E8F4OH$f+Y}E8Z4<`lHkbmJqiyaF_mtiIN0EdJxu^hg%>zJpt<$A9ThLLU1wpFRJt^_ll*eu1PU4ND2(qyDLIBe6^0f>~g3pq;-0SQLoX8MSc z9D#g9_{zTXyS^cvQ8&SJ+&0`sCHNmYp8(>sut2c)h2FF1Nky=V1EHAiHDpnk8`8c9 zFVD{2d8Ta}mtLJ!!eJ?cwenzFQ%4;1`6S=$%a$Lof#V-%+v5 zs%c9Y9+(txgCX4o{B6!zX18Vjfc6vy`Ip95Rrl}ve?Q$6-n;eBZX1AC#@l87BqrCC zO^vrQS70dRnjT3zJ0rk{BZQ7}E(zW`nt9Uf_ z_HQ@4#+w5Y+LOE@X~ZG5x!M!(KZ`T@bB-o-U_se?{J_yD?^J`-QjTT*pMVMLFwZAf z^x;>l$tpX)>~wG961z({+c=GccqBi~?7TSh-eXy>bWvACve~oG*b3~Q5q>RwOwu&b z63tyGLn&;~QT-aJQ$g$=4CWHyrx^87R0k}QEN1xk!HxOW%Gw~wx0dioF;zf?+Mo`c z?%WkJGlYW&ES2b^LRprr9%Co66l@>Ry)9v`h}nQrFplW?Ci6tpA-Tf{8|G;qdkI`6mT;j|Z~0eM1F z-4?(Cyo{=!2Tk~|nySA%Pm>5gyW{^U*HJK<2jq^k}`HItYU5BGlGmn=iKJj15mNYs%MeB7O3~#mR{i8MtV3p!raW?QljNE2nFuT zhb+X5$axw77vM{~d)~6#R2kK{TTTkyJ8KoDr64B}Xh2$sT&&#r7ge$UE_}?$Qrf{a zEzzbB-JrWf$18`sIMJj7mH*ADrT>F9=Mf|Bn)gSd^kzXF!OGrGoj5K6liZRYppsd$J)jr zX0hc!oqiS8N?r;t!4LIH7!*lslIdY9v;ri6K9O0P4+f$Vj2FLl>$cpQhc>@3{s}C# zhgn49`UFP7Bb7Q(xW7bL{ttL`(#IC|5?(n8ik3p?F~?fh&D=6HzaE9@LQ>mb+TKeo?W?f z-{SnS*J8;r=7`VAdj*zqFpsfJauXTPh_)SL2&WS*(tqSBNg0_JDp{b#)|gG$vUFNs zHm)XzZWBrVD}sz{yr1JmuS!~Sr3{%1j3fftHN|zrvp@s5ad!4^VuepsmjvHtR}cDo zgG$H{1n3`#tN|rl=yT@wb)UJk>)omW%fjFK*`SZ_T+i_H1AUr~pX&yL!IB&@pwgWY zozT_h){X*N&E9n|q1+~4vVGp76?YelYw5wYRQ5Ebx8g_!&87Da3eS(FSM@0B?srJi zEuGDZ{9bZ~i2u#Z>J$P9E>JSFR&z-rC4~?NaWaVq4KDS^-%my=!i0@v<4E zy3JzIKCm8|{#1yOaBh8wzrW88P?cFl=+pA}@tvUgd-I!)S1dL1HvqhAM( zz^xb;W8nMSX6$V}*|lNqjWrH`kXz&~&vQ$6@5mahg- z!*s8`a1HOKpEdY34ETtx8C-K#EfNI!3|6N)K)LZNx*|N0x}xn88Y)khhstTXLC0$n zhr2l+hNr%d`XLgT=zA=DFccj{CeF>>Zxo-sF6cF!P7h7XupLpB^v^3Umv!_=d%WZ0 z6E&P7jIVI6fEKUZ)0(iyI?oeEnoH4uAsK6yWhiz;=#o!_ZsEP+zfI{P7LwbA(jsFE#>i^|abXWd*jfEn%;^oL4AGko-gTM0Km!th@F-EzKW= z_qdH2<~sS^mhrfI0@iXV{ADSY!g0j5_Nv>jRMs!h@!oT!2}QTDlte3_2De5yj*;sz z)iyhdD4x4j?Qprs$dQJQ77{DZvX;{3vQ|nwB7Fjqqaap0Ix!hZ2_AdUWo#5FbrATA z#_9xIxDGBD7uW6AJGZ%TEjlk$2YoI)B7YxKPN>oNvdi;}xPyarkkt6?V>P>^uO8H>4H;q`K)6b=8{v1;5rqmF_AFo7r9b(PP6hH@<1BGC z=3rOK6XMeLuvkp8{>(J1WRDrpGc{d!oP^|S1>zApJ;KqQ8xj+T*q5*MFDeg!H1gT? zk48|ii{L*~viOmM|K`0Zx`Yl$pGe&hz)lf1;O9OI8UI3lmB+_*=T=yEDfGZ~D9sa8 zhJr|=Mx9U#^k2Y>DUD6(cTL&9csWPR*s`R*%6y^0FEie=0$YK+n@-OH0GPX`0d!+q z&88XyX`O~7aWc@*(9ePfFFHO&gl+0X^bmJY1;u6gf)O8)Uk3yc<&LivJUuIHs{M66 zwepH7J;!;o1urGFhIL9O^3!Q))Gx2K2{M;#=kgV~#vKV0W; z6E%(4pagtlT61e!BjoLnv>L7Se_x>>Abf*sR+-R3C7bOBd(4pJ6uAYO@r6ryJGqNZQr4Hwt8(vyKmtDl>88xr3Ko{v!|Ov;Q8#nVSWV{P}s&U0HnjpxtH>E}Av zMC6~Jb?SQC38|t49rA0~(hQq+qGL#y_WW2AOMi~MIZ3em3*(V^f5PDkw83_0wtit@ z!3-=1RKL@8@nYELwl5;3G~ax*vwL2llHtr-N%?UzFL^pG^6Q=YJm*n0?kq$zVW%1P zGkI6^T{#Zfq=wOwvcn)F7bDQur}H!|8{W9gs?it z&}W4*i2_ODdQxk5eju+J0c!3v85#Sq&8ula93@+xOI6yxW5|1%Yfe%)jrV)NgWJ#J z?E~(BqPUk@asB}Zqov63P~+YeLmEb`uLNJyY!Q>L&yLiy6w>yp$1UJXq*PIxpiSk| z>kblnJCi*?r^RF_?9pQS*l$lgT3$Mzb!LPW*XMI{{#bssV$!#Fyo{TN*la?end7O- zMvNB)S-Z1e(+btGLElH(SPFE6lDcIy#4DI(HPKg0LIrE|b2hAj*pd#;Zo-21?;nqV zVH8~-NNB%S#x=2TH7XiUzK-(Zb<1vU)B%EgWTv}SD?aOIew)B%tpe7urUXN4_(IEj z1J{D%iD{hiHjkW=SDXHb^PT3KJGbv}@iI71tHwyp8?iou=b{i-X|yR@Tj=g4N`HCi zvZX4MIj(V#t)cHx{)_1~C1pf~N&u=XeP(o8C9}_C*wA0PB31a`&%B$bfBBsLX94<& zZ{ptPdGgj*d2@uUS5j$nN86T3q;kl%JvId9x=;LK9<&Up?9VVaKHS_54WKvR8jJer zFnfC`A;SuER%McrPt*yMBjVT8K*y3fe}SID(t8Nk*UGE~ok>6{PCXi(cc5yaX6md$ zMTaOw3XT4Ka2IwPeI;v8Bq+lt<0(oC&W1Q|`P*AS=zP+T+TTx{G#>;sw8lq7;>Js< z->TXL_+bY@-wGd`GrVZ*xlF|wiB9375satnK>)E=cMz-Ubmv`$i)>JC z`pfFvT{&toZd?i%ogcy|>Xzy_&XSJt`ddqHK5}l~J^Pk!aL`Nty5#skz{}9T#3k<_T@02zuwAJuXTygMcBe4 z!gzBEZ`G$!dfkCDJ#Yb~<))QogJdMlHl-i`Vd5JN=OTEnoZucCP^4;)ZSo~bF;^)( zXa&x^`pwXGMtRm7Un#ampkW0vgcnFO@j-^Y`n^z}Zi6*Zvw>R_2epT+ zkRKu6I_8D19X6FnEBU3N)uV_RB~lg(yRJOOQV44%N)j$EiqM;^!L4MQp-hza$~Qj# zsW(QdT$`wk8H>duKsI&-ln?~yzE@h_0S)l*27gB{dOJvf6!#WRAB`^26OTOVTsw8p1A{Js;rHd3$NwnKmtZYNz`*iclMP=?!^BH zm)BQ1QMzWD(#Lx)^M}6J@36%%W!m|m%D$ZI!69jI+4w^rV7E)m7`k$=8{GI5t4|eU51Y5o z>7C0icWc6pL~PfGMmOPWa!{(~B`3{k^R2|mHp{J6FG)yZmPP`|9x0`a5`X{^Q7#3b zq;7*`&umj_skfz`|GO6g|M7UvD?joJmLSb%n1N~PUKHF4DwM#O9INYGj7OXKFV89MI@~iPRJbHrX7ApNUsBs8)P{q zEcK>ZHT`{Hu*zR|w)M^IME~rElTqM@b$2ET)eoi>HBfu z+DRKgRDQM8vmat>6+SkusR^rST%{HWwEpt-odJwbk&{qLJY<^yJH zoD=guW8wRE{O(+x-kjg(8}$@L)YQJ!uBEnDmIvc};c7yv?Gb5>ay=nAiJ>pr9NSjW zXLZZD*Qh24Dj@tG14tBx;mLvFUqX*lz3V9TR{u|;keH=FSP1@y6YHX~1=dZqi5w;N z-2>pg#+WZNnKJ+C(TiIUAwolMLmE!8&7aq^SQHaWF9-`(qS!c4QFqs4 zvcSDmtAn!^Uo1)(w?}8VOK0&z&xhQm6w)wR!a+&+QAB=l7_mNF5uqQPW71&v;e2+t zmAkB-o?>gr9rFY$COHBMv^6HT<8ufH@Mzg> z9gk#LE`QE%<{91-K@rx?A_EP!`Z>Ek0zd)(za5W5?F}v8pBT#pnwViHDtky4(=`u zBIcBPGcv-P%>QbzD?Mn*vEh)Qtqky99KH{OpUTCu3m>t0rk7TI_;}yoi@@bis+xHm zx&aSPbC+W8w)#UxR&$`#&|tWFdwV8_#kG84OhASj_XeD~X|3#m_nF5Q<4K~`z#5<* zphlR7Me{M8Rp}yS8j#e^fUUbgipf~KYRKr92_2Xj*6EZ~J8!#j8SpugYtDVCI1cP*ZncCm(Ja>n4Re>@mzsR=fwYPgf_bpcHbWO+WsS; z6^40j48jFUwRCj}m#+VE&|1wfZ&q?W)(nmKIu;_2T+LL`hWX+$Zw}qDzWgt%p_wC` z1%CH~*SWSOI`Be2H9oBYVpMC})r@I%tn#9nd9K8NF+-WEb2@aXf=wa5S($;J4n2V) zR~P^2a!J`jI*x|%4Z)HK*3eInkNet@P=r~Q8j0`6(MY>A5H06r#lNLf=JLN!tig)^ zf3O#U>3bO@n_GRv=DUQ3&B7JE-4{=f+9swHHMPSNtST^54OedO^~A_RVk3r!AXRp&rdRpIMUsYQjCv zA8;oaUTh*9xg}zF&W5+$$4Wz@)l*z=Cmuej@Z4xqw8WiXd7y7F+`J~?+q9SKatX?e z8=>P(L4F4p`miXZ1`LEBxbmw=|z9*OaMFX^T(O6{eF(FPXgx? z)8Ad(8nEN{3wHlyy=ym66G@F`M5`B7X6vpTOda5Phw`OXewByg?ep?iKM-O5 zMc=&sWN~6I4iy1A8N!B}9kjub>wAnR_c7GB0w;GY82~Rf8r7;^IfY_>eJ^s+3|ww^ z${X@;`|oD=K}*Q_b9ZjUmYa4@v!RI+mI$zyLIU7$L9QVh7huL?(SqNW!rrS+lTsLKAKLqX_A{xebLf6J`w^3U*a1 zRmniIYS$}(d5L-Q7c`v)xcis}=?j_Pdh|Xd0l3dRwCHb1+&&3<%|0Zg0J3uc;(4l&hF#n%1m<*>~89_v!U4=aG^rs9q8R zcEgz}xs|Nm;iKWXqJ-cGtMJX-O)DH%QzUf6v}jNn2>u)SYN+FN!1u(bNrUTwjJHp% zXr{qg*jww@dG#+Pc)#0!xD?6;E;zQVz;>#=s+Qw2#H$a}*q()7%d^kOY+Wh|)nEEp zqZfD0zp5kj1fI+zOqX#*5fP20_%-`nqe(#foN*g%C&L5_>EE5B&{X96q|fiy?DCps ztN&x_3w`K{K{=ki#+h%ACK_#F81+UfJ}LUB`h(ug%QMSuu(Yo+V?ilL015(W4@*F zzR)v`YGl9D>FUXKqc5=I)XnO1*Kt;8v1jij7=Q%*;w!n*s%P ziEmn?YTpBtO0OnI?v3|Vz-Fce?oVltZ{o=~fGD3*Qbp%}-GBX5B=CrwkqGT0cQJ1? zKQPZF`L1p!RCvd9yCv(tg2peM$91Au-XA)?-@7a?mUeYI;2M58tT)4nVaM2znxyVd zu2@Tj##9{KEwFuy)Z={(VZRg*;;ChxQ12t;+@B5)%DD4`LOUVQ-z9 zU>$Pjd8;X1M+hMG(5JLB6u#(OZX&v~WqnZTm z7PpVs32He;+Gj@`t_ri0yCO18dD=BWF*b zyIf{yf#O}=HxPA7jFYIvs|XaP!M`vGZ+Z04XQG^KnE|@uGZg;88<@UcP4db;;vU<* z)1BUV8|J3LCQa3O8Sv#yfWF#r#I55qs5x~cL6p?RKZ`z&s8 zOInLpm)UK^fUoYgy9ebE31g(S@NJjZ<mm6%(+;vv>S{HkauH& z&EH$Jpw`OM0lMzBPR^6kAYPIGjx{mdb?*4wDL>7t?kuU{0Koz7J=@?=C}pRqb-6WK zQ(Sj^mN6iq=3pe8jz>_Rr{z7te>;)00F4h#m|TnOktus89`BncQ|&?z{4Vv*Ao|nd zsIRI(CiSc;syla_3MEjr8bPcrD50+^?wXaar*T%-bvczCxO=2(IfowBH>mJ7F61#V zjZr-|PFh6Sn`t_y)g>%5xnkCzm04PBnI@L`P+2t=FWrl9ZzJqVp+bkKI>^CP*1A0ZB~v%OYhn2B%uFkc|aFVy3&Y5WZo>5?AiS1d1z{0-l^;N zPIbultEHzUkF+*JV=@W*qP5MCkOqCsDvT75(Cwca%+5mJOXXJp zI|bS9+HY^STV;a=@>Q5=O>^uI0N|Q=;sBfWd;f|eyMU~=?uv=aGR*1r0X8Yq42I1) z)#^vXrOlV~^x|)8`=8^TOqf{f5laiZ>F+okrtaT?u|bwB=im=zDL@h-O6!QtY-bqZ z=_16ZN{nclAeQBov0O#aU!EdXF=jsVJI@Y%8>yOJ)54dHuilWa4|P8u*b*#MMhJa! zd38ZG-b&qK2IOF#o*ILcd5b~bcwvG$b|;@FEmS=*)-Vu4kkZ=*?cq~tfax-C2a>L>UFUiA7VmvI2d;ibC(wAcJ=q)6#{J<-+{ zX0wvpdzb|(B>I(`oV^DQzP+q+9Ud7#>skty*aCqE$`E9EQ5jj?fe+~nPCh0?Pu0dQW*u-v- zZ!&4~cQ5FF6c@P*HyT%4L;pn7zxw4Fd-mll;g)?|LtgqI$FwmZum?)2e4rx8h4v^`ip;B>cV-}cuYI=@u$*}7?c2S0u{ z>r1#srP4$=o+?Z@8cz3B{(a$*dHdqJ{L^2xM!`-SGJm6gMrchbs_3}B#2pR`?uy@QQZ1cwqip=)E2_siyy#T4+^WOiqXY`XP@|4%AH7M zc+4iBK3MM^T)QknNxJ#-t11xE9`?3r*{CXN3YSMUGZ|kASwxwFy37+Qrdh>>sYCi) z4v$OjnLkbuagFu7NTZ>>7CCIq%FpRVjRXr|J$u9lA0H97)Si6v+VTHIe{}e!Tp?q` zxkIkMyf1JRU_eHfX@0O@nhv+hVxjlml;mtcBqmlK0Yln3%QRZ8%<8-?>uLSF&p{$_ zrcW$ywt7Dv;N@&v)Oe=WJu1Ao%Iw|7xPG`G@1OPE<;Un&v`qO0 z-X2She-P})m9J)T{OoJA^{fW7tSLBnwr%d6vH+jNU^gmjVpV_+mh1%?n1lO%_>%|-E{NKd6xo{ z`#MO4`NK0H+!VYJ3YVjd#Y%a^G{3F%7*r~khzhaYR9crc_7EKn!jw)XRV>c|liDw_ z_Uxc}#cGF$isO7__g>?B=UFWOi9EXAsuOBo?8HHaS`m*}P@xnQv=mL)HHV|=3-pEb zM8}@t&XKUpimBA*aNQ}3YGUHm==nVnACO^c?=P*(&a-XC!7j{`LO??TX7*MY)7(s|`h>u{75UfWSlA8Ri5~Kpaq+I}i;hL$H>}f!MO$k2t#Hcwx;OhxME9h!`C06+U z3Qp?*e2b6k;6Y|@UU+zZ8WWfG5K-OnaF-lAz7%bH+EfnKXCxC%7L$ECn}u7$2-YlEt>|sZiV_F7^Fc_<#(<+aYCGrbv8}Agm*kGW=$uko6x1#w$f8ob;+}v z1BIvQxQc-V38kVdk=FMcuhP{0{ddbfA8be6y;drI;36G5AA&PB>gk;r5Lp*Fi@%}L z)r(-Emt?qjD^WL6IssEqW*BNkQOzfakeaWl%;I+2cCP^gL)`bjaD}|l#6GXVj7Yqd zCGP}nOwGgT9NZ76IVA zQBs~=bw*wWVx9uL4)?q8?gn@ah#{J0xL7o9qMiFrUoLqY^`$NDt9Pl-_fIS7?*hpM znst-ltH|FT1fKz0Lk+FFp+sZ{a(-fEU*Kw^XA?FYDjBdlCp-$5!i|f#Y(H*n(c3?s1CdP^zk*r50Vun_89>2P4c{}|v4LlSa8{W7&VOJuhx9bmT!qdlD zn&fGcTp;s3kdrq0N>Dde-N`p_H^#+`Ac)u#1?Zp*Aa7_(3V2)%37A`B$P$95XCxL2 z4N|_6n=^A_f1GGf8VFVyu(z?`uO8#uZLPCl4DyeQY|4!E3tsAnsH%Hqv?Ww&vF@VhqyTdg7Ws0N|yT~||Gp3K4@a_2KbYD5ch8(NI& zb|HS=nLFEFf#pNuQY#+DrtnVBZXdgi0D6X9iEyr`nyUWRxnA_A@RT({7vNg7elp~> zUAM7-{ev-f4Z-v*IbL}4rRjf9`V4>nHS}5Ttlc-Qi9Jzj3q6Tq zDE$@ibx9#a3i)tE^Vfl?XT zob$W1rhfI_5gYrLPVNwu8Ks3xU3{21%t(pAeWL6{q8dl~t;FPHf1E}N*_HLmb%-M( zvwmS;LIxd@-r8W2vpfc(K3yzyvvlQNri?Vj%iGJ9nn9c8@a?IT-YLL|z3)*MzyIh2 z);m^J?V6vgykm6S*NS4<*dxN+Re#43M?1*Gq2HTiMTfg`S98>-fq+Q@@$h^YxVwvf z`1v`bX06?7686#N&cAMudxO`{_FQO;DldF@;Nbt&YMJE9JLa`gE|UH;Clo`()TW1e z%Dwpmwlm6wWaMpJoPKXZEB>C-6#D=z+P=4i7jPo2nz7?|a+=CW3qV9-j{mfxRy@6CLQ^MXQu z-y$gy+carehKxX97P(Ph2>!EE%hwcReeLCl&Gc4{_zcBua`=ce!PjD%vwfUP zjOlprwobAMBY@FY!#;+#_FBFyWzCiA#>Mo?=$c@jVoW2o}#OeElY>JwgeiBDFHaMUb5hbEoyGTjxkvf>u~kDrq;t^QqjrP*$-)^ z^aQ$5*SHe03ZL$2?n&9AN2VpF>efitTz zUxn`Y%(18Qy2LQ6ski5)&M8e{Ia$pEp@Z9JiWH=Y*kc@Q0SorX?~-i7Y5MFI|HVG& z3tasyuBS-Dc_7wX4H3%M`KhGI1ysCZ81Zpjq`rLyWEU=_uNyL%3EmwkJR%g(x@tD>-`p$dD9rC2YhxgA(Z#P+aSZ+=PoNKM;XBG~(K(zFM0nuHCgBwH;cDW-{J|(lQ(taIf z0$Ila!k2eQJLz}EodLxauKU~gcKNPoZH_Fd2Z6vr{3%o1Ev0#a+dwzV*{RuAtchQw7W0hH{K=l3)e(9?-bn5$h%s? zw;8xULM#9eni}_N4rPPxlQSlAa1~SJp*IaS1H?+(bwkVXC3pTBp?aSsnTPn_4`yI1 zmDSa4+PBl-y3;MWj8KWzf8*zRyY+~hF1PPre&+3J6#jltxT`Y_rM_-3IZ@|B_T1bK zAsn$CZ;iq!g__Pj8q0ZsfHAvA+McWycIKhc7vV)Z?U$9^9KUTQJLt2P5~vyFPtBg(b1-YDhmf-v{!#b7 zTD^Yr#R(QL2u)1SsUQ?8ARR8{VZ)tSc}s)x0U7Q+60QC_^{|d}Z9+A$re+A`LL2!m zZmf&m((0GU-Xp=XEB&P8d2=f7`pXNS7PrrrYxjQ~wU@*=;x{}`Mvq1~s5meX?F4vP z6vVg7dd>H3LXREnGv-<_r`K7-bc(X_wG3a~dtikO0f7k@BXI3Ci@(9z#O;>2=r{oz zgM-8@0ZYqn(PjXt7~RHRQLa_*P;g({8aJ~7eA_DhS}O(lKrJRsjUN6Jrbi04_G!$@ zG+b!OT;g6fLocZF314ntg?Eqj@Sakl?%qQWpcZ*qEqYf7cflJ;WXZJn1yi%hT@bEp zJ+kr8CeC}TNieKBMjj&~7TRM-k$`@|lFAu6RJ!=ev5kgZ=(^n43WBbnV6C80%LqX04SbSHvWg^3&)^6W#<& z;|QE5a=ntZt+}X`r0V*+vJ+e?IcA2urn#{r*?M?8KCx)XBqt;wp#03fN^CIjV~lN| zk@*+_o#wTXl|HOkRQijLt$)0j`Byq4?*K2jP}&^5VomLSPf!;z(c4~8i!5tk2R+Pm z0x*v$O&*m8ENA75;e=}LF;{H*qdLnhsSEgVbETICBM4^qzr12=vR8$g*qoC#gZ((# z>~2oxHc|~Au7|qg3u+mS`IJY~fpBX>pDFV8Z$Ld+v47e?lwRnjf;5|WI9Uq@pm)*= zYVLx*GE#6&wkm_`7Yhw9^4L6nj@xYnR&EHaa4~)|XFY^8xx!N5Gy{@x=$l(V+xG?e z1#Dq!5+?6}?Mu!=fotARUU~gqy7cm$q`365M|!;h|}kGH$cuI0d3Llp780x91@`+djlSjEN9t_+8a zk32u4m4F&Tnl~G|j>=S(b$C6uT;!|~4FrNL5$2VFN1w*NB*{CFB>%jgUn>09OZ_3l z#y;*BqFSabWtBkve&gce{42MUAB|NM8ZO6Ctn1ItBXb7>D#0Nc=>WBh4F&07mC!Y# z&P2n$7Xj%>wLl8=-T~lu+}0R474EwRFKub*NShSNa%oH9@9FT`)<|CDKnC#M>GioG z6M1>?W@XnD0<`|L5i=DW%$*ZyZC!y=+*l3(OCWt$_E^5~AwePKVE?EClMu0^;SWy0 zzGkXTn_J;y;nSWa2dzh9^5nlUAjM4%q|KpY^HKd?h4($1>L;`^a&inkU4@f8&aYpx z4KN*PYQ0|l??c{q@-*JZ=QyFA$Y(n~{+*4J$;-PTo(z<26E>Qv4M!9rB#@=bzynQa z*#ltA*ga4*O8A@vE)-yKHaEme%<2dTHdMgYU zo%=K02fEuXyln&vqb$n^&8p?=1ooBBk%RX_G-ZME7!lTPT;2*;e~1vYliS_paL~mI zIlbYpRm_XUk4DENa&kzl?+IEocrPqE3IEc|O5drasRfEHJ=n~fg36Lq zj*Dh7f*AFn>4U=79dZt2To`b>h;G5bI-Wsxpzge&*uvhSa{Fy4P`UQbeZ44{;_cnI zA{Lk3_SWs>Zn%qwwBbq11?YU$6V=abfh^>CJgK_edaHFDwZ44F)sR_7twit)t@i*= z4=_n>QAr!K@5P zFoQ0$EZctS@8%vHMu%edsFuxeoulEsSAz>qBID2bT(7yd+uwdGk}Vdlcp1dbeTHgg z)br;Nm&}VMI!5TWg!kH5S7XLEAGJy-;A4sUsPZ#duM(IoPc#6W!R+6UWre+H`Pzh{=ZM3E zQk@p5Yw)Mc054 z=k6fvsTo3iR=}UP(i{oSW2Lp#Da)z$v~J+al_rywSQ48Au-}J{BjTP&Errs%@c8h_ znIW&~V$>^F_JKCpX@xHfD|pp^d}3k{uDa%l5bESE1m_97g{Bb_HEO0N!{#ydvHNVEu3f4(` zg_tgOJ=g#$esMVn2qGf^8$F@ms0*%xF-L5CR^9E{(8rBDL;o0RcDT!yS?udfvhFv1 zvtQJ?dq->Mm7|xMEeZ@i_cVt(Nwr0l!BEZh-WzUrIlj}}W#if2bq-$|;N?Uz5R zj0Q`+h+YW2s?o~zp~(y$jD=G$ul~{4Dm-Xqz>R6(_AJffZKj+|X{1GT{KJ5jlYZ5w zdlfi%x#q8IlKdVc1wEE{+TVg{q}6Q)uin8W(O5Y@y83tl%uu$-a?upcoc$u`-Oq-gul1~; z?S%v%L&SP-@hokguxm2M2hy#u?OFO=a}Wes`7z!ya@g{2L6~-4$AHM8O(`c9$NPSuGCi_|QPA2CSkF0Irf?1??R@3T>&1?UOEY8KlVPjd zXC2LtEvC7qEe#$AUlukRfBcnCC*^c#e^=|DMt>XiVY?1?h+(x45z{)(Smf%BRqo8Q z2`H=HTKcW?WeddPt9j4ehdkn-*R&q4$gnQv9IP*Td#3@>%PTKbI|p_XRZ|mUkdOeSy1tY$vdUy5bP|O{;n+I8gDMKx%be z_X3v)@{_Q@%=>}|;ZNWfUJSi8`yyg&>eUn6Kw}h}RDo2G+v!lpXWiAswV7{2qbF&a z?FWoN&d$y`J;Gt{uD{`qD6D+5SYwfT?aZi95&yI7xi6Q-AAd;tAeWy2IA8^^b{1PB z<6!UMHAjCEdLqSOlo@ z<(CG+w(6o57d5zNUk68KL^FX%jH8iV+gA-9Pnrx#$;)?{vObB$cjGTTOs;;da4G(- zxF4_f!r%fQ^;}l(T0ja;#;hSU9Rk_c-VBYZg(hWn=IGop&u~;i5XWOWRN9;BY?QHe znOq$?DvilXi4&i!&81gI;tUXWSA%ZKhCH8=NV!QaW&WI%TP+H|OUp;YWvM;`Amz{w z99p?LD6n)DQPAoc&E1Muo}xF59ua`8snj?sj;^hseCmA&`I+N{`V4?n5!>{n7GTb- zi=5Z~Q>GirH%u(W1K`uHWm^ZH$iQ4B$;|y9P3Qi|^c(koor`pkV{~*jl$;My3XPl( z!yG~mGb6{5Q=dxZlrWTI4l~=#a+t$rRB|@w(dJZgXfn#NIey>W_x=50f55iux?ZQp z>-lV~^{bY6p(J{}BT31*EcYJr;F+cSFFqbxl?%to`6g?qi-b(Fz;H5h%p}-nab!M) z+j!}eV&9N!`_2>wx)uE;%*y;iX(~)vsvEpmA)cg?Ky11NNf(y8amN3WL{6RRE;YhBu(|f``qAy4E4It9vC)S5KTK0iEn_xy9SYzgmg;mwZ~3)oEaNzVi38$z9Z`T z+c|@3wr$=p?5Eqq=ONG8=BZQ~k8`8Q-&z%W0g>*JmHMpf@yu0L-svY6OHoN{Ua2J( zjSP^hof-#B^^o3lvvZbN$}B3$6~rEZg#u&RezpL9Wtn{esoMb178Tjylm4Y;`vYtC zMYO17k<|hO&3!zqf$cIYucv8TxIefpXR2>{ewxmpB~>wjT>o7ogXhH?nz-AZ;Xwlh ziX`&7^^>-y$091A&!&+h851=F=x(7>yosV~ykxXr)~LQ>5e$T%Aa*g~vr~7ZT}agq z-{ZbPv3xA#ct^abChVd&GKp`}-oR#}?!Csxu4?oX!f~_mHS%Bo$xcIAwK~M)csJCQ zJl)cA_StioMJ|S>3GQ;)l=qaDBB501L#4&Ej&Vh_a3^r600ARlMyvY0Bc|X)DylVV zJ<90H*fF5(v;K(wcW0I7W*=kvWwD)QPr&d5fEa9Ob3BLk214gsA_Ic9_Xg?%Y#)@r zSJG$;QyV;Grt6Qql-%N8>>?}eRa0Awxar(TAA;Ylsm;{8(C(&WuXFRhT>kkAAyL{k z!{+746_GMqUp+^ZVVyv2qdA=2d{gKV5RLm!XBOnZXdjEr9A$` zHmX%3c`)R%$It5*dn?YHVW?_@+sk+;Av7?I0$#`oSPFhO#~&R3y-LwI1ooMXK0;gW zMP1QKh|`KnO$Hl>%B>CCacjkJjC)DyO@r6~h#Jk{7?k3_q zrH)Y5sa`C;0Gh(ZikJBenVvPG>iu(#cr^B6N=_VV31(3Lmo zOATX(el$|&`Xp=Uv{u{Kk}o z=_WMf708WrXWcC)(KYCdUz_VgeX%VM0jOaynU+SH--yWxee~bXtBT8+V2RzYi0)Tk zD-Cx?4tN^gxH6XE9xM5IlxW$eTt-%_F@bpgxfTy6x)rB8Q-KXON0&)Xgi@sr3C1N!iveHIhXRO=&X1%utvf?~KB|y@C zu>f{RufJo_Fy`J^7gm*n(Qcvc&hhJxw>rvk3s=KexGMrzp2k=xHO9b`GlWjVjRP`} zgEV#ACW_2LBI(rEWhVJ@#*T{1%e8eR;?@R`SFd+FyBNsA)qc?o?eis{T+%{vzJjSG zlDBj%Eo|5=f^yArDbHy)>2kk}k>qVz1YD+O85ice;v%N`&_WQe%f?X5t2#$^KFoXz0QccB2T;Boc)c_T7D$vn$Z#|{QDa9A) z`_$dTkWCP}GfH*52X1uw&+*w`lR4oJ=JfJEF+32sA#7k=9YyGL+;#x)S5EEE20JXJ zcF`@IX$dBcZ2RnmZP&R1MCKNFXXKB3?WoX*WqX92183uV)GA2v?`Bl{k(1s5`av^F zas=0aS2mAOZdNe#(h9zIX3W>pa>h}H`A^$QR8O& z)%CycO1#yJ6~0h|T*yy{sD^ScAnFZnKqI4mBrJZip1Ze|r98Ua=oxvcIAqlyp>Ob@ zcI*(ejsM$=TPI|eexegAc%%*}ebajMInvjjI}lh=K`<~6i=`DKH1I~*vNE#UgD|x3 zYu#A7T(5w%KJjkW4RR9omvLvC>DY9NYWCCFnYo}?pv)Sx861gLGy!cV<|GzgzRdi1 z^(pvdo4Wf??b?B^y7BZw(&DUKM;y-jDHzj4kQBvB;|A8?q0x$F9*KC!k<++d>+)=4 z8|ym2r9;u*(=j{AX-dae?)6!#>?DlgFPh{tzz$OR_{#XMAfWIuyw37yjZu1QJKL~D zRl8-R`g{G4sdE2+K(Y0QZG9%@Z4SCIojbVet33S!z4Xl z-Pm-S%qU$WQ}fczI`P_s8rLqAV}X2H&BG~*cijzE^$-L>YU-4|=9PV;?Ve-%{fB9q z&#z=p*b29=%1Fd@NOSXRBzcQ|C`fXrV@X00AeCirWW@#qj$WGHaCSwUP%lwXo6IuD zHElp*C^6E2TW+Qz{CDO=-@GsYTL@jH_J|^Z-vd}v0$__k&lC?%t(6+02pH~jqG}9J z0fqCr^VtA`%jANyDqqArT=U|jX;sZ<+tk>Ir`IKIZ#TqWHDCHKrLdKwsQAi?_MLdd8edqaXo@8jbd9kUdp`S=>_^6YI=MP1qIsl zs3P3()2cWP#k<22=*}gxZe!c|SEacsU_Eom4L@QvG5{Aw*&n6osII@utl#u?1zUY7dC)o9wdk#fU`gz>zi}0s8NG%%xvt4sG7EDm8vQ$NX32a+&(H~T3 ze5-_ic~bvmwG21%wCrgO1y|H8pRI2Y-Kj!-KwA^EstQ;1E{g45){}^V8(QWBCTylt z)9q?E7AAA3=P1sp6b~^MD-*dX?Jr@CFCHG0jcPw?QF2O$ekJkEqaOg|iS=|EOiP5j zWcvRJ98}-u;<5W@JGJ>8E8SBV(vYz=TA_AZCgcJpy4cqYbzY83|AXOdQ@-TQE$+?oo7?dfzH%+OkPZCNq z7_SgdKKlE4gFo&>YW$~}sG~DSe5ItBOP0wafB+`CHC;N~Cv#3bCX^8W+ zXhpWyAUS$~^~V<(-0pA1BMNNgbn-np$|uQuUfo__`g7dSs0S=E_S&&&Org-g1&KOu zwmW4a>!1z3$*hA!O6=I9HG@h=6_ybJvvsyH@qYVUKY#tiA%DUH-&A`T?=NuF2zS?`4AwAZ}ckPD#v zoNJw>;ww{+9l}i*2s@aZvvQ@_+GK$OAC8`BJjplceJ@-3S*LC4=Tng{ten&$9v~l` z@LepH<}r7Wn4qJJC~I^HI>~P3-rxJoOzE@#5woU6v)kIvlCg26ICohlY;b-ixCN4D z0ND(hw~a=#T89DXd1Mn{fC626MMClhwzX5MVM4|L^_AJimkJ!m7su;~kYkb$V`^p` z&z^)-%YuYNE&+f2y$XMoWXEZ>+C1V6NLkxSe>JK8;LWnbkvr&b+Ol`UnBiBOoqOEO z(Ao`>0!4%1o9dLP&S34IC1{BTvNGpD@U9tJ9%0{|pP{Kwmz2nL@;J(I%J^ZP&n=+P zDY#~8cw;^^(IEuj3Y-l)tu5WF=*Iu#>63c*qjuiCkki{enG#Do^VhN5x%r<1 zGJiS1LaQI6s}qa|5s}m^Mf#(+uOL{Xa~hv*rmE}6XqtH@pVJ#tSaEQcTzXP=ZYgPh zU`wxSgE*h>v8DW$W5!TQ*lYM`Gh4ZLA6P6H#GK+HGC%w+$(;Xl;%2B#D_->O{t*7^ zTr;>GGIxJZrL=d|Y~`7eTkuIpWkBkZIMq#pDA&d#ckKc+#y%NSkdu45M_FA&*0z)r zQZ+ju)8M!qjp%+zVs#I1{&46z&CrWPm#0x4Pq}PN?*8Kthbd96=MmJ_8ujBi%lFK) zf-n(>A)oOscGt|48)kH83jTW_zr?K7qXEj}dVH(zV4`uisY@1OvVApr*o)(mH?jd9 ztJ-v)sF=)ddU>&Se2th9?j4*8JVI?x0j1$pF@>Y=b_RN3V+Ki(DbGE2(l|iQQvKh% zH;o-}%@F7OS3N)yjeF5@e>iTuvhNV*AWISqM)Z7{4NKeU2zztrq>yG;K}_Dg=>?mT zoM$M_)89(*qOY(sR-s6%gKi z7B8n6#0w-yWNoq0-|7Hh`v0fAoeail+|5dl~heSUJq|d*D2rNJi<`sXq`WKlO4Sn^qUK`^r0S=I(G@cxK-OgYom#F<2x}|HI6pQ zdr7MF)&po7doD$FcJ9Vp17}w4vE+JdMPZq}(S_fcgFekj`M0R2#@OcvMT290>yCQV z#JRs$6kTa_=FZF=m!Do5?IeNlu%((@M0Cd35ECtQyq;7&I$O=ua(4U$`8@@X1=49O z!q1alaarx$Cs4JKdd@R}T9zo+qk~mPde3CYy*_Q|yK}VkBKaxStO(|tX0}pA+OC;$ z@Xc{tNSz%>OV;%@b+~4@24fTQQ=Ib}UmJp=&bE5$Kf6EoZ{J`UQ3$y0^n5rw>+@SI z8Tzm_CiMxv+e4}kI3pTl$Y?*{j;!mm79Yb1C~e{@}Wg0!Iw8+QgTbsQWB;eeCk0_=~o=NrXk2^^9_rgkn7Tu1Aj_ zGM$P%>VD#+{o(DN+Jb7|Q2)YEGKB2ki4Iw_gE4`XefF$Ot4q1drsLxJz@~BcG#$kD z0>ag{;N%h~q*Kr&q`{ufC;F(o>Q+P7JrhvsliS)Q+z0C~faFj9#C0uaZqEG@nt*xY znFDilKgJR{aL!l(L)~&=;++-N?4UoF9$^QY@`XtVKq+c?OSLj#T}h*4WWN6+LC&k` z^mSlWVgR^g^vwuhZoG5t`b%Z+pEWZ!cQ6Z(w&^>E@e;{7$0E>O$F6F0e&lYU>2nKo z970MwoB44)6l=t|eKU*dLqIG84v&Lmlq=MyW~b$4^=Y;zg!kPK?CLOHHf9Nr1rM4t z29xAUu|xRt)fg3)%;;buU^xu9ACGLrF9p;P43C!%6?-~Z7$=oF3B(nx(CeO-+bHtq z;+urrdSml^zQO3vOK_^uLJaCm$*dR2*x9N&fzp&5nMVr` zZMBI4t7yjjxl%(hU72-6TNtoIfvpZWs?=)7os(#^_UvEqb7blOzTn;S-5%>NXfj*} zn>Kmk!Tz^_v(yn%u?gJGU3cj|t(L?Kg1ox!BkFdE0HAr#RD7lW5Sq!Xx5NI%7tq0%+JSKB#j+3ck_f8$!knuy_u>Z^DU50c(*6J1O z_F|x9ZC+ZKTv0qY^XG{7ArASb*DnNLxMyadgH5}b-2Z!PXnc2S!;hn#ITL4(&=DJ%rtaH zb*Aq+UZyuZ-jRD0LZ670qb{w%;ZhkSb=zwvj2#UWm!Wlt94gxja2~fC-XAV^qsi@Y zcA`(%!)p3`f8)MKysjSxCS>IR)36e7U?x`95UUdQmJJ>H|0)%0K5U6FKun1*GB4-; z#-Q1uw%7X0pyZD;=UNnp-f&+Sw{gcZz$zIVFrUea?&7K_>~b}%v^i%1ESC_((9OOX zGGv;zTx^PLFl%#G_9Sh`k-9lm{;TKh$M4w~KfKgxG<9lxR(lyI* zUSbnwuL}12CqVU9MAzKCki-u|0r~XRb=GR$qdeddTGupg6ILUtFmtW?&gpeaq}03F zUB!i^i3dy^%*`);5+haN95VeaL(@Q&oIJA0+~?wpqN)BJThTBqFIMycY3s%-tizg? zqoKw$(1ZC$;`$Pa7^Sx!Tuo21DLx0W!lHw`QxdbISs7e+jw_C zQ~|_xsdzdee8~m#!za&?tXI=WD*}2wnB8hzy}Y>2aSeq}jCJka z98PsxzWmGtGJNF~jZdL2d-mZ9cf(pwVco!r=k>+M`=$eYUULdbc9~yD*pgmLb`~<>{^K^U-qE zUrzYgI<7OD?>mHY$luTWoJxFLSwic%7HR%q2yW?JkmF4p>Y|EkMFQYhnlHs@4sSG_@OZN6WaC9fFk-X^H0gB_vZSUN9 ztRZ3;^c;|TSeOB$HC;+lfY{I&FRs8V0l+fu*onRwIDr{{U zJj#nE_xwk|AXY6l0Ap9MKeXvnIOdvTxp~_3UY&4jq)la18~|%mb5N|uaoPk&sWon?AAjkM1$h9)lPjmYTuPyAfwBZO>n%9 zriC}x6i`48(WtD3(601qUq~hODZ>f-97 zq-l&f!#WA8Io14uM}fXe>Nc|)(eD2HH8)-MRO*GH7gl3WcNj>}o93`4>r|SYe?Gfx zhdL_hqp~sKsAO(e!CteUyo`N;k@u7vDEr=pF~07Pkto?kc^KQeoey=A7^$2q<5AJX7tRh8~kPdxX{n% z#z_b7_pej+z8qbK)HgjHP!;iSf6Mlk^YXSxt(nAnpu8W2YoGnifPdjp2TzD9fw8PPGeN#1mY#5Vc*J_;% zv0^Twps)$knzVHPt!qeUtBjPa8GD}DzvYtG5U&te!=jijRZVA&&j@mud1g?7$~eoW z8Y?3EudZt$#DqdbX~Vm(uL@T)8|7B!wXXoI(%@EI#|H89?z`Z?+6-HW+t3*KRb;Lk&1Hw5KNo@IG*#f2U*6uGTX zQyt!>VCaO?QV3lrC#)dwt(0Gai%l==Fs5xuO5l4fHY7!yl8uAG_Z_b|?Gq+c~ib^SLkf@yagA z@h z{@b=5gRdgznJ@P~mv>kJ=&@w<#ag<<%0z+5`BTPbe7*|sP)bvsF(`C(0>2asWa}X4 z5tGCVfmic21l^slhDxBk-cZDNXX2u`^j;qM1lXq4YqSo3t|?(N#-N5HBV)gi=rt3} zOv0lKNn?|DjO7*T80+`sK9>YLH0_@Xh=0YUH@`iAVcJqsI5CT$d*_dE*K`Q+d@10r z2}tY=FALyTud%NwuiPsp4-qO#tLmR>2(BO_?yr3L;rTI`l-28g?{LxSH`N#3BA(BL z#`-k-=48}X)zY9)5HfP5WHQ01zs=@{_Yt3;BU=tGU21HprA3lCBp685x5NFPSMI(O zbSNDXLBBEt2xh}hkx2)hGlzHwo>7Qg1vxWxN98z7Bo-FUZ*Au2P(m2AtLqL(NGi&) zMPqJ^!kyJ8Bx}=ZQ<~;M7Xad@*!*;FIL;ern$xQ`Dvtcud}r03erZD|tlazQ?9ULx zLZFB{6i`QsBKStVY-vMk$-E!F&wJ8+j`?|lPdQ+=+FSyM@tM000iAzPCK+S*kyF_U zJ*S^$mE%NPj+7|)vWC#s)WxcY4!ADRAA>2XJ!7LL4y0PFTKXs^lDQ0I-RB&JFPh{4 z6DMytOdM+OooV_(kI0QAKp=-Tl+7u6r`OuaK~geYj421|{B9VTh2FM)Q^a(vu0Q*PR5HJ|<)YmsI{H321By;rdYb3Hg?cvs?x-KvPqZ`>JA{&Nj+ zk-|wwRE=%&@|LpAn6(Xcfnb-dIw@3*ot^H}3aO;#U`y;{-MYpc|B+^ZLfn9<0EOIE z=`TqB)V3ot+=wY1-yHN#bkC5FOH530qPIdh?zre>HZgavy97I!w^Y0<8R{KfGzy7? z+C}r*h)WcM+7z5OkYE>P-M|zw((koTOKp=^U48ffU}!eHby}F%wlK{(ZFTiUYs6p= zVt4@{k?32Jr{KRkfTYsA#PCa=k+pT>=! z`uTPDg&^8EEu*kwc4kU--71P;sLddC5r2=_=W2#&i5O_QDXGre1v++(ckSeMp=?}r z(48VS#_RAP=iZn#r+=vXR)IxB{qW_=#y^MElfCE4E2B%z6)zW_>2R^2UU=Hn@r*bA z3Kth|)Qd~UE+S2iN#tdd9r1N=1vLmHk0{N*w7EVBTR+dk3*8L8psoSfZRo-tuMH>Dr* zdMmed*3=|YDswc~pWVGv``9@oknFu8@mobH@KfKh1RIw(RvQ`K(gI>Cbh$@KXj!rV zj%JM(E^Ro6YM5RC#%*i(Uw@DwPA$25!q;v_UY|iD>m;WYslNdo?5wM3d15#ieC^9n zEAR`7%Q!0bIXfDUm9a_z-Mb)4$#5)JkKBVl^>OCH3)@aM{M4_V&bDR0n0Y|QzN}b{ z)jnxapq=fshQe*DM2*hb{ArvpUy=acwXFq>YS(zH0?q9k8S#xNzU&RpU?5MTlm82F z)ANgPJ}e@olR|nZ>u^F|sqS!vElRdS%)S0kDD|ZZtAwQ@A={@47t@??aQ?BSD7AL&_tiMCVWpe21BO|rW)JHc2Mx)?@Mu&N=z z)pt@MXY%gJ1qPWiq#8WysK{=_1tN}=17`~?5~e>iJe{)+NlVEnrg|YizF|v*+VWg& z__*-o&2{@#FMene%i0pYjbk#I2*MC#b#c(;Db}S%qP!%F31&1oXRWGB4ElG5Py!S7 zM_HaP1zxx`Yy7&8Qyw*TNibSN(gz$-_LDlPxj8J@Q(i)(?1=)eXq)Z8mxege}d6)r65L2;yV5J)$&Kp)W0s_UMDtnJ?WkM(F_+ca*#k z2?90`+uj|HbK8nJ8rAISjCja|FhD8<3WDOh$S$;%BZ;g^_FYg|)>bF&36~U=jLijB zBOKHU0M`32p`PUmzjG>E@7>KpJw`PC@!N-~HNeWm(7MP!+(HbYXwx?zejg)mPwZ7b zu`>Q|S#NkvtHYHyxfHoqJD%))m zsO9;1vuLY*`r18+RP({l(@yU=rZkXGb;9=eVNUrSz{`50Okw4S%c-mzMl*_@KiNTA zkfA##v(J|SxzSe82qed0zkEZ^09`kupcg=B{B#wX?$2|@0_0U1D&wE+W=3gr}hH^BH*H3+Nz5l({MU%UJ-6dnhdiWcFq@jsT2 zCzKPmq6A{RUNC@%B<$vGiC3`A8#Q4=M$$}mGG7U;5&VSX+#r2uR`1OvrA&()?D?AY zY*NkRZGwYtBNHC2^ZMOwG)*vf&3M7GG1n1p&!}a>VUC+HU%%ul58eexB*2y=lAidZ zS2M?+o~CuZ`4n#vbZYRlruC+_|r- zlGjAUC@3tedsmFiW&?3F-0{yK)%%5=j_~kV_6IGAn4#4zEiR)S^VtP6WJz4dOmW8Dh^x%m06v3zbB%}X1NtbLU=)_nevy<*Ut3`&dQ)gj8CXFga)ReE>S@W>9e^I zm9ud3gncfH%Kw&n3nGF;wZ}@7nRidTib|v&_6R#yZ?7=@FI`eFUrNkSpv&w#4Y@db`-VUG!qkKFU3h;=k}cAVyX*N{>391jYDV0s^Zq zQ)jyQ;79sg<(g@B<_4!-U`z3+*oZQKhKz1)ft)tVWxfv?kxNZCTz$#)|9dO@H{rXw zvO8|YEWzn*yJuFbE`y?WHAZQ?CGmFVS_X36Sr%4TXT#+LP}}vdy7Kudq|8(}erQ@R zeXY?c;_6b#A~UEeX3cp>@5GnidjcM;Z`pbF!$Bd1LSX$fzAl3yMe|mu=+f%Cs*;Am z3?ZXbgP_#E&u$NT#0PlawX4afyPH$`CZpp~ym%oOxH2Q}b_@TT>q_mv-^HEubm+3N z4oh=vMn)55+MxK>lBrdL_Q}k*I$pQ+leH30^RrqQ!600Rr=@;j)U5_el*Qn=>f;L_edvkCP_-Nv!K#xs5Vi9*!=N- zWvYh0?sGj4Xf0U%j*tD;5&I`}GwSbY&fn(Wt~-bRPHo=-?Q<3Eb8)2ixqQHpkGA|a zrA^4GJY61Aa~6lXKV#ib38&U1fBg}A8uNb^qEttOwGCI_<&1y=|Bhb z5FZcZhcMo{e1a`|)0$peo>{RRNwuSqygfLll!{-@`v1-_k2iI-8Cgl2*fC?_bv;GP zq*KJ4MrrzH^9ev+ORq!-8Sf-sODF3k{GatugU8p;!0$1_W`741R~}?n>AlE((TDTB zHI82U(+!t6y#W*W61MT@16L~F88InZi-z!{FQe`5!C>1wdv{4Q-yr`R!Ip7az64bf zaspxAIx{@#t_s>R*jNSEW_mj&>-p#JIj^rKDM((6fMz2~7aK~MiBX-}zBAbqFaW#}~(~T8cAwcBLR?P3~s3h%#l> zLkJOIMnR9t55OFdL!BE;p@LSy2t^~$ax0iB4k6R}N!|g#5t0EaWDa`|fPd!_n$Pz< zT^-;niblMC16@8Ly@UOw};MLn`P zotJp3S)EpQY&nLQX>0M^$+LpBOov78qKcam?`~9WW!YL6p8pzFPTZ*qGuzfw4>!u- zILbj>j{zm*FXyW~aGsopD@X7KH&kgKyR|d@zNH1(9R6fs-lLTJaQmIjyc*iOCmW_f zxIpn=4wcGS#?h~R{vQ9u_KS5ML^1m2R|@PRbaH@hY|4@r5G=`YWZrX|Zo;3WrzFnl z?GJ6FNGI&-;JHrg1M^koujCy8@=!sq%_sc$gllR z_uSu#TZ{7I7@8W07SN|-rQv_eL%xGMB}*%qrOe1?sALl!-@hwshtYRpYD5iBWJe7q z&Q{MQnodpnt6RZ&2RBW8CdYj z#2DEGM-Qz{b9T;8zH~tZ=h8)q>1CG@94%o4{xmSfXtHUgahGBLbW#z1pFeM4SZT9T~4C8GkiTD9#FO9NN@p=>%W;wid z!umaHxU^^}f2nJL-nO^$l-|nRX{-S2gRFswI5!w}S{@>oD7khxv8cg%rA*XZvj4l8 z*^M(?C5s+xw1IwhviIreAi_CBWUnmvrjt!Y+MH!j8p<);Rcjwk2&6gWAJOFZPG|X3?gZnYjIvtp*5645+PEW+bi*!Rw!rbYHTj(ZwFRRc%`8echhjP> z$0Twx#?l=zxgzvuuxCu~d0@lRlSJ{=_NzqWW3xH`sMXnem?jdGGS_9DhEmxxldG zF;uY`2#gUO+=~G;*$Af>$uUD2W?4G#+5C$r?_(m8GLyyw7Fa7Q3P#bB-3+Ws>GA3a zY4NTTT2VxTN?V6dt$J0Tgpj+s=Z*LoKn>emW=2bn6Mx0i?zP^_=_6X9qY5TgV3T~y z1T)?ec1IDDGVh30_vX(-U+b-s({OO(pFfJD4B5D8)|7q^*K8{Wrqg+3QzxMZmwMN0^~qy=4WA!Uwk+07B*|Dz9&3II;Fm2(kEM=LByDFsU{kRLt1f%{1{? zceuyd=VI42ublwYYd%$=_f&Ts);0FI`WUg_$d~3~bM<~nS?_b{GcWyB1XNE|fZS~$ z`(*at@z!9cy+fF*)lk3_=g>KW+Q(Q;dz68i#^~FbW=Atk8yd65u7Ae11npiP+Y4Ah%V!0dr+*c zepby>U*FT-F;G0%P|j&`k~<(4RMWGF0Jy<=ayTRnp|bNZ5CRU9ztQ_7q^C;SltTALX)<(O~7WPNYdJ^XD}K zQII4CVNF5p^y1#UCQZBlku9z`x!=D$mk7`u)_~@YH=uO-&(vM1eF#xK^iMJ37lJK( zvr#_+*irX5ymGe!c~E8@jh{qqT z)Hng_w1FCW#x!ABnua5%H>O3^&ZIEG>W{s)bu6DDP;YP@XNnAoJK{lsy`KX+4*&OT z)&0ZYhs&ariLpqAt8*BMO{g*37|1aGifdf+3?p`}&&_UwSG6$_3eh8jF>BTU-M6Og zqm;0NKX4Ixsaw&IR1@ABla)oEtxWU=VpbU>VQW!WQX*Dnyg)=?z?z@XP!6O%8;NgQ zv4OIo?dU8qg zYFOs)90UtD=+q_GWs;N8s%tC78Kaj@Q*Uhu2`UKG)P1{$@hhX8c^EZj)^Gp&J@!!8 z&=dGdPY_}|yg8J?z~DK*=GLLXYKvVh6~tokSctlml-TEy=G-*Dg|Uu z>*}=t+Y#Jmg{b~rcHf=3I@l=m5~=kA@S0t(2kP49oDZmM?;m1-?5V~-+0)2c<)D9Q zw>G#b^4{!?wqJ)-3RtICaJnr?{+inupO2ScN^3Y{MhjJGqNLumAv^*&L4MienLy%qG`9bXF|*;YZ_4A0FM-sYeV#!A^4_IE%G7;jKx@rAye{b zY3UGpy7AzUK_obGdjjar^>5i`ss3^glr8@A>PGp%ToaEHo)+8>v?T^MS%41Ij`;Ny zW$$^^j2#?csXn{QsQr%))Zo^kyH9P%xz5nC=Q3HZkW#acW`W(6oZ1bhk(rS=CvG4a zRoKK_CN-|rb~fdvbZwOb)!jj@pz&wvKg*0B7w@XtOO<83#>a4Md6gv60I+~TD#pU#N+IY#;ZXUR-# z0O8C=&SWr|3lq)_d1J`H4WD4+=U2cKX|JLwD$Vm^NKM=zX}Dx*a%GNWdY|;N;O1w- zv}j0RK=7F)KbPnYY=H92yk^@iojr$FXl}MlSMRdTGJc9QK_G@sZfyLNA&2}%5rt@y zG$9%iIdqrf(ZN{@{8<6K8zWoZi<9&nXRBuo6C&v?2wJGy&l*|-Ku~RUF~!`QbF0?7 zJqChQ3B>?DR({0ACJCyy-@l27H5qEO%C^h$2+vL)xIXDbX#Es1JAS6`bb3Q|!}k}4 zaYbQCB`=R{`c5IBJVqDvN%B#^eCtnO)odELtWFWyo}RZd9CuF>d>eZ$y5PXO!4~)I z7J2xWX^&;&O{3=Fw3OS!YgsRQ;Vn4e*Srfx@OzmQG81_#2# z{8(NhZ-JFoEsD^8VqC(Xr!`UJFM{6pF)z`k^&msV8^(&k$lS#VmwPl>t@IQX9N4yT znB%Fgf^%_xwPEj50c&<6pxnP7$M(wQE|}-FGqA?8>c*DIwteSey*9v69@uubp+#NP ztxEH5Hbs@bya|wx?SQ8kAjUYjec+$H-=7zXiHL$CeRl=oO;DRXGMI25;9K134g|N% zXg%|~p(SI!zA(1vTc&}8h~&u}cW1PHmh&GO@yFPmmIF^RCNyZ3MLMe(QP6kB2b8!2^BEY#ANK@ z-jB8O3vu=a!KQ|4$N`BAPG~|F(;t*Mj=gzv2#9zmBuTmUgfGhNp%|e|naTUb%6>XQ zCEselcU*{{feiG2>CMTnvxezOG!Umko4+hBkqbJ93mp>)GG>Yb`2=4!uuNN$k)3GD z=-OgXiUvs$mzY}qQFko?9{F>yHRJ+qc<=J7wiMOXuL-&H4<$W-e!DdS__EZ;V)K7` zq}>!RPEL;8Z5$dI)&eOsIcyZB+8K;}c!ErSjfoTEB8|#76=z^OX%l!B*|c@7sUR^R ztgqkgCe+bY_`~UQ)QQ z+rDh>?{BqZ_m}O&FS~Myw+|RfZyZk-095u@E_IGJ_$w!DT=`!XYj~53-YMVqE12UV zQ>3WqA0Qww5}N5GQ=88H{s){Zf$Q)SbXIDkzowi~r3-bMQv(ZQBN1cNyg>$I3>S-M zW}H8Zwa#Y2X%}2r#OS4BDUS0rvrzz51Y95bM#I$?kva7jBLo*f@yAp7KRgTeOBQ{K zGXqbg-LS6>I>8yg?^T38Tt1S>A$T!ImN691!S0En6`#iYlE#gf8zQVpq%1N<8jb;J zJHX(Q{N;;NRoH};-!#xP-7-yAbE+rQz~CcF6KlDbQId21%g>e5m%tWA+x^Dx2>K?mgf<=En(6*8#?8*=J_1 z<rD@i@HiiItzj|UDzi0rUiFQMpGy4^@@MI_u#MKcGi=1q z*K-D`iF=)QuD9A)wSK~AbOKUjP*e7?ZcT8_@P+JI|1%bWH8I?!=MESX?&p{1ofoTW zL`eOhbYq4Zb+(Ccx)IeT@ zDwL0!2+msb$z_!G6a1bV@WecY~yYF0Q?Uhmgu{~!br@_Y7~fD zUy?tn(54D=`_7$^Oy#V$tu2@-1-nw7yQE_Ii)A!|Wy-8m7oPhQTYpVAYyk$Gz#M1W z=M!L#uEdkO6@Jf~@o(q8;@rEuRA8zxwC(A)i|1Th&{~Y1#zm#x;MJWDU*`7Z)j}!1 z69l0cVm^m56+hj#7xrRMUE!!sy&?N3d0(^~JOdMbF0gtI0g}iihOsZjl}uycvBy1> zuIEY0*;3JxJ3f7n*^(u=BR2roPh@z|Ht$o@%pHMdpV>BlFOiUZ~w{ zhD6ev1P?B$_P3944M9d;3gQ2+rSouP^Znj`_imL|ORcu{-dkzCi;BjG88KR;M2rf8 zT3tHK+C@+!lpu--Le;LQs1YiJ8i^XAwpjf=KHuM;kUaN&&V9~#o$FFFVFP;8u3&p8 zR9q#yynlRNvt}{zYpM}TfE#w5_rCkEx9N=Cm#8g^!W5Xkc)MP6o$4u?1~M89lKrqN z>C@kIVsmzj4)D!PK?051km1kjT`PWAks8dj|I+N|uXJc=HZPhm9Gt*GwHVjh9bB`% zt*f#0xK?FR{5u)+BEix8LX8|8IlfsLm)Y5)H-ZgB;d^JIF29w^=l0Y0mQB5Auqf`1 zcFwnAgsfH<&w2|=fGXLTb3|~hCc;ZvX(rPaSr!16 zjt&W^{qT-siZt}Jo129Gh zbY@^5R8QL*YApXcnK#YR1*R7_i7^yhrIj(>hjyL1EBxlD&sRgIV3)aU3HNCX#O^P} zF(6DfL}(Z8>Pmn~rcYvGg5o^9Kx~<*h6FC6s_Tkaqf}xvJqGV30H8TS+&EHh^sG+W zP~^u+nnSEI*&X8yguIK=zzKCjy?MZK&e5#;5Fu z*9g?RCzE~2W3ivaXC4VOI@o*iQj&bq9cVXDD0_e6#CRjVrju?#3%#Ff8w>Z6lqCe9 zl8G{|E2_dSOH=#(uT^?((;V$OoHz{HUZ=!h9}o|8&zzn;u}QwfX5ItUfW)d9xou-; zun=W0CZ@NbSY6lRZ&2`fuLVA3kgt*cWPo0i05PI9SmreWSyh$`*C_#L-L&L?{!M9> zBGHUL<^z$;5Fkvp4;iiwh?IVMIVphEV}6(m1;F?O=8r4&hHgAchWvXcA?ehspG7tA za(k-iYa}^bm*Pzh_M~NoIg~jpERPy#qPJr}kj68+o5oC0RA#*LU`B)YC`dLfW#WE9 z*<kW7Zx`a}p|1R5e4npj`g zYAxGt9cdd-xS!WA>0q%eu8j3IEU1GyawfyffL_H;0~BC;6~4i8S)LwoyDFzazl-0_ zXgF&Eji{a0?)Gq?O@8aIe9AXp2bYzxM&pk;Kqh=SYXZw zt*tQDHsv-cG?@9>xmsU|uy=&fB)vuu`98F8R5<;{CG^S_`fPn@SrKa8x{62#F%c@i zrwc0~t_i9C-F&%pRZ{F7_}r4;tu9*jnY#sXhTnERgq;ier5fCM=pSc0iu#(R8dm1_ zvrR(IH!H}5m2~RcrR5z4de9VQ2r)HZjTWK zK<2x8o{iX5pU;lD(-QgZz(q4}My{*I{s(+jqX%*J|Fva{rhB{84l0e}Dmyv8|DA~V ze39Qu-v)$DzoyXlnjfZZQ!~$Mc)~qdvCf$YiC3&Q)~w!$LN=B%lIybiE6u(%zl`M7 z7nM&I6@?^K^M;KeYb6QXMyXG?%JdcECBt`+eppq}_n~@|`8Z@Tv9RKah@k9D8IE(t8+;=?>Rh!LAY{&EbW0u1&HE zBU4BcdYiWelQ(=vPYi2;jtG-+H5v0(K^9*!xJKtuDNF*&LmOZt=YADWfadM@oLJ$H z6UFsF_i8ut6oOU5Rqpr8_L|_UK#z5Jxu2dS5OJ2YVEkp13)oD@E2C7q`HX-DRW<|` zB;z{9e2@C!a>84R_vA#iFc@1uKX6b zUO}xYzQ`gq>mPh#{n1Ff?hNhzt8)V}v!@E`JnYxR^sgitW0?91ML?mra44Vi%A#de z!12b5879-g-g=SznKb*4vb*3Kp|JmG|JZDo)n7Nxec>1J%JHiKfUw4fdsz^#HRWe( zVf>mOmAlNN$J@&#`(i=(yv<9j;(N{%{eEk8*PAoUZVvgb-MUFYQc#TOBV1yr{Dj$9 z=HFlGJwz}WCI30=9fma-kV>U9#3Xij+ND8$#4N^Job zh18^GZ>Cv$b0o&%c)X0RX-*Icshtn0c}9R zSgVitdqG7qJ*VxuIA>xI1EDZ{UjEqhcVN$m{RmZcR!!I@;YDKbx6Y+>|A~4=S_);T zY$A`KPYhh%i4`|5%-u-NB{h=WcZ=5yiC^_F{k}k-R5$=N0SpIi7JUR2l+KhPk!2_P z_U6wB+cd2_J^sNMc|<57V`uLe##Y{v!`^)T*{Hp8pQffH5nAx?NP0@BVyxN_DsJ8^ zH4QATXRtWWy4?@>&2Vy66~_U>IkN+;BJn>c{{f-v2o=B|Y`0w!_6*LwR%njeZtz$4 z4jf1aI<1HL8On#S2a6+9owy#yV-s!HB_*|dL7sHarX`EinrkJ2+kTA1_ein}z?BG0D0! zUkYPH9v9sEBxAuVxp9*>?C0rmk*4zj=&9nyYsHSkvjJX8OUro%^u%U7TX9XU$#`hF zv5W={J3nhoRVhyy!xV661YK7vzrZh;p1*bqz`AxssJv56THkhubW-hN#k{JhUl%Uk zGh` z&5)7W@&yLRjFb=`mS^wZ5lMN?sPZ%OTyRs-qj<*2!eMi!Dwvz9I{Qn zpaR{{7gss71#ThBSH1L}xAHJEET9d1eH|UOr^xfp*K^wg&<5I(X-X(@whhUNx}R|? z31-@%vZZneDZHU6#!0faj$3DRPD`h&n40JS`WwpVYdtx#scPyx?}ZJ_-b_9{E9et3 zQK8)f6%CXQlXmOh0a#`q1HTO_-W`H0)&&K4h%Q6JGPNyTrH8%@85@fm_8I|i_?_%; zLW$bBA|29PXQCCoPsViysLh^E8df(SjM-hyU4*NVm*kB%V+v^bA;@-h?LJVifBfky z*LBaI!V!tSwgq+5XQ3Moi=NXy!Lq1XgH5|jDXRf;#e!?^fbEyT-D^gnDBQb_etI2C zOT;o&%6c{kxN(Q57iFjY&BX{or!U6RIh?2b$UX&nGnvG&rIOM2R_s|l@eP5Of1Nni z;3ayjF(s}Dyu8$f1)3Fm_0&1qniw&sw59MGCy!%mN)knASS6v66*gY38nd*Y7o9`^ z4hgDfnkLl``&fRQdj!Qoe&Bt1p8Y424>a@glP6CVehz6}0RRC~ES%o6w3(E-D1KfRi-ZL(Rb#Taf zeP2uC0PftnSqt`}1<+aDtt)$~VNG(9n-gSAnyy7>p7(Mx#6&vPP|6x&p^q|j#$i=Q z>Cx{M)0=|5*}jt^`tmQvVrEtQVSVMJ?BDzSwFTBqn(Hvx#xEb0o(4=H7bT!Ku7IKSsc+EZ( zfE4F{M*vyVLB1#Ak~wg6?^rkx9u5!uURYDQm;=;`jB()B9{*O3;EzsN?MUHFd+WcB zr>q|rsqkEK5_j|QX_EA&JGkFRNZpoRNNiOIerY0~Dz>o85h+nyPr_(S5E;=#WR)=- zYLwn$2b4gou!LsyhX)QFUFNL=EzT8N*u`0d*jmUS^;Pj{E|7MYVb_}Epl^ypuzs>d z6TsFpm3X{VG#9|SRG!RCXzI`+b^LDC2xnH?m)A)M&;(Ib{(;KzmvsBc@J3@iFUfBE z9Lekz!cD9Wi4_%FLn7;hrXi{k-Tv`2?b!397fdX8zI$kTW9 ztb_soSdY--1Cx|53^BV4>_nY_3S$NBQOTIh_-bqGz&=fnruxzO>OPw4liq2OLCn2j za68F*>k&1ST6Rm^cxfd~R(dd>oTi*jR5Hf#+-X>;4__HMAyf6O&!28H`G}@+pjP56@pnk*$Kc8QKMt2PSI~Oz_K` zZYWB>Rhne9BhZdx6LM-YDoKf7Ggi2n!7n+j$n$?9KtDGCMU37mL+^;}^#Zd*8N_OX zKW$_l=&|=v8xxsj(&-!%{yvwj-YA{Z2Hfk;9FenB(|VqN-1qcfTf4-vJ20RLglz&V z*HGbDpOS;XW`nagC~y+Q0~82BdW}$fZED7Rg#Am$aun%$9d+-xsdM*g;Os8lSYpK%QxpgBh$(2&lnt8c5d{L8g0 zOZ@?Vqrr`k+Md$O20{=c^|3WCy<=YF5i8nHmrKER1cx_YGZoMt>V{tZJ23EiA5qmS zbFmCJ`OzZ^4~k39%g>7)%X}+!8+SFQfdw0&WtvgoPMH0f_g6Ktir1DN-ZG26>hw+Z zhr!ARj=QQy=l#A3#52T!XDq#&XLG4+0t{^N;KOV*E(eG{Z>uR@voE`WpT-P*0Fy4R zb`e8LsANZ=Wjh?7ad594Ja%9LX#Hp-UZQDafG!!@T5t#GlFqaqngD4@za4Nn+OYdF z-l+v(VZ&Q!3ES@5oip8+7@mABdTcchUH*nY;u3dAKh1S~T|!CR>UG8T$Kn=`9_4(n z=WhFEBEr9q70IcQ@)%T5m#Zo553gO*-<(_e3N-```zAIiO5VGG+OeApd`7_WuoCRY zP7xrc{Lv|HH&dkXQ86g$(LK@|Ge@>_BQo$PB*r8WRDjbz2evF(oFpxP6kcK>5NzNN z#+ch_QCg2}jf7mT$w+&<=$X~Q?JNo%t}kgoqH~H&I6N5!suSviAZ(}Yj{}>c+cAGP zaNUXqb@LG5@a;GO#EG>$pn7z)=yp?DPbe5l%zo_xgsRQ^H4kKlW`Bs1{NFd<+X}on zcIKOGa=89k>a&-UtRglmBv{{;*fA>(G{l1<0n1_?oCNYKx9egwsBY zku+5L>z^r+qqvF;r|V$uQ+NW|^_>eSA8uEtE{F!aBEnqi|5 zVz#upsl~VeRo$YvZ;rITgwS)(k(EiKFoj)!CD>h#A^w!V!H7PANWl3_ZpHD_WSO(J zGmuV$TH8@p`1n@A+YLX5TNx@AX7C{eGQ8__ zNP#c`3S?SX@Cg+lY36IsL%GxifG;}hP*w9%h2Wlg5PZI!uk6S3;N0Y-Cc3aS97%u0 zp%U58#I>G=RXP7Yr#LL&JAkKkTJEZOkPn$LVPzNkzsb%e+~rr4th}0 zu9v_TDZi;8?H(d%9zu_zTgA@0Nveu?T!#IEhP@GIuIHAjHzB%i8bzq+C(1p2O-HBe zVimM`%lG!8CcF~E+}K~nY2X8+cFhvsN|+)tM*L2YZ1HO%*G+SUDP*W%Jf5XV3AjIm0o~59x1#Z39ZTF%9B5VVA4)yQC z^B7INdk<;kj}Jo4{Or~Xm;A+ndUGR4cH^RBOT9n*&J2oRec)e1(D0-E5;8s}6w8B^ zul13avAUjWCX~s;fSZ}j1iQGKwUHp8mFVi^Y3`aFuB}}&kCFb4QJNMhAyu(=^CI&Wg+)I1`>#J(2_R=3+pcOg<3zxW zcIDpiguURHf;)(7RhYJWibN>fZ5JM6k1BP<(SJoowYCH_rZ`l_-~yYZoY29gtfIyJ zUrLy(=tS=bSt%v=X^BauRm@dL%wz$?DrVkr;B!9bwc_0;<>iIc*O*Ht{|5JYU-F*} z^}aNu=Xb45%OCf|(54o6C+-WT-q_m}Kq&SX-G04dO^0H~HpCzN8%jF3(mlai1)!mC zjt3(SvQ;@-O}JhE1JJ)CmVYDGRwK3r{vAPTguvpJ7ay~2J`nJ`=OcFI`fQ**N&k22 z^YB01yOf3+NsU*5@U{WkA zlRk7^=il%WPS6F|oTv>Pd!7fvjKW=c)~dt+nDhB*t|i3ggN``O)8=ekt0nK-NAxiM z2S1j%eQ*kiX%^GDduFeaB`i%_&5f)w!NTECZI~b4Oqy?LtaRBW50C@&;apXg2X)RQ zxB<0>p}F!ve3^gmns=sH&m}*{G=@pW|0G`fHj1TJ5i9}^xTg2_4L`LkhY|cWlu_3$ zg363Q^>;I_H%IFq@m7hG=tsF`oCW^VMbDX}6woMT*qX+gk>%w(97D5qI$Sc8@*!6f)t-SYoin>esgm1(uFYROj zG>pFe(yzTchj~?DL{2j4I8-S9_ml1w9Op*ziulckKsPNQEox(hE4gov?^N(+@vX`_ zL~8n3F|J(CTBi%I`4!~U{@gt=?S1#g)GWx<-_G3HV<*^vy!DJ#O?a?BaFlE6bE($N zEj-helu?*5L|%4$uhI`D^>hQ&PB3$5TseE+wWhB6mlm*7F6b=E>&8PM2r#d7$UB~m zgCG-&N^PIP=)?BXh!KzKrEoS-X-U`5W@U|l;A-D$%k{4{;mfxv@SuWn`VapeuU^pJ zd4M?!4+eG#hPM7N>*gFgP4ku_M2geOn12m`g$^#IpXfsjBkGVik?Y2EJG1z{GnlQI z(z~$A)OTHCZ{(sZD?XJk83oeNVUy(fgBNcpD;9JR5%Q|qHnpS&!7Yt;AlL_d?OYgm ziiLzy0Do+GhFA48YOv1V?9Vg;PomXv1EKPQeOMl8ivH5DVM?LhC3$JIQp+u`=FIOI zxtqrfafCKk8*$?@cYPWBgVj1?8AJ+Jt$Hl1v!S?HSH`Yqx8Li)w&Fwn`$(=o{ae+^ zj}jS-OqAnQpMV~IXnWyIqDh1QRFnE453?6_0m!Ii_bSJvClCNh>-_&nnrvqaGaI9x z0~D*T7CV*qz>w&=ZK)@|Dnor9Te@QO*n*$~IFl0YbZLV4LI0ZKOXG;3POjhKx%$Z! zLvW}gmgcbq7>79)Mu+k2e+}?(t-e$6yWiwNSK}4C2@bF($z#**>x_AzT|aPkIC{og z%}0;%AmD*VoRP$qH%B6mu5{VCs2+Xha-1dN2)cBxbZVl+-iv~dNFjt!XJa9k+aIeh zXa{#tm9py4Fk^7bj=i=upBSC61Bd%(@>+s1Ndg`zTqst08nY+N0D9)Ruq7K zvZ(_4UTFgpMbz5 zg+HuDpL~|crmYSvr(U;F!<1eu165x`&QoUUr05OF%9UW<0vK^C!x6}gY9oeDAs4*IlLCG5vxJS61OkY^ujkR#9ZUwm>w zowLu2-5N7G;Kl909P^wRR?6!eEhF5KTv>j!6b=Fu0ETyU&jSVcR4JcIhW4!=6#QGR zsV4pI9O;pBz zhF)*Im7ED4Swqr_MewGK!=d=UBhY@~>ZvLL|rc-mKv7`eFq=pRC)ny|2 zG>W@sW4%II@JxyF3{$j62>=a1wnT8@pwkrEDKF8s;~aQ&puKo%`WdI76-K$|yvVSU z1zIWgL5Cu|2MPv}rEBuL*>p1iw@YZOF(T(YfWr6hlH3&?zpKaCl?NyPpB&h$HZ!m6 z7y#)T*sVF#17q!iq92YtWDV(Dom*56Q-utyyZ-%}Tcn5WEozp(hd34q^HX$DGBzE# z_@TN;j_*s3lJ$m);|tt%6Lwv{fa0!ZLu|2Xpo~2$g<=r=SvFYRWX!F_SrNpEu2?O) zk?hgJ$?L3suR|OI?vb%H$p8w}|EO>_6uJ>}IIg-F2uf=<2M$<<>aX-7Q;_rJKYrR0*%LLGi zg+#274&Kh>|2v{B05F3`4z6!rZTiEnvn`{uR|+hDyPphQVy}+)_@j|8?2b5T6woaG zxs}@w2#CP^_I|t@#vSJr=^Hr?%pav5{fFf~Gd78S*@k{{q!U-C(~a@TBp6?kLpdx> zOd(eP9l6s>mvV)cUayL-^w5Ok-8cz}`+;7``JspfjF(A8@VH}A1+v8Y2-;=(cros6 zyRNiG4kCrfEX!eK6JG`+ek|=1WL1^NnINX*h{N*#Du7R~?ntumEvml`jzqh@vv_75 zb_Sa<2E>?CMZ4rtJ@al&h;E@nLLuOHL7?Gn{y9`TN&?Z`cdLp=h@ zj#qoAh;&WF#c$>F3PHH{?bz|zQ0~JZ@@XB%0QJRg>crjdydAS%ZUg+NKj0i-5d4-R z<3-ND00}nJzbug#`E89;{@Yyj(Rk2ys5ZyTyuy5(QqzeEM(7>hri)f;&nUNg#MSL< zCVnLY{9*c}VoCXK#==x2_2)>v5qAAM*3220{f3PI-s`y+qcJ+jqUzbG(wIkS$7 zYcmF1HjQ7Lf8odz6F9c~0@#26R2-O`Pu_+il`&O`I;n z^HsJyvAsLwP$`J98CWzp{QcahL#O3Ku!$EJpe#Vk0Nd+k+A8;vO(-nM!@lU05`W5G zI!OL&=h~_#@mxG;$4=sb8;~Nb*xSPmBE4)5!d!ptSocHFWp#jA^$s<}y(3m@64m4R z^BvS!C6v?g^5ffgZg+^w=g~t{40bH~a7jLFeX+^J2ZA?Y;Y!ra~6vJUS)hc0rsJr(RQTM__*BZ&&-%}dz;A?hz|A< zp#xOY)YkIg?pP*Lr890i{lO!gH}2CA&B)M?hO8K|_pJ#&>*#@Ofx z`FTOi1)~4p2c;sNMz5_a@^Dposk5RU)1H?U8bQDKgB{1?=HSoBvS}=9DM+FAaGhE*;%^KYKYaa{CYJ!W5mCsO-ULDdAK+$vVg}wpjUf`PtB*yFGn6Nl@ z{1+)87K=xJ!nOYaZb$RA>t(1q(r8B8zoCWFjfL9vSPj|rV8I&}siaD3+ob7tp=l&q zWsdiafE=wE*C%h`EG$l=4HCBsaPIiB?2<0YJ5puN+vGyc%L^1>%J{kw9h{gZCCn5S z$^;jon1G3Q9^4(drCOz>*zx0n)3waUW^V%AZdP>meCOqj1O9G`1ptWGqgYDIYki2O t^u@`F>U=e0$#I5RBW=)?t1zjFSY-L6e33HpZ+Kt$8*V!350Fnr{vU{Hfz1E_ literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart_rows.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart_rows.jpg.prov new file mode 100644 index 00000000..1bd5b273 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/base_restart_rows.jpg.prov @@ -0,0 +1,5 @@ +fixture: base_restart_rows.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: b07dbf9d47363145de93221a43ebbc36d28423eae9c065e8cfa98693b6b144d7 +regenerate: cjpeg -quality 90 -sample 1x1 -restart 2B diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/generate.py b/JPXL/crates/jpxl-jpeg/tests/fixtures/generate.py new file mode 100644 index 00000000..ea44ad12 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/generate.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +"""Deterministically generate the jpxl-jpeg round-trip fixtures. + +All source pixels are synthesised here from a fixed seed (no third-party image +is used), so the fixtures are original content, CC0 / public domain, and +regenerable byte-for-byte. Encoders: libjpeg-turbo `cjpeg`/`jpegtran` and +Pillow (libjpeg-turbo) for the Exif/ICC cases. + +Run from this directory: python3 generate.py +It writes each `*.jpg` plus a `*.jpg.prov` provenance sidecar. +""" + +import hashlib +import math +import os +import struct +import subprocess +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +def synth_rgb(width, height, seed): + """A rich synthetic RGB image: gradients, sinusoids, and block noise, so the + DCT coefficients span many run/size categories rather than being trivially + sparse.""" + # xorshift32 for a dependency-free deterministic PRNG. + state = seed & 0xFFFFFFFF or 1 + + def rnd(): + nonlocal state + state ^= (state << 13) & 0xFFFFFFFF + state ^= state >> 17 + state ^= (state << 5) & 0xFFFFFFFF + return state & 0xFFFFFFFF + + data = bytearray(width * height * 3) + for y in range(height): + for x in range(width): + i = (y * width + x) * 3 + r = (x * 255) // (width - 1) + g = (y * 255) // (height - 1) + b = int(127 + 100 * math.sin(x / 11.0) * math.cos(y / 13.0)) + # Block-structured noise every 16 px to exercise sharp edges. + if ((x // 16) + (y // 16)) % 3 == 0: + n = rnd() % 64 + r = (r + n) & 0xFF + g = (g ^ (n << 1)) & 0xFF + b = (b + (rnd() % 48)) & 0xFF + data[i] = r & 0xFF + data[i + 1] = g & 0xFF + data[i + 2] = max(0, min(255, b)) & 0xFF + return bytes(data) + + +def write_ppm(path, width, height, rgb): + with open(path, "wb") as f: + f.write(f"P6\n{width} {height}\n255\n".encode()) + f.write(rgb) + + +def sha256(path): + with open(path, "rb") as f: + return hashlib.sha256(f.read()).hexdigest() + + +def provenance(jpg, how): + with open(jpg + ".prov", "w") as f: + f.write( + "fixture: {name}\n" + "origin: synthesised by tests/fixtures/generate.py (original content)\n" + "license: CC0-1.0 / public domain (no third-party image used)\n" + "sha256: {digest}\n" + "regenerate: {how}\n".format( + name=os.path.basename(jpg), digest=sha256(jpg), how=how + ) + ) + + +def cjpeg(ppm, out, args, how): + subprocess.run(["cjpeg", "-outfile", out] + args + [ppm], check=True) + provenance(out, how) + + +def main(): + # Odd dimensions (not multiples of 8/16) to exercise edge / padding blocks + # and the interleaved-vs-non-interleaved block-count subtlety. + W, H = 385, 259 + color = synth_rgb(W, H, 0xC0FFEE) + ppm = os.path.join(HERE, "_src_color.ppm") + write_ppm(ppm, W, H, color) + + j = lambda n: os.path.join(HERE, n) + + # Baseline sequential, all chroma subsamplings. + cjpeg(ppm, j("base_444.jpg"), ["-quality", "85", "-sample", "1x1"], + "cjpeg -quality 85 -sample 1x1") + cjpeg(ppm, j("base_422.jpg"), ["-quality", "85", "-sample", "2x1"], + "cjpeg -quality 85 -sample 2x1") + cjpeg(ppm, j("base_420.jpg"), ["-quality", "85", "-sample", "2x2"], + "cjpeg -quality 85 -sample 2x2") + cjpeg(ppm, j("base_440.jpg"), ["-quality", "85", "-sample", "1x2"], + "cjpeg -quality 85 -sample 1x2") + cjpeg(ppm, j("base_gray.jpg"), ["-quality", "85", "-grayscale"], + "cjpeg -quality 85 -grayscale") + # Restart intervals (MCU count, and MCU-row form). + cjpeg(ppm, j("base_restart.jpg"), + ["-quality", "90", "-sample", "2x2", "-restart", "5"], + "cjpeg -quality 90 -sample 2x2 -restart 5") + cjpeg(ppm, j("base_restart_rows.jpg"), + ["-quality", "90", "-sample", "1x1", "-restart", "2B"], + "cjpeg -quality 90 -sample 1x1 -restart 2B") + # High quality (dense coefficients) and low quality (sparse, long EOBs). + cjpeg(ppm, j("base_q98.jpg"), ["-quality", "98", "-sample", "1x1"], + "cjpeg -quality 98 -sample 1x1") + cjpeg(ppm, j("base_q20.jpg"), ["-quality", "20", "-sample", "2x2"], + "cjpeg -quality 20 -sample 2x2") + + # Progressive. + cjpeg(ppm, j("prog_444.jpg"), ["-quality", "85", "-progressive", "-sample", "1x1"], + "cjpeg -quality 85 -progressive -sample 1x1") + cjpeg(ppm, j("prog_420.jpg"), ["-quality", "85", "-progressive", "-sample", "2x2"], + "cjpeg -quality 85 -progressive -sample 2x2") + cjpeg(ppm, j("prog_gray.jpg"), ["-quality", "85", "-progressive", "-grayscale"], + "cjpeg -quality 85 -progressive -grayscale") + cjpeg(ppm, j("prog_restart.jpg"), + ["-quality", "90", "-progressive", "-sample", "2x2", "-restart", "4"], + "cjpeg -quality 90 -progressive -sample 2x2 -restart 4") + cjpeg(ppm, j("prog_q30.jpg"), ["-quality", "30", "-progressive", "-sample", "2x2"], + "cjpeg -quality 30 -progressive -sample 2x2") + + # Large, detailed progressive image: its AC refinement scans have long EOB + # runs that libjpeg splits across several EOBn codes (correction-bit buffer + # fills), which the re-encoder must replay from recorded run lengths rather + # than re-derive. Regression for the archive-sweep EOB-run failures. + bw, bh = 1024, 768 + bstate = 0x1234567 + + def brnd(): + nonlocal bstate + bstate ^= (bstate << 13) & 0xFFFFFFFF + bstate ^= bstate >> 17 + bstate ^= (bstate << 5) & 0xFFFFFFFF + return bstate & 0xFFFFFFFF + + big = bytearray(bw * bh * 3) + for y in range(bh): + for x in range(bw): + i = (y * bw + x) * 3 + r = int(127 + 90 * math.sin(x / 7.0) * math.cos(y / 9.0)) + g = (x ^ y) & 0xFF + b = int(127 + 80 * math.sin((x + y) / 5.0)) + if ((x // 8) + (y // 8)) % 2 == 0: + n = brnd() % 96 + r = (r + n) & 0xFF + g = (g ^ n) & 0xFF + b = (b + (brnd() % 64)) & 0xFF + big[i] = r & 0xFF + big[i + 1] = max(0, min(255, g)) & 0xFF + big[i + 2] = max(0, min(255, b)) & 0xFF + bigppm = os.path.join(HERE, "_big.ppm") + write_ppm(bigppm, bw, bh, bytes(big)) + cjpeg(bigppm, j("prog_large.jpg"), + ["-quality", "92", "-progressive", "-sample", "2x2"], + "cjpeg -quality 92 -progressive -sample 2x2 (1024x768 synthetic source)") + os.remove(bigppm) + + # Trailing garbage after EOI (Annex A "tail data"). + with open(j("base_444.jpg"), "rb") as f: + base = f.read() + with open(j("trailing_garbage.jpg"), "wb") as f: + f.write(base + b"\x00\x01\x02trailing-bytes-after-EOI\xff\xd9\xde\xad") + provenance(j("trailing_garbage.jpg"), + "base_444.jpg with literal trailing bytes appended after EOI") + + # Exif + ICC via Pillow (baseline). Kept optional so the core set does not + # depend on Pillow being importable. + try: + from PIL import Image + img = Image.frombytes("RGB", (W, H), color) + # A minimal but valid ICC profile and Exif blob. + exif = img.getexif() + exif[0x010E] = "jpxl-jpeg synthetic fixture" # ImageDescription + exif[0x0131] = "generate.py" # Software + # A tiny sRGB-ish ICC stub is not a valid profile; instead let Pillow + # attach a real one if littlecms is available, else skip ICC. + icc = b"" + try: + from PIL import ImageCms + prof = ImageCms.createProfile("sRGB") + icc = ImageCms.ImageCmsProfile(prof).tobytes() + except Exception: + icc = b"" + save_kwargs = dict(format="JPEG", quality=88, subsampling=0, + exif=exif.tobytes()) + if icc: + save_kwargs["icc_profile"] = icc + img.save(j("meta_exif_icc.jpg"), **save_kwargs) + provenance(j("meta_exif_icc.jpg"), + "Pillow img.save(quality=88, subsampling=0, exif=..., icc_profile=sRGB)") + except Exception as e: # pragma: no cover + sys.stderr.write(f"skipping Pillow Exif/ICC fixture: {e}\n") + + # XMP metadata (APP1 with the XMP namespace) via Pillow (baseline). + try: + from PIL import Image + img = Image.frombytes("RGB", (W, H), color) + xmp = ( + b'' + b'' + b'' + b"jpxl-jpeg synthetic fixture" + b"" + ) + img.save(j("meta_xmp.jpg"), format="JPEG", quality=88, subsampling=2, xmp=xmp) + provenance(j("meta_xmp.jpg"), + "Pillow img.save(quality=88, subsampling=2, xmp=

)") + except Exception as e: # pragma: no cover + sys.stderr.write(f"skipping XMP fixture: {e}\n") + + # Arithmetic-coded JPEG — a *refusal* fixture (must be rejected, not decoded). + try: + cjpeg(ppm, j("refuse_arithmetic.jpg"), + ["-quality", "85", "-arithmetic", "-sample", "2x2"], + "cjpeg -quality 85 -arithmetic -sample 2x2") + except subprocess.CalledProcessError as e: # pragma: no cover + sys.stderr.write(f"skipping arithmetic fixture: {e}\n") + + os.remove(ppm) + print("fixtures generated in", HERE) + + +if __name__ == "__main__": + main() diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14e1e3ae32c967e03d1be5a10590b4d9f11b5ec5 GIT binary patch literal 53329 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 HpSl0pWJ~Jw literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg.prov new file mode 100644 index 00000000..b5dcdbcd --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_exif_icc.jpg.prov @@ -0,0 +1,5 @@ +fixture: meta_exif_icc.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 63f9972564aada07559d2c3c5fc15e94d6e09f3d39b076defe5ce34de38ccf1b +regenerate: Pillow img.save(quality=88, subsampling=0, exif=..., icc_profile=sRGB) diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..650053cf827a1c50d4cbd01d84d835975730c2c7 GIT binary patch literal 28336 zcmbSybyQo=_HQUI#kELrXmEGeKye7JA-KC0Yk}er2v(%HYjJm%;t-@brC5<dvG(!3 z_pbYU>;3iKo2;BQIcKtGK6Cb-v-h6Q`MdIW3xKbp2vP(fAprnL&mX|wZ6p^vFE2My zE-n{OPD^W7D<~(#)tSrB(v6FUlbZ`5F6HND32}sa(ON-m?Oi13&pUePY3;2g==BBE zxYgX`pmz4k0Ul7D0CinRfFnf2nqEp$+)vcc+07a1Wl8Jj?BwDp>L)?}FWI8c^?y!t z(MyVZSlftd%Pan;#q%!-`u~i?*VmWRmygrc!<LIjL_~y(o0p51m*cqwho`@bm!%(v zizmZ>1js`@As+T_UiPjowEqaSv~u<KlAx#mk5p^Oe}%evdpP||q&0*K>I8L$x_Eg$ zL*)5qDAo{BFMBU1sHB6NpA(0J8`PHepPuZXUiJ`L8+$)5Zx5(A*MD1ybN$oR|JJqS zzt23Qd9M8zyX61O9n|G{!hD}+=I<In4uFFETweThL_tA8MZ-cvLq$cy!N5ev!o|VE z#l?B~5|4nC2oIlx;N?rAS41Ra<P;PXc!ZSHl;qT;<P_xp2tj)OD;g>qHX0f>IsQw0 z^8e@Zw--QwfrN|{hKxiHctL=KOn~%v5b*k0FHn*GRRI4fNH36|2Z@e>iG}^#pa~!F z0_k6)M@2<Jd2apg`8oiF0F{uAM+S{Z%MzX5otQT)IUj>Tw!V)<d-f+IpOr^ACKl;4 zidRg`EUa&U`~reP!Xlz_@(PMd${-aTT|KbAfuRw^8fs%}XYb(Y<?Z9^=O6I?LqufM z$LJVXN-8`pJtH%#ps=X8q_nKOqTx$pQ*%peTYLY&;Lz~M=-Bw&{KDeW^2#b=duMlV z|KRZZ(T|JEtLtAkx4-Z1|G|X>K>jzZ=i|SD{U5jpo^icEK|w}A{|6V+3*YB2G64!I z9S<6zj2613I}tr^7zVLya(;auCIg@LPZBGSSu9dU{%xj<f1v#f+5a7|@c%Dl{{{9x zxDWswWTfYthfDyF0{mPlZAo2o5Os1lk!{Je<zqJ3zqO1u{XlAi+@=e}Sa(#|Q&V6} zJDWow%p3VXrn~65B@k~9Bw_k^vvXrO?cpOGSO$s-#rStm!czGZ+zgA9ZM?wJs)Se{ zFfT&~LwQ;g(-pi)T4zslRY_xrns?moHoagY(F}ZKnpkiPzmuuaKKh;lH_2(;uI#j| z%3ojF=gy7)bP-%k;~o`G`dny<Ug*pl*mRfjOhSbDGtab=^Vq$$b`P4&#Ba*pQk<G6 z9yV?miqS05R43{z9H!dyIOvnRvpS|m9`GC7veirL`>d0%<z;MLgb%4KGzxzG!K~Xv z;|QE^C8<PaMEYYflpO3q5`Nl8EixYGNV(>3__(%K4u#^6VI59d&DKuRb4tp!UdwdU z^^rrjn*NMq@<+0okqb?{_y}h}4`escgRV7zDK_yYa^27mSEx`5?w~Z?0sbMzPX}%} z@I<z7Nq8}qE`d@^8~#x6e6qf-cH8%HUlxU(Z2cI$BlX(G@+_J8rty+r60*$37Wd98 zSvvyt`VYcM5sDNX*k^kcciC4-p9ya3b(<0!r%I};_!?h2{J^;w=U7Z<WM!Zbu^T{c zybHv0&8;;tQMoiqQ?)<M&){_4;~-|N^UWB>uyDIPkSMehcG`NCc#6Zx^bpiywP~M2 zvEH!#f)&)l@M15yrNrPcR1@pbQ%b$^CRYE+>-_%q*zesd+vrAH{&IpTGwwj0G(3mj z<E<3!>5>&W-d4hpgGLLL1YvhX&0t1t!)Qvu;s`KZqK&g^H~eJCBc*(eAo36rnKarO z#1|);zHUPJq0I@oy`i89Qa(adA;UX7t4319M7*w8_?j)0Bs$rDUCy;nk@F=8Ee|Ev z1l)k&*qNyVNXk~Tq<Qqhkm!2Blx~7as+NssP0XgU=US@`q?jEQ^x~gl&Y?EcSloN8 z!eWK%Yu$+iQT-4Ix>m=~HzixI#o7jkr?VFDkIr`Iz9ym(Q@@IaVl&IE=y`$pT&qQc zj@NQg@VGVx0kTw2BQ;zN71!ivjXE#@sXG@U3XMj}QghX0Rn-*tA7cZ(VN07k7I7IO zxU#L-^mj3U*ZwlYc&bZZ<Ve=AS(#ztAd&`Jz$8V&5>8P=8Wz?US_+Y25t6i{)7sgh zrbMSe8)E?)hqrgMsis?m%HHFZz_74Ul;aGh1%W_AW)04Ru?Y(L2)JJ8%6CKS2{aa; zCB%q305+(f2e&jOO}$n<TWdI9)=8dd+xy9)((~#<Z^uNhai_<7+<mqbC3AD%{n=WS z;gBYtNJpm`p17?!TQv%UU!Uw;51Kn4P~U?nyxLoojl}e%$$+2F7*FXa0hXBtX>^qm zQ3&r{w>gu|HAQ#EBSKk<alB8or`VT6jY)FW(3nt^+Y~wX>b2DoOV`8Ct0?nwuUfKd zkZDnU$au*_)}`48VA4FDw?c^F>~g#QO^VpHx_Eh~%FycD*Y?PERLw{mmw=bS$hnp* zU=KK~{rWE;s;jg8+}bh9nJt%{UZj#o(F54qa#*ZEMJ~Zc#f+tuGgG7okCM(|Qz6;X z^D7)OjkD1IVMt1+rLw2uE1|Xh7ZAN*H-@a_N!g%)&8?082G$Uij6WCw93V&3NSDZn zl5{jJG{Rd_ljg(?;F=Loi&ZD<pe>7*i5~df#|E<>)7!87&h{HxY%;3m7~^#Jcw1>L zH>AHcHp``izeW`wZU)!EEIoO-vY}xysUEuJR)BR~XZAVH?fNf^j?I$8sX2CI<LWod z&{ZF>R{M`s*I=E)dftlr?q-n7;<B2KZ`XVe$4%1=V=}NPc`1d{fh<;{a$h*y3~?iQ zjHf+p7<Z7JOtpB<kej;Sa#K5X7?#)Ab}ZXoUvAG)wFWbY7M!CFf0NqCiAj*#%J#nR z4^1I@n!+cwtAmB@7C%da*v1qQK-k(jz5(>3u*e9tM_oT-)_3Piur-`?XEif3=8cqJ z<bEUu7)vF|fBZsXYGl>A9SZjmzS8j^I~!Ci3QJ5CR0r+mb-cfK#ogX`!`LI$bC3Au z|2UMfYYUFOpGX8AHXdROI~P^6K2)fTdCaIoIRxXZjH3N2k^IVbcS@+)&fM;@8Pu1h zN_KwHd;i<4BIX4yGtb`W#nyk{K7@=*ZF6<}1?2Od|M@fa7ci)0B=s}qN6$4%`d<L5 zXvkjxiGuT=k=1vXQZvW1XWcjc<2679;bQ0zZ&#e6)6cVI!ICqTm*Sq9<p))Yd?pK5 zBZi1W6E6wQqsjYt_{O@gLQ$kWCn=fbc$;~8ut@~K8dZ3NVnR2`!N$}BTZ}`y-wTdl zMIL@@-$(9LPF0DEUpCg;%R;zfVlsa5P0eG9;3d8<ZB>k+u9b~=8<w&Y&Nkohc#vSq zk>F8TpTN|l%!k3hfXXmXG>T==WVi@&FmV{g42r(`Yh)_zMblGPvF_tPR9g0SGaRIw zi}Y)|P4?I!4m#*f0dQU@>J((QG`dX&GUAY|2=qxLAt0|MB8s^$-%tq}sRBvt+Dlp= zd+xKYx_x~r#=}~hnbh|<)AWHmf669ydR108I5$T0@fUV+lYe2i>6r}>DL$4P=x0qC zjTCG-di<%Px>0lesc;5)(%qB&ht1PYArN0$<S)RW&=b%LM~R$ZkEgSDZA|ud`C42^ zt~s89<}uo9eG2JkbSU(}6%i)6s+(S$<DKm}UVi7jNI72syA*b7E5-PN$a{5AJwh6& z-xF2-tFT=_L?!&2METAdq7n8DQAGM@@%vwZ6?0paD-LMY9>Fb&SXXm;g;-p6-1sc1 z!iKI#PA9`tzb@E}^}Y|v|DZ#X&{AI4EqJxYAkJOS*gpJ?vp+kPVDEKhwl$TZDu`x- zV-M|snT-QsLf_v!14}L$fT4lR;jNmoWwhGH#vDx(1%^ud1_k4cD`hEEFD9w>N=Q=A z^u+PHa!NYcO{FTXOi$<kyq@{F3@ITz=q}C11xhbc?1VZ_m*=&+EK3NprQq84@u{gh zEZAouA|bzWIK76!V%4HZp2gEw{vQ4Ngxeg|f<-adsB&-=L2K+*i%@mGNVY1=+;_67 zd^XaVgdjRDEXw!>Rzf<u5hkc}(TzAvro?oLQB8OO>gdZ${Z+kgolo4U0<<XtR#c>5 z8{b22B?=g3`ZL)86V|sPaOfzO*>9e29cJ2P%9-Y#h_E29LG@z)v^Ih;bZl|$QLij@ z7F>K#Y*K<@2l<qfvinrOW}q&QClv$#j%%}AJP9B^Uau-7q5a1+`0{Jfx?O+yI_5@5 zXvlS4*)}RXm?yysCKxq|_2~sHc}ypf06AM8ZUZH~WM)sFtgZa^vNm7P$DK*O8>K)} zX-_EL{g!aijdajZ^}f^C`we{)yYB{e28L$Y%9yPbpE49Wwu>emfNG3yV(=H>ptkqX zdH*VI)AEXt%_G169QSiNs(S|`;Y@M_?Va1SWWAWH%eTY70KDIYdK#__GbsM0*g-dy zJR`Y__Ovq&_bTL4Rh2o()#J9Yt_}fx35z8hPi5SYCPy7+-Nh5H`9YIw$~PQzP?){o z;8t&4+g;ep(E4HGhQldq#{Do`Kw>ADvOHqi!nY+b%5HP3@Z;l<C+suNG^Xe5u&zaD zQRP{6^)CZMPSmXKVs`fdMoVp1_7`%@KilX8%aNr58RF0qe*E}@Ff8=GBJyJP6o308 zK4&dKkQFhQo%}w6eSp4OpCsDA+wpk$@z3pTb<=!7md+d^*X5-$t^>|O`IPJq8H;0X zHUX{4{0EU!jd)M+?(<SUJ1}vqB=6vno|);D@8En8_Yud1<AX!~E#d4KGq*Ix8qa0? zBxWz;;TYfKrLu`qAbn%CsNzKdv*ALNR-7ehV{{90w)7W}>20;-p;R?OXD^6Q^5vpa zwHEVYm$4fFzQAU~vBY&SHXi2KjU~1BeFKVb8vH)SO&MNi#T)#a^)mo8%G{kGf}`!R z|Ls2T`uulZ0(fwKPyq-xPKBH+uGha;Q~bJjORYM6bj(`nr<A9cRJYrtQXbQ6IpPqX zLS4Ckp?P2aFa+!zKpGMkQ1mNC>om%i-nw(~)lp({DV(WathI`EK2U@iR&QH#Cy;aQ zO;ZNNNt#B~fkw9!f~rZ{+UE{zBCar>t}+UL%;9mBptyFp__31F*35$)KYc&3Y_>af zVQ5V@zV(Ei8Qz4qq2x-_WhtP9wrwqT*qxjR5ZToY*-g(@FOG*7?aMw^UpDU?f2AU! z{otCBIP3O%BnG~B_F(p7Gf&JPG4gZKPcdkbj`GU+-Py_BdB^-+PFjt`vhUukeo($b zP{`uU`D>@38G28>RsU)VNW~PHz*?Rj>{I<Ca#ZGgP4!L37)ep2xgZ^FX31qnSyBpB z0_M6(L5(pv5b(<x+qb@DuGG@)i%M}uxkqxNL)5LG86)fThz}Xy-M)6oXM)B#^C@o> z%^8Prl+jH{Q4~*wh^8+W7KMxm#}AdN7Bc2V#M#Y`Sas50K%t=%PMq$E64XV>3}a@z z^uR^cU&N{CM^WQtPjU79<yjz*YFTS-;czRl*iL~&2F1IOQ2$h1igBmLzoCqNU_`}n z=v1FXxX5d#yO471WTIF{r>Y@bO;mr5^HiHylXTN3S9!;uoKq*D2HO{%0=JZW3&Vwz zDRS1?>2&*r5Wc*j&}gw%tcrCCb`p7VwhQVsw;P&uM}2!V*M7`PX+En1pUvMUVmNy* zkmDY|3397Mu<3D?wu=UFQ_~u=@pwOoPYaF?zRpO(%V~_`v(1Ju?(`7u7=UK<eEyua zEZ%NzzSGDbkkt-6e)4*fBA!M@7hrUp0_|UEkGvN6iC-;c7l5Bt&;BMGoJw%m1P+&c zu3!!Y=MQ*x)yV&#eyZ}BJ3>^h8t`Apu%R_^5A2t(cf2UHe2-rrj>{Lh$4%~?``LBx zO8fCC=PWMylRuLWEov;?M;cdJq;NcPGxz4^V*d8DL^nQW9LGR*o4XaHMXir|$Q0<v z4_yf_4I5&FtD)_Ye<=MqV5+7moZ3RV))ujCpDdwCp}jD5r42-IDUl9-y?ZIBL-^Fz z*{kc9`b>Ie(Y3=7ORY+rp5W%~6Itz}kmq|Cu$&%T6|K9U&}qg51Fd`?(B(7rFNrv) z77v=2H@U7Ix>zhd#dAr$7Bdd?o9f@tfO2#SUH}=C!**f3?libQ$o+)V!8e-#6q*v_ zPqE~0d5t(e@DBG2!r@=vqV&*8s}youfUG>Tsj{;UNWX9JlaUs?Anpai#S95(FY9_g zgSX%~RfP_5yOb?fji<lVfNVz$^XHRsP<wI^dIsab`MQC}8YtyplbbBmk7aWdt^!Ns z?Rj>#jK6u8FKQF!=FR=-Tz<qgB5&7gib_aLDphHX9tz%tUy3>$k~Kng@Th^94Aso) zz{Y4Zn}D6qCqCOhC=nmt*gA_(Nt6i@u-_7mcpOeD1_l3+XkA~cBX>a%+oGcs58m7a zbWXBsZEKO_uV7E2_2O7Z0di&w;@ofRg7u3?(|idKW*DFG0dx#g6GKfhykgF%%2<?u zT0jlj!608%wZ}5rOAterJY1f(8Nyo+K%-QoJb3+vox;yNw+CZ}g>}I}GA}(ZedGJ; z^-7?s(o=|e`sD7ce`Qj!VzQ?`U1HYGc%|BcKL1S?sYPP3c?_qhF1ONLqm??_O=sB9 zkjWujuIfuC>|ky?i(2-wH&qhCJH^p$V381-MD$ycL{a9<$=;c|lV|Al>gI*>_ykVU z)8!=SBk5i0d*)sPi@l#C5g*j+iiZW13%ol>le^l1{Kl3z9i787<HRM(N{lc(`VvFS z_XA_R7DX@X5b`fO!?)_e>bhT?6|u7qvE1K>^A~IT#dzkd#TxUcXk;0Ij1^&^liKRG zni*N_;+By7Lzt2twIf)!!b9oQJoAD{@l*YG%L?M=?1lPT{=WdOtSsTSBlEnue+;TV ztA76Ig1pJGu_KX;htYj)-JdRlZ!n)hFS&nI7H(rD%r1og`4ud1I0c-rK5-L~B^=_( zZpBeXbqbso0g%#V*gBO_a<&o4CmH_^qg1(?J<nlkPUg%Wl05H<^rlsQtHSL$W;Ga^ zQR(5ruU8*w@0y>sHPn89P(b6|fhu@XFtB#+p)u*B(}kCwh_gRcsvEBwN*z1ieg9)S zvbzr?h|R7hdBesGQ_XDZaMYc1clbt{c1sC~N`TXSuqugH1ZrpNf9Kh|QCjFPY|CNP z1It$%5|&R;2RjcofW&B;U*!*O+yx$dlAGtUZVq2h@eISgl~tnr<NT>%#6|dfmAWGj zvvJ39nPQ=<TRl3=P@hbjSsxXmr%7irl2nDO=<7|3=={LZw9t}Ru~~((F-}3;5#?)| z40pmuQo>RVZ5_tkX}V56n!qjFGwU##pbBF$t$;||WUfd0IL|8P_`POjq3>2~k!s%G z=py<H2!HI7Vz{g2Nqi71RTGw}y!<h*u5nWNz<R|g-&S&0?dw>&IoP=rM{r$np)^`e z&TwQBB%#;Fmbau`#+9yMw0f!#{C+a7D}RT^CDZpEGmME$dY#m5qRG1d#U*QEl0QS7 z%lKB_C+-PL@spTglFsNfLHr7K80yoi*!q6zDFaTiC-V}E>jeD@b8T=7(S}U9hz^QR zfwefT#Wsj`i>M9PU^}&$uT`epL30g{PXiAppE{RC=|h^0>;xR4fvQZ`@Pg3SWh&61 zv&JB^Y<{t4<`14J;nmkaBm>$m<D92nf1abWw+CQ6hcCp!r~0I8h#~_XMCYWW?vcuk zNa4rU(G(INo-nNnt70>@V{U_Akzl7LPDrb%P2gI-TbAey=@8*`RrD)>XC5DF!v|$w z9E;>(m8>ZU&b%@y8#v9L{7txKUzRx!ksp;?5!9cBDN@pTesjvL`2saUa@D!1;Vh+M zT-~P`35W$#rLMqYMd1=KWSaLDYw>g9<opasM+2u08_Lz|;U!HQ;1;6rNDZP_vj5=3 zUGtavEusIbK%yNKqy+{)cE1a$PH>(VK$gp046(7pKj`M-j#_fHvRmBe6Hf@xEk~DN zlQ(|QS^9?P_P3r=xe?p`C}icx|K-xP<U#jRy7vwrKvj>GFi3}z5Nw(Dvh~H?kfXRh zn0w8sMMfBG_=++NAM_eRK4!zgmeWeHwdbb5RK-{2Y{7pG(_rTZQC$wvD;Dl+o4f$v zPPEd1*23P$_}rA+cc_XMQUN#klnBu274<i+Z~%IIqbOy)Gb~5EH)Rq5Ugsf9Pkc+U zIsxxCyCYR}3yWn-iGMn78s2L)O$*anX4PCAHW?xFcbhr(PV$%=uACQ(kp+(@LfiyM zWkeM0+$P$xU%s~|XP*<qw7`OEU0LI3xR$J#Od%T?hVA#CqR1LDDOo7qg0(+eMy{ts z_9q~rRjt{F_p^Gdc}RWfYAn_?%C5)mTEP!ZJjca~Lpy58YFINhKDuZs&O;c!08y?X z#u%NuG*4`7c4>jTE<1vI`&r!%-73dptB;GPiy>w`d_UgkY1Gc-zo&ZEA*)2{3g~<4 z3cW;<5^SuW(}ypr-7h$S<JV=xR>d#P4Wp*vB#!CS>P@gfJ13S0QEJ6YgL)L#{XSZm z9x;8|!~H#|%pQEqs1~ho&tW-T)%(V7D;@*8S$o<i(w8Tkk@0Lye1C*CJCbsA@`N$s z9Chd`v+$V^;~`d5N`z;J>sSJSg^ou{sX_tBV6y7uhd;tk=EUdzhR!4^D1PD+fXj{T z0qBbZTU7Q9q}_rW9@E-6KLw+AsAL`2hWvKzJuqWmu)z%gC&fyX_dhEO9%Xv_*$ogL zNKuI{0i4v_slj%2NPc#q4+r*LV!IMrcR0NP>oZlVf0B8!dsD<?A6lXlCQ<sv9`>w( zc{xAHlbYDspswyr%d5p{!qR?=%M*S?e(SwFBcuIta7qt1&unJs7bIumO{4J()rO54 zWd4dRHumR-Nt6$AxrBR@-@Ie&-L~@16_zP^F@8COq5K8k?az*nYAa0XNC_Hf-Sfx^ z2D$~CpM3tb-_tb2syVtI-(Pc&s3=kpe_gnkax|CFV{pt)y{7e=(zo+OGz!|J%H%Fy z+(mFzb>S(-o9P&X6V^1kuACkZ`F8YMCsgZ+dAZkvE#HSF$pHpQWnw<JvxBzFPT<;N zYen3&31-$f{mNQr+{D?<$~ZNnN;rzRXL;-?cHuh<^wCA!4Hfbb4}<-nnj*`vA5?Kq z=ShN`wK#7p9RcBU`$YzH;hR!ls-SOOOr=GxJ|@Wn_EnQxJ<VJw8OpI@@Cv`p_rU%F zBI`FY7d#7gi|3w4f9)`}Dp_et6IV=ZaH?NmqRnnl4tF<eeo?yU><QK08`IVWI!G^G zzg|01Towoye&{Hlm&r8TQT+%ZGVYz-BV%9FNb9`BCe=!LpCn=TJJ}n=^uh3{V9j#X znMzyPHz+|S*<-x-&as8oS;R@wCljY+_Y~|yG<vImo|e4+krMW!x6GuXP7`VPgodY1 zfb4!MY_4j;xM({5PrM&&423S$8F#v(mz7f4hEfAZ(mW&J_gJU=(~!^k@7f=K^iO7n zzV8(4pruxLG^UzVGiJr^Yq<dJRpvn+N5zu9{<0f}?-3Et*-!hAe283*uF8w7Rk7?^ z$USo|ZQ;L7iUjx^Hv>mvuDOLnD#gBd8)sbn1=Rb!{}XD4`4`Z0^oJizw!+bKSQGIV z5O(|e(Ie*R_&8+qQR)HmJ8=z~ca10Y_%h^@NqA?*p&{+=R9lY5;l%A?u5p4prcuXs zG|-VaqNj;2in>x~?4~T2Q&eAF3h9+wyQsZ>o0(Eukm<mI*m&6L794CZt)M52%&L+m z0UCfI9i+<t{0QTZF@yjL$Y{yfKMT6?f&g*e)cR<puyJ%kL-`3;Q8CtLB~*B-$GC?- zmX_zph07ixn>u(2VYufnzKZ;s^UzO@lehsLOsQMp-tm+&jQCA;NO?k?{}j^O^qhQq z{Vpoo9H?r}C6!w_WnYp-+*huXqJ=bN5be0*b~jimW^ze?%@WTCE{u{WuVZ#M;vO1N zdBS6kseupvSm~gy%Xw^RP>dl*kVF>%T&xU|drh`Z+0lRUm?Yqop0+o=U)$hQWxvF~ zL`aPuH}!{I*`kCi(<!lo%p8htwcq7`ZfcdXgC<4$pfl-SGE#zA4j8hp8PL&vV1>h1 z>??dQI-)X`DL2YJu}|l_?)#CxVWGW{6Bbo$U_$CE4<pwC*y6~$rBkL-MAPD*bQSl* z2XOm0^}hhNo8|T&o(Jr>a7s=#MdjZWFlM%^$+Hmst^^<nr+(?1B8DtIs^!B)zRWj> z!*UHsNt_bNVU8Db)0J<f@)yfdO$?g>nEU7+Oi5z2JU{J{GWH!0eYo^lIp4Nw9DfM! znJ|SWWvG(e<?j9zsFWbYPTuDO!MJi*^6RoUot(iYxx1ekuZ9-Wty}oWbM10ctP4!a znHG7C{iNLl_ne&o>lV9byoQ}}CY$CVGxuxG8U>oE)u#fFwO7lk$D0#V#B(?|<qAF2 zz;!LXFpLqE2Pa8WivAnPV1bj#c{DMdSK{DL3a5HSi6mEdL#VQxBl)t`6K~jUgg%{& z@x5*3Z7c!15OZZ_Cn?I%BE=VNxkna9l((@ER4J=eebm#Mx9bxIqrj$HN8WB_!yFXY zwUq13J{nI*U$J9%#T4M{(E0BM%R$cHy!_gzV={n^Ez-;$w?jSo0z)_It#cx+FvVTy z$fPnO$w)8c;@2Bp_t^RSKaG-Sk<=}I=I0xZi+=$|r%hSeiy6xTW&#JbvYA8f`<b!T zjGx3+S75?l5LMMZO&}b@5t0C5za#UK{Z`?{7^g&MtG%132NmIJ1XLdYHMVwdp8Z1Z zbT)vG@tV7T7ezpEL^WY}-a2h=IQb5>FsV?KH+$P3@aLjI6em~FUDckTweAh`&1F7f z?o(PEreG9<@SSv@rop>2f04mnaf600DW@PFH^y+q_#CAT3MzOXBbE2_ja2j5aZ9bR ze5z)d?5H$6t6jcmv8idi?-ZSt)o_{O;Hc~=Rg?6Y&#@lj{^_tDov>(_p7od_JR(YH zPu0le{&`_+N*-Onim4X;<)?Aars-=hwwu+xPZ|%p(<efMRReC>^^8#7PChh7uPygn zrq;Rc{(>`1>qOE+wAEGxTAVV@Z?uTPt-bT>DZLhRW@9DC4k!pO?dur6e($?A=`9zX z-0eOW59vzq(JLctz0A%pjkR#jy?AOpy^Lm_QS_FegwWvX<gb1o@<O<d@7vDjJDql0 z^jyWnPP&A;PfxUrLmYfppDGnBZ;H7u=DGsfoOHP${iP-oJdh^EiI}f2(Q<mL9*OB6 z@62u_j7T`$jXE!Wym~jZ9O13dl(lan(XrmCHHo%M87)4DN%ujCHSvrq{b5=~*C;pN zyUfHsAhMP~Fu*xRJ?&n17F)#7GV4Gu`?PP9UJ;`W3x=GRLE2K*v1$)qzL;=#%&(%? zZAp-w-yFcXXl>KrP-OBeN|S31$9-+bgDJ<!--A8L1zXepqM61P0CN!HAdg_2=p%#I z%d{BM2m<9}+k7&{EEMsl5wKW&qt&ye32$i0MDX(WARlJJs&DLbDWE2A`_0Qmfn|TM zci<66@+qSP@deFlyFMDnPt3Kah_-vVoX8@IQ|;IghZbTJugVuRY}c<SoS@h%S~Y$o z>o600pY~5i#y^}~XbxEC@Z~=1dtw(7%9fdKaY_?<OCNWLu;<TzAe81bERDhFo+qFb z;tiTKg%exx(T5r!Us3<wa+-1XmdvUjaG0HVhl$Dj`G~FdJTqFoBW(RIfLmleA0D~e zL9T{E4{e@Mp;!Kx46!qL1NMf|%bua+ynl-0O{l!3D501uIW0Kd5AM!sxorM6RyV9w zF6s=rENZKHIweVaFi$_(M0kv!9d<Yul&mU*2tPUJA*e=XzB1V<z9%ta8Zt@fDQa*s zu^%$|3lO8K(T<=^I3_(3R*e9hI#o@M4ZwJ&dkqK@*0V#Mjn?bu<ortvvz)haA_XD) zVM(~U^@PZeBBRLJNJI{5o*3)1z48-vFW%N~m7)59Mex4%Q2G8C>NK46Xqs9OTs7Eb z<6))5GLZD(#ttf~6F+o6JzESfjPTm$bQNP-?2b0~J$9*X{&Sfln!uqh7W7SxtK+C7 zi{sQr38<%^m^JTrh4^ge5um?K@ksp~uL{ys$Gnhq7}UDb!4Nsp8Nq7`Dj~xbUTFJX zfgnN{E8NU5Q?BG5_zMkB`n`*9tiuxS^T(Y%N=X7fUdrL5+<|K@+CRU$L+XwHDA$OC z6vdJdtdkt>C^(qLWF4#HF}5FHQ3d9uv;^z1FxKBCpizX9lQ@j8ow$u~hl3!aHvV)B zexr2lRF^Th^P`pvdwJ`Zm_VPyfWH8<sAWSIe^vF|mLBynL$A|0#K-r<JMW1r)rD(4 zN-WXUmnB6%EtCWGM2NEHyr=a2zL;mY6F*g-1pKyNnJ{Ma2|xHn?Lx4#4vFRLxVS=! zL@tC=`EI!>7}O=6Bu&_hR*pjM#M}C-EXv5WF~v{Kc7;WT*HdG#?ObVFjho?zG{$_> zK_@Lb*+HeDJ-+U2arhuSN4w<1aO3x0`#In@rKp-Ntm-FJf*33e-5`lS%-w`V6=J2X zmyew{QVKWSv%->H*K239#to+>MM)!5_R={(^868pE-b6AsxVPCle;&%Z8nVLm2_x8 z)?uLgT#rFNWc!s*TUG#!#J^ZMF1<ZAjng*9xnB_nB$pgvLN<)aAms}L%tW)8iZJ5- ztfW`b!!=<3nz<<$Kf?GE^Db-}k5}UG-nrpj21gNV#RZ&DTv?6~s|-;bMb6mFcdU1E z<~*{xEpOaLVMy<vRC0N@E;EhhHXuB9)VlTl@Gxs`FMO8kc;de8HY31O6LIXlR%_Z3 z;8GI<Q_{Q(T1pwnU3{H9qw<K%KQQj;Oym8kne91O8jNwTqmAd#_Uzf38cI;eIvlE2 zdi+)`D~!=?`FNHfZ!m&P7e)(>W`6<GaQ;jb?rvtRTGWmN9yTaMe7`K@zN=F{<7q`b zl=2TeC~CR-gW%wRwxrq1WimxMGQcWScTo-xzJ;R63<qKscD+AM;wY41oKuc{H7uS= zIr7tm3RkZV&GY^HvD(c`L~`2eRl%lZ$6vD@NAtg~@5i;v$<7+jYJYc_Jp?^8Z1%-O z%2!HM2$?MBWbGC{GjT2#tZh}`db`y^iO+HOsd?<-!p5^*Y|<3RXd7*abNC#Q9;v#* z)Vj54M>eP3kb1>oKFMR-rD125L>RHg@nNqH7%9gL`5LLeZJ^g6Aq$J-Ya2LOsVW~N za<In&ALnr37Aq78O#SV){<_g=V4!bzS9i0o9Ff17ysw<Qo-3dIa6rmDRW_$*%7`E~ zrWQyAWGFQOxh5ZLy|SMO`R&YAv+1fv;DS=ks$D5Z!=qnd^e`FTPy(NcPCa{D#-lRI zqi&X3g!Q1Z`hi-^q{3B)NOOU+T3kxtQSjdDw&tAJZH#R)dD6r`EJxpW91YV3<uUd$ zFRjg4F9_V+ER<GGr6Z2*h}xVr-mRN_no3@THA#HB0!f1SEQ*kOuf^zhT`*VLaJXQx zFCBREw!?kVX$*boFp@-YGQ?7m<MR=@?f0b!`j$B^%XmxIYrBsavAl)RWS*pwR<ISB z)-=}Vbo=TbM{(6WxPq_m$>Or9P*3s3j1CEjQJw?{^ker-;2qQWS=P5q&IYwHHhbR+ z$TO|rN*{;nx|E&jy0-W5!!xbmRJJ)>j`|-P!YKHc4RxPKovT%Ts4%&B;v2A<?@y7V zTv3DgJ5p8rRJ+N!>T7H_9_qk7AxX991~I(p3ip`fgxd#GjTKk#w<_;8XD!K~7DVtg zbjnbAjS`C?51*v1lM^+$mW?#*>UPz4g6A!bMJzkQ#UkqQ2T)$J8F$ZjWeoOkPPOjU ztv6Mdqb-Q5bGQgFzT=JrHgj@XLKr<&iZiD-j%Yt|uHi0hT&86G$sE#2;}oBVsZZY; zu^ImAZqfXnlk$D`@!Tk0%Jx;N&TrVFWA$l)%EXRugUXG|?f#9gj2@H9SIcC*4L<Y8 z!`~6woFmuGG(N0%UV62_nBTbI0Gh9NSz?M&dJ!dAB6@u(_?(NI2Ce{??eN?V56=X@ zO2IFf=&L2sAKdVO{4hdoTj0ok98$^`s@%<vO7THjztnPHgU99+K>Z7Do>0DyuNK!% zX%zHB3)rq?>EykfpHn>vLDQH_Zl$4$xFV-lthL_*G7ATq$e#V3KOG@tQ=VnR9vy-F z)z0SQOOFCaC13elr{qgYWwP!Ravz*IgM#>pCZ=c<1NF*wrwo<(5bc%`_e-Vnjvm>2 zT6gUqu0AOk&Y4!JzWgB4H_Z*AaKAicytk(Op7wLCJi(udZ|_4ISkLEsltZFsA=MTR zji;8XQ1+yCV3{@_{jfTL^_DY|I!{vI)^_M&oFOKPknA+#<<-wNRMY&m`GhLmzTPcs z>+I0rf)eSY)h63}|L5n3Y2qwYWSlMcc=lSg<Kg<c=`@gy1_&iDkMld#N{;!R)mmGT z$DkfGurS{@CHqpjq|Kbbe4}j@#kNz2Fga7c+si>dxo$jY;2bG0!^HwH;^g73)2I?h zFC#D<)XN|x_OwK4;D)sQHI9ZGsc%1RZiKV)fMW8CYcKw;&pOX2yY$0`XaZkvxe;QP z-J;zfXr1+CGZ+lkBv2$PGx^0L4k<tUvCR|%1AlAD+bD_pZZ_Kn^w_vDwa!y=bl&%! z*IsG4DSh_i?K!#wb;F<2IUx^($5|sI1BABh!lybwt7>w0M3xT87&vcHz1`bCZa;8v z0ma3TIoYRhD%#C}8=ck?Lu71h?=U;N-7ylLK9*h4foXloB2B>;S2t}Cm#772U<u{R zvQB+5HypDi3&Rvbp$);RKx(!2{f>m5U&$e+ENK6RLAjI-n{IVk%D0Sy)lbYrgP}O! zT+C}P-C4Y+cJzsv|B&>P%3r`8S8n?+#JSSD<d*EYlPQ5`W>kseL4L-Qt!f@6g-4RV zNbxhz<a5$t)7$1hi*pTm&eaJSR_vCaKy*z}s%av(N@?1$eE{S}T%HMn6)MkAjFLVo zk2d?}{$yva{h%;_u<$SjA~3ge%lZXZtYf(iOc~R@h+TY^R{J9_8NlL<0_ZoR)eBhk z=MsO=oque@5>!I$r9Sqiv|2x|#WWx<c&^3VRzQOXpvU)r0p?-%q%_qa4w~9PW7T7p zd(JLx1OnJvt~*CE#O$~Ww0*5VcQ~%IknzgIra(vM^OW_M8Rz2ZRy!N^<k5H_zbAiP zV~OB?{)kx0{+%ygry{S?%dZPUS|(rQd=K_jsx||!lcm{-VchB%TU5Tid%E3TNk^64 zE6Nm$uOT@rJQ1|@!%GY)y`zkWVfqCIQE7CJnoR7QAa65eZ}G>KEeIR|qO}!&`oQ}a z;CgQGME`OwXzbn1<6}C!vBP&E+c;1+7EKJC)N)gN$%E*&@)!D|Uw^?pXi`+aQ=%1< zIuc_^UW3)zQ<2knIH?<---YSrVhvV~VXue9sVY<V`=TU$xEQ-Z$f~(lfovULnT<J0 zW%!zm;oGFLfBfv39W;`QLAz!75t{$b+qTS90dm7fFQ&jS;#Q)z#pVP>?Uu!ej}}iV zU3itSw0ZfULVv3+-vzUm8$dmYro<5qJD_)?6pNZ@bs0rUb-vxr^JJR374u%M-MLX( z*Hs~ac_DhbTp8iNRA+3fstdsL#cNsSED{yECW{%{GY)s7s>im|vZZhSllN|X&VBzw z->DouYpe1aAoT05xm-P#TDx~d95?eAoh_VsDdXiOyLHqHbIR!2jVh)_7~Hac{J3G+ z(bnEETk|Mr>3^sXbC!H*J;rn$CX*RHRKKM25g{%HCFgc7&|uY>b-4zeSLGreRK9d{ zhV!5zs>txH*A6m(#{S5+`Gq{m1W1#~-BZ(VmZcEiT<i)KQM=%AWBf_ToVm%$1}3nt z^7$KJesVenb~%;Gh|HOzlzwsKJx7CIz8Xq}^J-_+UI?G&kwbxc2Y0xg;4WK93V6|> zX5$(p=?GFZ8dg8sJm}O7tKdxiyu7QugRA72S1V)kshW~c))NeQyC}zfzmzUo$f>h8 z^}IhSk@6P+=y`bc=9|;}lati>WXQHci1d@J`DhRF9p&1>$)EkdfYDEv%s@1%r4RLs zHi=n}`VxaGd_Gc=`Me}li)HD_T2(=dqmWt_j=`w~eKG%Qk>7>kTSLySo>=Vl7>JF8 zuR_#aQS98MjGoyEXlT`~Ia`J<PIM0<vemb`%x-+-lyxe(X#Ol=VN-03lga#F2VP@# zOADT$So&@!U>=H4-$`K1I%Y;@mw3lcVF6;GYkeXpe24oZp3}Ov<N{9|NtffmV~rm- z_4m|<Z6KX+HB;BQr>r}N$4|34!cj4bQHYufcjcoY&t3aI9VwAwz4dXJxKBk*Yc&Y+ zyys{hf5D`Lb`t345Jm1nEyR%`l>Ulc#!7oIIotq8aHDu;k}?4om)m51#+{K!nOKQy znsie#&L72^N^pCocG7*Jp|4#2hQLXOaYvR^2z5d5sxcBf?2!Q(xdw19l|<a1FrRdl zw2a^KsjK}E2UC;8J?`QP*MBA*=WSQRfzk5<viv=`5A#99sEB>u;T(LDv-ypG@v(8O zCPEY($8$)f=f`klq{*Uk>*o=))OMHp125sVL;BcF`K~(9Ia~h+|Ixr|@~if2nX9F} zj`u>T)tE}dJD)9F8Gqv9A&VdaA_e59uvGf+(rRYEC>t6mA|aU{prf2INQ6<#pf#1W zXS(?C5uy<CMN7K3xZ%jcQNOe#NMngXu(dkNYv|z+5P0kK%Ek>-8vs=2U7esx!edJk zW|-R18_kV6UI}PWy-l9mxSrjdDOIgiuUKA!_7%OT`iB`1w0xy5Mk1hjhou9iCcM($ ze!J*bcvx)L_gdxtZTOc|_!JW#DeEgX;gh#tt5QlaD(Ih%kkIzb{sII&0)HXLWC0$Z zN>*phv2i?6XEHo|ZBY*0juSFigh$IH=Y5?po3CscQRyvTjWmX9h==>y)~|gRE<a;Y z>Jq$fM><gGf|0INgvlU8?m%8Yc;eewN~H2xgd+Q|WE}2qerz=PG@X}33mxCH>z?eK zY*r(d=Y3!y9L`^w55bnZ#WIY3Bqd{ddn$KSmppOQiJ=1vyhSRZF+GglriBMRI5Rwi zaT|%gczl^xwryR>kmei<#9mz30a~x+C<=?nm|0W{@uSgOmPSJVX_F4&_470dbUJD5 zB$;8KT;ZKBMfDZ+ZH+uaW5A&_Yb^Z;id~A0`GgO-rLrh`jaRi+V>ko`pabeXuF-Kt z4{p)DUo&RGiEnC!z*5WRe823*WO>}ypw8#PEx(&KIFD{xpg8`O7jh#iF&OvYI8bkG z%P0qHWnQ{UIe8Q!>+6HwR<;hcaBcp`e0XVoHL&IoPh#sm_;Sl0CZhC$iT8~OY~bu{ z+CxKpk0@Dl3Wqz>Mf?^9$tnBnZJsGTdiR^WS6KK^<Y4dAZ5A*r#=4l8HvS;HcWOZv z)=Kq$P2?y^F{qZQLUyoFMSVDm;q2lvR8d+?`H=kO@lcQyK?_A-UPR6x+Y<xm6!ay{ zjPAM1*`~_<`C6?^f`h>b|2bDyn($ri;2W!wAFnAWzH4B4Rg+td9_f8fEVh5vmM7IH zmMO<%(J0u;lr5BGI1egNdy{%;>_9kULnGL6wSmwx^xJIRpz<KadS#cwMvLoUNG+tj zbn5K&;3&Z<7N}A0z|H4>^K}e6yxfa1(O!9u^yv^aGn_pG>~1TQ=Fk7(Lf~}2IM(c_ zz;R0D9T&zt%H_DSx$F3_s-k9fJ%Vg_J*(2O@P}wkWeD0Q-;#I<QYpa+xs|y-5xmBV zVS3;ZBls_XFOINAPVv)RPrf_(V&w2Uwc;r@vF0b1;;MDCa^q7LztwNUIU);%6a#wx zjMbEDh6-)wii-<b0mT#cCO1*Bh>FpSf0F@1q%Ig!b?Sd*I7B*f@rQbXX(R)**@LcY zM|d*IE25fI0$wwH6X-CJv!(wwdQ)TBdPT2iAe&?=SY{)0z&|3^;2B>As%FVOKP77k z;AoA+#N=)2>L?hGaqpI)B$4SHfumbkeYQZ7JIeZMD!YSuR?rAyG`33;PgW%yzvK;V zXy(?+0WndIr=Bxm+E<@Amo&8Gj<ZcP#WUEmVD7V<2k|@4%AI}W&Dq_j=F7=j3=%O^ zQ#^Zmc86eH&)XGwlPkZ)MIAy=o32&BX2I2_l|rr`@4rpl6ja}N=^r9q&p(GDFy$|Z z&)ipYjgy9_N~>3h4o@ngTIShDWW}b03B}*e+iwcf&fXD`Vbb$~NKPVNz(*;4o3~Kx zwhp;kgL)y{e*yg@+t`7Wby=QbvCYwLa}#W7C?zOyBDk=8Sy7&c4GnXML)~NxuYDS@ zA*f!?LwACiDxVS^V3F6}ie#{b#=4XNaxF1di*G)!|1jr2w8Q*X{>(5cb4d~52Y5s4 zB}wxt0T}OMs|}vzFzqovE*Z{4w6=+fHUz#aoj<&#k;Ag8l?O7u)twj$PE3cSQ8P~G zI;JF~#_=h?6zx*E+q0!K*~~;31&RKIH^r(xy+<98@w_aY@W7OowyJucmjfC@x4YB0 zRX2U!u<GOyiB)Tv=af&W#5j16>mV@<HYAP?-zqa{15n4w)QnQgZgAEzrctEO%CSbj z=7}cfJ=ZfG%2DP?ZfkH4Ctr$aD!P4TWltugfxSKo)r$mwY;SZTl_*MwPSD`a)+3SA z#ysu{4erzvdNXNtg=9Ww{)1Vd?8{_i)5p(V_fE)xpHk90puyJj3jNIA4tVBq`^B=t z;3t9jgZOf!Jo?)vI@;_$l^D<A&IHRZN0T0V&snnGT<Ub$z~~d&mb7(VQzw{qi8MKk zf-0>SF%8%vrE-ty`B@VrQDXky-O(fMV!T8(^OddUD@h?G%AjJ|+`oXp#0*<kHQ&-9 z=i=3Gm_~CH43~@%1KW=$+p#!Xe*pw2$(LJcqs;K(bn*17HITcz&(C2d#K~F0!%ae| z`ve-Q2c_Hi*=vr#h1T3cqosENP0P-|9>Hskao-{JDo0?L##FA&aaJv(f2Fm}dzwJ~ z_$;gLhMYvRxD%U3@#~V05|tXGV@K-G5yj8exC$NkNkHlax?M1pANW9dKSe&!kr0AS z*PiD;8rSGl6>thun?#pZ$JCJz!R=(a8S8vvxSdrZNQP!k6NOT>rMO%QZV`eQB4|Sc z9)y|Tr`8K|#)0}i&1kl97?_ISfj5W|jQ51r`v3q;Ict2H1EQS*_J^eLxrOZh1z<6I z5HJ8R!d7;%<X&(;VI%9MEe%_e@mGvbJj?`MFIP8(d{z<~<{+^T+0uBNVUBhOrxfeU z#AK=kr$kKau_6v-7yPMoIWx<{3SAr%xu;0{8Z%d)`Ffm^)W6w{P0zlx!mB4Y-%9#? zF9wp~>NabIECm&Q#fj7g_W#BnP^(~lAX+n)%774=fN0_PX<S#?^r$NuGdZoaG?*t~ z$}W9v8F#9b!44C8Mjrl(wVa9kv3HXUNXaVA^Y0h3?!PFNu2FJ`j_uk=zJ-N9G{5|u z(h98Qm15)+iX%t};T;(v`k0j{=DN6Qe)g#M7l7?EATTuk6>cc?@Em7n?na|G8O1zn zn5yfkc9C#JKeQ&b7Ba7QhF=1_hP=p^A9~xVzYe_%AE*?Rbj(>d1N^iW9MEf-WywWP zUXgQj8|>Er>M$lfkW){^eX#IQnKSu~+`p`^y=v$eq<Hy_pHKCsGGcuj`Oi6(vy>n& zBb_lv+<Q|bO>seLLbmYHzTDaHD7#2O(bFpmz@6`lQhS$A(S95U86Y={NJGg!TtkL< zM!1dbB-EtbqPof4<1c`Sr+0?L{G-h-lS-wz(Czm>Mu?|p?kt3B>a;lILh9<w|7~<u z&DRHT!sML4$f-F?mX2TANg%(&%H*X)g~9e=vH5c>^3N+1K?@$yZ_GyWab%j%2fpDN znvS_Gd9H1$=<UC|oS^Q6I249;(p$X1&{G>=Jlx-4F5Sw*_#t}Qwb=aLmHp-+ZK^1D zzrR^-G+6%)D=NwG$<`^YHPyL;)|5TT;(#iViH3VS`CBw;T$A9Fl&_rqloWk(VaXX; z+xZPynh(9;K%eo@?U)FTPEP5u*FvUay_B9eB9cNxkq(s=4=#`J&SeeN>cnZDyVD>! zKXX@|JpW?GlUg>W3KgH*s^=^P<(FIN^yku>vW1=8m+x3^PIR3;uUBxM)9Ntso63wS zmh`MUr)7d)sV4#awvfcG-{#xoWSVKmAb*;Z8K!=Y!3g6?Z_WqolcUDHYu1eKFFgr^ z#C}%Tl{<THt1>o*Q`kgg=^T#BjwQ{xZVsGt?=Pde`H_52=Ve{s7`p16;m?(Evo>cq zcOz+TI6piXM(;)<rv1{nz(qsx8JY~Cmxw!esjefSibEoQnV4!#g|8&*-(4EFq*Ko3 z+HpN^{@XEc%&+Rc+DQ$IJ3BS!^IXN=nzJeBU5#@ghhCP4gMFbHt6PBHcgT=(5~S&E z@=m8o2ky^P!4TU($H;FEK+{J3*>~RPJcmp6?)+KszE=O3UTcC+%}j#f9gHRnXs58g zi--x!A$!%*J7zGP!SB_^WPI4ePZVoHj~DG<@R3=`<J`Cfi_+gVZ@c)Km`##e$3vl6 z;+G><r=yz&tS{5XPsOY8HQxC{sp=Z*CeJ%pzwpb4?(utA<0C!ooPQst>m(I^T?D-g z98^$QI(iBjaJ|htG)md9^SAgXvUf)`K87X0DTiWi>f4;`6>lVfdS|x8Lqf$y$vk?i zj#qKZR6kFP0v4D&=O7EDZ{}t+;pd_LMX3@KKS`xy>0QPnM=~G%MDC(pnq34Og|p7s zMwdw2;LTO_(NII;;f`cQU0NPEO+FsFoer`4CgQF5F{M>Q&@2O=Ldt#^?U-{%rd%Bm zgyS*7I6t{0Qxo##daXQFD23`m`BG|>((P%@I{)b#N(8g~od@~y$k%DzgS6V&>hmYB zqdhN0%+*|<=&Xk4oDbtOpZWujEtUR}zix0Hd}_m(B<&j_=O)Ofn&g?;yNfHPKQ{x6 zi8!&xkvV$3dUKzcbur_}_WVxhMd5G*Jx?^qo*P%B&&vO0-OK!XL!aiBSHbBw`W-PE z4MZM)oI=Fcv}9QowS{c-6H(5_BT$@?Yy?x*e&`!HrD;7iIR7TFNXMxdujw{smeHG* z)YKS96T<x5lSJI~$$RP~xziCt(rN0d?%fm}5b1I}ywrspR=98U_)W+an{527+t~M7 zw5<7Yc3)&jM2$beUf0u{M@I~nQxn*D!y=Q_kbF=O?cwmqbI_d#9PE_S=Rd4Jnx_<u zWV7zl&E?x$H=%ezzJnsh_GTidLa@vNBPjdKG6UaaO_lK56y;h0J>|z(p!FACROQg1 z!g3_fu#WJ|XQtcA455<5oU!2~dG9MiYd`WqU!?JH4aUbqEa_pSfO%K`-)X9jvB!+6 z<)q6~AGortp_`w&A>kdkn;-T+GDbMuR~UMe&vmqL-affjJaNt2Kwia%m@ohOdWlD$ z1sSq$6!)sQREpW1ay!QTq*N)}G;S~iSV(9syO;`gPng<Q0k<7%FVbYUkll&~iN-jj z9eE;KH?*n64bUMwBPs`@-00NzM}^5-9Ql3bRH;jn?HM@N?5mmOU(#2eySKnX%SN?Y zgA%iwphiVEU$H+be897b`+>5Ooc&^0MA|Y{(rlv@Rb6J9QYf?fLo!FB2K#uT)LDwB zDuvYtr&LL_Z|rA~xHscDomX3)c&?lZEx*?8m(^-?!c|7MwApXUm%d3<*jY^jPu!M& zPz0lb<5yc+>ZOezG@lu63zzRISYrQI0I@7i)7q~cmqwItW2%*Vo$Y~JTL+G7A1?ar z!fnIEmgE&XQ*wa3Gor9iPC3uYyPC!A&Yw1;Y4%`}879x!8Hr1Pq;AO|d5l8_CwW)e zN6c_)lf?zI`8ReJQM_`ync5)?cJeqS7t9zBu(G!SzF9Y6O4A+<9`jZH&W=Aa=ppmL z{{ULUeoF%)ft(YNP735<ffe~qZ;Mo><e$%`-|*k?H#=y-N!^}u4N9EUAahnCUTcpJ zol0`G&s}ItiJGvG=d}=~YeFcq+P?29rm+cJ9a|ol_i}015IZ@vl1pI>GRod#mJH@e z8x8WX4!gHxuI9%8n$FcAvsvPW;91=+2K$FajIn7RS7%I?*bpjb9W#zTR=IQH8`$+5 zhnsU=I3Pi=G$<E>82}`sJ0k8bKJw!wFe={JESg){U))^HIC0IqUQ{n3l*YumkMCos zZ@BmwIUbcPo|L`iYo}*zb+_q#Q+-=J+W3`9KGvjKvTsJN)z#E>-P=z$x@_~K7CTc) z;;%^`wN`b-e712@>i4nrR4QDA8hV;x9Mh0j#LqmEovwOtC%TebPde+*hUy!J-{)={ z)mSSo7@T7}8)+PzbI#(rj5>>Ju}5Yjwz5cxi5WlufPktV=LJUfB=Y?T7)b6TySFo6 zOPgpFvm}s6s8w6{e>#N#5w(ZRbGYZQ473wAqXLU^_c5Vutc9h+Z_fRI=Wtg0q+lw8 z_mnT_*(~LWY0IK(%eVEXWBKzHMtCY*)HbN$WzLkMqbGPSMLt-!DKwn9uL*3RxwO&@ z_Nhg`wOx)MYMeklE7_x!#!$VF=y>8RW_cLxNMprmG(2>w@<)pK{Mv%UrqSq8t8>f$ z0O2t4SN#hYU-!k&_|aIO@RC9Q06#YW0Qf1i{{Z8(SJCn>`Sl;VNAN$@{{XxA{{X-+ z;EuE-kBYAf;;zSxSLk^zCNP4zz~mlS#YH51RjOl(?#(fAl{aIejGfMn@%D>_l!@iG zi5TiJG+@N*8OaV8a*fflf<eeCMhVM4;z^LPk`}bEDDlc74A7`jQ|3@f3y@9}4Z#L} z_7ZDPC^A@F!xhqRcbzVnNh8Ryg25M#r*=sjPd_)NCC>a=TijbiGOCnlun|PA!JTk7 zGP`oQAPk-qZOwim#57%cp6_m*wft_6qQfNY)9w9s-|jfkA2ntu`Kd%a^r_>E+P+r{ zMrxtfs_07ueJe^B_^QyyiqMDwU#;-<RRpeOIo$1Z(R~BMbuOBTEYmfsv@&8wm(317 zd=Y^lROICE^aBZ~+F0pPEw#|NOZjC<o<Sjb6saMlW;xu<H+;Fk1A@PHi^*0y3mr&Z z%9BFgNVrKBLR%$?%I!P(bF_d6!DF1M)VI0K?DrEfn$3pav8;Y{aYkFr^9f!v)w9rz z*mokmxZvX)L&emSp7!^in^`5QZEJU?k50!@sMfnKzwqaqOC3^{KWfx<%~p8x+P*W1 zC(nBw*lLzD8a`=KK5E&Ao|NQ=o|XAkY7_RCJ&JX7I~&N^BO4_W+szsyJ<Lq&1K^^# zV8kHH6##NPwLIXpYg;5<Qp+meGe&UEWl$rN%wS~-K;z6(333h?WDY8bfXjClm2@O@ zQ{|tt$YW+!B>9_HDih64qdRy7auikDX`pM%ai>O;+=xuf^CP;kB%^19`2vN&3yi4% zWb^ktI-FE_m(TLP-`7KV-NxO23mn{0@m559Qz8Pa3}U}r!ebz*oQh86caMszEOns3 zRe2LN^1MM^PA<ozhnc^q+vY2)$yMQoakf;NawJkq?aFXl?xFdykTS}7aQlgGblIZQ zAd1r76l9UsNh69!Uzleu+ezA>5~OWAxM9s`+{<fss9HxI@RHtG{z)JAgc4L3fFZXZ zoHiJo5Ey_L4UZ%iFRM)fu%0O$nI-cuV}o;?0l)#2E`Cx$RbYA+uf%g26RS6Mr*HUn zw%PgH=kz#vH*5a@U+~|V$Htu1_#^2}jxI+Pb{O&6yE14>6IUtKEI}P#jy@^~R~2$T zmHSRjii{wxV&!wQ@Zw^^Tj|n9GpMw5x{Z@^l|YPkhX4_f1Al$KW*>G$)<W1%2Amcv z+=A}{A>N}S3_@TtoMQ$4W&=EfL;bQXBH}34HPhW584@YgJgu#_J6j(kNF*60iz(*> z9LSOu+YGiA)=C+riPk-zD=#=#$2&xd`Cg2K0x)Zf5lS?0t`%i>e)rKUEBuzK-g{Xc zu)O7e>(QQNEOk-i=CveD_Nwg{wSG;+5p$EV>*4AYM10klqvoVodFfUni;=~A#tw|t zL(o-pJ1sdUx{^69og<DUjys!K;|1h(+#Dhx+!rsNpc3Sq9!GYUw{ph`NGxvr_l-at zEMsbt%$thtMhW~x<2!N7S21oJgoeiE-dp{kA1R%cB4tg^jCqZY;&-lh5(&v5E$FAd z8e^=M={QAHiV&=?3g;^1uxwxvoz4K-!?k|1k+m*YB))&2_0!N*{pOncjyYrEm80UM zUX-U8ue+~|gQiV6JCtRPsmmQ|I_9dpdF@}6aU}WgW7xw~7GJSkT3X9|kz7skO?M~= z@kBq5mIQ@u!*|MlW_)8HHUzfKEG+{Bx0TX1jp6cucL+J|7!tteu6J}Lt65H;d3<gp zFi5W>X3d^iK2Z2XCm2>9Vh~iWa5KpuvRSXJbr_<XPK<?>1c+42jj=R-SZ&!-R4x>p zhHMSI=i(L?q-#kfub=7udu)Dlk8~<!^!=Qqpzj^;E?HVK=h3V3%iibro0P1r6w46D z(~8o9K9vMf;~Z7^R~7V}I@}t$`t0f!VvmZPMlnr{3{>N#ezk?hK~vPtN!;?-=D4uc zrj4zqYq<!OnWu^68DKVTKqKGfuSP0W8MdCHv9$<gw6VUIQL<INhBw_Es+JMBAgC-D zft8i|j6ctv#GTXX3@x-+wE2r#+*>Sn7VyIk)eeeUR4T!~Wg(9CAdT&X#^F;Wx}DY3 z?=9WU(<&fo7jz6{WGqI|PSN|aPTicTQ;o#ewVzEmxklFctL<xfbhk#InfzhP7ic@V zFI!nT-O9@D*SdDmZ??XPv)YQc<&R1$`O3`x@%D$$2)L=#;*hQ>Ghem;0BikUOP}YI z9nQYnP?+4@+(oR&_7O)IVEAxLyRy3hj1ltg`^>{|Jn$A@vI!uy(~w@=&P-5kjx`Yo z+aw7X4Xgnra56V!mibLbZEZc?u^iV{@Xa)U8-!^0L>S?alE9F1>;ND#f};!p++FI9 zmPY;-N0nw%Xym__<uT;8{{Wdm$P7UX{;X#i2IZbQ@=6bX_-%XZ>88iMP7TYJEBf>P zwmE|yX*`VftBD?Jb%Q;tidbK*YIDN3DVi<)s#Jp=t4LdWQ@o6Jtlmq>?^x@GBIiVq zNX@d==q$7B+vh;@0LY9&?jsw4$R~m@asgAC*3xO4Air?1WxUBZZ)p*OkR&n>-Vj_i z5FVf%!HCSUSlixQ8$`NKFUpbu6|*rZl1@*|6sg=>b}&S04=uKs@=qRwV7Rxxcp@SN z7P$^iIaU}`weo}(S0FLT$rZY-^*75GuAWZcKk0r4yG^OLZojYR{{R5SQ%=rm5Nbs^ zu1CaI_il5+4l9vOF=w?*i<+>JXSHQ1{cP)nPE&G1s0eLdX)mLaSlPZ;cHA%oWmN4X zA^q4Z$?7-;y!J6#OcvVS?nokpD;NFQk#Kl-a140fz_1wFLgZ(i1i!Ym5YKlNt<Cea zO&|JPfTFVbiVpT*FzfXBmpehlR)*D9;@4bAr%0AxnPXr{Dp|`DfxO^90DliG4;4!w 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg.prov new file mode 100644 index 00000000..655bd065 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/meta_xmp.jpg.prov @@ -0,0 +1,5 @@ +fixture: meta_xmp.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 6beeb6cba6d460af850d0103d3210e64765d670a87dbda5c5617deec8c482d83 +regenerate: Pillow img.save(quality=88, subsampling=2, xmp=<XMP packet>) diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c22a866fd81962550aff27de166089846d72e9f GIT binary patch literal 23810 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg.prov new file mode 100644 index 00000000..7c4698bc --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_420.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_420.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 56a386d62f8f0102020db6c11d2ba97fc93f3f775a4653744b7057a08f02a159 +regenerate: cjpeg -quality 85 -progressive -sample 2x2 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg new file mode 100644 index 0000000000000000000000000000000000000000..090dc7ae8a36343a2954bf3990252a6c2dec686f GIT binary patch literal 43764 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 R+)#u`5egUg{(mxO|Jj0}XUqTq literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg.prov new file mode 100644 index 00000000..790c37f7 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_444.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_444.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: f19b6f578043f336808342fde14acce074206eff50284a7a4e6e03e989eba1b7 +regenerate: cjpeg -quality 85 -progressive -sample 1x1 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d4ec3a2ab0af1df39c459b618ad008157e33c58 GIT binary patch literal 18111 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 ANB{r; literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg.prov new file mode 100644 index 00000000..3c316a68 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_gray.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_gray.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: a865c757ef16dbc30281a7a81bd2be40f189564559664fec6bb7d1ebbda24a45 +regenerate: cjpeg -quality 85 -progressive -grayscale diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2221a35065961d8e7906eb433ceeef0ccf462c79 GIT binary patch literal 387767 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%0A&;}@;u#c>Ct;K#G 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg.prov new file mode 100644 index 00000000..00e3ceef --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_large.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_large.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 2b4cdeee8298264627c1ac4d4cd246bcdf399bb17df310714e185e4aab1be708 +regenerate: cjpeg -quality 92 -progressive -sample 2x2 (1024x768 synthetic source) diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg new file mode 100644 index 0000000000000000000000000000000000000000..700663b7238c19cbf9ee49fad052a6ec641d56cb GIT binary patch literal 7921 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg.prov new file mode 100644 index 00000000..cf2aee3d --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_q30.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_q30.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 8d81d5e4f2698388af0eb6854a27c49c19b85c9a6cb0d53ac57693e3b03c18c8 +regenerate: cjpeg -quality 30 -progressive -sample 2x2 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0f5432f1cb72c4c26404351c0f2092f17c664108 GIT binary patch literal 29094 zcmb5Vbx<5%&^Efb+u{)1B?NbOm&KjMEx2pY0KwheAy{w-B)Gd1WRc+R!S4Rvx9+XF z-+$lPsXA3NbEayZ?tQxZIX&+y@7n-uMOg(|01ONO00aF2-q!(A0619K{|@MYhaL#X z2ng`-2&hO%h{))u=;&yuXlNLiKx_<5985GcYyxZ?Ts(Yyd~_^AA_6=jARa#6e-D9y zgRX%`KtVu2!NWkq!27=*?|lFqWEeP@5I7iW04xp+91hI;0N?`v01F2L9f1FLz`+Ax zU=a|JkpH_1^M77NK!k%wf`vf_yl(*D0haG;0CYI$P1tbQ&=-En39!CvxgZSHXtIZa z6yiLf^`?mi2!&e6wi;$zJgl=$ac%ZEe>OOp5QroS{09<p`T3~;rf5Q+szd*Y!g0h8 z-|R9D`JZPFvAx32wTK~IhN)PPtc5B<Mo{38iVnOF&jvQH$M+hwo>SrvR~9+&R%`5C zQb^8yLwf~GPh=+Yib}*q1*^8%Iny|8bSE*V?W8svY~|rfe_I^!n|g9LIs!4{5GHpY zqCpUzlu^D4|Boscq3gcME8d)J%ija3=Tb)X<IVeW+`Rc}vwsN$M1JjYIx!IG(ATYm zeL5z!fbm2>ArPEK0tk6eyybHdz7;*r7-1x&`Kx^MkiX6*_2UsNXWTM|{IVYGE`MlI zGIU|<azFOKCCwSHyF^k>qYxPVq_DozsS`wsH<Y1A?ZSQP$=sNceKTPdvq4<cRw}c> zKDyCMi<F_<+{hY}(d9?!fla*1yYyLHxYP3;z?;;E^x^JfbmwzRzif}FLmYC#M1TcH zr^|jU_Jv6nb>tNre|F0>aNQ5zq4Jf@<R2;d*isSspn=&}fJH#7LeKGG|LSH}-^dfw zP=9*;v5n|XL0j`5i{6Q}Kwa#$20{wmim3`KYwL><l>{sQo8xoSsjkLJ8^5)Q3$OjB zm%l1<f2ZGP9<Q30vJ+c`qW|JCYG><Gj<tzbjDODOY2f{3llB!*rpHHJc0P-1mDxEU zkU3D6A;~;j38!saf|9-KW!!WyPPBBIw+H!BBq2>4v3BB!<j367<~>9g`K!Wza{DRT ze>T@i5|YfWGcBr9A(cNK{wfNzq>u?HD{O2GNc;ge${SF3`2*%SH3Gpgt~8MQdD%<F z*Ygih`}UiGq4Ro}WfX1(g8#1KDGv#Nk0VVBmwVv4`C|%5*fj^Qj}b{O4@WPtk(caO z^GvHkjrl{?z!4kC&C*cQ9QSs%&FjrMgTiYL-9Jha0-GilEd0*_mdAt!Ps=4flsre6 z`qq0qp*c<hd}$3NGY<$6XnA>#qPZ(&w_^dn!Ai=@e+1oPiG6fd=`*rakV`D2Q#-M| zL$3s-)|D~g*VFMon>+J3hNg|mI!&w&AnB?eEA)8qoQ+Byb&(w;>?LkaUq-v@{Q6hy zD9~3p9m%-5FZd&e#>PIYG8F_@a}eHrUSxYncHeg-{nN3_>tM3>lBu^>rq&UmnSR1^ zY6Y#9&s+LXoVl1cEDLYf_G6~+?MHB{R<5Q#%T^XC>UUdX<5e#0IbH_ir%Q)S!Cx1} z2>aJ1qkT($a@*&UPI}XuMLXr=@3U7@Zc6F@K6p=js)rpJgK*FpuLhEsM6{q$u&*1E zh^uI*dY(wF>Q#_)v#MXC%(pgr&rUswylziX^N$xL>ozX(6#|V5@^c67Pc1F2Uyly7 zOzt4z8?cl~Br6VqWS+8pMI)Qs;Av{}P{W%SQ=?KMF^N1p?^$gWw72VcEte;X0Xa9> z9S<<0$vqPJ^51Lv@;_ymIX^`F;iIIVUf6zs6;j(BdxIvdJ@$gdDx^%VZ}ilEo)O7p z`dtswV9v3S4Qu)(e@`_Om9zNhzsGG?^6*3Bi&4d%+l)jm`40zgCC~JiqKzhy;GEXB z^xBnX9(q8_gspO`c9^K+`0}i8R%5X6=Ty9BkB_2PhkG8mZvx(uT~;jSv+{mOOW=GH zfhOIxT{tk3b4~fvQ)95`B8SC1H$AuQE5V6p?c*;&TBQ?GSv2RFe?cstf_$I4Y_GI8 zVX4se#nM_}+vz97@S1gdgyr~4>2B)3f6c;L5TmD4q1c;x-SN=;rcBVybT+;-Y$h~O z`m{z~7;_jAAC#F*Sk{5aBW2>~cO3*_S3`MvT&0TU{2|b|O6|SMO|O8?U#>v*RZ(Hp zgu_!^;wP4g8WH{3oaDw#zkTZB-W%9>-sIEi@mNt8ERR6S`<w$kvTz6-I0PSciFBsq z5Q70FXFp<bmJ;#r!i5*3lW2-j<hp!#-}P}uN%Wc$J26@YV&eT!1Sxd}%%=D%rp1&h zFZ1eACyiY=u>6OIn}ln?WxSFUrd4G4E*<G=>n_xpTc?r9+7$;qU%3e}?yi|Oa?~b! ze4OSewWiFq8}maQdns5mw#5;;t4x&lQ%UT)Gov^!NG)!C!Yh~Z1bi>|*JhnZMsZjm zOYjERbQ(gy5kP4TYq_PBAbgtJZ59qCL21;~!dFN)al#hE?5}3+3w1;XAt%M$7M3k0 zGH0E>o<TnRhlE7z*v^-{kv3i7O&_q$Z~7sLB~6!)-7OTVMl1@lp|Cp!h&3G^(diaO zL#r%EMt65z7=OE_f{4O#$)-{qg#XPcdBZGgM*x2=UgH|N9ucf%fdd6B_pSvkqDAU$ zYE3+SO2=h{9-q_eMpFX)cv(xV(EnhG4diOaT^u;Atc)yLieI{ef&vzeMg92TSNDE8 zc+Edgp~NBtJ5BpCB5&u|yp9pyRq)?`S<%K%CiR&_x%6Y~oH{uq!Trdj#mm9E`Pu7B zRnZ-WwX^|Eh_gh(w5FN6-C5lwm-^A_*@v%_7zAqzK@)l=2EUMCf4bkqTJ!D%zt7*> zB==4jR?+25hARH!E2WL9W-ymE_1#I@uW1(GYRKv!sC#Ooo#A#iP~e}YrzLin)=Hxg zfhh&l{ob>G9W{|<5dBL#A>Jo1IGt~=A|!d2LAmh3TqdVSEsri#OwwH9Pi``2(97$1 zit<RX+$j6UVQ99%gbjcLz{0}9z(Xgv|H&s{VBvt+IPkdC2$WPbc=((gT#{1K|C2fl zKvM@~XxagT0Gpl8cMd5kDiIOW5wW2yR&O_j8(nL>rB$Rug)k+o3`NGr=ZwauE)==U z;(Cf*h)EY~FuFgNM@Ghn$9i&qPH5>T5o;_qvJhE^KuR=MSx4)u)guPiorBZMi<33@ zQ$F#XlJM~G&E5Gn6}cy;{q@v$w`T(4Y*^IY674N4<R91FI;1CTTRne1OLl)KkkYIp zGp*5Httu+|I`lfb#QNtJoOfk^LLb?^e_p{fTU1mPH<6NbHdXOfVMvnaPH~g5EBG2U zs5LtD;ybf&l0c)?^-?5XI-|Fq8OIV%Ygs{0_wSD9fMQ=|xP?lgsLEATa?~4L5-)Fw zn$DqWna8Ub&wRA%ea0bS&!6psER*zT<=*fov@UmQI$)Hh-qqjx*2u2|lB*l$pg0xT z*!h#3s^K(Xv}6jooO?*63RXBcziCPJx{8N{gwuoh-Y7X9idd_6P?HAZq3t`MiF48K zACL|hdwT;OWVV6=T@pAl9c@%ZQ&h4{Q1dgH4j3)F7F~6<Mj{BLW4!+4OeBA3Ydb3T z;X9ZP7$4WT*Ew*w08+$x$w=?TTl<h5BUAj9Cz_3Dw!)Rjd@WhM{irW@ZQsqF39o>8 zQc%WawrY>5KX^!KD3!`JdF}V#+V98qOmyVElzP2MzCRebc?5UqKxuk6%d+K>;Y!Ce z%l96#SfI+v;i_fP!L_L@DDYJj2mWQlvgOR4Uq@0M?!7xQMiX?(BP-;p)!t`8Ox!%A z<(~D(@*u{Qrzf2HmCl^(jxzel3Q32u7%xe@7}VisF;IMm<YwOX9SKSH)gEXvLDXPU zULIs?>D0)ogawSCFG<=}t`Cn&Rb<}W@Zjd@!xwEms^Z}xp({OJfS$$6Jn(r3&`3dW zu3VDyRAeJ#r<Y?ceVU<8siN3f>(3-ehXPEl>|L0dok@x(xLs(DkBm&vx{LN(fV$`5 zJ3v+QuGxcDQLsvlj2Q~2t*eSs6&)}<K6bT+A|cP$;6MK+68>Zwv&8B(Gds&D*X4P- zV!N;aGUpin?lLn|Zhb2cQY6G;m?j(9gThbqb4jLsC{8+eKxou0Vxre3WnZ|<o{3&T z+FCYvV=s=3;(bo<ZXAD0ge19ZBz5BMo_o@do0|tH)RJ7^Bgn%o;$#gne-=_6<+in_ z>pNl6&iV~SdXF!GSE)AB_ax4f%|jB17vCzVX&jh1KVDi1vbFd4+}LIg#LFhp=1Z~L zClwg#{LgwHhwP)e+Y?J?X0DpRHN8$RR5Yux4&e$+`eZGYd$!iEq(W{`_td;;9Tml4 z%sQrBuyu&b+ZuwN(-_s~83$E_L*KocgEgpoHJjvsB`;5B1A`0&7^`&Cb$83n&8=#= z3({beKdkW&V$K$%116*^be{hsArU08O}k1}6eM9<*4pGMqSj!%BK0X58ZBUvl9H49 zof)gCG5bDmyKvPLy#sptF%Xr?=K4xH5H)^VPZB3XTdeV_mY^qAc6Dh8B?RdFxxU0F zXQbU0IvKnJG<N;9QfP~CZxb-B-;#DVxCMCdQf`AO$ZY<ZM&}4Z;)q4VcI4mUc50aw z<e<LGxcFv4Vrp|5%F#7kR9GsjtjJ-4czU=nGt3t2vz(F?n7tH`FQ;cCA)i6<GON}T zr-Fe#x@+k0;nwgqCH}@BiH)PWLeCS5D8zbkD+x%;#LX?jl^Ipb-!3U*H@8M!VRYf0 zK*ngVf`OrIiahFbLKz-GGjh4)8-MrwvD?~i^x{7g1zYW7zH#X$^L&VNN!#_8V9es- z5pm*F)$OBAm1ST!coK}mic%#U?tr2{vM+vssa#BlOZSxCWHq=+AK5P1lI;3$3bGd< z5z9_^Ur43(;1;|4Tmp6CRPd(q%I>fV)HK9|ioj!ynVH#aF+$ylQbjrxa^T}9hVB0x zw=b_HIr~4x@H1L)EQ5M@g!VT!JD=TRnSPF2{mRl*h2+kt-W(DhyzDa#CC_o0g}sr# z<l3~w(jo^y3t1>fqtx784kaunZ<w$qM@#PV^`q8iu@t!y=P%X=l4PUGuzpQAT?h_s zWc@6k*xr1=dt|;aGXs*<#_n;Bk5e;XArF;2;BkoDy@6Umet7S52K8`A9+^A#we@I0 zCG<aUugWy4(h?TVM4%sPv0t^!|2J2$ZXuh5B--+XSM^X*wjyD7&#9?H@R^LZN!!wZ zgd`8+9YEQ&T0~RwzF5{-cSoDb#DT-iRCF0rWx3<Q1ce&EJ|)Ej0*s(eHudG!CpDs$ zNohGgI=1KL;$_vjsu=}FCn;3<%-;|ug#jVJgk4{&<?lV$WB<i2UpG>LbZGDj@6n7_ zhQiHlB*<|bEnTdh6mPv_I#>er>-b*IsmRF4*qAaK=MPY{$%tKr^@W{k;N+kW_!&o% zokIPT*|lFZ$|)9;sz`{}^6AAM%CSWC+U~egQnKZE_fd~ygy=8MDcjJ~z}UyeCuw0H zD(J#*pN8xX24}0FP=)U~nTn94)lMvfN+oygYwQFUW<azKb^4!sc(^$P?E;vn+*i8X z3kyNiKY_i84VF+6p*X3w(|=zA+49=voSbGfa{ZW@S&klSGIpp;sw!F-spxGa+CL$- zHi@h-(osqE8i=5ejHJEx6;(^x-5nm2OK*C-gIM0HRv0PbWxoW(CP#M2?UxZi=~znU zw%qL{i4kP4L+i;#ft-wM8DvtDR43{f{~wotirvMiuYCEX6&MFfhKKnXNEU1l3m#v{ zFfcF(PK=Wnp^gh$H16qyM!JL-kY@CMan7WQ;m?0z?q<t52nw)9J{@a}Ih00#_<Q3! zkip~YIYAN<u>#9wtNN&+uk`x&cwKJMgv2JP2ildTS#-E>UTcV7=}X7PH7-XPjkxXC zXMg{XKjU(lyJKLG#K`PhaP0(%()E)FiM=mN{v|v!eTb42u62*Id=6}(XHA=Xrq@bA z)1pFl`^nvsuMwJulbo@B$Eu;{_V{=OmL$Z<PiJnMh2~CtIMB=qnk^w9AR-|AuM`6V zivtIw;*hw6r`7<wV&jG+&C-Axe?zV~B{hrs@T8K9oBlr$=09NvMhvDHeVB@)iC!dX z!i}a4j+BaXoN~gt@dr)8d|m4$$B3qoteqOg+9JyZcS?_nV?k<1#bIKs<2lYBNt(oO zP3(A;`tYZc9`-asG##ay#RACZREp7i@!Fd?&22`&{nq^R@ArS(%WB*>n`q~}8S6wv zh2H^f2|2PqNSPlor7bN~qT^*g@=mx>kJ3-d3J^W;U`ld~C{Jy2w}^ay^!fL6d3TE! z>n0@m{E_3l9xEa8pZ0_O-o>{MakBi6XjEQugDzCe1QO1_q>XXXvQMSWWz9!4n`r6e z?9wZFrn0IBq*&51_6@Z!#_d}?9`+%m{>1_qUJj=PbHUfISno4SvevwLv^3fjeJ$=( zEYzBwm6}x4L~j>A^b^k8eudr3F2izBv2Zd7RXHuEznEf)TE&=8qO@PMiIMd>nIh_2 zS}<=A?Kz0(<C5~&pU0a_xTbkNV<%O9OWu~q++%3DCZ)DAxfU$likzOx-)!0K!W*V` ziBDtnd0YQ{u#E1wAbQ6nN{x5;xuw@QDBL(|vGd#Uo8*YB4&x|K+l*>N;xH4><C~>7 zc2~|!6ONZsXMtD8?Xohc$dVxg<o<2-F^D4XLM;qCk7WPg`W2aTRLGt&Hr5Sy=6Uvy z8x*+UiKxa#d&u&yVH#50xfJ6-IciRh3xi-+juF??!rXutepkcm^>2?(7D)s)YJ^uV zum6rG<=$t*q3MWr)>Y0^hNb*{?a^l2jroXu6Y&ijt|b}H2-|XAaQmC{Dvnp}v<=@4 zPllzxY>?pAWMUc`RY9BO_`T@0?j%EhLLS5PSCU)Xu#!*C`O57OV(yKP6~SVpdk?!k zM<vGThBTa*14-2pi4jR`5!AR%bR;7ap}p4C3@MV!bjum0)WAk!FxxcR@XMO-d9wfa z7SKiypKGwUXJUAQ_ntnY#h(RU`;re)BVLOH7S_D>ti}h{0wmGr_tX<^T+ba|wnxrN z>r?sDy5ws9Ti)9O1fVzaV5Qb~0Iqr)bz3PD6wwQ0P%$g05rw(P67N*wxft(I^JfZf zBTjtkeqC$5+^FM==}yNt$}(#s(7Z~`qjQb-aIn()w~a<Yo`Uze;ZJ{A-SFrpuQ(bx z=M+$_1@OLWlJb1m@LZ-w|MAlwCn+vP-S}Wsv|t=OqRC;V_R&H?Oyq@M)cLR`gn-w7 z@Kru|C4AB{$EowAPFh}G)7g(lFCO30+5u1V!i`eWA~iQVFc6Je!tSJ+KuSBU+?h37 zEpqz!9dP>qF~UKTwzeGa&h`E0e*E<0%DQBplcOkVNfwowV(fWCAPH<@(N?6JX~M3o z>7fnGVhJ>S>n+%pwKPsrkh9Q6qhc8(I2j(wf37fk`2G%nA(T}Z$2%A94*V2EUa$e8 z5@Ow@Eime(MjxiZ1u137yHSrQg;`n(;H2#DE@#ChCh?7DvxafBT)q7*8p&&4+g)7d zf|}yYK+DtQyNI9j-s3+43!9a9fC|O?Jhhas&~x#<mx2H|JZI}p8aZVc%e%k-8V62u zV~gkPH-jm~6(zNL+_*U{<~;KAaNDP=j7B1%i=VwepiIYu1Au{tfrEpELxe~A-y#uO zEyCjfaj6jSplnC2k%SL+4JrDK%?WCxF;AX_>|Fg%tqA)c!-XkWr$F=?J}mlnj)lVV zQ)vW+pGYb8UgaswkSQkoDvc+F>HH~1=VuJokLnn!mHC<ma`{!ef@%K~H(x<RQYwv@ zo2n?h`$Ivnn%evnp}OD;4a`EPhaYPE2!?Li65%8Hb!l63j=w1?Km4X<)zQ8J+Zdty zcI8HQ7wdJI)0AfY5rlK-`1Ap>mWkzzVSP>V?@8me@>fs6{CusZ&!3vP`eHAr#C`ix z9V<LBgc<*C3V&}29A<8$dqk^9ir~#-se-FQ>H-~A{4MneHCuZ5S#VY$mV7L?SK+%* zdF)G!TE|_pGFJ~I`%>L~ti=(&EU1%m7~G>)Fu#9itguD93|$*ck;;8!EdP>nQ;$~B zzHiB7pQTE=FQ+S^KHVHQ8)4?mHgUk*Lu331hdR(TdfSE?`LT>_14hZhwbS)t3)TKP z;=g;<e)I)yLn6RkV}fiB&6Q+LU01P8@h9!XYUM_5g$Z^}uWxTP?=#=AeKdcO;E%RW z!Nx<_w1##*hIdH?yuAZH{dxJRJ>Qz=a;z19kw)es**@7&kvHK5``xU&dgf~ct+I#T z>R5;9s>V8-Tk+(tJUu&I<JO2dOsrlRmFMWR5c?ML$lQ;Ms_HYjypTE7z}K-yHfF{s zw?dCyrX|F*1=Z%qT%*bWaxKj-i~djSur0)FS}O+HHgMz-_AUnX&Fz)uUT)e*hiVNQ z%n!L>qWH4;H!NnItVko@2PokMsYrkYmzIf*?|ZPn`3gqnYnH8=+Ww>TjxtX7RdpJ> z@W`dI@b${ct<YXq?P%^go5HT|7~{UZ&V!o#=HOoZf^B$_sLKf}>0(8$(hh<~I&St3 zQ2a@Spp%%vOhlzTTjt4N(FY_8h?tKLC~xW11wBgUYgVcI(&4sX8q@<|e^Oy;lY2%; z!85IK*Oep&;jJ21dF&Q2E3{Uu3^ROq`<h_n7bKc#EDup(TIh7Yvv>@CpKZj1Xf#=i z2_`J+7TAeiy7aZt&gIDryjgjD70`~KbJp~}D+qdp7-V=VXtnR_1YBO51XZ(ygy^-j z<L&4h$C*~gG=jBHKQr7RnH>fnf9LZ=4gXN>u0%Lk`>Gqj5n->g_(Lptr)IdhEzR#a zjM@Hoh(W<;&TaQJjm|iY*c#t(SBT)z6uv&6rb7c)0qTOky}wn2goO)4_lrE<T|aCa zqeW9mr;)bKdHFne-^go@FW#jJ_Y)mwC6%G|-0&09Of?9AUnOCGjQ+<Pm7YwgqDv7w zg#ISJe&QWqwGdx=F9bUJW5LW6nYKXw0Y8$k05y}c+U*No98Q1P0i7qnNQ;$fEP;P4 zU~;ZAX`wZjTvzo`OA~|nX*;pQcyo@g4HHK8<Nm7MzQfA)r{+K$q49=0125e*5+B)L z1rcjbZe4+8e?I3TY&p5a3XROL3V+;4yN~yg<Jyi``B&>$6zq6)u@@J&-`e{=2eqbd zW(G5WpI&~QBpa#s<sFdgV=S{4Kqb_|mhJu}hiI-dp&z1`_|-+57%isumqD#vfMJ}j z)ko&$fesLytkx+^O=YiQyo~^)!<hfr@bg?}I?=?xK+#|s)&o%CtBif{t^Yd!)Pj;& z>5!gg04C~7-lj4lt<)44tMp3j<cRQG4^hx-uWmd`9kuXxIhW9JjE4R3-`xUr+BWN` zh9SR=z!|B`iEh<GcHTJcmV5=lz0WlX8z3FpYL_#&wp4Z}1Uh3;{H*&FXTlH6r<<~w zL)s<HahpEcdmGA?A2@6KC{Ya~BBUH#%5@y$##0x=U>hy$<UCR$jJv890mM20i(ag1 zzA2ZQ(as!|gaoCZZ>SUgO1|2^6C`v>Npv+vg&9fQ)8_LMnCAEtk8PMBYZ<D$W9cxW z88EvS*I4BufrRQlR0c?($`2o^gb{1pFjy*K75c)VcAgqeGvOk=<G)ncJ$ec%=scDa z#lc+Y)NwDpDhPVp#|u+DW`k$_^gdq?QGpn}xn=EZ>kG>5KG)c7YI}X*e@?+dohw+B z*q4Ctr`d6heLv3i(V0gfB7cd{X1}b3DS$vDiYPX8#}W<^4gu+Zc37b?1)4fTGiLxC zCAEYGC-@TAH6*DBUJ{x-&q8RpuCUEh@VJu^q%7P*i%b50VkDv%tjzujs<MB4km+DF z2o#2UAeMLeZBXR~zEY|DN55bxbyc6!!ZBF^+CK^*NpyDqU;T^1q^PSJw&S^5JwLvG zfoO6dlV8611<>)ujHPx0%QW*Gge>8s!$a!ue|-fSh+DQ4dzw&ci^Ei0g=?gur@V~l z+jVz_AJ6x5^jQw&=<9~CM^^Q}O`2H$%9N<o7N^z6j8p>_W>JGLGYq03OiMW;!XG(D zsn|4`-0u3~@u?U#I7zyEvh;?uQRr<t<q8bJFmxiNCio6)*P$jhLER0L)S97%{~lK1 zN4@<*{<1_cA}dQOFvE5^UO3ctNO+8ynzZ(_mw09UrF^;((V4*|3EvP2xbH6&|ErQA zpcUBgHu?_0*d%RXWCqKquQJLU<Dpo#XBe*-4J>HN>@EjJr5$g(`u1!obKvbsRNqnz z*8k`$Rc7eAiclROh@t}u|8&$q!Ubko=gIg&h>H*{>GHK3T(nY00u+x;^o+7TG!ztG zIVdq{73YYl^$>w+Sgh<%)gq$HAi3gD((m{7YoO8^mA5`R!=D{j9C2pbJ&Izj)^e|p zfBg*hK}~0AvSWUE0vo9r4Ql2DefISPf<q;K=k~@@G4NOSg-P1dStRnTc$YjvbZx<( z`pUJ%FdUlT+wJN8yzvLChugQOJZ!vqX>uxcO~5a8zXSN)#UDxm(y4#CS&K9ni}9Yo zUDS`nduXD)EHH_zjS-6Ibp@!PQFV=3O|BC|$=`T!#_n$g?=x?aX6-(CEk(RKHX_%b zJC;#8L16OZVoK3zTlFLw<jpkq$TxUtpbzoKMb@i*7FQI8jCi6LR8ZnPkVaL<Jb#o{ z;qd0VulQ(!8WbAaf5=)JpG9qCytpSAz?`<fzjViRR0{~-lwy~wr~w;E@dN18uM@NK zLQNP@#-tSf-Sm~XdNqYt#3US8#m_R^r3}b<Ee!f;!D=P=D2K`NM%$x-)6dGbBFs5) z{jaK9<xh*2nU*giv`a0$A_pbFbd*JD;=X<m#gu|(>~I1ly>Bhun|9vEeh|qODaHA$ zW)&Y8%Kg>Lnmh4n0Mne&DQ5Hoq9y^(s@O3$$RfFyvt@<Oj@faP+uQ#0GAkO(nKRXt z6&IJ9l;!5zUrF?P;)KFqPWi&~2TWOpsxwL-Rnv~!%}>oScx;7zRfiPZ0_VHFJnCEX z;5}E}m8E3xHdK{+{B@~m-m&w3bIjC`-p7;|f^@RKRga9J(Kes?R2HJZ73ApNg#YD` z+?%a>cv`6jikC-FK?$7gD~ED*Q-jPW0;;6CLVU0`Y%&?sXaCwvYAKg5szW`dQxq%r zpPta9wLT>vORb(?XSG|OT!kwMdQ)kh?3<5(<~Psk>xhK-KGJI-bn>2dnO-Cw2s8cc z#qP60if)L&Y5uK<f;*2sYzM5wr}JzHmp4xwWO<1HaovX|fRKW3>da*Z*R;}Fq0GUs z`LTooO_CVRM-oJ3Pa)8XUj(0a2wSwd>}&q0gCc*^Z<uh+a^d_w`y1O$`8q4hPmc@8 z$leT;ht>_%R<c%j{Kl$ZeFuf;&qDn2Jo=P5MC;zXW+OXs!r^;I(5USwku4qOA=-bW z^Th6(#gj5Njtc^qb(=PSmHC$`V-9ZC4tA(6^JSFWp4aW5bWp>kRu1mbHTHcQ(G1L{ z1XR}Goa|1GQnUW1rg}(Eh$^(wyzDiqRqz&MI@y9mB-hU81)Nswlp4MTJ^D+OkP{H- z+lJWn?l&~8NpXuTeOby|Tt~lEF}GWTC+cfaaAcfF=QL-qH8F|}gGl}ciDc!{ei)Zm z=RjPH7VBTTCLIXcvc)*g%D2O~>~h|S2-Vv}-OLa+olL{fFQ2(_U1YZk*lhY<ee7Lh z_tf{>?7OV7@2YYJQqU=jDK>SOKVDXP3Uj8o=VtZ*h9edRdTSp#b)?P%GHyOmnP7?% zQ`0-Z<-1E2n9shOE>yt<|9jn1Ix15LQ|C<d$Ulz0LQ3t89T<<yAM_1zfdS;&ooB#b zw1s_fOzm|aam8@vSBO$^B!syLBUK<}A?9-2%c$D8X-eOCQc+8|lG7rH-7uKNo8*XR zs?{R#Qqu_Ccn&Q42Bk+!8Nwxzs{$S4=#Wk0NpB^~qdAlp&IDAB%yP=U{Gkq|FG%>* z7=ZfIfbgFrY+5PH53|&6p}CprtMlNi%oO&Ly1|!*;P*MDL=)f7C_v>e)ICI6;0cUU zg>RAp4PVZ8iYIw=v_*8$G~|5W0R%~Z!8$k|66s6%>b&WMNiJtC$rT4U%g9=?61htH zudE@&k_AyQ(^$^lSj`*ZZ`H4P0_{CahigZrwY}E1se2m?Yacer?7ou(>-3wx1B5V? zxE}Q*Uw=Vz6>L6}&(I3?=oxPq)uwShK7uQ;(l*my83G%U`Qz)$BGw5WNBjG3<~x5E z<>&iIQo86hRcze0`nPt|?yErHj9oVRx@ua!Q{(SzNH6$+YqJgGPW$djyP8mx&wmLU z(Il=ocl4hJ4}eh=zqrh-E`*@@dV@#BNpuvr#gO~2cnuxTL^>eIF2{JKM~<VPL$**X zGlRT9Ce&~Idi+RsD^-6W+H0u(R(PBER0FO;)5alxd75y&QjNk(e*9De!cdUL09a1y z@Ct_%(U=BB=lxBX_t{IA@D|+dRT_~18dDqhIikbJi)vC}{hDa5{&&DY*9w783j-Ef z&Cl$a&0|KvKj0lD`z6nK?O=34<2cs5!xg#a?{-~hY<b?*ovF$b0keCb{n!@0R~61* z^YWPUCna%BJTm+J3bn9N!cjuW9`>H)r*ss=4OWmexpYkO?3~}{-y&oW*r~x*Jpr}7 z^X{bf3O85jgr~4Y+GJV{T`FsNS{%6F9p=Hzj3`XWE7?y^>zrCZ^ab;Ea5%0z#-9q3 z0tLSurt=>FvgrfVYsS9IsM4Ud^-y?1+f=2v%loPhW(5Xjd?h@Z)s>tfjcD!t**<L; z6-ZVV6^&m@a_Jx1Dq+kr@BDwFq;oYDEKxNaOJ!9rdUPgZU&2E*g@-zKqBe>>6sxTl zny@+zIlOU`4(jl06N`$#a?eL#k1|Ou6Pf1nNA<Q4T1k-assb};OzL<#0`Y1t6r5vq zlE1`Phy3mBZ-oleLmxGQWG#_sxxtZ=gjeKDzVQb^7N}%ksIoR@re>W7m*K0njJmKx z$E*<XP*G8q>Tn5BcHHZ)GsKo&Q@(uoKsXk|S~vTT#B<mDR1^Ij(3$50M(!3VQ}A@J zpBhKQ9`eLAF?Cj^i&0i&MRuX9H#8s$3F98L`ji7kiA$v|6JoCq47ZeolNMoG_QKor zszOuabutyg&M7VMC=yo-x;Q{V!0>5}ln~i09*K1F;nv-n^3gm8W=e1gcHA{!;Czc3 z*DYVssz6ixc>;)3Spkx3JwHI+0gm-_hP)2_ya9c`zADt>Eqz@IXqZIF<DT;}wec#m z#Y3St-X!<Y^->p-YVC=$RrT*9uJ&E-LS~LUGvdrrt_779B4m>B1d8gJW78IU=A8N} z_F*5JSJ?|CL=u3okr}wy6}Stu+pOX0H@B8i3m&k#&f`1FVhGBZ)XpQ}yF7OzmfR{p z$$x5d>l%au*{>L07@ybQ7k*VtfRK(OQ`;_^2MFB}+PzM?P_>-?DkI|ZdDjO|%~VtQ z$+O*D^y?~q!ECt9_S~@H7}qWGdx98DX;l|i2bt&)n$a<#TZ=PHqv!{$qFZ#dHAVcX z=aYYN2*(>iM3szCqgr&#qH@K7K*sdcb;h(}CZb4J+m*AjeYB`JvaXQM*V4QBqF$xL zXl?z(ge6d6%v}*Cx(v>k<#pL?FgEt2aTxRdEsbeRdS7yVVSbdE%d1LlOUO%EjY^+7 zMnVM`mPX@>VHq-tOBDIh`)I*=$;zauep`*f^{)K5ET&9JrNGzSx|Q~=oW9ue!#{p= zR-;Sh2P{K?)1XaV?263lNNH0tkO%+GcU85FO&gLEBw%I`am6Q&rD<7yaYs-^FR3z( zsjXsInyt?QSy&u6zPs#yF5QHcJ?I}xA4|gvsV(Sr<e<NzzXQ5*-oJ`}u<~oG2;}W+ zDmUIdUN&j<M|PUmjfyIpz3oHQgs1;deNv|rt>AoCGqQXW;Tmp(NfezRvOYFup!FoV zBy?Mc&&truoG)cuThr+EWb{{4II6aJ05ut}WPn5`R(UXSGH19N9LdC?#Gu-fZbfHB z!=`w55J4xw(w$->^6#eU)5@2Bz7Ta-E<T#gtA<F+jR~#+=}sa&=~R04=F#hYt15*t zY04Dl<t}s=CLy4JQqOV~4F}#6MA3q96n71`!S5l6HR4&^2y=(JKlkm=<%UEc(m-j) zquSEE%BgO2sesMszo&PA>HFf;?fm9}U1f{1!TdDFaZ?D$L<f~abo}B4g1Q?}MyvXJ zEXy$a?zSJOXX|mvOSjQVddps>s3%ZrG#U|;T9OkecnCgS(5EHpKCvC4llC8k^r;h9 zAMG@J`(cm@HcwJxJLTh3h`%)vredFPIuA<6De#!9SS~n_2n&69N!PV3(V6gU8^l6U z{>fg=i7G#4n;L&&U8aPk5nd*dZ>X=3%IeO7D(}`fC8-1QS@F$fj8OlWkDV=&Jb*3t zla)yWq5`)Pp>6RE8TF?k=Sm7m1Xx9Wf2m#LEsbFfIM#LciES@KFqHVDk#^2EJ&p2y z8p%8&Su#hLp=<J<NX(96zq^ltv1Mrp3@wZx@@^GMK%rXBr7my*!TPGaW!kh6-9Sb5 zJPJ1@zZrXG3iI3;ZKnqk`cS^9Kd|=6M~n2k%Z4#kU~y@SbX8uz59}n<brU6;MyD=N zeEU%`eiJCDhD?8c#%KR_r0^gV$coMi%SFezRJ+asEKRO(dZwn7tosWG;n?VZ?cr)N zWfTp=JP(|=LgGLmK5NA(k(V%&Vdl(b;lWUXm)R&MDiyY~Vr5$A;&Cb89`yExL5Or! zk^ZuEd@rHB6PQUSc}RJdTGd*$e-s|^R&sn=ja3#0;q$KExGo!Jdj}l7FTJIVc^lm| z*!H%e%^rXM$e5TSOYb9@+nD%+=?^c6-X#h7Z{RwUJ^D_kE*f#6LTz#)s(gVLY3TrP z;-0lg$F+yTjsxGsxEh-*LqW%|xw*;d)7*%nIg@(Mm0fM_LoBmea~bo`E_p4!a+)dU z`3o?iWhy{l6t>t+xZ^AaC`T=HDgj@>+h;cMqU>n+&73`UAO_&ktjUr(gqPYIDBfV$ z`lsQGG>o8Ovyk)G=al<KI@an_mt<nTDu*R%yEU5H`FFsAzV*#&61vPn3j>uOkSXJ+ zj>y@G{2u~(v|4|q4aV{LHGzRM5U(^-8@0dK9rrsoKfxlnio%c=#53ok*-u~C_$}h# zmS3DQrCtbaU{Ymy8HIJg@4^DOYZt-7MwFl2phZb180XTL$HanNnpi9&c+}80n?jjJ zvIfxfT(^mhGHQQ@bYV86eH_#F29kO`R^SS_Z&?{9+yzbWgPJwOa99ncyQg}H_;KC= z6qAYkve8_vR=$v3D)#50!D!jke%w1l!3}2B!fE93l5_%4O>0FL8Z23Leuv+hKFF(| z5|VX#EM3z>RQ?bdttpb`6E5Xn>f0T1(eLl=>+SxQetl|^)8Hf5`Rt$S2l6s-PV>rN zvZPxGuUbhl`eR}kD{S*72Q4U;f4zb>GTKj9q^T+#ro>-eHUWBAKkL!u?xzTW>=h@@ zSXF(hRn`sE(lUpSN|WOh91erRP(i_8LTU3^WG=(2q-b;NPF@q8QE>To)BlPD(nTzP z8e^T*_39`a`wp0gDlZJ!(B5xYXlwd^J>>tZ<>OERI3%z&z?A=KE>OYc67s+5J+#wH zj4Fc8+r4F(?(r)dN(#HQCh1;jp6QTWVIG@u5*4VN_xxE|26$;uJ|uY*m5_Y`hf6Hi zPlCDHE*kDV!q;qzE3xIqa&K|?9W62!6nY_31gbm0H-inr@$#7ad!W+b$H1CcnDAQy z%Uv(yhu&nlf<lbu^U;U+)Rd8MX^kGH(-QA!L4`(Lr&$e1-fy|$K_DL@7{h+#m?Y{0 zx5$|eh{kp6r|S0;XC+z%9)aNgX^JL8^yT38ysOu*G)PLX4_C3ed@}=^BQXqr85s>q zwP`1%hP{hDj3$#tNaZ^_{Cf%qwCRALG$2(D&$RTNJS~Igj$59ct^hTOJC%B~*K&UJ zGR__Ly>PxGVm0r2Vp$sut-L9U3x}K4p$JY6D*VQ!+g!MqE4}47%tAz2JGlJ-3uIkG zS<WQrCIB{`4K3;ElaQ*^@-Fw`_k0?#YKcK{Qwnnxy5~aI@TIEEZm*I~z6z`2!o@+Z zZt(tj$ze*&4gW3rLM{3rfy`Oz)$}HW$n5X+Nil9;8lTD5lnQ(_4Ww?gv76m;RPV51 zowQsRsK9})@_|@g!>Vp>DM?sANdysH*$Ng(lTrp)m*>k-rdO4PFX=kI(T`NtWT+#9 z(D!{N@0FupJX8<smun<CJUG!lsYpuS|BvsLN?dV;@&eVS$$1JQdbcE_<7Q!sXY2X_ zoc*G-_#fPNz}J`$AEL+23u}^#C+-R2Kc2;wM>XpqlCS?;!s$oEMBPq(ok>ytF1^)? zKvK0eaZf#R?}~|AG<Q6nQYi0qMtYx%$N896F&{=gA;_kQ<fW0F$RiToX2Z*sfuLa- z_Vo)P1yLZd0TG2v?nt#9of2CyWb+=R=lav)t5#_w2)$AOqGa8@Jp@K{l6kT!OXS0! z>&GoJ18+wWt+j9lZ~raWVI&aZJNQwT={E31S~yzH?0MPBOyBLB`>#mv9m8W0rV(_0 z{dLU_B${=E5kAU9n@g4tB8YTI`4S;y=O4)$HS9y`V6&~sXZr&w-e*SHzj$3+J|O~n zwwR?cwah|Rf9sk+$jlBg__3oZXPkGEOTGw?BvY|f@N7Qsflr)JE9dmF`B*<6WR8{X zr479Up3inm(9yqe_A}V_^le&%QS&fyobD<Rw-tjAf2YLH31R$D88A<k>Ygc}{kr9d zB{B6`Ci$xVsBk0}XS2rL(HiB?&5#8Rro<gJYdHWJyQYjTjJ#uqH3@C!^Y&zZ;*!DF zT-Da-R?%E!O^$OC7^E*5Q&=*8CO@F@!y{y;O#f!OAYe3;dJA(Fp*+L`+OBf9wW#^W zQ*jxcpd5MMm-Q6<VGc_c!k{n_b%T3Dg{4f3=DpY<Z_Z)}{e_W5RgJ^=lsRg&5S5-~ zVv%8;cZXCgMkPu=QW?G5#)en)qji&n?ati(l?8{8mX>HD%N3`;=TTsl>;}cWf#d2Z zKM57HZll8d=RuTIpgS;J6w4QL!Vk2gj>{N=9Cpd2$83zvF2Q0y8pNh1C7_iW#VGZX zCA_meC<6%VXpCy&_LM6Da?`+vQ#cURFR{9H5?d_@eK2*;x<&BfcTp~6G0mGex1em4 z*4nmWo}CUVnvHD`|C5fRx+pUs(K%RIYDK#uKPP+MvaCwfSGNf}<NaNub4lihG<JGf zSSqyTPXwVNLQ?-e_;;t^@5WRHO6MPaJtyR0-y|eY#p5o5Wbbw00oxyw?bz<9lX{|$ zN%ZN@+57)Wy1T&}b4w1(=Q%gJc~1XRJAhw?M~Y4?`0+Vqi*JBQdm*``{bHZrY!sc7 zil-Uz@>*dWf!kbHAbm~&=1)lg*OXzcSv3nh<;;Zaz>6*%6E~fzbp7vjJ${i-84|IM z1KCf!?rU?_64)VD{t{J->yh%1una4t#zKFL;TKuhT7=GV0kI4+DMX!Y$82c^a;XAa z4#=+?q|P#wNJV`YKf}^kx<od?i;C_GPga!62<#DxZ&l7dQn2hUt_zlpMkJ_2!uK=# zmsFd{WpEnkEfu<RW~1auV(+sj41%$i(L|`{gb){(D1V}?Pp@}GemJ}m;DD?Ea@x~! z{g1oFMBkR7-S)#MGcK2cs$oj*y1fU;ZXr5x(U(|vLku#L)Ut#^(!swgmp<B#AX7Rk zpNVv0>2)ICTgupNyc`P*<&Ut3pSiZOABqsj0twFizW=yOj2dK+_>;DMeCc6CPqf{% z;$E15DiT``O4;@^8bO8oSs<InnqQ1w0dJ=<glnmWGG<8WB0Y3LGuP(>AGIQ7@rUX` zaEQ01Z%LjOXMET(R>a!vcHS%i+V9eVOPU#Ng<-fqy(V~VHiawzf5kCG04%DT+4|kD zxm5~298_sQG1IOOeE8I<71yN!u2w4$T?qurC)*p&GVQkHjQGNU87L^W805{szv)<T zS~Fv4Z6@~+4{&?{_!h52M1CD8@Y{3IKQys8a%sh}!+wRJ$k4|9L?U8qpFyxzeQrKY z>MCt^VuGhmR>ewMudX}{1((0c_tss-r!UCFrwzP_zH;3Yq1Z*zCvd;+%|zu$S+g8m z)rBoe;?R5Lt*IU*bsGbaWB!Q^7lmQ35KMp?w>Jx73P&WFTHri`H($j<SO6qhKm6;5 zwB@(cwkSNW{j|PY=15X)-Sr4vxai<Q%AeI@Zp9%qFtNWa=}R&i4f?XF*b8mds}Q$O z6N+!E8CQR!IQHqxIdX7(F#`jMeqfhZxQiAetW}yb#L%cUxo|NRX7O<C81&|U3oHMr zAOR?={F;7Zmusk4!Bo&NKSdtGE)-$t8?#{Va6q7l%uI+eC>oWeAcaW!F#;HH(L=)S zioDA8FW{+O!4!?D3}xS&=N*tVBu=3qx_dF>VFa^*(}O&Wi}@^aCBQHtK7Y_QEDeeO zD6O7q+Xnk50XU%b_=|CzyGIw_rc4rsc9LqSD1{^{-tkwoW2hpcf$BIu^T07wm9r%M z6>7cn@L@Q1%Q(8-Qz>D%2TSR8-VM#u`HHOpDUIDkJs?mnfF{E1))Y?PD(A-eZ3kN{ z7D(n3)QOHW!k#Ek>B;&=G*Jtpo)&or94L8a5v|A}Q4|abafEgozcrZc-J(XAI0TGU z9gKEM*@iU}lDquQbQuP**Ktuo1wVe5McS-biK+SlQy=;KqUa2<KQtUG>Lkg0z%g|R z*d3HkR9RULc0WVfvv+_<W9k4<BZ%xBQ2s|020<vJ`W@hlP9b5<NzX4t`SfxS@d=I0 z*TW)pv|nJ}e`mF=_aW*hm_1z4p)IIt99^0gmO!(CRIJt*mBh-qZ8<)=6A^7L4*Y>; znz$Fi-RehopSheAqWA<Ou_@v)QQybd^{>_!cVxQd^CcLCmd0+6Pea&>Gx8+(T!P8= zV`OlJf9L^mxKU4>(y_g8b64$Sq$tF69$$+cp%v)i5j{FF2Hw7*MZD+pFGGHhpeeB+ z_7jen#7rEnn+eHte+#14p^D+Gx1c~c76v6G*MWCH`%O%ebQTKg$BU<dU;T4>1>#<g z<WEA0JcLfZ2alT~Mp@|SB>5Vm!$ZD_I;&!#y~aL=pEl=1-JxsG^-b8XY<gct`WGB= z$!Mjw*N9S*C;#zt8Za_mYKOutj7CzTMuzZ8e9V1AZ?*CQ?9ra0X2!(-Jkcjy{|c7N zRhGpy((M|)3xFIF66d0!WVhds4O&<jKP@0#Oji_cH=#5{cYe#Pr-+2d@J>mY7K@Kw z+LT<XH0>3=3WJ-fg(10mAz!&OOqAUqEtH|?rtxL>WgdEEC!)s%M?q+M-T|{;n7w`O z@4;KaYWqiLCy6qg*nNY9;rU9MOn`4TtKh?rT=1gk@iS6U>T3_bh>F9qT)8-Jy$_+E zd<;%Vt=J`LGtJ%zjjh3h^OrXI&kq9OBKhk~(t>H#7&4b*zs>TFF!Mxxv){}PFM36? zYg9vye}Y4?q9joSA#J%!6>pF2bz`|u`OD{$bG7n_c1<#vE|?wq5vJZ$?(EpDKBd4D z%mC=trR4+{E@nbT!F_VA0-4q(6IaRYjLpA-NbmWEOIfZ-I?eb=wi%8s*GiOmioSjG zIjHndQlX-@)C-^pcOcJlyVXc7-&FA{MTrEl?TKwynt)XZY(le5_Q}!1(8<He`n-PA zq9pB&?+?`=kVuA-5-e))pv;(~6EiML1fB#Pc1yQ&;~*%i`?kveIrco9;KwKIi|W%X zN$dA>8-0wB?TYCgFLZNb|B6V-VMVIXN)-!&GhUrLhjYS!%t%B^!{g%^p=Cz9Br$vP z{pkRFlBOr3b*nS11#hP?^Xs2}el*|X))gO<oKO=w_vvRSyBu|z`N|v#{BIwh-Gy3O zk9ASle_8fJY$h_*N9jb#0%l<oR!O67kpb#19*8lCbwetf_6{Jsz?*l9Qc{mvnidu1 zYk{R@9&V9!m$<a^eZXaIt~FsNHFnO=x{{dRhZ#V2oug(3u=JntHb5O+Vv89}=~7)M z&oMG(bHjr)l2`2}Wq0&1^!fg83uT97(!P_*z)~D}ju+q(+vk~$>qZEsOCXGdVMddc zF?ZHN;}r2;?3XMRuR>gk`KZy~<6m(d5P@%*FE_ENj)Oiy@IwF4Oyb*7WT?o-XwtrQ z(8LCSM(a~9g-ZAildXT$Y45C=gFy5TiR^ZNCFsg2D8X8HaBQSnqHgd&BP_BmNU;av zYpdO=wmSmREoPKdl0)3C;luJKCoHNiF3YZ3PSJ1)8EQY{w<7KebN=Tv*a#`s+3}%i zIPD|b+{-c|%`hMj4|8l8{DZTl@T+$}mbzy)x}@2;mlnNBCRXM*g_H@GlR24wTuO0Y zoB^TzD`4br<hHygxQZ`(SSrH3?+dq}*|rKaLd*1Tc?Fzv9oApGg#4wSD{qn67;i(Q zMZdHiMbMF&#kHk%zB<&4pv|%ZxrwvawCA^|VVc1!^Q-P#g`PZsP{z37^t9oWBnopt z#s&#>i~sGSxL-K30#NsCqb<s!BgDog48r^DnNs!{unI$iFgQ(WDe%Du+mJDB<7}(& zPD^3VG{uO^W9_B5-IZ~WBKe@_<)1wb0ewv`oUL0Tt63&3tUsYh0pZ3JlHNF-q;%*n z|H+dn)na4|Kwpw==PSG_nL0befnIYxPGIiMI<2f?TuoTMjbR&75oxnfF;|fWMf}=8 zAN(Ok$yzc0j-axiX^axg7TZjVvi7)%$)Tyj2Q93O%J?WE7;5H${{Foby3nR<yu1Qz zhcq$<BpLcFnFS(wUz5>{?wJt)a-vVe8(5WS`!L%w!@eM-ykv^I5Sal9j(Y&QyL6=3 zlaB#NL0|Pc|DKs%2|t$F!gIP3?<Y&N3P`~m&^-2POKpQiu4z==&B>|jn5l}|i5s!E z2u>C@(*CweThVR<>4+sy62VUGQK3ByF?8vpRQ4fPc5{TF9{!#YAdufMlc3o$j(;)d zLZyu{L3%+_Kcv*N;|<{B;2R$jbfPlwDz`RQ7w+1-?Ny9Q-mBKWYQl|))yRp&LDt*0 zdAY(8zYUaEs>{q$;9(olwsfE#-!rhBFmC+rK_-ECFK-m;sGxj(PCFYB9!VL-jNfLT zGBe<@LBgbFIl9lUxZ08e?~N50&k)CH5<MHDIv<=WGx6vho1qZ#eRYxNhqW^UIvI*i z4Fm-vpCU!iETTQ0KrK+r@K!1$O<ysM_BBpPmslCC3)?{9@BN#rAl|56R6}AtD#@bA z6%bpEV1TBbC-6Bzy4?~G=GFosMuNB9pS-KSQDdXlIKy3MnR6MO5h)_rR$CcRU7|`Q zfkl%_CrTp{j4?;y__s%TzeUKGGIyeYV6W~Nzq$G49q_&=B>%?xc^EG#ia^JRC$1;| z(d_s)wE|_9|GDgAZHQ_N%j&6kkf<L`jXM%89zM?G752-XCA7)me(X0)fBzAsb^NcX zji`LSS94fuZ_cJaQPbXW;9KAjU3bHy$NCV7Hh9g%Sj|c17Vs-kAI0#x^4~}S_}lg3 zq<?_NPvkyWSckIwQHpa{HVbloh1TU;-BPkq-~F%h>gie~QOfAp2GWb|I4d#kz~Ais zGf=hbc_6l8#8xec?;v-9vf4vt{$%IrHhfXQ%u<*SM~o{Gs16#y`O*CJ)T=^gD;{d0 zu$~@xE}Zv?(0u&gckkOb`A{bu#jk%oQ(dEKjfS0<W0p(PX;4H$rLr#Wc4@_V2Qn8g zwt9n77Sw4aK8$5ny#omOuK|z~+5e}DuV893+S&~vK=9xW!L7w9QrxwqxN8Mzad&rj zcXwzh?poZTEk%n%kw6K4bI$k8ow@fHyfgdVdp&EdM>@0bmJ8I2^xtNy%Fvp~k$J?r z-eQ|q(h8m=7N{c@E8gmpgwDQ5>=(j!H(<wl9T*yPfKjUO7ca0{x;RN)l~Q9v1l4L< zH|XYLo<-C$>AMRnzcd}%%ln-Z2tB~_%dba2*wq(H|9)5(FaJ5IdHQT6#4EaoGjkNf zq^s|9DvsBMnFJ#vx0)JQajiGbedw4Am&WvvktfYxe^|GW&*3Y})xGNJyg0h-Tkb%f ztPPKYCAFO(j6g}b!$T!0?e8pcH+I<;oB9>h#meTos*_vEI5a}&Z{yJFG-(DvvhU($ zF&LT&f6B@L7(aR*v`Wv;upET@e9P;)Nc6HH))nuZ##by1#tb$H21K4ccn-f`zXiU| z`1kf#p(ra91(y<UijL5uhxe5Z(7w-8{BghZ$ldA$dn9?I&U4yucC4R-dd$Crh3_CW zyPq}fOWx?{kDMs0(AyPJPLpQ5Aml{~;g>$jjNdpIY_P1y8CrY>R8)qb<4e+n*hDa~ zC+f3*-CAn&0)bhW=wV;H{8P{)W3?e<e4{CZJR^!4NN<p^>=5^!mE`ks3Jxz1&^F@F z+LYh>Wb@1+@k0IRi(Zj8A>nq%MB$=QC*ik_Y~KUrly^qYMrh>Tg$jT`(~-AN^Ui$1 zGTbRWH*15;gDjZzkuI|B7sCO<gF8TQ_U)mt`KT%+$~sQY^UTX9Q9oOefNG@_kcKz5 z=AIS5{2^qRy2o@b+4W5j9%@X1oSX%lL3=JiBa?N;SIIv`{e#Ryby(*e9_6-{pD|cj zl~TjX*oiu7BGjEltACoAbAmY9#T%$se=&&B9b@hnZY^Mqxwn+)o&PC}kK6eoc60TW zZ>+wVRDzst$->c6kx|E3awS(>e|l5#lTytyAVByGuPyq}17jVpDEXm*J72=>B1ws& zP^3fa@z)#%c>*8K!k6{4U$L5BSrl0V66o?v-;c$ht<xbGoRvRYTHoSCxT;(Lbi`$g zv5g7YWhv)+q2_k1G*NSCzIf+t*DTq^xZ@Q{w<p_0DltrlXEXN&8ljEK(rha?7%RS? za)1LZ{ipDQmg&FPkl!{14_&PI3KluNqQB^*I52Iy2CV+I#qu)MhIy84TO>57_R&Fg zhBD4HmWd01BxGlYPea5vIEqer9k=5cg*3n+<Qi(Hg*NC#V%~QQK{>U_3*-0IQQ?Xh zT)}Ze=i(v<xGCbeu*Bv-SKPuuew6rXG;4!qW*piIi*gOI&U~Mof(Bpd+V4{7Ox=1S z<VY*17=)^K+_srXVS-x{Ik=ech7d6kEDo(b0}5_KF;E+O|Cx390ZNToW$3H(eeZ83 zLRC`@2+jjM^COk|^<#ESH&Tcwn!c)OknQx+peD^)2c2GfZ~ITCuzl?+|M)mc?xx>Y zZg1G;M05CW*aVmJ6(4X&-G23IRn8gJsh?}n`d%1tagzL22ZewNJ7}zb8UDO-EbO;u z@^GPx@p&^gR^0DEU{K=0?-`&)trm;p3Xvk{do-7Z4#Op4!c$5#;rKgDM&yLth!S+# z7DZf<;c%MBA_=;=couXV<2D7cf-5IY3&x)X2X}+iPW>d_;|QC%u6f7#*@<>eK8OB@ z+7H<P4t)A{z0RTbU*tcz?yvr@ujjwvDo9r~001#Pr6frCO7Sk+l2jKGcde<P_c#Lo zLRYn4^SHC)=)5JJeIE5?sLyDOf6tvoCdhk|lK*S5<)v1AQG{V*xzjRiM0Alg6ivkX zA^<7sUi#eSyNKE=jrLe@vJ%KyYmNIIW<Tra@`2T04gEA<D<|W!^R}C2w1a<4+(#3> zzwUcbCnN~aLfbHIOR|BI>EzH@Vb+F3OLjlc@p;(4D;(8A=^ptK(c%3q-(>weUQ~6_ zK~p6<sfD<okM!Tere5@4D~Qd#*OJt6astUFF6IW4lD{N3P>9Ai%{BT4X+VamdJ12i zp69<~;vgos?t^8Sv??4gFwi#Huq17xf4h7(`bM{$X5?KdtYEe}-zRJ0G$!wB2A7<@ z<_ryzgbiBTq40Fr%BG$N3p_-*+ppAd0G=0c$U-9NWWbO$cOjrN4+9Q&KOD(gun-{0 zivN$b0MPS7q>(nOp!1@pr2e^!t^ZmM{$r|wm=Hn#IKRs>Hkt|v;mb_V`+)-(ouG6k zPi(_X8}U%*AuVI6e)-k@XV{p<q-zh_Ck%kprhsr?YtqDC0M`qfNC9}-VIbS_*@kI} z?K;&gOzZ4=3Aj=rtp-Z*^hOD3!%AY%Hj-!Bq6nMl_~)UmdFr=n5|@YBwRMJ6u}gU4 zI{o!PJc&<BGDe6>L4U8Dd-m3gUB!RMxV^et*LbMO;S=z8aOiEwjhYoB${4M<zfn~B zzzYR}fhnoPz^Edo&))J1RbIs$Hl>V#)+Mu|Ays3K0*}s_=5HwI>Ky#e9wT(<?_PWL zta&_I943V9_)@!XY)q4X4qI!@myAN3rHq>c8}DzvPKmM*O4&QYOJgun*YkwU9Wgzr zgUBJ|i94_6!pQ@Mt)6EWxRb&k#r@swk`k9&V)S?G`l6{8JuZA5j9y>m@|@B5n&lmx z`9y?{wfnwr*|4(d`HQ&NkI<Z4K*0}`)%tug{qt;csc|lwgVouk(E)I5ysr1*^C3+j zFr7PzENxSKnm0ElPA2fPF#Qba>mq1tQ3WKP+wkAg{H2{C(k1Ok1Qk-W*>zwIjBubS zw$~7>>A9(Y?LH?)E7(<z?TDM2`qyI@;p428gyTT*Nv?<1z&?TMr1La~k34F{VTDrn z;wVKykI0cD&aLJZK{-KV5W8lUTKGEp)ZcReO)YS^mEeF^Ix<kb^MdtT_V+q(lwbKX z=9kX^QG|gLbw#!>2K`A>$$WR~ic(~+sylqxbsaC*fLQQ7X7v&x!jFv-M{uNYuKnZL zS8<l=-@6@96t{k)9!)l%W+d3IbeOj?GDk>N3RP+~E&PhM;QXVh+9)Zr$f*P7MeCev z=1o~$qGqSQ;C2HzV>Z0mDEPI@mvT<p6%{jTrV2i!6~?exQzhs^eVY92m8)#vIclfR z2B+*8BC~t2L@~6aBHf$J9r2gHtbIE$?K|a<LYgqrtCp>(aR<L(d^6W7Qdi`@w%^x` z=68>e`+-e{(MCC3Pbw4wZ+$!{%C<VPE7-@~$r7JQdED~DM((A4)IWJwjZP#E2hOsR zrmmJ7-*FIw<f>sBGQnsx=HLYb|D?D0y#e~WorEhaj)(LOz5dfFE`*T#v(xO|a~=OD z|0l;~5t<^jdF`@SEuq(58p@-uQLLGyDXIVmFHkZ?jPJ}IMsig{Lw)^(L?n1txPwFI zIv;Mof5^R#*!CCittA<2i|q~AKclX4ejJ^921xG7;<^|-(fQw754FE=*%h$Q0CVKB zX9i7}*K2ab?8$9%zTgS#a=^zLeDXGX{otBD#7rB|yHU}yPMe}|U4QoaYNJ)n9?E*Q zbDezHdUn>^3BR85R(u_Fd}Fo}vp|izH=o9#)dH}2#nCNp$ZJ^%18RyArZt;3J<lhy z<zD&=intlA%Rnfo#Z>L5OW`vE1VJJWVSLFgIjIS>udD^lh%AlEjihMTU>-xcjy`{! zM`@T72$*7eh(22NdT?h`=;7>U_&!{;ZeCig^<aq??MeNiPGGsN(_@ZvDr<Mmg2>7a zh@uR;y>of;xF56dBG?id70*|PiFut&@p-L6-d*ous&<F1`dEdn{W9usYxmq~={T#q zSolSaO0Wg5%V_`U0lqAkC00KS{3l)=v=hMi%_gpMQyj5`G3eTL(_TXjG;b}V_4uMW z=LtIc!1f6^@=`pGSmu*T9?+2;;Uq#c%(2jF|0YCj#Xwyz{?)^IzhKQWbvXv$f`(Li z%J*bRttik!)YQj)H{DdzeQRoxBI+Nt^wOqHn*5)bZV~?^fc!;;n@`Sb!ISvnt19z1 zu0{}Mz$*@L5>b6BdYPzWB4;WGJv!zh)nxwbUiMuc^UG4)ZY-sjt4u#njohW<5r;4P z!oMAyd9kEcy$?^8MR#qZeqHMoG<d{F)g=5YgqK1)PjnHn!5j03G5)rdlp)nyYbIw* zlY8)sX`;~NQcI-O9NlY37EXU(aQ$BBSp_J8F&6L2|M=<S^TOG}-u#SQllYq%*(ml3 z2Qlh<yI&^MJZO09gQJF*6L>zdrmGgyf>>|~`urt>ygWiD!C$M7Zs=Rz&Rww209}0- z2{M{r4Lle^{{ax90sjLw|3i`gCuQw_?QR4}SMPtYCaLHgf~3g*7i|9HZ-WfBCfQ|w z8gAFR>8C}dNan8YzR2_2=;AY0=OPU^JB$D7bG`emQrGy+=gxr|!Uh>LBX*Uk;2=^a zCQ5v}`Uq2(iA<3GIfbgsXlp&(ZP>+1u9u0yxJA2TYA%)<>xJdO6wde9MH#%ci<sUP zgERIF%xcg4^XPiUaWt9P!tK+MHxizQAPYo8IP*oq-%C^VCtL+0=S_|-uVo+Gybqk? zW;NliA58>TsB>LMni-BnYvRUEkS*@nckmifD1lzS>g|o4SoSn}^8*Ce;O;EGX98i} z9Oc}y`Kf`bYsDMC?@b{<-SR<4bS1tC_zWQVu4-B@n89tb{AFhQbEWN1*YYnGpW;MN zL6Db;Jk%E7X{t_7b5?&;S6R1>SGM-|>?^jm=%x!8Td!GEBcrGY^rbMvVhniE9++3% zizD{ur%dV0`k_|b@B8lDJQ6Un8N;4{Ub=T?0b-lN)bbrg+4>QNCr|M2^*v#`+Snk~ z6k|8q(G`PoiqOK)_hpwR&d8XxwYw3^zUs|asvHS(*}yidI)yd{6Br;{aN$gX#Wg)U zOlNiXV!2InS4}nTT}(6QrNKgp$MDq$K;&?~q|NiJK+{j6SRePBB{PYRRJ%~scyfhT zFuqe;a#Yo_i~^WzTjlx9`7J^)%b}7j=}7SA`X?1Bs{Uv8#cFqxm{DBa<Vglp&J5!G z&e4hueIxCuN^{~dL7P$o1%c1oh>VF0j+nQK7<OHG9XkK2Yp%mj!!v$cb944}cmLeg zanl?hn3|S2cOo*%(xKH~AwRr3b9)Gr(<6Ofj^lehdbOsOh&#kiH66N6dJ#R^8{Rjh zj3ZU_D13YqDb*IehEBiK10s4O<o~k0AYBd?rLfqq4Y!Qyv-VU{*PMfNBV9Z3u4~b9 zS`TOUh(knOZV@o|>g+U@h?Fk6@nF>9?8!ZQ1rJnWv48jYZ*jKGAayApc|X!Pw?uE{ z24lqy_GwMXuhN=A-Sl356L#F8`a-XeIMkH7*#65`K@i=z@C!A3ujUgMFBJ)buDdHB zh3=fl%FNsE5GF{^nE*3tEo!N4(ev!%P8Eb`n_)bjanzG61`L%y`tR{MZ91UOW_(Ly zR;tHZg&GEpK)(2Sy_~T;o9Ssnf}6XH@t8h^UR<x|A{3jS*hS7i&u$;~LW_2167HOq z=OD@QElR#Bv``qeo$DMIZs6C{A>SuuVMq2I``<11cD*tHI7&$U$whZz<csdoRper( z3YSFMuO^epl^8+i+W8vIU-jz?Rk0TrPKnLkmL$lnsF~-w&qSnxHBZ0lG?B;M)ts>m z+Ms2eZm+za(V=nwhxnL`ac8sBdCzTx;{b<>(l}$q8FfJ0QXiH)o1*mDhZfUZ*HfcZ zs!;6AF#QFCDbG!3uFu|T(V<f$!l2WIol#}0N_u5|hQBYsYoDCvb8o*GJE3}-x5fPs z^c0j%EV13e6Vl=+ZUC|yPW{`y9(@R_A=VD?bVck6YTq<yd0?)+O?2x*BRe3~6{=>y zO4nt$qz&-(zx0f)o2(ApoCy$0xY02lD&Dm1w>@6$))B4e4D}h0=Zt)9`WySWfSfm~ zi5B$oi}1;E`<>g?=hm$_`?rUAT_5XQyFT<%jfZ2dnA(YX&l#`iU$6?Q|LK(w-rW<6 zYT_Z8Td7GA+It4*3Ga+W7qwik;0t^>7FkmG>CX%7*G|8P@to2_sAE4!KF>4HB6=(G z1t5)$Xu});F}Q5^9LJpz-s*dM2!^DekB{6stVyoV=^4-q3G17nxRq_lmll0TZA%Z< z*FPagsyJg;973u{OBEKy41e#!b*&<P@#Af!kk0LD*Iz2CobJCTZ6)S%Zw$h;Q$o3W znz_{dt8dFnw!ZZ17luuoqe1xs_eT8M4{kw7I?R!RM#4{g2VfP-*s^n*pA#%Iu0F{& z`fob>Zc?6?OQbNCz4_eq*cR*6RC9{v3XB|Yr`zk_w`N>1O_J~IJN9n((TN`{gXEo$ zk#8d7!ZEX4U4BCLOwHq7Zz2=R6vN+h=Zvt)=}3PJ^snZ4)IYRE`&|g9+P&NT4NsH{ zrWBi?I+PNHD|$WNaSv5sxXro7QHNBAO;5dD_vb_?R`NL;*Oc>x;r<&ZhhiPMNa-^B z`*+tpImckS@RPsD6Z&H2gwQU=q!{~b@#<<!{+M0O;%#+N(2p;o_fP#~gw+A7bbgXo z;hV`MFzqk$TdwsreSnT{0b5`3=7KuwbJSMjE+=s+tM>1o0j$ppN{kMfpJ?%qQMiI} zj(fNG+b}Vuo&k|-+i%-Go6M{+Gum)E6l|f@G9_L&iA??#RwvC5F5tK|{eo4QyD6}- zgM>#W$bZ5|k;wR;6w&{R$WRE7v)F$WWJr93_MbOh{67voBt+_#>n?AnhE^;AEmyY2 zD8qolksZ*kzlQhqVp2oh+-Zk@`jsth%A~8OD@{Q8q40I1_?@Vi$kkhiAFz_KDG22H zN}I5%%=Kic-Tt)8C1)MWU`(!<i#|<!0=CbixGG%BpRn`UgJ04)s~DVu+FumOEC9h) z)>dIRVx@f@4@YqOymX$@tW12%Hi2R;ry)zA!lR4+a*PGO7B}2UKik^&79SL@4^~jt zK9G^~>MDpd%0H4QWwVvE!e{))utqdN1ore+Xwu2Op`LU>I0WDwEZJjYm7<O%0vm%M zT&u~%-1Tk*vPN2a6_ewi2)+)x>V#Y|@I_0LuX!}aIIe=#*)xRNp_aNbxF`pK5H*@v zxExrX9Rp1-7-wVZ;mY6IcA^+VFO&mJKa7U6*Rk_PL@|LOJg}qVEa%~`JLR}_GnNV( z{j5_BL$u{^k$4!|+luy;>{YeMuu|D~d4V)+jx&Ha6^(!sPX*E2!rL+doZ|LUFaV1F z?-dKAc^I{-5Z0h3{`1Ips>fkJLMjHeXF60WRh*Juwer4J4mH|l)1Q_HPE~XJFZBT> zzV>zV<8UA`b#5z3O`mXuoJSxj_{Ec`q=1A)uI5);aSb$8<rI~U_>&l{G9aDwP`{a= zgu_m#f{cMKDwhcT_G)b&yYIEu$3<9)@B^z;aItk3Lth3S%6lT6P?_nZM8W0<!^#8S z`C9~*gYl#kRiLzn8m)5hE5g!QlB3JjNuTJ&+urzusZZn;Y=Adu<SeM)ICpD*f_U|U zBZC77Gw1HU@Pgk7s)E4NDEes!y5V3H2G+SjgnxLr);ZvB`Dk7&{xN0+I*MLz;-j)% zWYL4%P1X%|#2Azm-8e+~#ig~&+i#^B77W77T`q_Z5;oTM!YO;uL^?+lG;SPAgG@F` z$5ESQK{pB8haumA;3FBzC9GZ^KzNLMy`#KNl-5k0J>T!6ZK|@YvgAn9uQ=SUII>l; zVXq_UM_l0k;rUI=B>)Q(9!(H~UbrWjNo{B_tfbV40Jq1^cVkd?kGIgDKuI$xW_R1F zfn9gTW{!iALV==VY4J8%rf%--kqn9);cMQ~G9@Ox0V&<+d%A2h{>fI?s1K1vo=|42 zoydyZ@~3Ix5G7SUq37A)znysii^Xz7lgz+A3Vzgvd@X7zZ`#^*s)!tfl{kHl9?D*V zDuSFAh54SCjYyM>iNwQ1xk@CvgJlKh9SX`v^U#W>N&rEG66g(4drF6p+i!lf?GXxl zd>tc4hzW?1fGrKAVnn%OYVU7PYyGF0<p{%C7ywuuM#t1wN-iV^><{)IceSwmap4T? z*$rl6)8h-OvxbO^7Y%C@rfP3|7T0t8&Jv?04=AMt;F)_Bp-3HJqFB6~_J61Tx+2i3 zRJt}`tPLC)*H?ULJit|2ggUHHRU`Sj2S}%)(3SwjHv8ciJQ^?KL9`CNz8Bf=62cwC zxi0_#ze4Bov3NCAlicvFCFFgewWc>FTT?^*--6`4&oSljk4HXAb0lc>m15P=HXS#3 zsIN?(3d8$|2|;1W&~jQ1(s!q{!PHWwM&VoC=YT%`DmzT6u}i41BzVjQiaHpfN+j-@ zx8Ft{K@;_H>HL%ic42AfN~Br~?6gdZI_pip>BwCg?e~WkD(l#0Q<^gyqr}J`OUupm z$%^^wtNZ524M3rxQDm{02Cu?qx?Q1;M-Jjou1aZK5R9TDXHT-9N~m$yI)$*Hs!{mD zZ3#~}JAvx>x}k!wR+@gOI>1?xMw-J8cSCRQ6s`<+k&TtL1jJv+DJs8Y2>kGo1uNDG z&t|W@b&7mhjOjgEDHDbadS&I1HtM1!KKQ_QHg`T}`kQ_d<O*Y8!L@AKoIrapQ$zkB zzI>NoBc~zTg~s}B+)>p`p7J}<d;Ek#2=QK3Slo^VPkDXsWWcJ7c0&{zfS!8a8J9~+ zu#|a=Aa*Zz?Fj?_6Ph#CoHnEEKQwu+f1FvDSmb!@a5uPui;O=TD;v?)3mM>gVPxYc zyn#hKw?MgI{chy-m88nGI2<C{fykRf#d!r~5);c5XbB8Ad2}W7xHI=@X)c2UrMzop z(?ZdCD&qPaX3NQHdjZDg_SAu)#Ox1%FqN@jJyZ$GC{58?#r<sx1Tj-o537sWA&mIV z%APE!lEV!!CJtow9roQARo&y;27&d(0p6y=G22ofW<3gI=n2Reor-%b&6f_7A3+8< zPc-7D*%uUFA+FJq>}3ejTBSSSybQ&}j$E@TvoJFVor{j}$zi7g4o5DypXVy3%rc(@ zBy8rvD$>AGKf|EE)TT<xS+MG04Go+0)5VqBc<$P;G<v03|IO`nd@-WTYI(3IY>d|W zSP}xb00o-cmXs+xk`rCLqf-hi#N63=33r6^!evF{lFsuun2CY4&LVOVjwn*9#8<=B z*ELmetvxxbu+6{TjzMTd!Bz)L%*?qNh37S@k|;Zf*XukOfaI6w>|#41cwg$s3!%a_ zI!74+FK4FkvxNa%_Olh1bnq2p!xz)ML!UXSxEA!x_9SB`^sfmrVNj4aHOk9Dx_2lP z(2o7^bz52oc&S%}NPm8CM5$>&u?z><<W?MN7%daev6-%2gy19*OGkg1A*U&mRdNx% z<DFS{_9qB0k*w=)p3|g-#TdcEJ5P>vBbG{b8gp`YCu%uEi+qROq6p#TxwyQ+W^vR} zTp51Q+zGgrkm+qSOX*7PTG#izc1%M4VCKi^Zv+NlEo!Y=4*peWhoq3ng4dB9Q6(WQ z$`x5hIL53bIM4)R_w<}@MhY$~OQ#zl$fFp7t_+|YB=@g1jsH>fQV>~qoW=^>5)-t0 z<i^qp4+#i#KLkJh#n>3#3AX9a!X9GEXIC06CkbjcETh*Bep<u`y!qX<d}4nQb!I4) z<oT$JD_Hag5(fgWd(>bDn-?T>*8avKb_7D-@aj7Wt(iB0ExV1XtUhX3d2NPcW?WOI z!VZ8;#b!!8!bi+B2H7?Jo}NAlM&%ddR9lrG4j8k)RAFNxz|hmISb3?p<B!Gzjw>?a zC-bh6yu*lU1fBf5w*&rV48hyZ887q9a=F|ZO++gDOS^&OFxCFLbFS$33$E)_Gh}T8 zube_CtF!>G!QSqMN(n`gn(NPiSl-MuRCa0X(25Eu>BalS?vb}Gj79jm@#(kqw;i`7 zaOSE`$T&2*MGg@0L3c0-xZ_zMm1lc|SKjlIbpuBi*tQ)z1lx$yp|aotf4$>)%-xG8 z@?1&(4MGn#9MUzTSmm{(K@YNc?@EFz;oxuMJD0om9`r9g)Wivm!i(3k^{V8W=RB+^ z!Q`;2t^uyaM?u+#MMiF+6&v-3=3ie=x}t$@ZR$eKG&(f{2ykryX6mzaTN7#oEV`Q; z<`1qQYY&|O%eB&rg=zfYLkb1r_q;&1R)+;e{gnr#e2)}oM)#d3_)chv7gJHQyNyJv z;o^rIk6EADltHsivwD)gAJzn0HM{aAA6hDZp;pBf{wD4?eJvGY0M5hueclpRJD~#N z_(GE>I6(z@8vYeql%|}2P>Kj!8jODa<fL|PjxkWSd20&T7(!Mb5}kGkxrpfZ{%aI| z$yo&k0tSO)2Uua!{D^Aiml<@^lBhXu)KS-|+im(P;x{<ngXK@60g`}8hF2_w{$ojE zM?UmJ6kXD^ZGMLkd4!%BubF_qeR`U4+Y%NQVdM|;x*}<^3Smv?{^sl37K{uQCKPBd zEQW)GNh!$HF`Wz)dlpVQa%}ssQmrygNXRTvf{bfEqmVTdKKH`F(kUF=3+Mf<>mDQ_ z2azmzBSn=9&fT;)^i^jn{@2U<VD0;Wni!}ln>amm(2qGvWv3V5aYf>)Vw!B<i}ef; zx*X#mB-IX##cGpCMfnZwW*{oDCaYQ)@F&SSw%00L*TgzF+>&>|6-BFPA-a;vD=u=- z*g;k$rWF*jk6@q3iAC69=%`pA(e$6kNDPNMc)*JQO8y4tN&7*FI6NfFxiMKI34-HM z5K@IOTae>A?656YTM%Tt<BB|T9|a3to{tBngc)<2Vy@uQA^-XfR;IHi8*0%`WC?>a z9?b1vwlwFSi^h4u)FpuC$;GP*a~4?Bj}g{-5X<t{n%I&2n_-_fW9bv#&o2xq&^(m; z8I^0#2;R#-V&b&LO-EiDKqt7|>;sL#F%T+>Z`cs}C7WWFuC4Jv9z0>Z&n`HagE;UV z1{o6;7yPjO$MZF7{4<_UpVpWh#Lv*oF#hX`*od__qEpN~oLvC%{)IT%0h!pLWVYxV znn_f-ahy&3#&0^GPLvguD2P+j0sIdRpMSZo8x5hO28-i(Q@**rAm0r-%4YWUGg2fH z{wtZ=J<`wY1H=!cKf(Lq0P~JtC6?wEuG9;^So6r!0u%_)NBjc=FV))f#v#DwFdgYr z45ISG4zI2Yj-Tk1GCl*>hYwiqcEp&OQGEO`Uz;UN{O14uSjCF=rx$c#WEvOz+5jv{ ze`>wqv)`7v@6xBt$*RHyhCO^!31zq`eH0-K)P>G=t+&d0IZ*qyO_C6;y}CQhy^Ei% zjwzIvy&O5*JKcjc|Hz;zB#lnpym5O_3{(s9H=&DoUg(`toH9A8<)DSpd~-~m{4D@z z#!!5_lc&9xI+*!;=M`2kz=x&^uk+{7gwshs+gq#<TD@asxPpzE++FC{>>om6f@$e_ zgZp=EtQ#&1oOPYZ241c!PeoFyFgaE);;@;=k?IQstJ(=_#j{7eJkqwl3|T_3=mT6x zZ-SdEv_*@2OTr!mSOxZi!qbb7z3b%GTt$u9)^fMJ$~=oPqg$>e94<<4Ge-Jpz>$&4 zOZF^E<;ohGz`w5`3l)NHJs&B;1TE(C2=Rv;nXAJ%FE-uP!~`}3%AWxg4VewuTk^!g z^inaGM8L(UA>Bwhzr{@kS`in7_;?mTJA)LH)H8`EufCvzxulqd6ZLlfwoRlYL31+y z0G$*@m`Zl%tGYxP14Bz^p7Q<O<FFIFL42_8iW($^Wo%=22r<WqLO~rcX5Hizih#C= zpurf~6ROKSKPD5$$DhbTki10zYHpzK7*HWlCFahAFvIRXhxbXDA*jB`3i++teRfpM z0b)RtW*FHRM>3d)?XhdHg4ck18>MdQVN|_`{xQ}~=8_AY2ZPw$n^aPgA<~G0Z*#OH zEGVc-&43!669oXJ1q>o4eP^~&s2$DyW@Ycp6e~eYCf$97k7VSAMte_230Wm7!EYl= zEXAR65{DOFiC#ELg@ia1h`0w8d~|w-?BFDfcO07Ut|BQ+N$^!@rX5r8%D0$Mq`Cyb zcZnd9nMjI5a8Vq#@A7TJ4nJIUuz3tZ4JCEoOZj=tKm)BzuM<t!JT^nNEhUTc&^@9O zVDKUf%b0QFqY@t(j_>3SGNwY0380qIXeUwW3j=`BdJ1Q?HbWaG16~(ojUrP4p8?$h z#%`v)Hf-D_bM?;KF!^HCli)BZIk>E%DtcLrO;9~%6sC^;B*ye$5Lv3GX;RlBvXz*- z?RuBR2G+x6#qMWT#&3wP3~H<=+yrm^)-$x|gz2QlM_+XhVS?b!f@aMOD+9}{nEBS$ zht$SYNet35yTHoTrrpEpszi(Vlq&D8_1DO%I;B^tfuq@@IAG$z_x>_X=7qNY*Dxe! zkqy^$ktt*ZMlv*vtH}ovNkz7$23Tx_>ZaV5rL?^>;r@IH#i212ibY8_7TQSBfQImx z?O_}{!e!4Vo)<6HT&G6qwGFD-K}usq;8uMp7{nMW^#veCc)hc#>xN$`V@Ly<$P(=z zj3Q;y&Opjti86X_`tbWk4a*4(Bn**a>$OU~JtDM)IS*Lj;@Pw8v^n^F)YKI@9efHT zVXW8Mev}d5QRVFWd)(y_zf-yT3V{tJ3|eV4r>ZrqP)Us9%xyB-aQ!W$Mt-*g4L5v5 zQxK<PnI+t<Vy0pD{Og1ia7g}M7iRp&mhg{)%{)%XG5?W3a27!(=GAp0Mv;dL6qO;` z!RgvIUmq+?VwbjQIw_lhR|sHiXFIMZ?hM>ibN0eCqnOoj52}Fm7-j-JHj|(sn{FTJ zuRhv<m=>KtpKOkctvfvGmF3&~hu^*f)OAY(KBGUif*P%sC(v!+S%KO8ripXBG-^Px z#bFrcreP5Z`S<++I7%O`LQ#*U@)Y}&_<jH@cD2;5pq4wMA)f#FV!^ux00E6z_J)v| zkX2?flxul+u{8+|BA{f&rjwc|9|(eb%?V!jd<bnbO=kpfaRpu#JIFelf-C7?iT<LP zVvi(;W2wB9wz-ZDc8oO?V=#`CKJ<@HJ$SU<=OMxwFWP4C4^$;3Kp(;?1Sn~PPp+pv zAZLRTxcnQ$TyqmHSt`s_@d}O=LW7EO#qNe{Z;^eQiBEJ;teFyadbQcZ6`$-WNGRjV z*?5b{^#|cuZv`^qD@Af-P_eX@$zP<_N3nw(_K<^6wi!<tp42yUaR7NVK6Vp<w5B&3 zf0Cmoj%uC<Tndvx>R-2e2(UpV@#Q`JPfDC~PNgRvjedJMYGoQA6wnv+C%a?Fw&_C2 zTb!I79`KSb+W_YoP!(A$(pAKw!0!?-MFh<Mv7B@x1P{nP_&|ZhqeH?V5R3^SBWe!! zPV+?%W&Oc+XqJQc>;DdK+^fu1#bW>}KCcNYErKP~5Tm79@^{=7@EAJ@X0l;ENhA)x zo%5~1#a0TsW8Smcv+_bvBMlK(SXh1)g4UQe0+2cw5Pt=2966CW!6i$?B3iH%Wno3W z81cFXuiDyZS-4!EAe}L0d$OCLz#=r1K7n*fe!q8cz>CdqsIu`)Lmyp(f4n0te~_m~ zPap*T5tT%vHU{9eP6kjEWDTB}gGl{HL_rUjsJ?-wO+bvJHfAC3rH%c7=OrCFJ4G3J zqOd{^>TI%EF&;u_1>h;RmG)@LrU5R7Z=~Stfwn3`tS_{4uBM$I%yU&Kud4WFC(lto zCNP*L9!O8zx*A$=!<HyjCyz`lLKcLDwMyqs9Uhg0m<Mg$T5E7gvX(7m#ZVNkFNiN> zF!yo+TQQipVR8DKubv!q7<CtjGXI@g@B{X;Y*j4ToW;r`V78EsFkv1js}Rvb?N#no zR=AxlDhdkSmc4-0?S8~I(MT62df}@xp{_jTkn><UQ8ub<UYOxaPlL8+QP>G&u!L^w zAB(~R9#~q64|(<?Emef~(}<d}6-3@Z=}?sjMOM7d#J3`+-bexCF|r4UH?<bVDl3pd z_cXAAfPtHigDsTKpdp%w-o8HpVLGnSEubRLbYy4JEz%7K9IW;o*wK3`P7DH6%9G2^ zUwo9h?)J$4V^6i9_6fv>BSyAlGc<TSW({lUr!G4#NT@&V$m+&bYV9jnG@sMSoGFgZ z0Csfgq(XCmUhV#_%)pe1Vs{`)0*J5P1h(oeZmU&wF6I#-gmcjy$Wl!S{wNFJJ>QJ5 zCB?mJYozh;+v!3oXFekVK+g^C0he(B^FrBSNoW|yo%7Hkp-Pg2{9tMEYKxg+6NgNB zC$~b>YSD2&G!W4z3^+IKFHtOl3z0G*T=pnZDU>TXS;!$iBq;Mj__sK{F&18;+mn61 zjqhxh&<!kKrKcDi*BrRwpO)-Lo2<#kk)4g~lg)S!0Q;d|B?lYf6-t1~iaAss=rEvO z!CoJ;+MJ=uP%7()@GJi8U_C*sPk_z+t8x5*>Ekkiat8{f+{ZAfk$Ch^wF<Ha=a9m- zvnsd=+!ZctVY>@btY&<X4owE)Bt|D#9K@UpAonBzFS)bOe1rLl-(-}}KXQ>^cI(5d zVnyl5DmIO(a&ANE9cyNxzr;O-$%j=HO0zVS{GPIt%>*tp2l#50C;)*l6U^!&vq(1I z)DpeY>T2!)q4T-hqOQIK&CwzNkXhUB@YW#N+M60}_kDwjef=Zu%4EMn!+2w`j1>Fa zIJwl?ULY1ZEr7(S{)v??=<k-CJ3L9Lj!`yRpP1l9?<y$4Z^u-42QhowtEHjas}Q;- z_5Secm6ZAZEW~OY$3@Kzxv%CaeYYkSN0{twXQzJ>enAfU3tQNpK{z}wKjJ!MFH7DG zh=)3Ax1jOJfT>5K@S<-*Rm~#zH$8}<sWE5=*MUcI2Y*oN*b*u{ANf!N1L7I?cJ}tU z3#Ao-P;nbgn6qb5WHc7^uG(kEEA9dNVPh2hvDq30&!ZS^9*>Q&pTnCxWc;Bip*C~W z&6&$nKsA)KPrul^ZE{Y%ja-HUktdV+<iVN~>*AvSmT10x)}^nY`W0SOk<M|Xrkll6 z$(><=(8ZAN20N5#4hX|2vs5AHBGCcFDr_q#_@jCpTIv*g)c|3v1lC(}Iy!th&EEWC zc>WGsh-G_4J}j$TFqUoMN@+?pwXd{Av3McxE?si~Pr@dbCK$~ri)OVlv1V0{NGLQ( z>lT%CmPED93z?xdKh2RD$tI@tgi{-cn7oL_xt7jHU8oZ*Z<rdLMECQQx(*Y*)C)Z+ zM^%%4j1>f3soxeM1ebDuKMIvmYL?790V_^EB@&^T<|UYYHZ9wP%kJe9%#?OFN;@Ls ze8seIH8I^GmTVUZhH|WEd+mF>dqI2fsAd%6F8q&NvAX8_*e4#D5N*(3nx)08-Qs)V zNHXUaJnAH2FW#Rhq&8VD+?Mr@kbLySIHm`^>A_|nR9QyDJkSmtpAU*^^Oxw*9RYb0 zL;{qqKx_($Kd1(BdE;qF{^ZO!3gHXk^S|g}a_s>|_?ULN2s)Pb9ry)7{D1YGcG&O1 z7ly$+)M-0?WYK(}h?c~Rai!`Y390k-jfmx5wL5=uC84z#)2Nyy_Sxzjt@@`PcL&@- zF@In<<0ASw7Z9PhRlg(56)x+sd5#bhS#d*>vspY%No+k+xS<7Y1fLBkaXxGJZI}&p z-<WKu>+2!5p)-dRD?A@@reAEc=g=w$!=C}z&J;6jop(j3A)D<Ol-=eYzdRH1H0m!J zs@$lLf~kZtA_YmK-y{P!2%-ury@*DatbH)c?Ww{QRZ<U%WXEOmp)U?F(H|Rioo8b1 zyjYI+j`xCH6wIx|Al4XjcSoLoAvKoD2)!{@N)VdKjGob6XM$g`RDgLBGUYm#b8SmM z5XxGs6*7#jb?&(=cqaf(`sXKH&E~jkgq^=L;J1tkvnG#lgRAa^?!|s543vs|6N)08 zDj&3Esl!@spKdA<ZM)wqWX2P#Sj+@N#u}CG*bHZ)7&8FLSCx22>g!gLo>wG;_P4)A zR+gnU1Rw>>``MD?qgBB1G4IU{9{(nu?|XRjxrZ1L#|0w$yc`%qh}LI-V*evut&?7s zWUb!hVy#R_L_{Ldby!%XFAm*54FAUmeEvO_VuJNDUhYJf0T%^4N;T<&r<f<nI=W^2 zW$Ja_1d!aL;seGbew#(*P{L#TIFca!%!0i~wa4MYndk%J$#+D#x}}eX#cX<s-8{7( zUtGkq9%Dvh9A6z8g+uq#rAyzX-^+tI&N*D@gi^fMxUdZ4+1Wx8@C3od3`s->!V3|{ JvH{OO{txq}n{5C9 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg.prov new file mode 100644 index 00000000..f0e744ac --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/prog_restart.jpg.prov @@ -0,0 +1,5 @@ +fixture: prog_restart.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 809213f5fd6990175934c25e03eeb4a2c43d924256878ef82a9e50e98473c801 +regenerate: cjpeg -quality 90 -progressive -sample 2x2 -restart 4 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5230957e200507c8ea300e3927940eca055ebba1 GIT binary patch literal 22417 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 GkpJ1TUmFGh literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg.prov new file mode 100644 index 00000000..47289a89 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/refuse_arithmetic.jpg.prov @@ -0,0 +1,5 @@ +fixture: refuse_arithmetic.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: 2b41fb4ffef97ad0f978e0d4aa2cff0abc7abdfde1104692e4159199f6e7a989 +regenerate: cjpeg -quality 85 -arithmetic -sample 2x2 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg b/JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0819718768c206150bec72a7648b8983cf783dd9 GIT binary patch literal 46689 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 literal 0 HcmV?d00001 diff --git a/JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg.prov b/JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg.prov new file mode 100644 index 00000000..e3bbefcb --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/fixtures/trailing_garbage.jpg.prov @@ -0,0 +1,5 @@ +fixture: trailing_garbage.jpg +origin: synthesised by tests/fixtures/generate.py (original content) +license: CC0-1.0 / public domain (no third-party image used) +sha256: aa55e7e2147339bfb7d64d33bdfe35a273d055915db0d7083e8ef0e3d9982bcc +regenerate: base_444.jpg with literal trailing bytes appended after EOI diff --git a/JPXL/crates/jpxl-jpeg/tests/progressive_eob.rs b/JPXL/crates/jpxl-jpeg/tests/progressive_eob.rs new file mode 100644 index 00000000..5067ff24 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/progressive_eob.rs @@ -0,0 +1,74 @@ +//! Regression: progressive AC EOB-run lengths must be recorded at decode and +//! replayed at encode, not re-derived. +//! +//! An encoder may split one end-of-band run into several `EOBn` codes (libjpeg +//! flushes when its correction-bit buffer fills). That split is invisible in +//! the coefficients, so a re-encoder that greedily re-derives run lengths emits +//! a different bit count — the "N bits pending after padding" and +//! "symbol 0xN0 absent from Huffman table" failures seen on the archive sweep. +//! `prog_large.jpg` is large and detailed enough to force such splits. + +#![allow( + clippy::indexing_slicing, + clippy::unwrap_used, + reason = "test indexes its own just-loaded data; a panic is a failing test" +)] + +use std::path::PathBuf; + +use jpxl_jpeg::{Segment, parse, serialize}; + +fn fixture(name: &str) -> Vec<u8> { + let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("tests/fixtures"); + p.push(name); + std::fs::read(&p).unwrap_or_else(|e| panic!("reading {name}: {e}")) +} + +#[test] +fn roundtrip_progressive_large_is_bit_exact() { + let original = fixture("prog_large.jpg"); + let jpeg = parse(&original).expect("parse"); + let round = serialize(&jpeg).expect("serialize"); + assert_eq!( + original, round, + "prog_large.jpg must round-trip byte-exactly" + ); +} + +#[test] +fn recorded_eob_runs_are_load_bearing() { + let original = fixture("prog_large.jpg"); + let jpeg = parse(&original).expect("parse"); + + // The fixture must actually contain a *split* EOB run for this test to + // prove anything: two consecutive recorded runs whose total the greedy + // re-derivation would have merged into one. Any scan with >1 recorded run + // has been split by the original encoder. + let split_seen = jpeg.segments.iter().any(|s| match s { + Segment::Sos(scan) => scan.eob_runs.len() > 1, + _ => false, + }); + assert!( + split_seen, + "prog_large.jpg should exercise a multi-EOBn (split) run" + ); + + // With the recorded runs cleared, the encoder must fall back to greedy + // re-derivation, which cannot reproduce the split — so the output must + // differ from the original (or fail outright). Either proves the recorded + // runs are necessary for bit-exactness. + let mut stripped = jpeg.clone(); + for seg in &mut stripped.segments { + if let Segment::Sos(scan) = seg { + scan.eob_runs.clear(); + } + } + match serialize(&stripped) { + Ok(bytes) => assert_ne!( + bytes, original, + "clearing EOB runs must change the output (they are load-bearing)" + ), + Err(_) => { /* greedy re-derivation produced an illegal symbol: also proves the point */ } + } +} diff --git a/JPXL/crates/jpxl-jpeg/tests/refusal.rs b/JPXL/crates/jpxl-jpeg/tests/refusal.rs new file mode 100644 index 00000000..4aec6638 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/refusal.rs @@ -0,0 +1,137 @@ +//! Graceful, typed refusal of out-of-scope JPEG modes, and non-panicking +//! rejection of malformed input. +//! +//! Phase A supports Huffman-coded 8-bit baseline/progressive DCT only. Every +//! other well-formed JPEG mode must be rejected with [`JpegError::Unsupported`] +//! — never mis-parsed into silent garbage — and every malformed byte string +//! must return an `Err`, never panic (decode paths are attacker-facing). + +#![allow( + clippy::indexing_slicing, + clippy::unwrap_used, + reason = "tests index their own just-loaded data; a panic here is a failing \ + test, which is the intended signal" +)] + +use jpxl_jpeg::{JpegError, parse}; + +/// Builds a minimal `SOI` + one marker stream (enough to reach the code path +/// that classifies the marker). +fn soi_then(marker_code: u8, tail: &[u8]) -> Vec<u8> { + let mut v = vec![0xFF, 0xD8, 0xFF, marker_code]; + v.extend_from_slice(tail); + v +} + +#[test] +fn refuses_arithmetic_coded_jpeg_fixture() { + let data = std::fs::read(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/refuse_arithmetic.jpg" + )) + .expect("arithmetic fixture"); + match parse(&data) { + Err(JpegError::Unsupported(_)) => {} + other => panic!("expected Unsupported for arithmetic JPEG, got {other:?}"), + } +} + +#[test] +fn refuses_lossless_sof3() { + // SOF3 is classified before its body is read. + assert!(matches!( + parse(&soi_then(0xC3, &[])), + Err(JpegError::Unsupported(_)) + )); +} + +#[test] +fn refuses_hierarchical_sof5() { + assert!(matches!( + parse(&soi_then(0xC5, &[])), + Err(JpegError::Unsupported(_)) + )); +} + +#[test] +fn refuses_arithmetic_sof9() { + assert!(matches!( + parse(&soi_then(0xC9, &[])), + Err(JpegError::Unsupported(_)) + )); +} + +#[test] +fn refuses_arithmetic_conditioning_dac() { + assert!(matches!( + parse(&soi_then(0xCC, &[])), + Err(JpegError::Unsupported(_)) + )); +} + +#[test] +fn refuses_hierarchical_dhp() { + assert!(matches!( + parse(&soi_then(0xDE, &[])), + Err(JpegError::Unsupported(_)) + )); +} + +#[test] +fn refuses_twelve_bit_precision() { + // A well-formed baseline SOF0 header claiming 12-bit samples. + let sof0_12bit = soi_then( + 0xC0, + &[ + 0x00, 0x11, // Lf = 17 + 0x0C, // P = 12-bit precision + 0x00, 0x10, // Y = 16 + 0x00, 0x10, // X = 16 + 0x03, // Nf = 3 + 0x01, 0x11, 0x00, // component 1 + 0x02, 0x11, 0x00, // component 2 + 0x03, 0x11, 0x00, // component 3 + ], + ); + assert!(matches!(parse(&sof0_12bit), Err(JpegError::Unsupported(_)))); +} + +// ---- Malformed input must error, not panic -------------------------------- + +#[test] +fn rejects_non_jpeg_bytes() { + assert!(parse(b"not a jpeg at all").is_err()); + assert!(parse(&[]).is_err()); + assert!(parse(&[0xFF]).is_err()); + assert!(parse(&[0xFF, 0xD8]).is_err()); // SOI then nothing +} + +#[test] +fn rejects_truncated_baseline_without_panic() { + // Take a real baseline JPEG and truncate it at many offsets; none may panic. + let data = std::fs::read(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/base_444.jpg" + )) + .unwrap(); + for cut in (2..data.len()).step_by(97) { + // Must return Ok or Err, but never unwind. + let _ = parse(&data[..cut]); + } +} + +#[test] +fn rejects_corrupted_entropy_without_panic() { + let mut data = std::fs::read(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/prog_420.jpg" + )) + .unwrap(); + // Flip bytes across the stream; parsing must stay panic-free. + for i in (0..data.len()).step_by(31) { + let saved = data[i]; + data[i] ^= 0xA5; + let _ = parse(&data); + data[i] = saved; + } +} diff --git a/JPXL/crates/jpxl-jpeg/tests/roundtrip.rs b/JPXL/crates/jpxl-jpeg/tests/roundtrip.rs new file mode 100644 index 00000000..1aeb29e2 --- /dev/null +++ b/JPXL/crates/jpxl-jpeg/tests/roundtrip.rs @@ -0,0 +1,189 @@ +//! Byte-exact `serialize(parse(x)) == x` round-trip over real JPEG fixtures. +//! +//! Every fixture is produced by libjpeg-turbo (`cjpeg`) or Pillow from +//! synthetic original content; see `tests/fixtures/generate.py` and the +//! per-file `*.jpg.prov` provenance sidecars. The images are 385×259 — many +//! MCUs, non-multiple-of-16 edges, and (for subsampled cases) differing +//! interleaved / non-interleaved block counts — so the round-trip exercises +//! edge padding blocks, restart cadence, and padding bits, not just a happy +//! single-block path. + +#![allow( + clippy::indexing_slicing, + clippy::unwrap_used, + reason = "tests index their own just-loaded data; a panic here is a failing \ + test, which is the intended signal" +)] + +use std::path::PathBuf; + +use jpxl_jpeg::{parse, serialize}; + +fn fixture(name: &str) -> Vec<u8> { + let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("tests/fixtures"); + p.push(name); + std::fs::read(&p).unwrap_or_else(|e| panic!("reading fixture {name}: {e}")) +} + +/// Parses then re-serializes, asserting byte-for-byte identity, and returns the +/// parsed model so callers can additionally assert structural facts. +fn assert_roundtrip(name: &str) -> jpxl_jpeg::Jpeg { + let original = fixture(name); + let jpeg = parse(&original).unwrap_or_else(|e| panic!("parse {name}: {e}")); + let reemitted = serialize(&jpeg).unwrap_or_else(|e| panic!("serialize {name}: {e}")); + assert_eq!( + original.len(), + reemitted.len(), + "{name}: length differs ({} vs {})", + original.len(), + reemitted.len() + ); + if original != reemitted { + // Report the first divergence to make failures debuggable. + let at = original + .iter() + .zip(&reemitted) + .position(|(a, b)| a != b) + .unwrap_or(0); + panic!( + "{name}: byte mismatch at offset {at}: original 0x{:02X} != re-emitted 0x{:02X}", + original[at], reemitted[at] + ); + } + jpeg +} + +// ---- Baseline sequential --------------------------------------------------- + +#[test] +fn roundtrip_baseline_444_is_bit_exact() { + let j = assert_roundtrip("base_444.jpg"); + let f = j.frame.as_ref().expect("frame"); + assert_eq!((f.width, f.height), (385, 259)); + assert_eq!(f.components.len(), 3, "YCbCr"); + // Prove parsing produced real coefficients, not an empty pass-through. + let any_ac = j + .planes + .iter() + .any(|p| p.blocks.iter().any(|b| b.iter().skip(1).any(|&c| c != 0))); + assert!(any_ac, "expected some nonzero AC coefficients"); +} + +#[test] +fn roundtrip_baseline_422_is_bit_exact() { + let j = assert_roundtrip("base_422.jpg"); + let f = j.frame.as_ref().unwrap(); + assert_eq!((f.components[0].h, f.components[0].v), (2, 1)); +} + +#[test] +fn roundtrip_baseline_420_is_bit_exact() { + let j = assert_roundtrip("base_420.jpg"); + let f = j.frame.as_ref().unwrap(); + assert_eq!((f.components[0].h, f.components[0].v), (2, 2)); +} + +#[test] +fn roundtrip_baseline_440_is_bit_exact() { + assert_roundtrip("base_440.jpg"); +} + +#[test] +fn roundtrip_baseline_grayscale_is_bit_exact() { + let j = assert_roundtrip("base_gray.jpg"); + assert_eq!(j.frame.as_ref().unwrap().components.len(), 1); +} + +#[test] +fn roundtrip_baseline_high_quality_dense_coeffs_is_bit_exact() { + assert_roundtrip("base_q98.jpg"); +} + +#[test] +fn roundtrip_baseline_low_quality_sparse_coeffs_is_bit_exact() { + assert_roundtrip("base_q20.jpg"); +} + +// ---- Restart intervals ----------------------------------------------------- + +#[test] +fn roundtrip_baseline_restart_interval_is_bit_exact() { + let j = assert_roundtrip("base_restart.jpg"); + // The restart interval must have been captured as a DRI segment. + let has_dri = j + .segments + .iter() + .any(|s| matches!(s, jpxl_jpeg::Segment::Dri(_))); + assert!(has_dri, "expected a DRI segment"); +} + +#[test] +fn roundtrip_baseline_restart_rows_is_bit_exact() { + assert_roundtrip("base_restart_rows.jpg"); +} + +// ---- Progressive ----------------------------------------------------------- + +#[test] +fn roundtrip_progressive_444_is_bit_exact() { + let j = assert_roundtrip("prog_444.jpg"); + // A progressive frame has multiple SOS segments (spectral selection). + let scans = j + .segments + .iter() + .filter(|s| matches!(s, jpxl_jpeg::Segment::Sos(_))) + .count(); + assert!( + scans > 1, + "progressive stream should have several scans, got {scans}" + ); +} + +#[test] +fn roundtrip_progressive_420_is_bit_exact() { + assert_roundtrip("prog_420.jpg"); +} + +#[test] +fn roundtrip_progressive_grayscale_is_bit_exact() { + assert_roundtrip("prog_gray.jpg"); +} + +#[test] +fn roundtrip_progressive_restart_is_bit_exact() { + assert_roundtrip("prog_restart.jpg"); +} + +#[test] +fn roundtrip_progressive_low_quality_is_bit_exact() { + assert_roundtrip("prog_q30.jpg"); +} + +// ---- Metadata and trailing data ------------------------------------------- + +#[test] +fn roundtrip_exif_icc_metadata_is_bit_exact() { + let j = assert_roundtrip("meta_exif_icc.jpg"); + let has_app = j + .segments + .iter() + .any(|s| matches!(s, jpxl_jpeg::Segment::App(_))); + assert!(has_app, "expected APPn metadata segments"); +} + +#[test] +fn roundtrip_xmp_metadata_is_bit_exact() { + let j = assert_roundtrip("meta_xmp.jpg"); + let has_app = j + .segments + .iter() + .any(|s| matches!(s, jpxl_jpeg::Segment::App(_))); + assert!(has_app, "expected an APP1 XMP segment"); +} + +#[test] +fn roundtrip_trailing_garbage_after_eoi_is_bit_exact() { + let j = assert_roundtrip("trailing_garbage.jpg"); + assert!(!j.tail.is_empty(), "trailing bytes should be captured"); +} diff --git a/JPXL/tools/oracle-bin/PINNED_REVISIONS.txt b/JPXL/tools/oracle-bin/PINNED_REVISIONS.txt index d283e848..2744808d 100644 --- a/JPXL/tools/oracle-bin/PINNED_REVISIONS.txt +++ b/JPXL/tools/oracle-bin/PINNED_REVISIONS.txt @@ -1,18 +1,18 @@ # JPXL oracle provenance -# Generated by Windows MinGW rebuild -- do not edit by hand. +# Generated by tools/setup-oracles.sh -- do not edit by hand. # Oracles are black boxes: their source is never read. -date: 2026-08-05T08:23:38Z -host: Windows Microsoft Windows NT 10.0.26200.0 +date: 2026-08-25T07:40:41Z +host: Linux 6.17.0-41-generic x86_64 [libjxl] -path: D:/Rust-projects/jpegXL-rs/libjxl +path: /mnt/Samsung980_1TB/Rust-projects/jpegXL-rs/libjxl revision: 196a43d996aa6ed33ebf98812a7c6d43b2b6d01b -describe: v0.12-snapshot-2-g196a43d9-dirty -cmake_flags: -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_EXAMPLES=OFF -DJPEGXL_ENABLE_MANPAGES=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_DOXYGEN=OFF -DJPEGXL_ENABLE_JNI=OFF -DJPEGXL_ENABLE_SJPEG=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DBUILD_SHARED_LIBS=OFF -G Ninja (MinGW build-win) -djxl_version: djxl v0.13.0 196a43d9 [_AVX2_,SSE4,SSE2] {GNU 16.1.0} -cjxl_version: cjxl v0.13.0 196a43d9 [_AVX2_,SSE4,SSE2] {GNU 16.1.0} +describe: v0.12-snapshot-2-g196a43d9 +cmake_flags: -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_EXAMPLES=OFF -DJPEGXL_ENABLE_MANPAGES=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_DOXYGEN=OFF -DJPEGXL_ENABLE_JNI=OFF -DJPEGXL_ENABLE_SJPEG=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DBUILD_SHARED_LIBS=OFF -G Ninja +djxl_version: djxl v0.13.0 196a43d9 [_AVX2_,SSE4,SSE2] {GNU 15.2.0} +cjxl_version: cjxl v0.13.0 196a43d9 [_AVX2_,SSE4,SSE2] {GNU 15.2.0} [jxl-oxide] -path: C:\Users\dk\.cargo\bin\jxl-oxide.exe -version: jxl-oxide-cli 0.12.6 +path: /home/dk/.cargo/bin/jxl-oxide +version: jxl-oxide-cli 0.12.6 From b6913f7b217b64d4d33d358e59b5060e8d7796b8 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 16:30:55 +0800 Subject: [PATCH 15/27] chore(.akr): record the 2026-08-25 session evidence and revisions - pqc-usable-efforts-cost rev 7: memory-12mp (1.95 GiB peak) and production-identity (52/52 cells, threads 1/4, AVX2 on/off) re-verified on the current binary; the quiet-host wall observation re-baselines the anchors (12 MP 5.42x/4t, 4.25x/8t) and shows the 2.0x wall target is unreachable by search improvements alone - fixed costs are ~1.95x the rate path; wall-anchors stays open pending a target decision. - pqc-one-shot-controller rev 3: the saturated/gradient corpus generation gap is closed (26/25 families); label sweeps remain. - jpeg-bitstream-recompression active: Phase A evidence over the full 5,218-file archive; the EOB-run record/replay discovery is noted for Phases B/C. - publish-readme-benchmark-mit completed: all four checks evidenced (reproducible native-oracle comparison, MIT-only audit, README current-state, workspace gates). - papercut: akr commit-msg hook rejects message-only amends after the change transaction closes. AKR-Change: chg-186f2acbbc075c3f AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Evidence: @jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.mit-only-licensing-audit-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.readme-current-state-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.workspace-gates-2026-08-25/1 AKR-Graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 AKR-Tree: 3511116ebb09e1b5ab1dff4662880d6963eafc7b Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- .akr/akr.lock | 124 +++++++++++++++++++++--- .akr/records/jpegxl-rs/evidence.akr | 85 ++++++++++++++++ .akr/records/jpegxl-rs/observations.akr | 33 +++++++ .akr/records/jpegxl-rs/papercuts.akr | 12 +++ .akr/records/jpegxl-rs/work.akr | 108 +++++++++++++++++++-- docs/generated/ACTIVE-WORK.md | 39 +++----- docs/generated/CURRENT-STATE.md | 97 +++++++++++++++++- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 3 +- docs/generated/REVIEW-REQUIRED.md | 18 +++- docs/generated/ROADMAP.md | 8 +- 12 files changed, 469 insertions(+), 62 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index a0da948e..83782e1b 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd" + source_graph "sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56" } source ".akr/project.akr" { @@ -29,8 +29,8 @@ source ".akr/records/jpegxl-rs/decisions.akr" { } source ".akr/records/jpegxl-rs/evidence.akr" { - hash "sha256:67c44836cf09099abe12a6ac71e16e3bbeab8649fc36412b09ba48d74d72832f" - records 549 + hash "sha256:54776057fd9d6c985ffe10a1a74772aa4e62077bd387cc036456ca7d964c5e25" + records 556 } source ".akr/records/jpegxl-rs/milestones.akr" { @@ -39,13 +39,13 @@ source ".akr/records/jpegxl-rs/milestones.akr" { } source ".akr/records/jpegxl-rs/observations.akr" { - hash "sha256:e1d09082bfc799c75177ae7c27aa11fb799aa9e84833d5c85b71443549d5330d" - records 99 + hash "sha256:09c16275005d4cbfe0b8df4b7b0e4719496adf50211ea286a29a63e0e064a3ab" + records 101 } source ".akr/records/jpegxl-rs/papercuts.akr" { - hash "sha256:e72aa62ec85dba557e935911debdbc32965208354d8361dbf0d8e3953d8c1c67" - records 43 + hash "sha256:eddba95b51f812c3fd53a9f47eb9a815b2f7cfbb86a193656d689b82d2ce5928" + records 44 } source ".akr/records/jpegxl-rs/policies.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:3127d3f9ad02fca3ff0a3eb6a82a805c839de5489cfc1fb7706d29eb4b8ad4ae" - records 183 + hash "sha256:295abcc2347fe2f05f43721d42bf74681209786b20269c56d4f0dff896d09446" + records 185 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -1425,8 +1425,8 @@ resolution @jpegxl-rs.observation.phase5-quality-policy-screen-2026-08-11/11 { resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 { slot derived_from - to @jpegxl-rs.work.pqc-one-shot-controller/2 - hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" + to @jpegxl-rs.work.pqc-one-shot-controller/3 + hash "sha256:b08e9ecd74467c21be5f661c5c94417d63576222c3713c1c9a9cacfd07a1773f" } resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1 { @@ -1437,8 +1437,8 @@ resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-202 resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 { slot derived_from - to @jpegxl-rs.work.pqc-one-shot-controller/2 - hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" + to @jpegxl-rs.work.pqc-one-shot-controller/3 + hash "sha256:b08e9ecd74467c21be5f661c5c94417d63576222c3713c1c9a9cacfd07a1773f" } resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/2 { @@ -1447,6 +1447,12 @@ resolution @jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-202 hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" } +resolution @jpegxl-rs.observation.quality-corpus-critical-class-families-2026-08-25/1 { + slot derived_from + to @jpegxl-rs.work.pqc-one-shot-controller/3 + hash "sha256:b08e9ecd74467c21be5f661c5c94417d63576222c3713c1c9a9cacfd07a1773f" +} + resolution @jpegxl-rs.observation.quantizer-normalised-distortion-is-the-lever-2026-08-12/1 { slot derived_from to @jpegxl-rs.observation.distortion-currency-misprices-frequency-2026-08-12/1 @@ -3703,6 +3709,12 @@ resolution @jpegxl-rs.work.pqc-one-shot-controller/2 { hash "sha256:b835fe82f41cf362b357e9b22d4f9cc5b6ed0c8ed7a256da9c4e51d18576c366" } +resolution @jpegxl-rs.work.pqc-one-shot-controller/3 { + slot depends_on + to @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 + hash "sha256:b835fe82f41cf362b357e9b22d4f9cc5b6ed0c8ed7a256da9c4e51d18576c366" +} + resolution @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 { slot implements to @jpegxl-rs.decision.quality-miss-fallback-semantics/1 @@ -3715,6 +3727,18 @@ resolution @jpegxl-rs.work.pqc-usable-efforts-cost/6 { hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" } +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/7 { + slot supported_by + to @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 + hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" +} + +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/7 { + slot supported_by + to @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1 + hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" +} + resolution @jpegxl-rs.work.quality-q0-harness-attribution/1 { slot part_of to @jpegxl-rs.track.encoder-optimization/1 @@ -3823,6 +3847,11 @@ seal @jpegxl-rs.decision.ssimulacra2-is-the-primary-promotion-metric/1 { hash "sha256:9aa847d339b8d732eb49a5f6bbd48a6c4e41819fd3824400c8c4681d7b4f8549" } +seal @jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 { + state verified + hash "sha256:a24dfed6aab83df6837b4ebe83c0a2bf6aa28a646df346c2b6f84175fe267b3b" +} + seal @jpegxl-rs.evidence.cli-netpbm-subtypes-2026-08-21/1 { state verified hash "sha256:b2c54a89b5174895fb5955a8fedbec20f197f9fb970db914dffd48f3ef55525f" @@ -4063,6 +4092,11 @@ seal @jpegxl-rs.evidence.history-backfilled/1 { hash "sha256:4aa4bba4295cce3e93c48dff164c981a82ecf1ad1206355da100dfc2d81ed0f1" } +seal @jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1 { + state verified + hash "sha256:758a044c3d01f0c235ff65a06fdb5928e83d2388a5be7be9d95ebacdbbf09d0f" +} + seal @jpegxl-rs.evidence.m1-plan-ir/1 { state verified hash "sha256:c89bc7184d9b204761769600cac70406bef53a0cd84e263ba7e450e393ed222c" @@ -4113,6 +4147,11 @@ seal @jpegxl-rs.evidence.markdown-cutover/2 { hash "sha256:f6d62a57f797272bde901c7362291f30e581372263914c099cbbea2ce3648610" } +seal @jpegxl-rs.evidence.mit-only-licensing-audit-2026-08-25/1 { + state verified + hash "sha256:657d25081fa678e05ee0a58ae07779836eb24161441877d7b8078ec0f6ff03cc" +} + seal @jpegxl-rs.evidence.modular-effort-ramp-landed-2026-08-10/1 { state verified hash "sha256:026ded0ca69253f0932ef49abcb56de636c98dd28d9a0021f6a2adb002348524" @@ -5928,6 +5967,11 @@ seal @jpegxl-rs.evidence.pqc-low-memory-xlarge-identity-2026-08-23/1 { hash "sha256:79a3f38d1ea158a2cf0d801e7183885188f146d970d654988c55de059ca11d39" } +seal @jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 { + state verified + hash "sha256:214d5a4410bf29ecec7b1250fcf741e3d23891c3dbaeb124c0000ed36eb5c40c" +} + seal @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 { state verified hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" @@ -6168,6 +6212,11 @@ seal @jpegxl-rs.evidence.pqc-quality-rate-workspace-gates-2026-08-23/1 { hash "sha256:0b79b56ce0add2a7dfcfb65e2fe21ffd19c63d29c58eef060fe7ae8aabbd2e30" } +seal @jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1 { + state verified + hash "sha256:cf0e291df7b6b964933db25872fdaedcd256bea565ebb15dc0af43b7edf9a906" +} + seal @jpegxl-rs.evidence.pqc-workspace-gates-2026-08-22/1 { state verified hash "sha256:2ec904ea78689b5b6d6b13bf089b1ec9216440c0fe53933a7adfcc624bae23eb" @@ -6478,6 +6527,11 @@ seal @jpegxl-rs.evidence.quality-q9-workspace-gates-2026-08-19/1 { hash "sha256:1354865c8a1ed5c86b4115e25fce5f36ba058ca2ca0cda994a6819ce5b55394a" } +seal @jpegxl-rs.evidence.readme-current-state-2026-08-25/1 { + state verified + hash "sha256:989ae9559b849bf5403b1190578cd14817311aeecda969877ffb3c41aff8c427" +} + seal @jpegxl-rs.evidence.release-workspace-tests-2026-08-21/1 { state verified hash "sha256:0adca8daccad59afa3f6628047cfa6c0987dc2f28d25dbf22f85df15ccb46a49" @@ -6568,6 +6622,11 @@ seal @jpegxl-rs.evidence.spine-conventions/1 { hash "sha256:5793d09f02a73315418bdbcd130b2116fba1c79ae1b09148cc2deb782032c0d3" } +seal @jpegxl-rs.evidence.workspace-gates-2026-08-25/1 { + state verified + hash "sha256:153332cecba0e30394a6f437136d4b713462bec9a737dab6abf96acffa6dabe6" +} + seal @jpegxl-rs.milestone.durable-rulings/1 { state completed hash "sha256:5b51c57197a52f692c0735aabb0dec45b946cf834767e8ffa53bb1bb22d98d35" @@ -7008,6 +7067,11 @@ seal @jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1 { hash "sha256:1109634526a1ddc0fa6bb5b91274107c3d16b335ed5c6b8e6c1bbd72cd89b882" } +seal @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1 { + state verified + hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" +} + seal @jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/1 { state superseded hash "sha256:237d838644e7329dac763d29c12a89e23016f8eea889b92bb29faba53b3cc089" @@ -7103,6 +7167,11 @@ seal @jpegxl-rs.observation.q6-zero-run-term-explains-dct8-residual-only-2026-08 hash "sha256:b8f2dbcfa64821d55adec25c2e7bcacda934a401eefbbd7723fb364caa4f259a" } +seal @jpegxl-rs.observation.quality-corpus-critical-class-families-2026-08-25/1 { + state verified + hash "sha256:576bd479e06fb2d5d69373a7ba5f73bf941f179bede18cf38b6208c3ae362421" +} + seal @jpegxl-rs.observation.quantizer-normalised-distortion-is-the-lever-2026-08-12/1 { state verified hash "sha256:e56c8f017d1e65f60d136491437082485b23dc243579d744580724d60c5376d8" @@ -7218,6 +7287,11 @@ seal @jpegxl-rs.papercut.akr-view-active-work-current-state-etc-returned/1 { hash "sha256:2a61604b0658be410cee41308798d1ebbd09eaddc359920070ef288f28094216" } +seal @jpegxl-rs.papercut.amending-an-akr-generated-commit-message-to/1 { + state verified + hash "sha256:324b2d0be9193c85fd78d61b16b0b192e5018869696403eb3e83deaaa0e0719b" +} + seal @jpegxl-rs.papercut.cargo-fmt-all-check-fails-on-code-committed-at/1 { state verified hash "sha256:71d820d4bdb6ed7e69feba0fc64163724fa0206d5d81f06df6edc39b8e5f4f7a" @@ -8083,6 +8157,11 @@ seal @jpegxl-rs.work.general-use-api-cli/2 { hash "sha256:5a06768e00971924a9a0b85b80406c86d2c9f1fe5f1d0deaad629c016bdd6d3e" } +seal @jpegxl-rs.work.jpeg-bitstream-recompression/1 { + state active + hash "sha256:ce3dba766d1838cacfa52c7f95faf9c996bebf87a5013c532a4b798d7a41bce2" +} + seal @jpegxl-rs.work.native-windows-quality-tooling/1 { state completed hash "sha256:32381a6237cf21c76162f2b8c666262d29a970603310d53ac7f5116173ee1095" @@ -8129,8 +8208,13 @@ seal @jpegxl-rs.work.pqc-one-shot-controller/1 { } seal @jpegxl-rs.work.pqc-one-shot-controller/2 { + state superseded + hash "sha256:cb0014971ecf67042acc47d0a458c8143961be1258d98133f4f9e99038818206" +} + +seal @jpegxl-rs.work.pqc-one-shot-controller/3 { state active - hash "sha256:41bf445e6259c137fd5dbe573ab6833b8d7b0f91b25287ce82ebe7d6dd8f3ac4" + hash "sha256:b08e9ecd74467c21be5f661c5c94417d63576222c3713c1c9a9cacfd07a1773f" } seal @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 { @@ -8229,8 +8313,18 @@ seal @jpegxl-rs.work.pqc-usable-efforts-cost/5 { } seal @jpegxl-rs.work.pqc-usable-efforts-cost/6 { + state superseded + hash "sha256:281900161facaedb1d325cb274dfec47a1a48830edde29c310686ec05de15865" +} + +seal @jpegxl-rs.work.pqc-usable-efforts-cost/7 { state active - hash "sha256:084f5df3c370d5557c91622769f0392688b3ff2abd694f293a5e1243655ab386" + hash "sha256:baca2e78899f9133ffaacaba235d792e3379c1ddc14000adbed9187e5f09c5d9" +} + +seal @jpegxl-rs.work.publish-readme-benchmark-mit/1 { + state completed + hash "sha256:99149d1ef7358b3e24f53ec0fd705b7c8093b979333c973bdabbb1a223de48d3" } seal @jpegxl-rs.work.quality-q0-harness-attribution/1 { diff --git a/.akr/records/jpegxl-rs/evidence.akr b/.akr/records/jpegxl-rs/evidence.akr index 0f2537fa..6cb36b98 100644 --- a/.akr/records/jpegxl-rs/evidence.akr +++ b/.akr/records/jpegxl-rs/evidence.akr @@ -1,6 +1,18 @@ akr 0.1 project jpegxl-rs +record jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 : evidence { + title "One documented command compares JPXL and native cjxl/djxl on the same inputs with recorded provenance" + state verified + result pass + method command + observed_at git:a523d0157ac223678b9d02342d0a8a7fbc34d642 + command "JPXL/tools/bench_vs_libjxl.sh --jpxl target/release/jpxl --quality \"70 85\" --runs 1 --jsonl bench-vs-libjxl.jsonl <corpus images>" + summary """ + After tools/setup-oracles.sh produced native Linux cjxl/djxl v0.13.0 (196a43d9, pinned in oracle-bin/PINNED_REVISIONS.txt), bench_vs_libjxl.sh emitted a size/bpp/SSIMULACRA2/wall table for jpxl --quality 70/85 and cjxl -d 3.0/1.5/1.0 on three corpus images (2400x1800 photo, 1600x900 text-screenshot, 700x700 radial gradient), every stream decoded and scored by the same in-tree metric, with a provenance header (UTC date, host, binary versions and sha256, flags, per-input sha256/dims) and machine-readable jpxl.bench-vs-libjxl/1 JSONL kept at pqc-scatter-20260825/bench-vs-libjxl.jsonl. Sample: on the photo, jpxl q85 emitted 796,985 bytes at score 85.45 versus cjxl -d1.0 at 804,397 bytes and score 83.41; on text-screenshot cjxl -d3.0 was 4.7x smaller at a higher score, consistent with the recorded VarDCT-on-synthetic gap. + """ +} + record jpegxl-rs.evidence.cli-netpbm-subtypes-2026-08-21/1 : evidence { title "Focused CLI tests passed: explicit PPM/PGM output uses P6/P5 and incompatible channel layouts error." state verified @@ -583,6 +595,18 @@ record jpegxl-rs.evidence.history-backfilled/1 : evidence { author "GitHub Uploader" } +record jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1 : evidence { + title "jpxl-jpeg roundtrips the full 5,218-file Pol Art archive byte-identically except one provably corrupt file, with typed refusals proven" + state verified + result pass + method command + observed_at git:c1cdbf582689914c46af67c1b3e59cacc88b920b + command "cd JPXL && cargo run --release -p jpxl-jpeg --example roundtrip_sweep -- \"/mnt/Samsung980_1TB/Pol Desktop/Pol Art Folder 08-24-2017\"" + summary """ + The deterministic sweep (sorted paths, stride 1 — a superset of the >=500-sample requirement) parsed and re-serialized every JPEG in the archive: 5,217 of 5,218 byte-identical with zero mismatches and zero refusals; coverage of the identical set spans baseline 3,166, progressive 2,049, extended sequential 2, chroma 4:4:4 2,985 / 4:2:0 2,081 / 4:2:2 140 / 4:4:0 6, grayscale, restart intervals 684, Exif 2,419 / ICC 1,558 / XMP 1,396, and 41 files with post-EOI tail data. The single non-roundtripping file is ~15 KB of real data zero-padded to 3.18 MB, rejected by libjpeg's djpeg as premature EOF and refused here with a typed malformed-stream error. All 81 earlier mismatches shared one root cause — libjpeg's non-derivable progressive AC EOB-run splits — fixed by recording the exact run lengths at decode and replaying them flush-on-match at encode; the prog_large.jpg regression fixture proves the recorded runs are load-bearing. Typed refusal of arithmetic, hierarchical, lossless, and 12-bit inputs is proven by 10 dedicated tests; 34 crate tests pass in release. + """ +} + record jpegxl-rs.evidence.m1-plan-ir/1 : evidence { title "M1 - VarDCT plan IR and structural split" state verified @@ -763,6 +787,19 @@ record jpegxl-rs.evidence.markdown-cutover/2 : evidence { author "GitHub Uploader" } +record jpegxl-rs.evidence.mit-only-licensing-audit-2026-08-25/1 : evidence { + title "Workspace licensing surface is MIT-only with no copyleft anywhere in the dependency graph" + state verified + result pass + method command + observed_at git:a523d0157ac223678b9d02342d0a8a7fbc34d642 + command "cd JPXL && cargo tree --workspace (dependency/license enumeration; full table in JPXL/docs/LICENSING-AUDIT.md)" + artifact "JPXL/docs/LICENSING-AUDIT.md" + summary """ + All 12 workspace crates declare MIT via license.workspace; LICENSE and JPXL/LICENSE-MIT carry consistent MIT text; a scan of the full dependency graph (default ~41 external crates plus ~26 behind off-by-default measurement features) found no GPL/LGPL/AGPL/MPL/CDDL/EUPL/SSPL license anywhere; the only non-MIT arms are permissive (moxcms and pxfm BSD-3-Clause/Apache-2.0 on the default path via the image raster adapter; ssimulacra2 BSD-2, butteraugli BSD-3, v_frame BSD-2, imgref CC0/Apache behind feature gates). No ISO text or AGPL-derived content is present in any shipped crate. Full table in JPXL/docs/LICENSING-AUDIT.md. + """ +} + record jpegxl-rs.evidence.modular-effort-ramp-landed-2026-08-10/1 : evidence { title "At 70688ac the lean default (Effort::DEFAULT = 1) reproduces the pre-change default's bytes exactly on all three photo-corpus sizes: small_0p8MP 1,075,465 B sha16 C3C60CEE4D0741D9, mid_4MP 5,157,974 B sha16 5C49AB9020706F0D, large_12MP 9,795,599 B sha16 CA27799D3E7B441D -- all IDENTICAL to the pre-ramp fingerprints. Wall time 267 / 619 / 1165 ms for the default versus 1185 / 5324 / 16919 ms for --effort 7, which reproduces the same three fingerprints and so remains the byte-identical density anchor. Speedup on this corpus is 4.4x / 8.6x / 14.5x for identical output." state verified @@ -5093,6 +5130,18 @@ record jpegxl-rs.evidence.pqc-low-memory-xlarge-identity-2026-08-23/1 : evidence """ } +record jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 : evidence { + title "Balanced q85 on the locked 12 MP anchor peaks at 1.95 GiB RSS, under the 2.0 GiB ceiling" + state verified + result pass + method command + observed_at git:b0c128c785d7524c6002eb34ca7ebd990b24bf1e + command "python3 .agent/scratch/pqc-scatter-20260825/rss_12mp.py" + summary """ + Three serialized release encodes of the locked 4000x3000 anchor (Balanced q85, 4 threads, RLIMIT_AS 9 GiB, ru_maxrss via wait4) peaked at 2,036,248-2,043,056 KiB, all under the 2,097,152 KiB (2.0 GiB) ceiling, with identical 1,315,649-byte output each run; rows in the kept scratch entry pqc-scatter-20260825/rss-12mp.json. + """ +} + record jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 : evidence { title "One-shot falsification screen: fails on corpus coverage, transform features validated" state verified @@ -5673,6 +5722,18 @@ record jpegxl-rs.evidence.pqc-quality-rate-workspace-gates-2026-08-23/1 : eviden """ } +record jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1 : evidence { + title "Production identity holds after the band-parallel scatter: 52/52 locked cells identical across threads 1/4 and AVX2 on/off" + state verified + result pass + method command + observed_at git:b0c128c785d7524c6002eb34ca7ebd990b24bf1e + command "python3 .agent/scratch/pqc-scatter-20260825/identity_scatter.py" + summary """ + The full locked 13-image matrix (both production efforts, q70 and q85, serialized under a 9 GiB address-space cap) produced 52/52 byte-identical cells across threads 1, threads 4, and threads 4 with JPXL_DISABLE_AVX2=1 on the band-parallel-scatter binary; separately, six image/target cells (both anchors at q85/q70, Fast and Balanced, text-screenshot and gradient) hashed identically between this binary and a reference binary built from commit 1fc3ca7 in an isolated worktree, and within the new binary at threads 1/4/8 and AVX2 off. Rows and summaries are in the kept scratch entry pqc-scatter-20260825 (identity-scatter-summary.json, identity-ab.json). + """ +} + record jpegxl-rs.evidence.pqc-workspace-gates-2026-08-22/1 : evidence { title "Release test suite green across the workspace (the one failure seen in the background run was the PR 1 placeholder CLI test, rewritten in the same tree), clippy clean under -D warnings with default and extended feature sets, fmt clean. Rate-mode production streams on mid.ppm (--bpp 1.0, 4 threads) hash 05bae79d4c96f77b2bb6bd3b1ad6a794331323d11e7c55903db4c4359798701b (balanced) and 07d71108de1fc69fd4fa5cb9e0e71917ec0887510bd21bc7db5eab4a0bf6bc6b (fast), identical to the pre-change binary." state verified @@ -6434,6 +6495,18 @@ record jpegxl-rs.evidence.quality-q9-workspace-gates-2026-08-19/1 : evidence { """ } +record jpegxl-rs.evidence.readme-current-state-2026-08-25/1 : evidence { + title "README describes the current supported scope, exclusions, clean-room policy, build steps, and the recorded benchmark" + state verified + result pass + method manual + observed_at git:a523d0157ac223678b9d02342d0a8a7fbc34d642 + artifact "README.md" + summary """ + The README's claims were verified against JPXL/docs/CONFORMANCE.md and the actual CLI contracts (jpxl --help, encode, compare); the quality-search section now correctly describes the promoted one-shot seed (qpv2-st-1 pooled quantile regression behind the default one-shot-controller feature, with the calibrated-table fallback for out-of-envelope inputs) and the full-resolution canonical verification before every emission, replacing the stale claim that the calibrated table picks the first probe; the benchmark section documents the reproducible bench_vs_libjxl.sh entry point, and the license section states the no-copyleft position with a pointer to the audit. + """ +} + record jpegxl-rs.evidence.release-workspace-tests-2026-08-21/1 : evidence { title "Release-mode workspace tests pass" state verified @@ -6651,3 +6724,15 @@ record jpegxl-rs.evidence.spine-conventions/1 : evidence { """ author "GitHub Uploader" } + +record jpegxl-rs.evidence.workspace-gates-2026-08-25/1 : evidence { + title "Workspace release gates pass with jpxl-jpeg, the scatter change, and the publish surface in the tree" + state verified + result pass + method command + observed_at git:c1cdbf582689914c46af67c1b3e59cacc88b920b + command "cd JPXL && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + summary """ + Under pipefail in one chained run: cargo test --workspace --release reported 76 test-result groups, all ok with zero failures (including the new jpxl-jpeg suite); cargo clippy --workspace --all-targets -D warnings finished clean; cargo fmt --all --check passed. + """ +} diff --git a/.akr/records/jpegxl-rs/observations.akr b/.akr/records/jpegxl-rs/observations.akr index 830e40a0..a7a7d944 100644 --- a/.akr/records/jpegxl-rs/observations.akr +++ b/.akr/records/jpegxl-rs/observations.akr @@ -1929,6 +1929,26 @@ record jpegxl-rs.observation.pqc-pr7-full-holdout-quality-rejected-2026-08-24/1 ] } +record jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1 : observation { + title "Quiet-host wall anchors: 12 MP 5.42x (4t) / 4.25x (8t); the quality path's fixed costs alone sit near 2x, so the 2.0x target is unreachable by search improvements" + state verified + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl-plan-render/src/**" + ] + statement """ + Interleaved matched-score timing (wall_current.py recipe: one warm-up per kind, 10-run schedule, 5 GiB address-space cap, host state per run) on a quiet host with the current release binary: 4.3 MP anchor quality/rate 2.021/0.521 s = 3.88x at 4 threads and 1.824/0.566 s = 3.22x at 8; 12 MP anchor 5.464/1.007 s = 5.42x at 4 threads and 3.988/0.938 s = 4.25x at 8. The 4-thread rate median (1.007 s) reproduces the 2026-08-23 baseline exactly, so the comparable quiet-host series at 12 MP/4t is 8.00x (2026-08-23) -> 5.42x (after the one-shot promotion cut probes 5->3); the 2.46x recorded on 2026-08-25 divided by an inflated ~1.59 s rate denominator measured under load and is not comparable. Phase attribution (12 MP, 8 threads): plan 366 ms, render_metric 1985 ms (three pixel probes at 642-697 ms each), entropy 79 ms, emit 99 ms, two exact prices 89 ms each; ~1.4 s of the 3.99 s wall is outside the controller (source PPM load, metric reference precompute, feature extraction, output IO). Consequence: the quality path's fixed costs excluding all scored probes (~1.83 s at 8 threads) are already 1.95x the rate path, and a perfect single-probe controller would land near 2.7x, so the wall-anchors 2.0x acceptance cannot be met by search improvements alone; it requires reducing per-pixel render+metric cost ~2-3x (score-identity risk) or renegotiating the target. Separately, the band-parallel varblock scatter is exact but wall-neutral at the 12 MP anchor: render_metric 2043/2168 ms (pre-change binary) vs 2107/2057 ms (post), and the 4 MP varblock stage is 45 ms at 4 threads in both trees, so the earlier ~50-100 ms/probe scatter estimate was high. Raw runs, traces, and comparisons are in the kept scratch entry pqc-scatter-20260825 (wall-scatter.json, trace-*.jsonl). + """ + observed_at git:b0c128c785d7524c6002eb34ca7ebd990b24bf1e + method command + watches [ + "JPXL/crates/jpxl-plan-render/src/lib.rs", + "JPXL/crates/jpxl-perceptual/src/**", + "JPXL/crates/jpxl-encode-policy/src/quality.rs" + ] +} + record jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/1 : observation { title "Pre-optimization encode wall-time ladder (release jpxl bench)" state superseded @@ -2336,6 +2356,19 @@ record jpegxl-rs.observation.q6-zero-run-term-explains-dct8-residual-only-2026-0 ] } +record jpegxl-rs.observation.quality-corpus-critical-class-families-2026-08-25/1 : observation { + title "Quality-corpus generator now provides 26 saturated and 25 gradient/banding families; locked holdout fixtures byte-unchanged" + state verified + scope [ path "JPXL/tools/make-quality-guard-fixtures.py", path "test-set/**" ] + statement """ + The synthetic quality-guard generator was extended with distinct deterministic content processes so the two critical classes named by the one-shot memo's section 10 now meet its >=25-families floor: saturated 4 -> 26 and gradient/banding-stress 8 -> 25 (each synthetic fixture is its own family). The corpus grew 56 -> 95 fixtures (calibration 43, development 32, holdout 20); the split audit reports no family in more than one split, regeneration is sha256-idempotent on a second run, codec_compare.py manifest-check validates all 95 images, and 16 generator unit tests pass (including a new >=25-families gate). All 13 fixtures pinned by the locked PR4-closure holdout manifest are byte-unchanged, so prior identity and rate-curve evidence remains comparable. SSIMULACRA2 oracle-label sweeps over the new families were deliberately not run; they are the remaining step before the model can be retrained on this coverage. + """ + observed_at git:6e13305b647024c4fe4a1eabcef36560cd586a26 + method command + watches [ "JPXL/tools/make-quality-guard-fixtures.py" ] + derived_from [ @jpegxl-rs.work.pqc-one-shot-controller ] +} + record jpegxl-rs.observation.quantizer-normalised-distortion-is-the-lever-2026-08-12/1 : observation { title "The standard's own quant matrices carry a third of the frequency weighting; measuring distortion in quantizer-normalised units cuts Y mispricing 2.65x to 1.86x" state verified diff --git a/.akr/records/jpegxl-rs/papercuts.akr b/.akr/records/jpegxl-rs/papercuts.akr index 123beb49..f90b6fd2 100644 --- a/.akr/records/jpegxl-rs/papercuts.akr +++ b/.akr/records/jpegxl-rs/papercuts.akr @@ -128,6 +128,18 @@ record jpegxl-rs.papercut.akr-view-active-work-current-state-etc-returned/1 : pa created_at 2026-08-06 } +record jpegxl-rs.papercut.amending-an-akr-generated-commit-message-to/1 : papercut { + title "Amending an akr-generated commit message (to replace the generic…" + state verified + statement """ + Amending an akr-generated commit message (to replace the generic "chore:" subject with a descriptive one while keeping the AKR trailers byte-identical) is rejected by the commit-msg hook with AKR-C031 because the change transaction closes at akr git commit; the only way through is git commit --amend --no-verify. Either akr git commit could accept a subject/body override, or the hook could allow amends whose trailers match the last commit. + """ + observed_at git:a523d0157ac223678b9d02342d0a8a7fbc34d642 + about "akr" + author "claude" + created_at 2026-08-25 +} + record jpegxl-rs.papercut.cargo-fmt-all-check-fails-on-code-committed-at/1 : papercut { title "`cargo fmt --all --check` fails on code committed at HEAD (25213f6):…" state verified diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 15fd2b02..3ec850c3 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -6784,7 +6784,7 @@ record jpegxl-rs.work.general-use-api-cli/2 : work { record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { title "Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode" - state proposed + state active scope [ path "JPXL/crates/jpxl-bitstream/**", path "JPXL/crates/jpxl-cli/**", @@ -6794,10 +6794,10 @@ record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { path "JPXL/docs/jpeg-recompression-plan.md" ] intent """ - Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients in a YCbCr 8x8-only VarDCT frame plus the jbrd JPEG Bitstream Reconstruction Data box (18181-2 s9.11 + Annex A), so the byte-exact original JPEG is reconstructible from the .jxl alone. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. + Carry JPEG1 quantized DCT coefficients into JPEG XL directly (18181-2 s9.11 jbrd + Annex A) instead of decode-and-reencode, through the phased plan in JPXL/docs/jpeg-recompression-plan.md: A jpeg codec roundtrip, B coefficient carriage, C bit-exact reconstruction, D density, E oracle interop. """ note """ - Plan written 2026-08-25: JPXL/docs/jpeg-recompression-plan.md. Five phases, each separately gated: A) new clean-room `jpxl-jpeg` crate, a 10918-1 coefficient-level codec self-gated by serialize(parse(x))==x over >=500 archive JPEGs plus typed refusal of arithmetic/hierarchical/12-bit/lossless modes; B) coefficient carriage into a YCbCr+jpeg_upsampling VarDCT frame with RAW dequant matrices, gated by exact coefficient round-trip through our own decoder's new coefficient-export surface; C) jbrd writer + Annex A reconstructor, gated by byte-exact original JPEG from the .jxl alone and geomean size reduction reported; D) CLI/facade surface (encode auto-detects compatible JPEGs, decode reconstructs when jbrd present); E) black-box interop with cjxl/djxl both directions. Encoder prerequisites verified: jpxl-encode currently writes only xyb_encoded frames (no do_YCbCr, no jpeg_upsampling, no RAW quant signalling); jpxl-decode already decodes YCbCr+jpeg_upsampling. Open questions in the plan s5: LF/DC bijectivity proof (highest risk), CfL/filter signalling exactness, OCR-scrambled Table 11-18 field distributions to verify against the original scan, 10918-1 source acquisition before Phase A entropy work, Brotli dependency decision, level-5 check. + Phase A landed at commit c1cdbf5 (crate jpxl-jpeg): coefficient-level 10918-1 codec, bit-exact re-emission over the full 5,218-file Pol Art archive (5,217 identical; the one exception is a provably truncated file, typed-refused), typed refusal of arithmetic/hierarchical/lossless/12-bit, 34 crate tests. The load-bearing discovery for later phases: libjpeg's progressive AC EOB-run SPLITS are not derivable from coefficients and must be recorded and replayed (ScanSegment.eob_runs) - the jbrd carriage in Phases B/C must preserve this per-scan run-length record, exactly as 18181-2 s9.11's HuffmanCode/padding metadata anticipates. Phase B next: coefficient carriage into a YCbCr VarDCT frame; the encoder still lacks do_YCbCr/jpeg_upsampling/RAW-quant signalling (verified earlier), which is Phase B's first work item. """ acceptance { check phase-a-jpeg-codec-roundtrip { @@ -6805,6 +6805,8 @@ record jpegxl-rs.work.jpeg-bitstream-recompression/1 : work { jpxl-jpeg serialize(parse(x)) is byte-identical to x over >=500 deterministically sampled archive JPEGs covering baseline, progressive, 4:4:4/4:2:0/4:2:2, greyscale, restart intervals, Exif/ICC/XMP and trailing garbage; arithmetic-coded, hierarchical, 12-bit and lossless JPEG inputs are refused with a typed error. """ method command + command "cd JPXL && cargo run --release -p jpxl-jpeg --example roundtrip_sweep -- \"/mnt/Samsung980_1TB/Pol Desktop/Pol Art Folder 08-24-2017\"" + verified_by [ @jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1 ] } check phase-b-coefficient-carriage { statement """ @@ -7166,7 +7168,7 @@ record jpegxl-rs.work.pqc-one-shot-controller/1 : work { record jpegxl-rs.work.pqc-one-shot-controller/2 : work { title "One-shot program: shadow-first common-case one-shot quality controller" - state active + state superseded scope [ path "JPXL/crates/jpxl-encode-policy/**", path "JPXL/crates/jpxl/**", @@ -7195,6 +7197,36 @@ record jpegxl-rs.work.pqc-one-shot-controller/2 : work { } } +record jpegxl-rs.work.pqc-one-shot-controller/3 : work { + title "One-shot program: shadow-first common-case one-shot quality controller" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/**", + path "JPXL/crates/jpxl/**", + path "JPXL/tools/**", + path "test-set/**" + ] + intent """ + Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the open Balanced wall requirement. PR 1-4 (trace/2, production-endpoint oracle labels, source-only and transform-summary shadow models) are implemented and measured; PR 5 (model-seeded first plan, one slope correction, exact-navigator continuation under unchanged caps with canonical verification before every emission) is implemented and, per the user's 2026-08-24 directive and the 2026-08-25 promotion A/B, is now the DEFAULT controller seed (`one-shot-controller` in the policy crate's default features; building without default features restores the legacy table seed). + """ + target 2026-09-30 + note """ + 2026-08-25 update: open item (2), the saturated/banding corpus coverage gap, is closed on the GENERATION side at commit 6e13305 — the generator now provides 26 saturated and 25 gradient/banding-stress families (memo section 10 asks >= 25 per critical class), split-audited, sha256-idempotent, with the 13 locked holdout fixtures byte-unchanged (see @jpegxl-rs.observation.quality-corpus-critical-class-families-2026-08-25). The remaining steps on that item are the SSIMULACRA2 oracle-label sweeps over the 39 new families and a qpv2 retrain/gate on the widened coverage. Other open items stand: (1) the 12 MP wall anchor remains above the 2.0x matched-rate requirement (the quiet-host re-baseline in @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25 shows the residual is fixed render/metric cost, not search); (3) achieved-score dips above the floor exist in both arms (pre-existing bounded-search artifact); (4) 108 unswept ext-polart images remain for future label runs (resumable). Reproduce: quality_oracle_labels.py sweep/labels, quality_predictor_v2.py train, one_shot_promotion_ab.py; artifacts kept in .agent/scratch/one-shot-qpv2-20260824/. + """ + depends_on [ @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-one-shot-controller/2 ] + source { + kind external + role origin + document "jpxl-one-shot-quality-controller-memo-2026-08-24" + use """ + Adopted the memo's verdict (common-case one shot, canonical verification as the correctness gate), its staged PR 1-5 sequence, its falsification and promotion gates, and its explicit non-goals. + """ + } +} + record jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 : work { title "PR 0 of the one-shot program: enforce the hard score floor at the facade and CLI" state completed @@ -8119,7 +8151,7 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/5 : work { record jpegxl-rs.work.pqc-usable-efforts-cost/6 : work { title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" - state active + state superseded scope [ path "JPXL/crates/jpxl-encode-policy/src/quality.rs", path "JPXL/crates/jpxl-perceptual/src/**", @@ -8176,9 +8208,69 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/6 : work { ] } +record jpegxl-rs.work.pqc-usable-efforts-cost/7 : work { + title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl-plan-render/src/**", + path "JPXL/crates/jpxl/src/lib.rs" + ] + intent """ + Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. + """ + note """ + 2026-08-25 quiet-host re-baseline (see @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25): memory-12mp and production-identity re-verified on the current binary; wall-anchors remains the sole open check and its honest numbers are 12 MP 5.42x (4 threads) / 4.25x (8) and 4.3 MP 3.88x / 3.22x — the comparable series at 12 MP/4t is 8.00x (2026-08-23) -> 5.42x (one-shot promotion cut probes 5->3); the earlier 2.46x figure divided by a load-inflated rate denominator and is not comparable. Direction (1) from rev 6 is closed: the varblock sample scatter is now band-parallel and exact (commit b0c128c) but measured wall-neutral at the anchor; the serial residual was ~30-45 ms/probe, not 50-100. Phase attribution shows the binding constraint: the quality path's fixed costs excluding all scored probes (~1.83 s at 8 threads: source load, reference precompute, plan, entropy, emit) already sit at 1.95x the rate path, and a perfect single-probe controller would land near 2.7x, so the 2.0x acceptance CANNOT be met by search-policy improvements alone. Remaining live directions: (a) exact metric/render per-pixel cost cuts of ~2-3x (blur fusion and SIMD in the metric, faster reference precompute; score-identity risk, unproven); (b) score-changing reduced-resolution navigation with full-resolution final verification (Contract B screen required; by itself lands ~2.5-2.9x); (c) renegotiate the wall-anchors target (e.g. <=2.5x at 8 threads, or define it against a fixed non-probe budget). Direction (c) is a decision for the operator; the check stays open rather than weakened unilaterally. + """ + acceptance { + check memory-12mp { + statement """ + Balanced q85 on the locked 12 MP anchor peaks at no more than 2.0 GB RSS, with the measurement serialized and process-capped. + """ + method observation + verified_by [ @jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 ] + } + check production-identity { + statement """ + Pure scorer/renderer/lifetime changes leave Fast and Balanced codestreams byte-identical across the locked identity cells, threads 1/4, and AVX2 on/off; any search-policy change instead passes the standing Contract B gate. + """ + method command + command "python3 .agent/scratch/pqc-scatter-20260825/identity_scatter.py" + verified_by [ @jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1 ] + } + check wall-anchors { + statement """ + At matched achieved SSIMULACRA2, Balanced end-to-end wall is no more than 2.0x the rate path on both the 4.3 MP and 12 MP anchors; phase timings identify any remaining gap. + """ + method observation + } + check workspace-gates { + statement """ + The workspace release tests, clippy, and formatting gates pass. + """ + method command + command "cd JPXL && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + verified_by [ @jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1 ] + } + } + depends_on [ @jpegxl-rs.work.pqc-pr4-quality-navigator/1 ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-usable-efforts-cost/6 ] + supported_by [ + @jpegxl-rs.evidence.pqc-low-memory-wall-anchors-2026-08-23/1, + @jpegxl-rs.evidence.pqc-pr4-holdout-wall-reported-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-memory-12mp-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-probe-speed-2026-08-22/1, + @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25, + @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25 + ] +} + record jpegxl-rs.work.publish-readme-benchmark-mit/1 : work { title "Publish-ready README, reproducible libjxl comparison, and MIT-only licensing" - state proposed + state completed scope [ path "AGENTS.md", path "JPEG_XL_CLEAN_IMPLEMENTATION_LESSONS.md", @@ -8201,24 +8293,28 @@ record jpegxl-rs.work.publish-readme-benchmark-mit/1 : work { A documented command produces a quality-and-speed comparison using JPXL and cjxl/djxl on the same inputs with recorded provenance. """ method command + verified_by [ @jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 ] } check mit-only { statement """ Workspace package metadata and repository licensing surface declare MIT-only terms. """ method command + verified_by [ @jpegxl-rs.evidence.mit-only-licensing-audit-2026-08-25/1 ] } check readme-current-state { statement """ README accurately describes the supported scope, known exclusions, clean-room policy, build instructions, and the recorded benchmark. """ method manual + verified_by [ @jpegxl-rs.evidence.readme-current-state-2026-08-25/1 ] } check workspace-gates { statement """ The required workspace build, test, lint, and formatting gates pass. """ method command + verified_by [ @jpegxl-rs.evidence.workspace-gates-2026-08-25/1 ] } } } diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index e535eb3c..627a4e11 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> @@ -43,38 +43,40 @@ Define and screen an independently authored JPXL-side input for normalized seman ### One-shot program: shadow-first common-case one-shot quality controller -`active` · `@jpegxl-rs.work.pqc-one-shot-controller/2` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` +`active` · `@jpegxl-rs.work.pqc-one-shot-controller/3` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the open Balanced wall requirement. PR 1-4 (trace/2, production-endpoint oracle labels, source-only and transform-summary shadow models) are implemented and measured; PR 5 (model-seeded first plan, one slope correction, exact-navigator continuation under unchanged caps with canonical verification before every emission) is implemented and, per the user's 2026-08-24 directive and the 2026-08-25 promotion A/B, is now the DEFAULT controller seed (`one-shot-controller` in the policy crate's default features; building without default features restores the legacy table seed). ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/6` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/7` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` · **at risk** Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. -**Acceptance** — 0 of 4 satisfied +**Acceptance** — 3 of 4 satisfied | Check | Method | Verdict | | --- | --- | --- | -| `memory-12mp` | observation | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1` predates the last change | -| `production-identity` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-production-identity-2026-08-23/1` predates the last change | +| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1` | +| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1` | | `wall-anchors` | observation | not satisfied — no evidence | -| `workspace-gates` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` predates the last change | +| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` | + +> **At risk** at depth 1 via `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.). See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory). ## [VarDCT encoder (M1-M8)](ROADMAP.md#vardct-encoder-m1-m8) `@jpegxl-rs.track.vardct-encoder/1` ### Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode -`proposed` · `@jpegxl-rs.work.jpeg-bitstream-recompression/1` · part of `@jpegxl-rs.track.vardct-encoder/1` +`active` · `@jpegxl-rs.work.jpeg-bitstream-recompression/1` · part of `@jpegxl-rs.track.vardct-encoder/1` -Support the JPEG XL standard's lossless JPEG recompression path: take an existing JPEG1 bitstream, carry its DCT coefficients in a YCbCr 8x8-only VarDCT frame plus the jbrd JPEG Bitstream Reconstruction Data box (18181-2 s9.11 + Annex A), so the byte-exact original JPEG is reconstructible from the .jxl alone. The user's collections are dominated by JPEG sources (about 5,200 of 5,378 files in the 2026-08-24 art folder), where direct recompression is both smaller and faithful to the original bitstream, while pixel re-encoding is generational loss or byte expansion. +Carry JPEG1 quantized DCT coefficients into JPEG XL directly (18181-2 s9.11 jbrd + Annex A) instead of decode-and-reencode, through the phased plan in JPXL/docs/jpeg-recompression-plan.md: A jpeg codec roundtrip, B coefficient carriage, C bit-exact reconstruction, D density, E oracle interop. -**Acceptance** — 0 of 4 satisfied +**Acceptance** — 1 of 4 satisfied | Check | Method | Verdict | | --- | --- | --- | -| `phase-a-jpeg-codec-roundtrip` | command | not satisfied — no evidence | +| `phase-a-jpeg-codec-roundtrip` | command | **satisfied** by `@jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1` | | `phase-b-coefficient-carriage` | command | not satisfied — no evidence | | `phase-c-bit-exact-reconstruction` | command | not satisfied — no evidence | | `phase-e-oracle-interop` | command | not satisfied — no evidence | @@ -90,18 +92,3 @@ through Opt-P in after-order, supported by the static advisor assessment and the optimization decisions and gates. **Plan of record for** [Encoder optimization pass](ROADMAP.md#encoder-optimization-pass) `@jpegxl-rs.track.encoder-optimization/1` - -### Publish-ready README, reproducible libjxl comparison, and MIT-only licensing - -`proposed` · `@jpegxl-rs.work.publish-readme-benchmark-mit/1` - -Create a publish-ready repository surface: an accurate current-state README, a reproducible encoder quality-and-speed comparison with libjxl, MIT-only project licensing, and the local Git remote needed to publish the resulting verified work to LegeApp/JPXL. - -**Acceptance** — 0 of 4 satisfied - -| Check | Method | Verdict | -| --- | --- | --- | -| `comparison-reproducible` | command | not satisfied — no evidence | -| `mit-only` | command | not satisfied — no evidence | -| `readme-current-state` | manual | not satisfied — no evidence | -| `workspace-gates` | command | not satisfied — no evidence | diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index f4e1060c..b50cb26c 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> @@ -764,6 +764,8 @@ Phase 5G promoted AQ Off, quant_lf 8, and no LF-fill after improving Butteraugli Per-probe phase attribution on the locked 12 MP anchor (fast-debug, comparing 1-thread vs 8-thread walls to identify serial stages) showed the reference side of SSIMULACRA2 already precomputed once per search (PrecomputedReference with ReferenceRetention::Moments at <=24 MP); the serial per-probe stages were varblock reconstruction (500-730 ms; a 12 MP frame is one LF group, so group-level parallelism contributed nothing) and transfer-curve linearization (105-180 ms). After banding both over the EncodeExecutor (map_ordered varblock chunks of 2048 with serial disjoint scatter; banded LUT lookup), varblocks measure ~235 ms and linearize ~18 ms; per-probe render+metric fell ~1.3 s to ~0.77 s. Release wall on the anchor at 8 threads: 4.923 s -> 3.906 s (-21%), matched-rate ratio 3.11x -> 2.46x on this host (the recorded 6.31x/8.02x baselines were another host/state; the relative move is the reliable signal). Byte-identical streams on six image/target cells, identical canonical scores, cross-thread determinism at threads 1/4/8 in both profiles, peak working set unchanged at 1,899 MB, all touched-crate gates green. Remaining per-probe residual is co-dominated by the still-serial varblock sample scatter (~part of 235 ms) and the already-parallel metric (~380 ms); the largest further headroom is score-changing (navigate on a reduced pyramid or downscaled reconstruction, full-resolution only for final verification), which requires a Contract B promotion A/B. +> **Stale** — `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#parallelizing-the-serial-varblock-render-and-linearization-cut-12-mp-quality-wall-21-ratio-311x---246x-on-the-measuring-host). + ### Perceptual encodes of 50 MP sources reach ~12 GB RSS; two concurrent measurement runs OOM-killed a 31 GB host twice `verified` · `@jpegxl-rs.observation.pqc-memory-50mp-oom-2026-08-22/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/**"`, `path "JPXL/tools/codec_compare.py"` @@ -780,7 +782,7 @@ The kernel OOM-killed jpxl twice on 2026-08-22 (12:54 anon-rss 9.9 GB; 13:28 ano On the expanded 56-image / 38-family corpus with production-endpoint oracle labels (fresh Balanced pixel plans over the complete effective ladder; zero censored rows even at target 95, so the old predictor's 81-94% high-target saturation was an artifact of its HfMul=1 ladder cap), the memo's pre-PR5 falsification screen fails: best leave-one-family-out median |ln(predicted/oracle crossing)| is 0.220 with p90 0.805 (bounds: quick p90<=0.45; production median<=0.10, p90<=0.30). Three findings shape the path forward. (1) Transform-domain features materially help, as the memo predicted: source-only p90 1.28 vs source+transform 0.81, median 0.42 vs 0.22, first-or-one-correction success 1.00. (2) The failure is class coverage, not model form: photo, photo-scene, text-screenshot, noise-lowlight, line-art and grayscale classes sit at held-out p90 0.36-0.67 (near or past the quick bound), while saturated (p90 3.58) and sky-noise gradient (p90 4.07) dominate the tail - both effectively class-held-out because their families carry near-unique content. (3) The fallback discipline works: with honest intervals the simulated controller routes 99% of requests to the exact path, keeping bytes at 1.0016x but eliminating the wall win (expected reconstructions 3.98 vs the current median 4). Per the memo, production controller work stops here until the corpus grows (>=25 independent families per critical class; saturated and banding-stress content first); the PR5 mechanism exists behind the off-by-default `one-shot-controller` cargo feature and remains inert without a gate-passing model. -**supersedes** `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1` · **derived_from** `@jpegxl-rs.work.pqc-one-shot-controller/2` · **verified_by** `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` +**supersedes** `@jpegxl-rs.observation.pqc-one-shot-gate-fails-on-corpus-coverage-2026-08-24/1` · **derived_from** `@jpegxl-rs.work.pqc-one-shot-controller/3` · **verified_by** `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` > **Stale** — `watches "JPXL/crates/jpxl-encode-policy/src/quality_predictor*.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality_predictor_v2.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#the-one-shot-falsification-screen-fails-on-corpus-coverage-not-on-the-design-transform-features-halve-the-error-the-tail-is-two-under-represented-classes). @@ -814,6 +816,12 @@ The current low-memory implementation completed the 27 formerly missing Quality > **Stale** — `watches "JPXL/crates/jpxl-encode-policy/src/quality.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#full-pr7-holdout-closes-the-oom-tail-and-rejects-quality-promotion). +### Quiet-host wall anchors: 12 MP 5.42x (4t) / 4.25x (8t); the quality path's fixed costs alone sit near 2x, so the 2.0x target is unreachable by search improvements + +`verified` · `@jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1` · scope `path "JPXL/crates/jpxl-encode-policy/src/quality.rs"`, `path "JPXL/crates/jpxl-perceptual/src/**"`, `path "JPXL/crates/jpxl-plan-render/src/**"` + +Interleaved matched-score timing (wall_current.py recipe: one warm-up per kind, 10-run schedule, 5 GiB address-space cap, host state per run) on a quiet host with the current release binary: 4.3 MP anchor quality/rate 2.021/0.521 s = 3.88x at 4 threads and 1.824/0.566 s = 3.22x at 8; 12 MP anchor 5.464/1.007 s = 5.42x at 4 threads and 3.988/0.938 s = 4.25x at 8. The 4-thread rate median (1.007 s) reproduces the 2026-08-23 baseline exactly, so the comparable quiet-host series at 12 MP/4t is 8.00x (2026-08-23) -> 5.42x (after the one-shot promotion cut probes 5->3); the 2.46x recorded on 2026-08-25 divided by an inflated ~1.59 s rate denominator measured under load and is not comparable. Phase attribution (12 MP, 8 threads): plan 366 ms, render_metric 1985 ms (three pixel probes at 642-697 ms each), entropy 79 ms, emit 99 ms, two exact prices 89 ms each; ~1.4 s of the 3.99 s wall is outside the controller (source PPM load, metric reference precompute, feature extraction, output IO). Consequence: the quality path's fixed costs excluding all scored probes (~1.83 s at 8 threads) are already 1.95x the rate path, and a perfect single-probe controller would land near 2.7x, so the wall-anchors 2.0x acceptance cannot be met by search improvements alone; it requires reducing per-pixel render+metric cost ~2-3x (score-identity risk) or renegotiating the target. Separately, the band-parallel varblock scatter is exact but wall-neutral at the 12 MP anchor: render_metric 2043/2168 ms (pre-change binary) vs 2107/2057 ms (post), and the 4 MP varblock stage is 45 ms at 4 threads in both trees, so the earlier ~50-100 ms/probe scatter estimate was high. Raw runs, traces, and comparisons are in the kept scratch entry pqc-scatter-20260825 (wall-scatter.json, trace-*.jsonl). + ### Pre-optimization encode wall-time ladder (release jpxl bench) `verified` · `@jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/2` · scope `path "JPXL/crates/jpxl-cli/**"`, `path "JPXL/crates/jpxl-encode-policy/**"`, `path "JPXL/crates/jpxl-encode/**"` @@ -917,6 +925,14 @@ tests/rate_proxy_audit.rs now fits actual ~ s*proxy + z*zeros per varblock on th > **Stale** — `watches "JPXL/crates/jpxl-encode-policy/tests/rate_proxy_audit.rs"` was matched by `5fd8e357`, which touched `JPXL/crates/jpxl-encode-policy/tests/rate_proxy_audit.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#q6-a-per-varblock-interior-zero-term-halves-the-dct8x8-rate-proxy-residuals-p90-but-explains-little-for-dct1632-deferred-rather-than-built-into-the-scoring-kernel). +### Quality-corpus generator now provides 26 saturated and 25 gradient/banding families; locked holdout fixtures byte-unchanged + +`verified` · `@jpegxl-rs.observation.quality-corpus-critical-class-families-2026-08-25/1` · scope `path "JPXL/tools/make-quality-guard-fixtures.py"`, `path "test-set/**"` + +The synthetic quality-guard generator was extended with distinct deterministic content processes so the two critical classes named by the one-shot memo's section 10 now meet its >=25-families floor: saturated 4 -> 26 and gradient/banding-stress 8 -> 25 (each synthetic fixture is its own family). The corpus grew 56 -> 95 fixtures (calibration 43, development 32, holdout 20); the split audit reports no family in more than one split, regeneration is sha256-idempotent on a second run, codec_compare.py manifest-check validates all 95 images, and 16 generator unit tests pass (including a new >=25-families gate). All 13 fixtures pinned by the locked PR4-closure holdout manifest are byte-unchanged, so prior identity and rate-curve evidence remains comparable. SSIMULACRA2 oracle-label sweeps over the new families were deliberately not run; they are the remaining step before the model can be retrained on this coverage. + +**derived_from** `@jpegxl-rs.work.pqc-one-shot-controller/3` + ### The standard's own quant matrices carry a third of the frequency weighting; measuring distortion in quantizer-normalised units cuts Y mispricing 2.65x to 1.86x `verified` · `@jpegxl-rs.observation.quantizer-normalised-distortion-is-the-lever-2026-08-12/1` · scope `path "JPXL/crates/jpxl-conformance/tests/perceptual_frequency.rs"`, `path "JPXL/crates/jpxl-encode-policy/src/lib.rs"`, `path "JPXL/crates/jpxl-encode-policy/src/quantize.rs"` @@ -1223,6 +1239,16 @@ Phase 8.6 rejects raw coefficient-residual allocation as a sufficient perceptual ## Evidence +### One documented command compares JPXL and native cjxl/djxl on the same inputs with recorded provenance + +`verified` · `@jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1` + +After tools/setup-oracles.sh produced native Linux cjxl/djxl v0.13.0 (196a43d9, pinned in oracle-bin/PINNED_REVISIONS.txt), bench_vs_libjxl.sh emitted a size/bpp/SSIMULACRA2/wall table for jpxl --quality 70/85 and cjxl -d 3.0/1.5/1.0 on three corpus images (2400x1800 photo, 1600x900 text-screenshot, 700x700 radial gradient), every stream decoded and scored by the same in-tree metric, with a provenance header (UTC date, host, binary versions and sha256, flags, per-input sha256/dims) and machine-readable jpxl.bench-vs-libjxl/1 JSONL kept at pqc-scatter-20260825/bench-vs-libjxl.jsonl. Sample: on the photo, jpxl q85 emitted 796,985 bytes at score 85.45 versus cjxl -d1.0 at 804,397 bytes and score 83.41; on text-screenshot cjxl -d3.0 was 4.7x smaller at a higher score, consistent with the recorded VarDCT-on-synthetic gap. + +**Verifies** + +- `completed` `@jpegxl-rs.work.publish-readme-benchmark-mit/1` — check `comparison-reproducible` + ### Focused CLI tests passed: explicit PPM/PGM output uses P6/P5 and incompatible channel layouts error. `verified` · `@jpegxl-rs.evidence.cli-netpbm-subtypes-2026-08-21/1` @@ -1676,6 +1702,16 @@ M1-M8 render as completed in ROADMAP.md and akr check reports no diagnostics and - `completed` `@jpegxl-rs.milestone.history-backfilled/1` — check `history-green` +### jpxl-jpeg roundtrips the full 5,218-file Pol Art archive byte-identically except one provably corrupt file, with typed refusals proven + +`verified` · `@jpegxl-rs.evidence.jpeg-phase-a-roundtrip-2026-08-25/1` + +The deterministic sweep (sorted paths, stride 1 — a superset of the >=500-sample requirement) parsed and re-serialized every JPEG in the archive: 5,217 of 5,218 byte-identical with zero mismatches and zero refusals; coverage of the identical set spans baseline 3,166, progressive 2,049, extended sequential 2, chroma 4:4:4 2,985 / 4:2:0 2,081 / 4:2:2 140 / 4:4:0 6, grayscale, restart intervals 684, Exif 2,419 / ICC 1,558 / XMP 1,396, and 41 files with post-EOI tail data. The single non-roundtripping file is ~15 KB of real data zero-padded to 3.18 MB, rejected by libjpeg's djpeg as premature EOF and refused here with a typed malformed-stream error. All 81 earlier mismatches shared one root cause — libjpeg's non-derivable progressive AC EOB-run splits — fixed by recording the exact run lengths at decode and replaying them flush-on-match at encode; the prog_large.jpg regression fixture proves the recorded runs are load-bearing. Typed refusal of arithmetic, hierarchical, lossless, and 12-bit inputs is proven by 10 dedicated tests; 34 crate tests pass in release. + +**Verifies** + +- `active` `@jpegxl-rs.work.jpeg-bitstream-recompression/1` — check `phase-a-jpeg-codec-roundtrip` + ### M1 - VarDCT plan IR and structural split `verified` · `@jpegxl-rs.evidence.m1-plan-ir/1` @@ -1771,6 +1807,16 @@ docs/generated. - `completed` `@jpegxl-rs.milestone.markdown-retired/1` — check `cutover-wired` +### Workspace licensing surface is MIT-only with no copyleft anywhere in the dependency graph + +`verified` · `@jpegxl-rs.evidence.mit-only-licensing-audit-2026-08-25/1` + +All 12 workspace crates declare MIT via license.workspace; LICENSE and JPXL/LICENSE-MIT carry consistent MIT text; a scan of the full dependency graph (default ~41 external crates plus ~26 behind off-by-default measurement features) found no GPL/LGPL/AGPL/MPL/CDDL/EUPL/SSPL license anywhere; the only non-MIT arms are permissive (moxcms and pxfm BSD-3-Clause/Apache-2.0 on the default path via the image raster adapter; ssimulacra2 BSD-2, butteraugli BSD-3, v_frame BSD-2, imgref CC0/Apache behind feature gates). No ISO text or AGPL-derived content is present in any shipped crate. Full table in JPXL/docs/LICENSING-AUDIT.md. + +**Verifies** + +- `completed` `@jpegxl-rs.work.publish-readme-benchmark-mit/1` — check `mit-only` + ### At 70688ac the lean default (Effort::DEFAULT = 1) reproduces the pre-change default's bytes exactly on all three photo-corpus sizes: small_0p8MP 1,075,465 B sha16 C3C60CEE4D0741D9, mid_4MP 5,157,974 B sha16 5C49AB9020706F0D, large_12MP 9,795,599 B sha16 CA27799D3E7B441D -- all IDENTICAL to the pre-ramp fingerprints. Wall time 267 / 619 / 1165 ms for the default versus 1185 / 5324 / 16919 ms for --effort 7, which reproduces the same three fingerprints and so remains the byte-identical density anchor. Speedup on this corpus is 4.4x / 8.6x / 14.5x for identical output. `verified` · `@jpegxl-rs.evidence.modular-effort-ramp-landed-2026-08-10/1` @@ -4886,7 +4932,7 @@ Kept /usr/bin/time evidence: .agent/scratch/pqc-cost-20260823/large-q85-v5.time. - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/3` — check `memory-12mp` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `memory-12mp` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `memory-12mp` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `memory-12mp` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `memory-12mp` ### Low-memory quality path passes the full locked production-identity matrix @@ -4899,7 +4945,7 @@ Kept raw rows and report: .agent/scratch/pqc-cost-20260823/README.md. The frozen - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/3` — check `production-identity` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `production-identity` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `production-identity` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `production-identity` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `production-identity` ### Controlled matched-score wall anchors after low-memory PQC changes @@ -4918,7 +4964,8 @@ The exact chained final-state gate passed after the quality-rate harness and low - `completed` `@jpegxl-rs.work.pqc-quality-rate-curve-closure/5` — check `workspace-gates` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `workspace-gates` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `workspace-gates` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `workspace-gates` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `workspace-gates` +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `workspace-gates` ### Low-memory quality path makes 50 MP usable and preserves all xlarge quality streams @@ -4926,6 +4973,16 @@ The exact chained final-state gate passed after the quality-rate harness and low Kept identity audit: .agent/scratch/quality-rate-curve-xlarge-exact-20260823/xlarge-identity-audit.json. Balanced q85 on 8160x6120 completed under a 9 GiB virtual-memory cap at 7,532,608 KiB RSS, replacing the former approximately 12.5 GiB/SIGKILL behavior. Across the three 50 MP images, seven targets, and both production efforts, all 42 current codestream SHA-256 values, byte counts, and controller scores exactly match the historical pre-low-memory rows. +### Balanced q85 on the locked 12 MP anchor peaks at 1.95 GiB RSS, under the 2.0 GiB ceiling + +`verified` · `@jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1` + +Three serialized release encodes of the locked 4000x3000 anchor (Balanced q85, 4 threads, RLIMIT_AS 9 GiB, ru_maxrss via wait4) peaked at 2,036,248-2,043,056 KiB, all under the 2,097,152 KiB (2.0 GiB) ceiling, with identical 1,315,649-byte output each run; rows in the kept scratch entry pqc-scatter-20260825/rss-12mp.json. + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `memory-12mp` + ### One-shot falsification screen: fails on corpus coverage, transform features validated `verified` · `@jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1` @@ -5296,6 +5353,16 @@ All 20 codec_compare unit tests passed, covering matched-score interpolation, BD The complete chained workspace build, release-test, clippy-with-warnings-denied, and formatting gate exited successfully. +### Production identity holds after the band-parallel scatter: 52/52 locked cells identical across threads 1/4 and AVX2 on/off + +`verified` · `@jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1` + +The full locked 13-image matrix (both production efforts, q70 and q85, serialized under a 9 GiB address-space cap) produced 52/52 byte-identical cells across threads 1, threads 4, and threads 4 with JPXL_DISABLE_AVX2=1 on the band-parallel-scatter binary; separately, six image/target cells (both anchors at q85/q70, Fast and Balanced, text-screenshot and gradient) hashed identically between this binary and a reference binary built from commit 1fc3ca7 in an isolated worktree, and within the new binary at threads 1/4/8 and AVX2 off. Rows and summaries are in the kept scratch entry pqc-scatter-20260825 (identity-scatter-summary.json, identity-ab.json). + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `production-identity` + ### Release test suite green across the workspace (the one failure seen in the background run was the PR 1 placeholder CLI test, rewritten in the same tree), clippy clean under -D warnings with default and extended feature sets, fmt clean. Rate-mode production streams on mid.ppm (--bpp 1.0, 4 threads) hash 05bae79d4c96f77b2bb6bd3b1ad6a794331323d11e7c55903db4c4359798701b (balanced) and 07d71108de1fc69fd4fa5cb9e0e71917ec0887510bd21bc7db5eab4a0bf6bc6b (fast), identical to the pre-change binary. `verified` · `@jpegxl-rs.evidence.pqc-workspace-gates-2026-08-22/1` @@ -5764,6 +5831,16 @@ All required Rust workspace gates pass on native Windows. The conformance link m - `completed` `@jpegxl-rs.work.quality-q9-production-presets/1` — check `workspace-gates` +### README describes the current supported scope, exclusions, clean-room policy, build steps, and the recorded benchmark + +`verified` · `@jpegxl-rs.evidence.readme-current-state-2026-08-25/1` + +The README's claims were verified against JPXL/docs/CONFORMANCE.md and the actual CLI contracts (jpxl --help, encode, compare); the quality-search section now correctly describes the promoted one-shot seed (qpv2-st-1 pooled quantile regression behind the default one-shot-controller feature, with the calibrated-table fallback for out-of-envelope inputs) and the full-resolution canonical verification before every emission, replacing the stale claim that the calibrated table picks the first probe; the benchmark section documents the reproducible bench_vs_libjxl.sh entry point, and the license section states the no-copyleft position with a pointer to the audit. + +**Verifies** + +- `completed` `@jpegxl-rs.work.publish-readme-benchmark-mit/1` — check `readme-current-state` + ### Release-mode workspace tests pass `verified` · `@jpegxl-rs.evidence.release-workspace-tests-2026-08-21/1` @@ -5931,3 +6008,13 @@ jpegxl-rs.decision.ledger-conventions fixes namespace, scope convention, and the **Verifies** - `completed` `@jpegxl-rs.milestone.spine-bootstrap/1` — check `conventions` + +### Workspace release gates pass with jpxl-jpeg, the scatter change, and the publish surface in the tree + +`verified` · `@jpegxl-rs.evidence.workspace-gates-2026-08-25/1` + +Under pipefail in one chained run: cargo test --workspace --release reported 76 test-result groups, all ok with zero failures (including the new jpxl-jpeg suite); cargo clippy --workspace --all-targets -D warnings finished clean; cargo fmt --all --check passed. + +**Verifies** + +- `completed` `@jpegxl-rs.work.publish-readme-benchmark-mit/1` — check `workspace-gates` diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index e2c72672..a4af1b74 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index 003f37fe..e11766bd 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 8d415c72..48417c3a 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> @@ -28,6 +28,7 @@ Small frictions hit while working, logged in the moment (D-027). None of these b Frictions with something else — a tool, a harness — hit while working here. They are logged where they were hit; `akr papercut collate --about <subject>` is how the project that owns the subject gathers them. +- 2026-08-25 [claude] (akr) Amending an akr-generated commit message (to replace the generic "chore:" subject with a descriptive one while keeping the AKR trailers byte-identical) is rejected by the commit-msg hook with AKR-C031 because the change transaction closes at akr git commit; the only way through is git commit --amend --no-verify. Either akr git commit could accept a subject/body override, or the hook could allow amends whose trailers match the last commit. `@jpegxl-rs.papercut.amending-an-akr-generated-commit-message-to/1` - 2026-08-24 [codex] (akr) knowledge.propose's generic `slots` schema does not expose that an observation's `method` must be one of manual/command/instrumented/observation. V-022 said to add `method`; supplying explanatory prose then produced dozens of parser diagnostics before the enum expectation appeared. `@jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1` - 2026-08-23 [codex-gpt-5] (akr) knowledge.propose exposes `topic` for every record kind, but a work proposal containing it fails only after full validation with AKR-T034 because topic is normative-only. The tool schema or preflight should state/reject this earlier. `@jpegxl-rs.papercut.knowledge-propose-exposes-topic-for-every/1` - 2026-08-23 [codex] (akr) knowledge.propose exposes scope as Array<unknown>; passing intuitive {path: ...} objects fails only with 'unknown scope form'. The accepted MCP shape is {form: 'path', glob: ...}, which should be expressed in the tool schema or error. `@jpegxl-rs.papercut.knowledge-propose-exposes-scope-as-array/1` diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index d162b00a..0216b70c 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> @@ -7,7 +7,7 @@ What should not be trusted without re-checking: records the build flagged `stale` or `at_risk`. Neither flag means a record is wrong (D-003); both mean look at it. This view is generated on every successful build, including one that exits 0 with a long queue (D-024). An empty file on an active project is more often a sign the `watches` globs are wrong than a sign the knowledge is perfect. -## Stale (48) +## Stale (49) ### The cover/CfL objective misprices Y-channel error by 2.65x across DCT8x8 frequency; the mispricing is in the ruler, not the lever @@ -171,6 +171,12 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/tests/rate_proxy_audit.rs"` was matched by `5fd8e357`, which touched `JPXL/crates/jpxl-encode-policy/tests/rate_proxy_audit.rs`. +### Parallelizing the serial varblock render and linearization cut 12 MP quality wall 21%; ratio 3.11x -> 2.46x on the measuring host + +`verified` · `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` · observation · **stale** · [Parallelizing the serial varblock render and linearization cut 12 MP quality wall 21%; ratio 3.11x -> 2.46x on the measuring host](CURRENT-STATE.md#parallelizing-the-serial-varblock-render-and-linearization-cut-12-mp-quality-wall-21-ratio-311x---246x-on-the-measuring-host) + +**Cause** — `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`. + ### Cropped frames, orientation, kBlack channels `verified` · `@jpegxl-rs.observation.cropped-frames-orientation-kblack/1` · observation · **stale** · [Cropped frames, orientation, kBlack channels](CURRENT-STATE.md#cropped-frames-orientation-kblack-channels) @@ -297,7 +303,7 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-core/src/color.rs"` was matched by `deed1f65`, which touched `JPXL/crates/jpxl-core/src/color.rs`. -## At risk (7) +## At risk (8) ### Assess the 2026-08-21 libjxl-gap bridge against current JPXL @@ -335,6 +341,12 @@ What should not be trusted without re-checking: records the build flagged `stale **Via** `supported_by` → `@jpegxl-rs.observation.libjxl-comparison-2026-08-18/2` (stale: `watches "JPXL/tools/compare-libjxl.ps1"` was matched by `4f528696`, which touched `JPXL/tools/compare-libjxl.ps1`.) +### PQC usable efforts: reduce Fast/Balanced wall time and peak memory + +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/7` · work · **depth 1** · [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) + +**Via** `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.) + ### Execute the evidence-gated G0-G6 libjxl-gap bridge `active` · `@jpegxl-rs.decision.encoder-architecture-phases/2` · decision · **depth 2** · [Execute the evidence-gated G0-G6 libjxl-gap bridge](DECISION-HISTORY.md#revision-2--execute-the-evidence-gated-g0-g6-libjxl-gap-bridge) diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 6600d212..6ee4e917 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:55e79d09551a4c24e5d889fe846ff51f9fe0154a11423475bc7554fc025ffcfd + source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 tool: akr 0.3.3 --> @@ -359,8 +359,8 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality **Work items** -- `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/2` -- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/6` +- `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/3` +- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — **at risk** ### VarDCT encoder (M1-M8) @@ -371,7 +371,7 @@ backfilled from git history as legacy-sourced completed records (D-028). **Work items** -- `proposed` [Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode](ACTIVE-WORK.md#lossless-jpeg-bitstream-recompression-carry-jpeg1-coefficients-into-jpeg-xl-directly-instead-of-decode-and-reencode) `@jpegxl-rs.work.jpeg-bitstream-recompression/1` +- `active` [Lossless JPEG bitstream recompression: carry JPEG1 coefficients into JPEG XL directly instead of decode-and-reencode](ACTIVE-WORK.md#lossless-jpeg-bitstream-recompression-carry-jpeg1-coefficients-into-jpeg-xl-directly-instead-of-decode-and-reencode) `@jpegxl-rs.work.jpeg-bitstream-recompression/1` ## Summary From 7f7c6d1433906f0f6751af94b2903cd3e3f80f72 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 16:35:25 +0800 Subject: [PATCH 16/27] chore: Rebuild generated views after the session ledger commit Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. AKR-Change: chg-9465cc41a44894e8 AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 AKR-Tree: ed9e0af2d0e2c2a55799784e1bcf1e48b611da77 --- .akr/akr.lock | 1 + docs/generated/ACTIVE-WORK.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 83782e1b..846fff28 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,6 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" + commit git:b6913f7b217b64d4d33d358e59b5060e8d7796b8 source_graph "sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56" } diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 627a4e11..64a0f34f 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -53,14 +53,14 @@ Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the ope Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. -**Acceptance** — 3 of 4 satisfied +**Acceptance** — 2 of 4 satisfied | Check | Method | Verdict | | --- | --- | --- | | `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1` | | `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1` | | `wall-anchors` | observation | not satisfied — no evidence | -| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` | +| `workspace-gates` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` predates the last change | > **At risk** at depth 1 via `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.). See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory). From 50334ab8b4b13cfb91461c5ac4f9988a5e95eebc Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 16:45:06 +0800 Subject: [PATCH 17/27] chore: Log the current-together evidence papercut - jpegxl-rs.papercut.a-work-revision-that-cites-already-committed new -> verified No AKR work record: ledger maintenance: papercut record and regenerated views only AKR-Change: chg-d73accb685d2f11e AKR-Graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c AKR-Tree: 7fceaad27dcf0b945efdb4ba1beb4de6a529659f --- .akr/akr.lock | 12 ++++++++---- .akr/records/jpegxl-rs/papercuts.akr | 12 ++++++++++++ docs/generated/ACTIVE-WORK.md | 2 +- docs/generated/CURRENT-STATE.md | 2 +- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 3 ++- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 2 +- 9 files changed, 28 insertions(+), 11 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 846fff28..807d74dd 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,8 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - commit git:b6913f7b217b64d4d33d358e59b5060e8d7796b8 - source_graph "sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56" + source_graph "sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c" } source ".akr/project.akr" { @@ -45,8 +44,8 @@ source ".akr/records/jpegxl-rs/observations.akr" { } source ".akr/records/jpegxl-rs/papercuts.akr" { - hash "sha256:eddba95b51f812c3fd53a9f47eb9a815b2f7cfbb86a193656d689b82d2ce5928" - records 44 + hash "sha256:67544526475808e1e8a7942cfca73f711ae32087567db85bfe38e42c6c80d094" + records 45 } source ".akr/records/jpegxl-rs/policies.akr" { @@ -7248,6 +7247,11 @@ seal @jpegxl-rs.papercut.a-harmless-scratch-setup-command-was-rejected/1 { hash "sha256:02b2818aed32aa725a64082233cdf17902c2e392312d3bf0efc844f9c7a82fca" } +seal @jpegxl-rs.papercut.a-work-revision-that-cites-already-committed/1 { + state verified + hash "sha256:a9a46c3accb9e5662018b6c51e6963c46227779915fccdfbad4ed2760c7cb5b9" +} + seal @jpegxl-rs.papercut.after-a-temporary-comparison-worktree-restored/1 { state verified hash "sha256:1c15195f3309872a0066c124388b0c7067fd750b408fde501d053993771b3ad7" diff --git a/.akr/records/jpegxl-rs/papercuts.akr b/.akr/records/jpegxl-rs/papercuts.akr index f90b6fd2..98656fb3 100644 --- a/.akr/records/jpegxl-rs/papercuts.akr +++ b/.akr/records/jpegxl-rs/papercuts.akr @@ -37,6 +37,18 @@ record jpegxl-rs.papercut.a-harmless-scratch-setup-command-was-rejected/1 : pape created_at 2026-08-18 } +record jpegxl-rs.papercut.a-work-revision-that-cites-already-committed/1 : papercut { + title "A work revision that cites already-committed evidence in verified_by…" + state verified + statement """ + A work revision that cites already-committed evidence in verified_by lands "not satisfied - predates the last change" unless the evidence rows are committed in the very same commit as the revision (the current-together grace); revising a record in a later .akr-only commit therefore un-satisfies checks whose measurements are still perfectly valid, and there is no way to re-land unchanged evidence. Retargeting a check citation onto fresh evidence needs either an observed_at at-or-after the revision commit (chicken-and-egg) or duplicated evidence records. Hit while pointing pqc-usable-efforts-cost's workspace-gates check at the 2026-08-25 gates evidence; repaired by dropping the revision commit. + """ + observed_at git:7f7c6d1433906f0f6751af94b2903cd3e3f80f72 + about "akr" + author "claude" + created_at 2026-08-25 +} + record jpegxl-rs.papercut.after-a-temporary-comparison-worktree-restored/1 : papercut { title "After a temporary comparison worktree restored a modified source with…" state verified diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 64a0f34f..031e2b17 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index b50cb26c..178d85e2 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index a4af1b74..532b36ab 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index e11766bd..cd9aa04d 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 48417c3a..abfa8082 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> @@ -29,6 +29,7 @@ Small frictions hit while working, logged in the moment (D-027). None of these b Frictions with something else — a tool, a harness — hit while working here. They are logged where they were hit; `akr papercut collate --about <subject>` is how the project that owns the subject gathers them. - 2026-08-25 [claude] (akr) Amending an akr-generated commit message (to replace the generic "chore:" subject with a descriptive one while keeping the AKR trailers byte-identical) is rejected by the commit-msg hook with AKR-C031 because the change transaction closes at akr git commit; the only way through is git commit --amend --no-verify. Either akr git commit could accept a subject/body override, or the hook could allow amends whose trailers match the last commit. `@jpegxl-rs.papercut.amending-an-akr-generated-commit-message-to/1` +- 2026-08-25 [claude] (akr) A work revision that cites already-committed evidence in verified_by lands "not satisfied - predates the last change" unless the evidence rows are committed in the very same commit as the revision (the current-together grace); revising a record in a later .akr-only commit therefore un-satisfies checks whose measurements are still perfectly valid, and there is no way to re-land unchanged evidence. Retargeting a check citation onto fresh evidence needs either an observed_at at-or-after the revision commit (chicken-and-egg) or duplicated evidence records. Hit while pointing pqc-usable-efforts-cost's workspace-gates check at the 2026-08-25 gates evidence; repaired by dropping the revision commit. `@jpegxl-rs.papercut.a-work-revision-that-cites-already-committed/1` - 2026-08-24 [codex] (akr) knowledge.propose's generic `slots` schema does not expose that an observation's `method` must be one of manual/command/instrumented/observation. V-022 said to add `method`; supplying explanatory prose then produced dozens of parser diagnostics before the enum expectation appeared. `@jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1` - 2026-08-23 [codex-gpt-5] (akr) knowledge.propose exposes `topic` for every record kind, but a work proposal containing it fails only after full validation with AKR-T034 because topic is normative-only. The tool schema or preflight should state/reject this earlier. `@jpegxl-rs.papercut.knowledge-propose-exposes-topic-for-every/1` - 2026-08-23 [codex] (akr) knowledge.propose exposes scope as Array<unknown>; passing intuitive {path: ...} objects fails only with 'unknown scope form'. The accepted MCP shape is {form: 'path', glob: ...}, which should be expressed in the tool schema or error. `@jpegxl-rs.papercut.knowledge-propose-exposes-scope-as-array/1` diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 0216b70c..7b96502d 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 6ee4e917..8040a5ef 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:30546cc5534a77086da577a50522e7178dea87561649cc48bd29bbec56746c56 + source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c tool: akr 0.3.3 --> From c0253b3134674f9223100ccd992fca59815161a0 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 17:59:07 +0800 Subject: [PATCH 18/27] perf(jpxl-perceptual): exact cost cuts in the blur and evaluator hot paths Four value-preserving optimizations in the SSIMULACRA2 probe path, found by phase attribution at the 12 MP anchor (per probe at 8 threads: render 195 ms, score 290 ms, of which scale-0 candidate blurs ~160 ms): - The vertical blur pass writes each column strip's disjoint columns of the output in place (DisjointColumns), replacing per-strip buffers, a mutex collection, and a serial full-plane scatter copy. Per scale-0 blur the vertical half dropped from ~10 ms to ~4.7 ms. - The horizontal blur pass runs four rows at a time as independent recursion lanes over a lane-interleaved padded row, the row analogue of the vertical pass's column lanes, dispatched to an AVX2 build like the vertical pass. Per lane the arithmetic is exactly the scalar step's, in the same order. - Redundant re-zeroing is dropped where every element is provably overwritten before it is read: the blur's horizontal temp (re-memset up to nine times 48 MB per 12 MP probe) and the mu2/s22/s12 and recomputed-reference scratch planes. - from_srgb16 evaluates the transfer function through a 65536-entry table of the same expression instead of a powf per sample, and deinterleave preallocates its planes. Exactness: 6/6 A/B cells byte-identical vs the pre-change binary (threads 1/4/8, AVX2 on/off), full locked production-identity matrix 52/52 identical, 12 MP output bytes unchanged. Measured on the standing interleaved recipe (load_1m 1.8-4.5): quality/rate 3.40x/2.76x at 4.3 MP (4t/8t) and 5.07x/3.50x at 12 MP, from 3.87x/3.22x and 5.42x/4.25x; 12 MP peak RSS 2,008,188 KiB from 2,043,056 (ceiling 2,097,152). Artifacts: .agent/scratch/pqc-metric-cuts-20260825 (kept). AKR-Change: chg-24eecb5fd4bda387 AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c AKR-Tree: a9240f5fc31564338d1005e56b89363b8cb9be87 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/crates/jpxl-perceptual/src/blur.rs | 320 ++++++++++++++---- JPXL/crates/jpxl-perceptual/src/evaluator.rs | 14 +- .../crates/jpxl-perceptual/src/ssimulacra2.rs | 5 +- 3 files changed, 274 insertions(+), 65 deletions(-) diff --git a/JPXL/crates/jpxl-perceptual/src/blur.rs b/JPXL/crates/jpxl-perceptual/src/blur.rs index 1fc17ae5..ab44ddc5 100644 --- a/JPXL/crates/jpxl-perceptual/src/blur.rs +++ b/JPXL/crates/jpxl-perceptual/src/blur.rs @@ -26,7 +26,8 @@ //! Each output is computed from its row (or column) alone, so any partition //! into rows or column strips gives bit-identical results; the horizontal //! pass runs its rows in fixed bands on the executor and the vertical pass -//! walks column strips on the calling thread. +//! runs its column strips there too, each strip writing its own disjoint +//! columns of the output in place. use crate::bands::{BAND_ROWS, band_of, mutable_bands}; use crate::executor::BandExecutor; @@ -126,7 +127,8 @@ impl Blur { if width == 0 || height == 0 { return; } - self.temp.clear(); + // The horizontal pass writes every temp sample, so a reused temp needs + // no re-zeroing; resize only grows (zeroed) or truncates. self.temp.resize(width * height, 0.0); let band_len = width.saturating_mul(BAND_ROWS); let bands = mutable_bands(vec![self.temp.as_mut_slice()], band_len); @@ -137,29 +139,13 @@ impl Blur { let Some(out_band) = outs.pop() else { return; }; - let mut padded = Vec::new(); - match input { - BlurInput::Plane(input) => { - let in_band = band_of(input, index, band_len); - for (row_in, row_out) in in_band - .chunks_exact(width) - .zip(out_band.chunks_exact_mut(width)) - { - horizontal_row(row_in, row_out, &mut padded); - } - } + let band_input = match input { + BlurInput::Plane(input) => BlurInput::Plane(band_of(input, index, band_len)), BlurInput::Product(a, b) => { - let a_band = band_of(a, index, band_len); - let b_band = band_of(b, index, band_len); - for ((a_row, b_row), row_out) in a_band - .chunks_exact(width) - .zip(b_band.chunks_exact(width)) - .zip(out_band.chunks_exact_mut(width)) - { - horizontal_product_row(a_row, b_row, row_out, &mut padded); - } + BlurInput::Product(band_of(a, index, band_len), band_of(b, index, band_len)) } - } + }; + horizontal_band(band_input, out_band, width); }); vertical_pass(&self.temp, output, width, height, executor); } @@ -188,6 +174,193 @@ fn step(sum: f32, prev: &mut [f64; 3], prev2: &mut [f64; 3]) -> f32 { const LEFT_PAD: usize = 2 * RADIUS_USIZE; const RADIUS_USIZE: usize = 5; +/// Rows processed together in the horizontal pass: each is one independent +/// recursion lane (the row analogue of the vertical pass's column lanes), so +/// the compiler vectorises the lane loop. +const ROW_LANES: usize = 4; + +/// Horizontal pass over one band: groups of [`ROW_LANES`] rows through the +/// lane recursion, remainder rows through the scalar one. Every row's output +/// depends on that row alone and the lane arithmetic is exactly [`step`]'s, +/// so the grouping cannot change a value. Dispatched to an AVX2 build where +/// the host supports it. +fn horizontal_band(input: BlurInput<'_>, out_band: &mut [f32], width: usize) { + #[cfg(target_arch = "x86_64")] + if jpxl_core::cpu::has_avx2() { + // SAFETY: `horizontal_band_avx2` only requires that the host support + // AVX2, which `has_avx2` has just confirmed. + #[allow(unsafe_code)] + unsafe { + horizontal_band_avx2(input, out_band, width); + } + return; + } + horizontal_band_impl(input, out_band, width); +} + +/// [`horizontal_band`] compiled for AVX2. +/// +/// Calling it is `unsafe` unless the host supports AVX2 (see +/// [`jpxl_core::cpu::has_avx2`]); that is the whole contract. +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2,fma")] +fn horizontal_band_avx2(input: BlurInput<'_>, out_band: &mut [f32], width: usize) { + horizontal_band_impl(input, out_band, width); +} + +#[inline(always)] +fn horizontal_band_impl(input: BlurInput<'_>, out_band: &mut [f32], width: usize) { + if width == 0 { + return; + } + let group = width * ROW_LANES; + let mut padded_t = Vec::new(); + let mut padded = Vec::new(); + match input { + BlurInput::Plane(in_band) => { + let mut ins = in_band.chunks_exact(group); + let mut outs = out_band.chunks_exact_mut(group); + for (rows_in, rows_out) in ins.by_ref().zip(outs.by_ref()) { + build_padded_lanes(BlurInput::Plane(rows_in), width, &mut padded_t); + horizontal_padded_lanes(rows_out, width, &padded_t); + } + for (row_in, row_out) in ins + .remainder() + .chunks_exact(width) + .zip(outs.into_remainder().chunks_exact_mut(width)) + { + horizontal_row(row_in, row_out, &mut padded); + } + } + BlurInput::Product(a, b) => { + let mut a_groups = a.chunks_exact(group); + let mut b_groups = b.chunks_exact(group); + let mut outs = out_band.chunks_exact_mut(group); + for ((a_rows, b_rows), rows_out) in + a_groups.by_ref().zip(b_groups.by_ref()).zip(outs.by_ref()) + { + build_padded_lanes(BlurInput::Product(a_rows, b_rows), width, &mut padded_t); + horizontal_padded_lanes(rows_out, width, &padded_t); + } + for ((a_row, b_row), row_out) in a_groups + .remainder() + .chunks_exact(width) + .zip(b_groups.remainder().chunks_exact(width)) + .zip(outs.into_remainder().chunks_exact_mut(width)) + { + horizontal_product_row(a_row, b_row, row_out, &mut padded); + } + } + } +} + +/// Builds the lane-interleaved padded rows: sample `x` of lane `r` sits at +/// `(LEFT_PAD + x) * ROW_LANES + r`, so each recursion step loads one +/// contiguous [`ROW_LANES`]-wide window. The pads stay zero from the first +/// allocation and the body is fully overwritten, exactly as the scalar +/// padded row is. Product lanes round `a * b` into `f32` here, exactly as +/// [`horizontal_product_row`] does. +#[inline(always)] +fn build_padded_lanes(input: BlurInput<'_>, width: usize, padded_t: &mut Vec<f32>) { + let n = (width + 3 * RADIUS_USIZE) * ROW_LANES; + if padded_t.len() != n { + padded_t.clear(); + padded_t.resize(n, 0.0); + } + let Some(body) = padded_t.get_mut(LEFT_PAD * ROW_LANES..(LEFT_PAD + width) * ROW_LANES) else { + return; + }; + match input { + BlurInput::Plane(rows) => { + for (r, row) in rows.chunks_exact(width).enumerate() { + for (slot, &v) in body.iter_mut().skip(r).step_by(ROW_LANES).zip(row) { + *slot = v; + } + } + } + BlurInput::Product(a, b) => { + for (r, (a_row, b_row)) in a.chunks_exact(width).zip(b.chunks_exact(width)).enumerate() + { + for ((slot, &av), &bv) in body + .iter_mut() + .skip(r) + .step_by(ROW_LANES) + .zip(a_row) + .zip(b_row) + { + *slot = av * bv; + } + } + } + } +} + +/// The recursion over [`ROW_LANES`] padded rows at once: the same warm-up and +/// the same per-sample [`pole_step`] arithmetic as [`horizontal_padded`], +/// lane-parallel across the rows. +#[inline(always)] +fn horizontal_padded_lanes(out: &mut [f32], width: usize, padded_t: &[f32]) { + let mut prev = [[0.0f64; ROW_LANES]; 3]; + let mut prev2 = [[0.0f64; ROW_LANES]; 3]; + let warm = RADIUS_USIZE - 1; + let lane_sum = |i: usize| -> [f64; ROW_LANES] { + let mut sum = [0.0f64; ROW_LANES]; + let l = padded_t.get(i * ROW_LANES..(i + 1) * ROW_LANES); + let r = padded_t.get((i + LEFT_PAD) * ROW_LANES..(i + LEFT_PAD + 1) * ROW_LANES); + if let (Some(l), Some(r)) = (l, r) { + for ((s, &lv), &rv) in sum.iter_mut().zip(l).zip(r) { + *s = f64::from(lv + rv); + } + } + sum + }; + for i in 0..warm { + step_lanes(lane_sum(i), &mut prev, &mut prev2); + } + let mut rows = out.chunks_exact_mut(width); + let (Some(o0), Some(o1), Some(o2), Some(o3)) = + (rows.next(), rows.next(), rows.next(), rows.next()) + else { + return; + }; + for ((((s0, s1), s2), s3), n) in o0 + .iter_mut() + .zip(o1.iter_mut()) + .zip(o2.iter_mut()) + .zip(o3.iter_mut()) + .zip(0..width) + { + let [v0, v1, v2, v3] = step_lanes(lane_sum(n + warm), &mut prev, &mut prev2); + *s0 = v0; + *s1 = v1; + *s2 = v2; + *s3 = v3; + } +} + +/// One three-component recursion step over [`ROW_LANES`] independent lanes. +/// Per lane this is exactly [`step`]: the same expression order, the same +/// `o1 + o3 + o5` summation, one `f32` rounding of the summed output. +#[allow(clippy::cast_possible_truncation)] +#[inline(always)] +fn step_lanes( + sum: [f64; ROW_LANES], + prev: &mut [[f64; ROW_LANES]; 3], + prev2: &mut [[f64; ROW_LANES]; 3], +) -> [f32; ROW_LANES] { + let [p1, p3, p5] = prev; + let [q1, q3, q5] = prev2; + let mut acc = [0.0f64; ROW_LANES]; + pole_step(&sum, p1, q1, &mut acc, MUL_IN[0], MUL_PREV[0], true); + pole_step(&sum, p3, q3, &mut acc, MUL_IN[1], MUL_PREV[1], false); + pole_step(&sum, p5, q5, &mut acc, MUL_IN[2], MUL_PREV[2], false); + let mut out = [0.0f32; ROW_LANES]; + for (o, &a) in out.iter_mut().zip(&acc) { + *o = a as f32; + } + out +} + /// Horizontal recursive pass over one row, through a zero-padded copy so the /// window reads are plain slice walks with no per-sample bounds branch. fn horizontal_row(input: &[f32], output: &mut [f32], padded: &mut Vec<f32>) { @@ -236,8 +409,11 @@ fn horizontal_padded(output: &mut [f32], padded: &[f32]) { /// executor item writing its own buffer. const STRIP: usize = 64; -/// Vertical recursive pass over every column, strips in parallel, then one -/// ordered scatter into the row-major output. +/// Vertical recursive pass over every column, strips in parallel, each strip +/// writing its own disjoint column range of the row-major output directly. +/// The arithmetic per column is untouched — only the destination addressing +/// changed from a per-strip buffer plus scatter to in-place row windows — so +/// the output is bit-identical to the former scatter form. fn vertical_pass( input: &[f32], output: &mut [f32], @@ -246,29 +422,53 @@ fn vertical_pass( executor: &dyn BandExecutor, ) { let strips = width.div_ceil(STRIP); - let buffers: Vec<std::sync::Mutex<Vec<f32>>> = (0..strips) - .map(|_| std::sync::Mutex::new(Vec::new())) - .collect(); + let out = DisjointColumns::new(output); executor.run(strips, &|index| { let x0 = index * STRIP; let cols = (width - x0).min(STRIP); - let mut buffer = vec![0.0f32; cols * height]; - vertical_strip(input, &mut buffer, width, height, x0, cols); - if let Some(slot) = buffers.get(index) - && let Ok(mut guard) = slot.lock() - { - *guard = buffer; - } + vertical_strip(input, &out, width, height, x0, cols); }); - for (index, slot) in buffers.into_iter().enumerate() { - let buffer = slot.into_inner().unwrap_or_default(); - let x0 = index * STRIP; - let cols = (width - x0).min(STRIP); - for (y, src) in buffer.chunks_exact(cols).enumerate() { - if let Some(dst) = output.get_mut(y * width + x0..y * width + x0 + cols) { - dst.copy_from_slice(src); - } +} + +/// A row-major plane shared across strip workers, each writing row windows of +/// a column range no other worker touches. +struct DisjointColumns { + ptr: *mut f32, + len: usize, +} + +// SAFETY: every worker writes only row windows of its own `x0 .. x0 + cols` +// column range, and the strip ranges partition the columns, so no element is +// ever aliased by two workers. +#[allow(unsafe_code)] +unsafe impl Sync for DisjointColumns {} + +impl DisjointColumns { + fn new(plane: &mut [f32]) -> Self { + Self { + ptr: plane.as_mut_ptr(), + len: plane.len(), + } + } + + /// The `cols` samples starting at `offset`, as one mutable row window; + /// `None` when the window leaves the plane. + /// + /// # Safety + /// + /// No other thread may read or write this window for the returned + /// borrow's lifetime; the strip partition guarantees that here. + // The `&self`-to-`&mut` shape is the point of the type: it is a manual + // interior-mutability cell whose disjointness contract lives in `unsafe`. + #[allow(unsafe_code, clippy::mut_from_ref)] + unsafe fn window(&self, offset: usize, cols: usize) -> Option<&mut [f32]> { + let end = offset.checked_add(cols)?; + if end > self.len { + return None; } + // SAFETY: the range is in bounds (checked above) and unaliased (the + // caller's contract). + Some(unsafe { core::slice::from_raw_parts_mut(self.ptr.add(offset), cols) }) } } @@ -353,12 +553,12 @@ fn vertical_row(top: &[f32], bottom: &[f32], state: &mut StripState, out: Option } } -/// Vertical pass over columns `x0 .. x0 + cols` of `input`, writing the -/// strip row-major (`cols` per row) into `strip_out`. Dispatched to an AVX2 -/// build where the host supports it. +/// Vertical pass over columns `x0 .. x0 + cols` of `input`, writing each +/// row's window of `out` in place. Dispatched to an AVX2 build where the host +/// supports it. fn vertical_strip( input: &[f32], - strip_out: &mut [f32], + out: &DisjointColumns, width: usize, height: usize, x0: usize, @@ -370,11 +570,11 @@ fn vertical_strip( // AVX2, which `has_avx2` has just confirmed. #[allow(unsafe_code)] unsafe { - vertical_strip_avx2(input, strip_out, width, height, x0, cols); + vertical_strip_avx2(input, out, width, height, x0, cols); } return; } - vertical_strip_impl(input, strip_out, width, height, x0, cols); + vertical_strip_impl(input, out, width, height, x0, cols); } /// [`vertical_strip`] compiled for AVX2. @@ -385,19 +585,19 @@ fn vertical_strip( #[target_feature(enable = "avx2,fma")] fn vertical_strip_avx2( input: &[f32], - strip_out: &mut [f32], + out: &DisjointColumns, width: usize, height: usize, x0: usize, cols: usize, ) { - vertical_strip_impl(input, strip_out, width, height, x0, cols); + vertical_strip_impl(input, out, width, height, x0, cols); } #[inline(always)] fn vertical_strip_impl( input: &[f32], - strip_out: &mut [f32], + out: &DisjointColumns, width: usize, height: usize, x0: usize, @@ -419,9 +619,12 @@ fn vertical_strip_impl( while n < h { let top = row(n - RADIUS - 1); let bottom = row(n + RADIUS - 1); + // SAFETY: this strip's `x0 .. x0 + cols` columns are its own — see + // `DisjointColumns` — and row `n` is in bounds for `0 <= n < h`. + #[allow(unsafe_code)] let out_row = usize::try_from(n) .ok() - .and_then(|n| strip_out.get_mut(n * cols..n * cols + cols)); + .and_then(|n| unsafe { out.window(n * width + x0, cols) }); vertical_row(top, bottom, &mut state, out_row); n += 1; } @@ -464,14 +667,9 @@ mod tests { horizontal_row(row_in, row_out, &mut padded); } let mut strips = vec![0.0f32; w * h]; - let mut left = vec![0.0f32; 50 * h]; - let mut right = vec![0.0f32; 50 * h]; - vertical_strip(&temp, &mut left, w, h, 0, 50); - vertical_strip(&temp, &mut right, w, h, 50, 50); - for y in 0..h { - strips[y * w..y * w + 50].copy_from_slice(&left[y * 50..y * 50 + 50]); - strips[y * w + 50..y * w + 100].copy_from_slice(&right[y * 50..y * 50 + 50]); - } + let out = DisjointColumns::new(&mut strips); + vertical_strip(&temp, &out, w, h, 0, 50); + vertical_strip(&temp, &out, w, h, 50, 50); assert_eq!(whole, strips); } diff --git a/JPXL/crates/jpxl-perceptual/src/evaluator.rs b/JPXL/crates/jpxl-perceptual/src/evaluator.rs index 7bb24f57..63b17f32 100644 --- a/JPXL/crates/jpxl-perceptual/src/evaluator.rs +++ b/JPXL/crates/jpxl-perceptual/src/evaluator.rs @@ -103,8 +103,13 @@ impl<'e> PlanRenderEvaluator<'e> { executor: &'e EncodeExecutor, ) -> Result<Self, EvaluatorError> { let max = f32::from(u16::MAX).min(((1u32 << bits_per_sample.clamp(1, 16)) - 1) as f32); + // The sample domain is u16, so the per-sample transfer function is a + // table of the same expression evaluated once per distinct value. + let lut: Vec<f32> = (0..=u32::from(u16::MAX)) + .map(|v| jpxl_core::color::srgb_to_linear(v as f32 / max)) + .collect(); let planes = deinterleave(rgb.chunks_exact(3), |v| { - jpxl_core::color::srgb_to_linear(f32::from(v) / max) + lut.get(usize::from(v)).copied().unwrap_or(0.0) }); Self::from_linear_planes(width, height, planes, bits_per_sample, executor) } @@ -229,7 +234,12 @@ fn deinterleave<'a, T: Copy + 'a>( pixels: impl Iterator<Item = &'a [T]>, convert: impl Fn(T) -> f32, ) -> [Vec<f32>; 3] { - let mut planes: [Vec<f32>; 3] = [Vec::new(), Vec::new(), Vec::new()]; + let hint = pixels.size_hint().0; + let mut planes: [Vec<f32>; 3] = [ + Vec::with_capacity(hint), + Vec::with_capacity(hint), + Vec::with_capacity(hint), + ]; for px in pixels { for (plane, &v) in planes.iter_mut().zip(px) { plane.push(convert(v)); diff --git a/JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs b/JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs index 1e6dbd9d..d589b2db 100644 --- a/JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs +++ b/JPXL/crates/jpxl-perceptual/src/ssimulacra2.rs @@ -223,14 +223,15 @@ impl Ssimulacra2 { low_memory: bool, ) -> [ChannelTerms; 3] { let pixels = width * height; + // The blurs below write every sample of these planes before the pool + // reads them, so a reused plane needs no re-zeroing; resize only grows + // (zeroed) or truncates. for buf in [&mut self.mu2, &mut self.s22, &mut self.s12] { - buf.clear(); buf.resize(pixels, 0.0); } let recompute_reference = retention == ReferenceRetention::PlanesOnly; if recompute_reference { for buf in [&mut self.ref_mu, &mut self.ref_s11] { - buf.clear(); buf.resize(pixels, 0.0); } } From 4bbb629fd4dc8bcf3cd33199b2d4aefdad39e351 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 18:04:34 +0800 Subject: [PATCH 19/27] chore: Record the exact metric-path cost cuts: evidence and work rev 8 Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. - jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25 new -> verified Verified by: - Metric-cuts 12 MP memory: peak 2,008,188 KiB under the 2 GiB ceiling - Metric-cuts production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical - Metric-cuts wall anchors: ratios cut ~9-19 percent, 2.0x target still unmet - Metric-cuts workspace gates: fmt, clippy -D warnings, release tests all pass AKR-Change: chg-ae2be006272a0327 AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Evidence: @jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1 AKR-Graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 AKR-Tree: 7036c466071827a635a7aee0f8a2fde695c2d6cc --- .akr/akr.lock | 49 +++++++++++++++++++--- .akr/records/jpegxl-rs/evidence.akr | 48 ++++++++++++++++++++++ .akr/records/jpegxl-rs/work.akr | 63 ++++++++++++++++++++++++++++- docs/generated/ACTIVE-WORK.md | 12 +++--- docs/generated/CURRENT-STATE.md | 46 +++++++++++++++++++-- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 12 ++++-- docs/generated/ROADMAP.md | 4 +- 10 files changed, 215 insertions(+), 25 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 807d74dd..b91f11bb 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c" + source_graph "sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5" } source ".akr/project.akr" { @@ -29,8 +29,8 @@ source ".akr/records/jpegxl-rs/decisions.akr" { } source ".akr/records/jpegxl-rs/evidence.akr" { - hash "sha256:54776057fd9d6c985ffe10a1a74772aa4e62077bd387cc036456ca7d964c5e25" - records 556 + hash "sha256:b425b135c46761fd8575e6d5577212edf2bf0e6da42a8ee5b17a0fc39569bcbe" + records 560 } source ".akr/records/jpegxl-rs/milestones.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:295abcc2347fe2f05f43721d42bf74681209786b20269c56d4f0dff896d09446" - records 185 + hash "sha256:4770ed2480941367b185a42afb6778ca6d161bee82af4eaa27dc1283c5a78e0b" + records 186 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -3739,6 +3739,18 @@ resolution @jpegxl-rs.work.pqc-usable-efforts-cost/7 { hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" } +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/8 { + slot supported_by + to @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 + hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" +} + +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/8 { + slot supported_by + to @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1 + hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" +} + resolution @jpegxl-rs.work.quality-q0-harness-attribution/1 { slot part_of to @jpegxl-rs.track.encoder-optimization/1 @@ -5972,6 +5984,26 @@ seal @jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 { hash "sha256:214d5a4410bf29ecec7b1250fcf741e3d23891c3dbaeb124c0000ed36eb5c40c" } +seal @jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1 { + state verified + hash "sha256:16e6e15888f020722bce4e4a0802def17b4ae1c69f3ed76f1076bf37ea1f72b6" +} + +seal @jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1 { + state verified + hash "sha256:c67b712f513538cacb4844962869a153bd83b751141860e725d26acee4157d30" +} + +seal @jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1 { + state verified + hash "sha256:5a6d21fd31d84490f75b0c46fba08abf06704594ebbebf82d627b65dc3c33f46" +} + +seal @jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1 { + state verified + hash "sha256:166facf3550075dd7f9f882fa6d057193a81efc2cb3e8cb488e76b32edab71cb" +} + seal @jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 { state verified hash "sha256:9ec7485c35348921be8ac2b3b07ae600325b4d9a13138ff0e6ea28abb6551730" @@ -8323,8 +8355,13 @@ seal @jpegxl-rs.work.pqc-usable-efforts-cost/6 { } seal @jpegxl-rs.work.pqc-usable-efforts-cost/7 { + state superseded + hash "sha256:33268ef1d1bbb0dfcc10f00996e905d82bb20e07021dc496a0564e80121af9b9" +} + +seal @jpegxl-rs.work.pqc-usable-efforts-cost/8 { state active - hash "sha256:baca2e78899f9133ffaacaba235d792e3379c1ddc14000adbed9187e5f09c5d9" + hash "sha256:75c67509f673f81221adf6dae05ec4567826347ab8cd3d9cc06b7a7f679c5634" } seal @jpegxl-rs.work.publish-readme-benchmark-mit/1 { diff --git a/.akr/records/jpegxl-rs/evidence.akr b/.akr/records/jpegxl-rs/evidence.akr index 6cb36b98..7ca2ee13 100644 --- a/.akr/records/jpegxl-rs/evidence.akr +++ b/.akr/records/jpegxl-rs/evidence.akr @@ -5142,6 +5142,54 @@ record jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1 : evidence { """ } +record jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1 : evidence { + title "Metric-cuts 12 MP memory: peak 2,008,188 KiB under the 2 GiB ceiling" + state verified + result pass + method command + observed_at git:c0253b3134674f9223100ccd992fca59815161a0 + command "python3 .agent/scratch/pqc-metric-cuts-20260825/rss_12mp.py" + summary """ + Balanced q85 on the locked 12 MP anchor after the exact metric-path cost cuts: peak RSS 2,007,068-2,008,188 KiB over three serialized capped runs, under the 2,097,152 KiB ceiling and down from 2,043,056 before the cuts; output bytes unchanged (1,315,649). Report in kept scratch pqc-metric-cuts-20260825/rss-12mp.json. + """ +} + +record jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1 : evidence { + title "Metric-cuts production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical" + state verified + result pass + method command + observed_at git:c0253b3134674f9223100ccd992fca59815161a0 + command "python3 .agent/scratch/pqc-metric-cuts-20260825/identity_scatter.py" + summary """ + After the exact metric-path cost cuts, the full locked production-identity matrix is 52/52 byte-identical (threads 1/4, AVX2 on/off), and a 6-cell A/B against the pre-change reference binary (worktree build of 1fc3ca7) is byte-identical across threads 1/4/8 and AVX2 off; reports in kept scratch pqc-metric-cuts-20260825 (identity-scatter-summary.json, identity-ab.json). + """ +} + +record jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1 : evidence { + title "Metric-cuts wall anchors: ratios cut ~9-19 percent, 2.0x target still unmet" + state verified + result fail + method command + observed_at git:c0253b3134674f9223100ccd992fca59815161a0 + command "python3 .agent/scratch/pqc-metric-cuts-20260825/wall_scatter.py" + summary """ + Interleaved matched-score timing after the exact metric-path cost cuts (standing recipe, load_1m 1.8-4.5): quality/rate 3.40x at 4 threads and 2.76x at 8 on the 4.3 MP anchor, 5.07x and 3.50x on 12 MP, from 3.87x/3.22x and 5.42x/4.25x before the cuts (that run at load 5.2-7.7; the ratio columns are the load-robust comparison). Quality medians 1.33/1.17 s (4.3 MP) and 3.65/2.62 s (12 MP). The 2.0x wall-anchors target remains unmet on both anchors. Report in kept scratch pqc-metric-cuts-20260825/wall-scatter.json. + """ +} + +record jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1 : evidence { + title "Metric-cuts workspace gates: fmt, clippy -D warnings, release tests all pass" + state verified + result pass + method command + observed_at git:c0253b3134674f9223100ccd992fca59815161a0 + command "cd JPXL && set -o pipefail && cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --release" + summary """ + One strictly chained run at the metric-cuts tree: cargo fmt --all --check, clippy --workspace --all-targets -D warnings, and the release workspace test suite (76 test groups) all pass, exit 0. + """ +} + record jpegxl-rs.evidence.pqc-one-shot-falsification-screen-2026-08-24/1 : evidence { title "One-shot falsification screen: fails on corpus coverage, transform features validated" state verified diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 3ec850c3..59f19ebe 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -8210,7 +8210,7 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/6 : work { record jpegxl-rs.work.pqc-usable-efforts-cost/7 : work { title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" - state active + state superseded scope [ path "JPXL/crates/jpxl-encode-policy/src/quality.rs", path "JPXL/crates/jpxl-perceptual/src/**", @@ -8268,6 +8268,67 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/7 : work { ] } +record jpegxl-rs.work.pqc-usable-efforts-cost/8 : work { + title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl-plan-render/src/**", + path "JPXL/crates/jpxl/src/lib.rs" + ] + intent """ + Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. + """ + note """ + 2026-08-25 direction (a) executed in part (commit c0253b3, exact metric-path cost cuts): the vertical blur pass writes column strips into the output in place instead of per-strip buffers plus a serial scatter; the horizontal blur pass runs four rows as independent recursion lanes (AVX2-dispatched); redundant scratch re-zeroing is dropped where every element is provably overwritten; from_srgb16 uses a 65536-entry transfer table. All exact: 6/6 A/B cells byte-identical vs the pre-change binary, 52/52 locked matrix identical, 12 MP bytes unchanged. Measured effect on the standing interleaved recipe (load_1m 1.8-4.5 vs 5.2-7.7 for the prior run; ratios are the load-robust comparison): 4.3 MP 3.87x -> 3.40x (4 threads) and 3.22x -> 2.76x (8); 12 MP 5.42x -> 5.07x and 4.25x -> 3.50x. 12 MP peak RSS 2,043,056 -> 2,008,188 KiB. Pre-change attribution at 12 MP/8t (~513 ms per probe: render 195, linearize 18, score 290 of which scale-0 candidate blurs ~160, pool ~28, convert ~17; reference precompute 285 ms; retention is Moments up to MOMENTS_PIXEL_CAP = 24 MP, so no redundant reference re-blur exists at the anchors). Remaining exact headroom is smaller and identified: probe render (~195 ms/probe), pool maps, XYB convert, and reference precompute; a quiet-host re-baseline is still needed for honest absolute walls. Rev 7's structural conclusion stands: fixed costs keep the 2.0x wall-anchors target out of reach of search-policy improvements alone, and the remaining live directions are (a) further exact per-pixel cuts (diminishing), (b) Contract-B-screened reduced-resolution navigation, (c) an operator renegotiation of the target; the check stays open rather than weakened unilaterally. + """ + acceptance { + check memory-12mp { + statement """ + Balanced q85 on the locked 12 MP anchor peaks at no more than 2.0 GB RSS, with the measurement serialized and process-capped. + """ + method observation + verified_by [ @jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1 ] + } + check production-identity { + statement """ + Pure scorer/renderer/lifetime changes leave Fast and Balanced codestreams byte-identical across the locked identity cells, threads 1/4, and AVX2 on/off; any search-policy change instead passes the standing Contract B gate. + """ + method command + command "python3 .agent/scratch/pqc-metric-cuts-20260825/identity_scatter.py" + verified_by [ @jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1 ] + } + check wall-anchors { + statement """ + At matched achieved SSIMULACRA2, Balanced end-to-end wall is no more than 2.0x the rate path on both the 4.3 MP and 12 MP anchors; phase timings identify any remaining gap. + """ + method observation + } + check workspace-gates { + statement """ + The workspace release tests, clippy, and formatting gates pass. + """ + method command + command "cd JPXL && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + verified_by [ @jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1 ] + } + } + depends_on [ @jpegxl-rs.work.pqc-pr4-quality-navigator/1 ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-usable-efforts-cost/7 ] + supported_by [ + @jpegxl-rs.evidence.pqc-low-memory-wall-anchors-2026-08-23/1, + @jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1, + @jpegxl-rs.evidence.pqc-pr4-holdout-wall-reported-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-memory-12mp-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-probe-speed-2026-08-22/1, + @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25, + @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25 + ] +} + record jpegxl-rs.work.publish-readme-benchmark-mit/1 : work { title "Publish-ready README, reproducible libjxl comparison, and MIT-only licensing" state completed diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 031e2b17..1226c9c9 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> @@ -49,18 +49,18 @@ Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the ope ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/7` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` · **at risk** +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/8` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` · **at risk** Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. -**Acceptance** — 2 of 4 satisfied +**Acceptance** — 3 of 4 satisfied | Check | Method | Verdict | | --- | --- | --- | -| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-memory-12mp-2026-08-25/1` | -| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-scatter-production-identity-2026-08-25/1` | +| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1` | +| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1` | | `wall-anchors` | observation | not satisfied — no evidence | -| `workspace-gates` | command | not satisfied — `@jpegxl-rs.evidence.pqc-low-memory-workspace-gates-2026-08-23/1` predates the last change | +| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1` | > **At risk** at depth 1 via `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.). See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory). diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 178d85e2..79e50fae 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> @@ -822,6 +822,8 @@ The current low-memory implementation completed the 27 formerly missing Quality Interleaved matched-score timing (wall_current.py recipe: one warm-up per kind, 10-run schedule, 5 GiB address-space cap, host state per run) on a quiet host with the current release binary: 4.3 MP anchor quality/rate 2.021/0.521 s = 3.88x at 4 threads and 1.824/0.566 s = 3.22x at 8; 12 MP anchor 5.464/1.007 s = 5.42x at 4 threads and 3.988/0.938 s = 4.25x at 8. The 4-thread rate median (1.007 s) reproduces the 2026-08-23 baseline exactly, so the comparable quiet-host series at 12 MP/4t is 8.00x (2026-08-23) -> 5.42x (after the one-shot promotion cut probes 5->3); the 2.46x recorded on 2026-08-25 divided by an inflated ~1.59 s rate denominator measured under load and is not comparable. Phase attribution (12 MP, 8 threads): plan 366 ms, render_metric 1985 ms (three pixel probes at 642-697 ms each), entropy 79 ms, emit 99 ms, two exact prices 89 ms each; ~1.4 s of the 3.99 s wall is outside the controller (source PPM load, metric reference precompute, feature extraction, output IO). Consequence: the quality path's fixed costs excluding all scored probes (~1.83 s at 8 threads) are already 1.95x the rate path, and a perfect single-probe controller would land near 2.7x, so the wall-anchors 2.0x acceptance cannot be met by search improvements alone; it requires reducing per-pixel render+metric cost ~2-3x (score-identity risk) or renegotiating the target. Separately, the band-parallel varblock scatter is exact but wall-neutral at the 12 MP anchor: render_metric 2043/2168 ms (pre-change binary) vs 2107/2057 ms (post), and the 4 MP varblock stage is 45 ms at 4 threads in both trees, so the earlier ~50-100 ms/probe scatter estimate was high. Raw runs, traces, and comparisons are in the kept scratch entry pqc-scatter-20260825 (wall-scatter.json, trace-*.jsonl). +> **Stale** — `watches "JPXL/crates/jpxl-perceptual/src/**"` was matched by `c0253b31`, which touched `JPXL/crates/jpxl-perceptual/src/blur.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#quiet-host-wall-anchors-12-mp-542x-4t--425x-8t-the-quality-paths-fixed-costs-alone-sit-near-2x-so-the-20x-target-is-unreachable-by-search-improvements). + ### Pre-optimization encode wall-time ladder (release jpxl bench) `verified` · `@jpegxl-rs.observation.preopt-encode-baseline-2026-08-06/2` · scope `path "JPXL/crates/jpxl-cli/**"`, `path "JPXL/crates/jpxl-encode-policy/**"`, `path "JPXL/crates/jpxl-encode/**"` @@ -4965,7 +4967,7 @@ The exact chained final-state gate passed after the quality-rate harness and low - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/4` — check `workspace-gates` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/5` — check `workspace-gates` - `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/6` — check `workspace-gates` -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `workspace-gates` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `workspace-gates` ### Low-memory quality path makes 50 MP usable and preserves all xlarge quality streams @@ -4981,7 +4983,43 @@ Three serialized release encodes of the locked 4000x3000 anchor (Balanced q85, 4 **Verifies** -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `memory-12mp` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `memory-12mp` + +### Metric-cuts 12 MP memory: peak 2,008,188 KiB under the 2 GiB ceiling + +`verified` · `@jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1` + +Balanced q85 on the locked 12 MP anchor after the exact metric-path cost cuts: peak RSS 2,007,068-2,008,188 KiB over three serialized capped runs, under the 2,097,152 KiB ceiling and down from 2,043,056 before the cuts; output bytes unchanged (1,315,649). Report in kept scratch pqc-metric-cuts-20260825/rss-12mp.json. + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `memory-12mp` + +### Metric-cuts production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical + +`verified` · `@jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1` + +After the exact metric-path cost cuts, the full locked production-identity matrix is 52/52 byte-identical (threads 1/4, AVX2 on/off), and a 6-cell A/B against the pre-change reference binary (worktree build of 1fc3ca7) is byte-identical across threads 1/4/8 and AVX2 off; reports in kept scratch pqc-metric-cuts-20260825 (identity-scatter-summary.json, identity-ab.json). + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `production-identity` + +### Metric-cuts wall anchors: ratios cut ~9-19 percent, 2.0x target still unmet + +`verified` · `@jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1` + +Interleaved matched-score timing after the exact metric-path cost cuts (standing recipe, load_1m 1.8-4.5): quality/rate 3.40x at 4 threads and 2.76x at 8 on the 4.3 MP anchor, 5.07x and 3.50x on 12 MP, from 3.87x/3.22x and 5.42x/4.25x before the cuts (that run at load 5.2-7.7; the ratio columns are the load-robust comparison). Quality medians 1.33/1.17 s (4.3 MP) and 3.65/2.62 s (12 MP). The 2.0x wall-anchors target remains unmet on both anchors. Report in kept scratch pqc-metric-cuts-20260825/wall-scatter.json. + +### Metric-cuts workspace gates: fmt, clippy -D warnings, release tests all pass + +`verified` · `@jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1` + +One strictly chained run at the metric-cuts tree: cargo fmt --all --check, clippy --workspace --all-targets -D warnings, and the release workspace test suite (76 test groups) all pass, exit 0. + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `workspace-gates` ### One-shot falsification screen: fails on corpus coverage, transform features validated @@ -5361,7 +5399,7 @@ The full locked 13-image matrix (both production efforts, q70 and q85, serialize **Verifies** -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `production-identity` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — check `production-identity` ### Release test suite green across the workspace (the one failure seen in the background run was the PR 1 placeholder CLI test, rewritten in the same tree), clippy clean under -D warnings with default and extended feature sets, fmt clean. Rate-mode production streams on mid.ppm (--bpp 1.0, 4 threads) hash 05bae79d4c96f77b2bb6bd3b1ad6a794331323d11e7c55903db4c4359798701b (balanced) and 07d71108de1fc69fd4fa5cb9e0e71917ec0887510bd21bc7db5eab4a0bf6bc6b (fast), identical to the pre-change binary. diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 532b36ab..4b90cc16 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index cd9aa04d..6715871a 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index abfa8082..62a092bd 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 7b96502d..ad95abea 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> @@ -7,7 +7,7 @@ What should not be trusted without re-checking: records the build flagged `stale` or `at_risk`. Neither flag means a record is wrong (D-003); both mean look at it. This view is generated on every successful build, including one that exits 0 with a long queue (D-024). An empty file on an active project is more often a sign the `watches` globs are wrong than a sign the knowledge is perfect. -## Stale (49) +## Stale (50) ### The cover/CfL objective misprices Y-channel error by 2.65x across DCT8x8 frequency; the mispricing is in the ruler, not the lever @@ -297,6 +297,12 @@ What should not be trusted without re-checking: records the build flagged `stale **Cause** — `watches "JPXL/crates/jpxl-encode-policy/src/quality.rs"` was matched by `b57c9d40`, which touched `JPXL/crates/jpxl-encode-policy/src/quality.rs`. +### Quiet-host wall anchors: 12 MP 5.42x (4t) / 4.25x (8t); the quality path's fixed costs alone sit near 2x, so the 2.0x target is unreachable by search improvements + +`verified` · `@jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1` · observation · **stale** · [Quiet-host wall anchors: 12 MP 5.42x (4t) / 4.25x (8t); the quality path's fixed costs alone sit near 2x, so the 2.0x target is unreachable by search improvements](CURRENT-STATE.md#quiet-host-wall-anchors-12-mp-542x-4t--425x-8t-the-quality-paths-fixed-costs-alone-sit-near-2x-so-the-20x-target-is-unreachable-by-search-improvements) + +**Cause** — `watches "JPXL/crates/jpxl-perceptual/src/**"` was matched by `c0253b31`, which touched `JPXL/crates/jpxl-perceptual/src/blur.rs`. + ### Phase Q9 fixes Windows AVX2/fallback cube-root determinism without moving AVX2 production hashes `verified` · `@jpegxl-rs.observation.windows-msvc-avx2-fallback-not-bit-identical-2026-08-18/3` · observation · **stale** · [Phase Q9 fixes Windows AVX2/fallback cube-root determinism without moving AVX2 production hashes](CURRENT-STATE.md#phase-q9-fixes-windows-avx2fallback-cube-root-determinism-without-moving-avx2-production-hashes) @@ -343,7 +349,7 @@ What should not be trusted without re-checking: records the build flagged `stale ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/7` · work · **depth 1** · [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/8` · work · **depth 1** · [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) **Via** `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.) diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 8040a5ef..cd80e97a 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:ef95ff5110be9aeb247bd9805fd425acc01148ef91bb9ca4e8c9dd0c7efb3f8c + source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 tool: akr 0.3.3 --> @@ -360,7 +360,7 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality **Work items** - `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/3` -- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/7` — **at risk** +- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — **at risk** ### VarDCT encoder (M1-M8) From 690cb64a239148f8a8ba7691d5c99f0560018fa7 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 18:07:19 +0800 Subject: [PATCH 20/27] chore: Log the canonical-truncation papercut - jpegxl-rs.papercut.knowledge-get-with-detail-canonical-on-jpegxl new -> verified No AKR work record: papercut-only ledger change names no work record AKR-Change: chg-cc21e841d39f4cd6 AKR-Graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 AKR-Tree: a9df958d1464bc0bc9be64bc5e12445cc0b529c3 --- .akr/akr.lock | 11 ++++++++--- .akr/records/jpegxl-rs/papercuts.akr | 12 ++++++++++++ docs/generated/ACTIVE-WORK.md | 2 +- docs/generated/CURRENT-STATE.md | 2 +- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 3 ++- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 2 +- 9 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index b91f11bb..e98d16e0 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5" + source_graph "sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679" } source ".akr/project.akr" { @@ -44,8 +44,8 @@ source ".akr/records/jpegxl-rs/observations.akr" { } source ".akr/records/jpegxl-rs/papercuts.akr" { - hash "sha256:67544526475808e1e8a7942cfca73f711ae32087567db85bfe38e42c6c80d094" - records 45 + hash "sha256:5ef7b252be9748a1505f9d11324b9fd25a192c507f59db7e4ce0011ea745cf5d" + records 46 } source ".akr/records/jpegxl-rs/policies.akr" { @@ -7384,6 +7384,11 @@ seal @jpegxl-rs.papercut.knowledge-complete-s-cited-evidence-gets-its/1 { hash "sha256:a6ec1e870934aaba989ff74eb2303653d98e95bbcd0fd31ee51ce413de0e2a46" } +seal @jpegxl-rs.papercut.knowledge-get-with-detail-canonical-on-jpegxl/1 { + state verified + hash "sha256:963859e4ca865daa4b566ce5dcee87f04cd6329e871cf488e0b82b09d58fa0ed" +} + seal @jpegxl-rs.papercut.knowledge-propose-documents-observation/1 { state verified hash "sha256:52ae41822ad3434cc6063f6406d9468358b6efff701e4c4e92813d0510f30e6e" diff --git a/.akr/records/jpegxl-rs/papercuts.akr b/.akr/records/jpegxl-rs/papercuts.akr index 98656fb3..0b82fc16 100644 --- a/.akr/records/jpegxl-rs/papercuts.akr +++ b/.akr/records/jpegxl-rs/papercuts.akr @@ -279,6 +279,18 @@ record jpegxl-rs.papercut.knowledge-complete-s-cited-evidence-gets-its/1 : paper created_at 2026-08-07 } +record jpegxl-rs.papercut.knowledge-get-with-detail-canonical-on-jpegxl/1 : papercut { + title "knowledge.get with detail canonical on…" + state verified + statement """ + knowledge.get with detail canonical on jpegxl-rs.work.pqc-usable-efforts-cost/7 truncated at the tool's token cap and the suggested continuation (detail summary) cannot return the acceptance block's source text either, so reproducing checks verbatim for a revision required reading .akr/records/jpegxl-rs/work.akr by hand. A continuation or an acceptance-only detail level would remove the need to touch the records directory. + """ + observed_at git:4bbb629fd4dc8bcf3cd33199b2d4aefdad39e351 + about "akr" + author "claude" + created_at 2026-08-25 +} + record jpegxl-rs.papercut.knowledge-propose-documents-observation/1 : papercut { title "knowledge.propose documents observation observed_at as required but…" state verified diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 1226c9c9..eb43733b 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 79e50fae..50aaf4d3 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 4b90cc16..baafe458 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index 6715871a..56b5e731 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 62a092bd..66c922e6 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> @@ -28,6 +28,7 @@ Small frictions hit while working, logged in the moment (D-027). None of these b Frictions with something else — a tool, a harness — hit while working here. They are logged where they were hit; `akr papercut collate --about <subject>` is how the project that owns the subject gathers them. +- 2026-08-25 [claude] (akr) knowledge.get with detail canonical on jpegxl-rs.work.pqc-usable-efforts-cost/7 truncated at the tool's token cap and the suggested continuation (detail summary) cannot return the acceptance block's source text either, so reproducing checks verbatim for a revision required reading .akr/records/jpegxl-rs/work.akr by hand. A continuation or an acceptance-only detail level would remove the need to touch the records directory. `@jpegxl-rs.papercut.knowledge-get-with-detail-canonical-on-jpegxl/1` - 2026-08-25 [claude] (akr) Amending an akr-generated commit message (to replace the generic "chore:" subject with a descriptive one while keeping the AKR trailers byte-identical) is rejected by the commit-msg hook with AKR-C031 because the change transaction closes at akr git commit; the only way through is git commit --amend --no-verify. Either akr git commit could accept a subject/body override, or the hook could allow amends whose trailers match the last commit. `@jpegxl-rs.papercut.amending-an-akr-generated-commit-message-to/1` - 2026-08-25 [claude] (akr) A work revision that cites already-committed evidence in verified_by lands "not satisfied - predates the last change" unless the evidence rows are committed in the very same commit as the revision (the current-together grace); revising a record in a later .akr-only commit therefore un-satisfies checks whose measurements are still perfectly valid, and there is no way to re-land unchanged evidence. Retargeting a check citation onto fresh evidence needs either an observed_at at-or-after the revision commit (chicken-and-egg) or duplicated evidence records. Hit while pointing pqc-usable-efforts-cost's workspace-gates check at the 2026-08-25 gates evidence; repaired by dropping the revision commit. `@jpegxl-rs.papercut.a-work-revision-that-cites-already-committed/1` - 2026-08-24 [codex] (akr) knowledge.propose's generic `slots` schema does not expose that an observation's `method` must be one of manual/command/instrumented/observation. V-022 said to add `method`; supplying explanatory prose then produced dozens of parser diagnostics before the enum expectation appeared. `@jpegxl-rs.papercut.knowledge-propose-s-generic-slots-schema-does/1` diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index ad95abea..2b6ce88e 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index cd80e97a..c078624a 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:11b87518a2e357f90ab0354254cb1ca3fef23f1be56d7cd245257f95d636a6a5 + source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 tool: akr 0.3.3 --> From c23bc009ae6cd1a288e1922a60fe534dc711324c Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 19:40:30 +0800 Subject: [PATCH 21/27] perf(jpxl-plan-render, jpxl-perceptual, jpxl-encode-policy): fused depth-quantized probe render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guided by a real cycle profile (perf, enabled by the operator): after the blur cuts, `__powf_fma` was 5.4% of cycles — a per-sample sRGB `powf` encode in the render's colour pass whose result every probe immediately quantized away, plus a per-sample transfer `powf` in the 16-bit prep — and the two-step linearize pass another ~3%. - PlanRenderer::render_linear_at_depth_with renders straight to the depth-quantized linear planes a probe scores: each linear sample's integer level is found by comparing against thresholds bisected over the f32 bit lattice from the actual linear_to_srgb -> scale -> round -> clamp composition, then mapped through the existing linear table. A guide table over the bit lattice pins each sample's level to a bucket-local range (two loads and at most a short ordered scan), and the classifier is cached on the renderer per bit depth so thresholds are bisected once per search, not per probe. The signalled sRGB encoding is never materialised on this path; render_with and RenderedFrame are unchanged for every other caller. - The classifier's equivalence with encode-then-round-trip is pinned by fused_depth_levels_match_the_srgb_round_trip: a dense sweep, every threshold's bit-lattice neighbourhood, both transfer branch seams, and the non-finite specials (which take level 0 exactly as the round trip's finiteness guard does). - PlanRenderEvaluator scores through the fused path on both the standard and the large-image branches. - PreparedFrame::from_srgb16_with evaluates its transfer function through a 65536-entry table of the same per-value expression. Exactness: 6/6 A/B cells byte-identical vs the pre-change 1fc3ca7 binary (threads 1/4/8, AVX2 on/off), locked production-identity matrix 52/52 identical, 12 MP bytes unchanged. Quiet-host 12 MP 8-thread quality wall 2.39-2.53 s (2.62 s before the fusion); interleaved-recipe ratios 3.06x/2.50x at 4.3 MP (4t/8t) and 4.65x at 12 MP/4t, from 3.40x/2.76x and 5.07x this morning (the 12 MP/8t schedule cell was load-polluted; see the kept scratch README). Peak 12 MP RSS unchanged. Artifacts: .agent/scratch/pqc-metric-cuts-20260825 (round 2). AKR-Change: chg-950455e93e20d5ce AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 AKR-Tree: c6a9fe73e42485aae8aba359ab4df49caefeab94 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PGFGWnmbi5WRCwCLu7s6K --- JPXL/crates/jpxl-encode-policy/src/source.rs | 14 +- JPXL/crates/jpxl-perceptual/src/evaluator.rs | 17 +- JPXL/crates/jpxl-plan-render/src/lib.rs | 278 ++++++++++++++++++- 3 files changed, 290 insertions(+), 19 deletions(-) diff --git a/JPXL/crates/jpxl-encode-policy/src/source.rs b/JPXL/crates/jpxl-encode-policy/src/source.rs index 1d153b1a..8e137653 100644 --- a/JPXL/crates/jpxl-encode-policy/src/source.rs +++ b/JPXL/crates/jpxl-encode-policy/src/source.rs @@ -384,6 +384,14 @@ impl PreparedFrame { let max = f32::from(u16::MAX).min(((1u32 << bits_per_sample) - 1) as f32); let divisor = if max > 0.0 { max } else { 1.0 }; + // One transfer-curve evaluation per representable sample value, not + // per sample: each entry is exactly the `srgb_to_linear(v / divisor)` + // the per-sample path evaluated (dividing, per the note above), so the + // planes are bit-identical to it. + let lut: Vec<f32> = (0..=u32::from(u16::MAX)) + .map(|v| jpxl_core::color::srgb_to_linear(v as f32 / divisor)) + .collect(); + let convert_band = |src: &[u16], r: &mut [f32], g: &mut [f32], b: &mut [f32]| -> bool { let mut grayscale = true; for (((px, rs), gs), bs) in src @@ -398,9 +406,9 @@ impl PreparedFrame { px.get(2).copied().unwrap_or(0), ); grayscale &= cr == cg && cg == cb; - *rs = jpxl_core::color::srgb_to_linear(f32::from(cr) / divisor); - *gs = jpxl_core::color::srgb_to_linear(f32::from(cg) / divisor); - *bs = jpxl_core::color::srgb_to_linear(f32::from(cb) / divisor); + *rs = lut.get(usize::from(cr)).copied().unwrap_or(0.0); + *gs = lut.get(usize::from(cg)).copied().unwrap_or(0.0); + *bs = lut.get(usize::from(cb)).copied().unwrap_or(0.0); } linear_srgb_to_xyb_planes(r, g, b); grayscale diff --git a/JPXL/crates/jpxl-perceptual/src/evaluator.rs b/JPXL/crates/jpxl-perceptual/src/evaluator.rs index 63b17f32..c4b48677 100644 --- a/JPXL/crates/jpxl-perceptual/src/evaluator.rs +++ b/JPXL/crates/jpxl-perceptual/src/evaluator.rs @@ -158,18 +158,13 @@ impl PerceptualEvaluator for PlanRenderEvaluator<'_> { &mut self, candidate: &ValidatedPixelPlan, ) -> jpxl_encode_policy::Result<PerceptualObservation> { - let frame = self + let (width, height, linear) = self .renderer - .render_with(candidate, self.executor) + .render_linear_at_depth_with(candidate, self.bits_per_sample, self.executor) .map_err(|_| PolicyError::Unsupported { what: "a candidate plan the renderer could not reconstruct", })?; - let (width, height) = (frame.width(), frame.height()); - frame.linear_rgb_at_depth_into(self.bits_per_sample, &mut self.linear); - // The rendered frame is no longer needed — only its linearised planes - // are scored — so release it before the metric allocates its scratch, - // keeping both from being resident at once. - drop(frame); + self.linear = linear; let [r, g, b] = &self.linear; let view = LinearRgbView::new(width, height, r, g, b).map_err(|_| PolicyError::Unsupported { @@ -196,18 +191,16 @@ impl PerceptualEvaluator for PlanRenderEvaluator<'_> { return Ok((observation, Some(candidate))); } - let frame = self + let (width, height, linear) = self .renderer - .render_with(&candidate, self.executor) + .render_linear_at_depth_with(&candidate, self.bits_per_sample, self.executor) .map_err(|_| PolicyError::Unsupported { what: "a candidate plan the renderer could not reconstruct", })?; - let (width, height) = (frame.width(), frame.height()); // Once reconstruction is complete, the coefficient payload is not // needed for this score. Exact finalists are rebuilt deterministically // by the policy if this rung survives navigation. drop(candidate); - let linear = frame.into_linear_rgb_at_depth_with(self.bits_per_sample, self.executor); let result = self .metric .score_owned(&self.reference, width, height, linear, self.executor) diff --git a/JPXL/crates/jpxl-plan-render/src/lib.rs b/JPXL/crates/jpxl-plan-render/src/lib.rs index f8a583c4..04b9a5fc 100644 --- a/JPXL/crates/jpxl-plan-render/src/lib.rs +++ b/JPXL/crates/jpxl-plan-render/src/lib.rs @@ -193,6 +193,28 @@ impl RenderedFrame { .unwrap_or_else(|| srgb_to_linear(q as f32 / max)) } + /// The composed depth quantizer: the integer level that + /// [`Self::linear_sample`] forms from `linear_to_srgb(x)` — the sRGB + /// encode, the scale by `max`, the round, the finiteness guard and the + /// clamp, in that order. Kept callable on its own so threshold + /// construction and its verification bisect the actual function. + fn composed_level(x: f32, max: f32) -> usize { + let scaled = (linear_to_srgb(x) * max).round(); + if scaled.is_finite() { + // Clamped into [0, max] with max < 2^32 before the cast, so the + // narrowing is exact. + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "the value is clamped to [0, max] first" + )] + let q = scaled.clamp(0.0, max) as u32; + usize::try_from(q).unwrap_or(0) + } else { + 0 + } + } + /// The planes quantized to `bits` per sample exactly as the decoder's /// integer output is: scaled, rounded and clamped. #[must_use] @@ -299,6 +321,130 @@ impl RenderedFrame { } } +/// Linear-domain thresholds of the composed depth quantizer for `levels` +/// integer levels at full scale `max`: entry `q - 1` is the smallest `f32` +/// whose [`RenderedFrame::composed_level`] is at least `q`. +/// +/// Each boundary is found by bisection over the non-negative `f32` bit +/// lattice against `composed_level` itself, so whatever the transfer curve's +/// branches and roundings do, the boundary is the actual function's; the +/// level function is monotone because every stage of the composition is. +/// Both sides of every boundary are asserted after the search. +fn quantize_thresholds(max: f32, levels: usize) -> Vec<f32> { + let mut thresholds = Vec::with_capacity(levels.saturating_sub(1)); + for q in 1..levels { + // 2.0 encodes above full scale, so every boundary sits below it, and + // non-negative `f32` bit patterns order exactly as their values. + let (mut lo, mut hi) = (0u32, 2.0f32.to_bits()); + while lo < hi { + let mid = lo + (hi - lo) / 2; + if RenderedFrame::composed_level(f32::from_bits(mid), max) >= q { + hi = mid; + } else { + lo = mid + 1; + } + } + let t = f32::from_bits(lo); + debug_assert!(RenderedFrame::composed_level(t, max) >= q); + debug_assert!(lo == 0 || RenderedFrame::composed_level(f32::from_bits(lo - 1), max) < q); + thresholds.push(t); + } + thresholds +} + +/// The composed level of one linear sample: how many thresholds it reaches. +/// Non-finite samples take level 0, exactly as the round trip's finiteness +/// guard sends them there; negative samples sit below every threshold. The +/// reference form — [`DepthClassifier`] reproduces it with a guide table. +#[inline] +fn level_of(x: f32, thresholds: &[f32]) -> usize { + if !x.is_finite() { + return 0; + } + thresholds.partition_point(|&t| t <= x) +} + +/// The composed depth quantizer as a lookup structure: the level thresholds, +/// the levels' linear values, and a guide over the non-negative `f32` bit +/// lattice pinning each bucket's samples to a narrow level range, so one +/// sample's classification is two loads and at most a short ordered scan. +#[derive(Debug)] +struct DepthClassifier { + bits: u32, + thresholds: Vec<f32>, + lut: Vec<f32>, + /// Per bucket, the levels of the bucket's smallest and largest values. + guide: Vec<(u32, u32)>, + /// `bit pattern >> shift` is the bucket of a value in `[0, 2.0)`. + shift: u32, +} + +/// The bit pattern of `2.0f32`, one past the last guided bucket: every +/// threshold lies strictly below 2.0 (full scale encodes below it), so any +/// larger sample takes the top level directly. +const DEPTH_GUIDE_END: u32 = 0x4000_0000; + +impl DepthClassifier { + fn new(bits: u32) -> Self { + let max = RenderedFrame::full_scale(bits); + let lut = RenderedFrame::linear_lut(bits, max); + let thresholds = quantize_thresholds(max, lut.len()); + let buckets = (lut.len() * 8).next_power_of_two().clamp(4096, 65_536); + let shift = DEPTH_GUIDE_END.trailing_zeros() - buckets.trailing_zeros(); + let guide = (0..buckets) + .map(|b| { + let lo_bits = u32::try_from(b).unwrap_or(0) << shift; + let hi_bits = lo_bits + ((1u32 << shift) - 1); + let lo = level_of(f32::from_bits(lo_bits), &thresholds); + let hi = level_of(f32::from_bits(hi_bits), &thresholds); + ( + u32::try_from(lo).unwrap_or(u32::MAX), + u32::try_from(hi).unwrap_or(u32::MAX), + ) + }) + .collect(); + Self { + bits, + thresholds, + lut, + guide, + shift, + } + } + + /// The level's linear value for one sample: `lut[level_of(x)]` for every + /// `f32`, non-finite and negative included, by way of the guide. + #[inline] + fn linear_at_depth(&self, x: f32) -> f32 { + if !x.is_finite() { + return self.lut.first().copied().unwrap_or(0.0); + } + let pattern = x.to_bits(); + if pattern >= 0x8000_0000 { + // Negative, -0.0 included: below every (positive) threshold. + return self.lut.first().copied().unwrap_or(0.0); + } + if pattern >= DEPTH_GUIDE_END { + // At least 2.0: above every threshold. + return self.lut.last().copied().unwrap_or(0.0); + } + let (lo, hi) = self + .guide + .get((pattern >> self.shift) as usize) + .copied() + .unwrap_or((0, 0)); + let mut level = lo as usize; + for &t in self.thresholds.get(lo as usize..hi as usize).unwrap_or(&[]) { + if t <= x { + level += 1; + } else { + break; + } + } + self.lut.get(level).copied().unwrap_or(0.0) + } +} + /// Wall time of one render's stages, in milliseconds. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct RenderTimings { @@ -321,6 +467,10 @@ pub struct PlanRenderer { opsin: OpsinInverse, epf_params: EpfParams, gabor: GaborKernel, + /// The depth quantizer of the last [`Self::render_linear_at_depth_with`] + /// call, kept so a probe search bisects its thresholds once, not per + /// probe. + depth: Option<DepthClassifier>, } impl PlanRenderer { @@ -341,6 +491,7 @@ impl PlanRenderer { ), epf_params: EpfParams::default(), gabor: GaborKernel::defaults(), + depth: None, }) } @@ -390,6 +541,39 @@ impl PlanRenderer { .map(|(frame, _)| frame) } + /// [`Self::render_with`] fused with + /// [`RenderedFrame::into_linear_rgb_at_depth_with`]: the linear-sRGB + /// planes after the round trip through `bits`-deep integer samples, + /// without ever materialising the signalled sRGB encoding. Each sample's + /// quantized level is found in linear light through + /// [`quantize_thresholds`], so every output is bit-identical to rendering + /// and round-tripping in two steps — the equivalence across the whole + /// curve, both branch seams and non-finite samples is pinned by + /// `fused_depth_levels_match_the_srgb_round_trip`. + /// + /// This is the scoring path: a perceptual probe wants only these planes, + /// and the two-step path paid a full-frame `powf` encode per sample just + /// to quantize away its result. + /// + /// # Errors + /// + /// As [`Self::render`]. + pub fn render_linear_at_depth_with( + &mut self, + pixels: &ValidatedPixelPlan, + bits: u32, + executor: &EncodeExecutor, + ) -> Result<(u32, u32, [Vec<f32>; NUM_CHANNELS])> { + let classifier = match self.depth.take() { + Some(classifier) if classifier.bits == bits => classifier, + _ => DepthClassifier::new(bits), + }; + let rendered = self.render_inner(pixels, Some(executor), Some(&classifier)); + self.depth = Some(classifier); + let (frame, _) = rendered?; + Ok((frame.width, frame.height, frame.planes)) + } + /// [`Self::render_with`], also reporting where the time went. /// /// # Errors @@ -399,6 +583,20 @@ impl PlanRenderer { &mut self, pixels: &ValidatedPixelPlan, executor: Option<&EncodeExecutor>, + ) -> Result<(RenderedFrame, RenderTimings)> { + self.render_inner(pixels, executor, None) + } + + /// The full render pipeline. The colour stage ends in the signalled sRGB + /// encoding by default; with `linear_at_depth` it instead classifies each + /// linear sample against the thresholds and takes the level's linear + /// value from the table, and the returned frame's planes hold those + /// depth-quantized linear samples rather than the signalled encoding. + fn render_inner( + &mut self, + pixels: &ValidatedPixelPlan, + executor: Option<&EncodeExecutor>, + linear_at_depth: Option<&DepthClassifier>, ) -> Result<(RenderedFrame, RenderTimings)> { let mut timings = RenderTimings::default(); let millis = |start: std::time::Instant| { @@ -746,7 +944,8 @@ impl PlanRenderer { timings.epf_ms = millis(stage_start); - // Annex L: XYB -> linear sRGB -> the signalled sRGB encoding. + // Annex L: XYB -> linear sRGB -> the signalled sRGB encoding, or, + // for the scoring path, straight to the depth-quantized linear value. let stage_start = std::time::Instant::now(); { let bands = row_bands(&mut planes, dims.width); @@ -757,9 +956,20 @@ impl PlanRenderer { }; let [x, y, b] = &mut slices; opsin.convert_planes(x, y, b); - for plane in slices.iter_mut() { - for v in plane.iter_mut() { - *v = linear_to_srgb(*v); + match linear_at_depth { + None => { + for plane in slices.iter_mut() { + for v in plane.iter_mut() { + *v = linear_to_srgb(*v); + } + } + } + Some(classifier) => { + for plane in slices.iter_mut() { + for v in plane.iter_mut() { + *v = classifier.linear_at_depth(*v); + } + } } } }); @@ -921,3 +1131,63 @@ fn run_items(executor: Option<&EncodeExecutor>, items: usize, f: &(dyn Fn(usize) } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The fused threshold classifier must agree bit-for-bit with encoding to + /// sRGB and round-tripping through the depth quantizer, over a dense + /// sweep of the working range, the immediate bit-lattice neighbourhood of + /// every threshold (where any boundary error would live), both transfer + /// branch seams, and the non-finite specials. + #[test] + fn fused_depth_levels_match_the_srgb_round_trip() { + for bits in [1u32, 8, 12, 16] { + let max = RenderedFrame::full_scale(bits); + let classifier = DepthClassifier::new(bits); + let (lut, thresholds) = (&classifier.lut, &classifier.thresholds); + let check = |x: f32| { + let direct = RenderedFrame::linear_sample(linear_to_srgb(x), max, lut); + let searched = lut.get(level_of(x, thresholds)).copied().unwrap_or(0.0); + let guided = classifier.linear_at_depth(x); + assert_eq!( + direct.to_bits(), + searched.to_bits(), + "search: bits {bits}, x {x} ({:#010x})", + x.to_bits() + ); + assert_eq!( + direct.to_bits(), + guided.to_bits(), + "guide: bits {bits}, x {x} ({:#010x})", + x.to_bits() + ); + }; + for i in 0..120_000 { + check(-0.2 + i as f32 * 1.25e-5); + } + for &t in thresholds { + let b = t.to_bits(); + for d in 0..4u32 { + check(f32::from_bits(b.saturating_sub(d))); + check(f32::from_bits(b.saturating_add(d))); + } + } + for x in [ + f32::NAN, + f32::INFINITY, + f32::NEG_INFINITY, + -0.0, + 0.0, + 0.003_130_8, + 0.040_449_936, + 1.0, + 1.5, + f32::MIN_POSITIVE, + ] { + check(x); + } + } + } +} From 9ac1f16060dae72b0d524c8450a8c3cdafbba2c0 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Tue, 25 Aug 2026 19:45:15 +0800 Subject: [PATCH 22/27] chore: Record the fused-render round: evidence and work rev 9 Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. - jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25 new -> verified - jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25 new -> verified Verified by: - Fused-render 12 MP memory: peak 2,008,408 KiB under the 2 GiB ceiling - Fused-render production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical - Fused-render wall anchors: 4.3 MP at 2.50x (8t), 2.0x target still unmet - Fused-render workspace gates: fmt, clippy -D warnings, release tests all pass AKR-Change: chg-65a35814006d248f AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Evidence: @jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25/1 AKR-Evidence: @jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1 AKR-Graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d AKR-Tree: 0a22526b8b8e1dc23a12c7554fa2d9a30baf84d0 --- .akr/akr.lock | 49 ++++++++++++++++++--- .akr/records/jpegxl-rs/evidence.akr | 48 +++++++++++++++++++++ .akr/records/jpegxl-rs/work.akr | 66 ++++++++++++++++++++++++++++- docs/generated/ACTIVE-WORK.md | 10 ++--- docs/generated/CURRENT-STATE.md | 46 +++++++++++++++++--- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 6 +-- docs/generated/ROADMAP.md | 4 +- 10 files changed, 210 insertions(+), 25 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index e98d16e0..d0f37f35 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679" + source_graph "sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d" } source ".akr/project.akr" { @@ -29,8 +29,8 @@ source ".akr/records/jpegxl-rs/decisions.akr" { } source ".akr/records/jpegxl-rs/evidence.akr" { - hash "sha256:b425b135c46761fd8575e6d5577212edf2bf0e6da42a8ee5b17a0fc39569bcbe" - records 560 + hash "sha256:813cdfbde0f67cee547fafce86d7716f2c2913795bb0cc58298b25607d9c5b4b" + records 564 } source ".akr/records/jpegxl-rs/milestones.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:4770ed2480941367b185a42afb6778ca6d161bee82af4eaa27dc1283c5a78e0b" - records 186 + hash "sha256:163d9779bd261b63cb9e07a2cdb04966a47cfdbbad121251c8011321bd900037" + records 187 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -3751,6 +3751,18 @@ resolution @jpegxl-rs.work.pqc-usable-efforts-cost/8 { hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" } +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/9 { + slot supported_by + to @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1 + hash "sha256:2b6173b98676727bede354bb3488928d0ec58c1efd7dcbbf4ef927ea0ec4c51f" +} + +resolution @jpegxl-rs.work.pqc-usable-efforts-cost/9 { + slot supported_by + to @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1 + hash "sha256:7cc5c06e34773ef0692dacc6763c8e677730dd1172aede7d96d744eceec361ec" +} + resolution @jpegxl-rs.work.quality-q0-harness-attribution/1 { slot part_of to @jpegxl-rs.track.encoder-optimization/1 @@ -5954,6 +5966,26 @@ seal @jpegxl-rs.evidence.phase9-quant-scheduling-speed-2026-08-15/1 { hash "sha256:cb264f0ebd981e140e9b4a3dac7845d50ea72489e8117e6198a957e436099375" } +seal @jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 { + state verified + hash "sha256:53234a275fac57eb5ceb155e3b7a7aa1ffb3a402518f3c84fc2862f0e9bd5c24" +} + +seal @jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1 { + state verified + hash "sha256:ece4d88bb5c22dc47060ff753fb9f765df8872806e563ee05d4b22fdf3fa0ef7" +} + +seal @jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25/1 { + state verified + hash "sha256:43c93edb9ceaed9c2d1494a3bca171017cbdc3a03deb70e5638cefedbbc91655" +} + +seal @jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1 { + state verified + hash "sha256:996ba0b5fb50020d07a6631f973453e5a18ae135dfd6ded1e5257426e1506b99" +} + seal @jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1 { state verified hash "sha256:53e8a0eba08edfa89e096ddc9606f88132a7905aa4cc96c8a0c224c3b1359ed6" @@ -8365,8 +8397,13 @@ seal @jpegxl-rs.work.pqc-usable-efforts-cost/7 { } seal @jpegxl-rs.work.pqc-usable-efforts-cost/8 { + state superseded + hash "sha256:7ce16e490dba480b9e9f117b466a7566c0af391b305812081973379f0e7ebb9c" +} + +seal @jpegxl-rs.work.pqc-usable-efforts-cost/9 { state active - hash "sha256:75c67509f673f81221adf6dae05ec4567826347ab8cd3d9cc06b7a7f679c5634" + hash "sha256:a0dd993d34e6e6b53af2c95644e83482dbd8aa80efa966d60dcc808f8b775fb3" } seal @jpegxl-rs.work.publish-readme-benchmark-mit/1 { diff --git a/.akr/records/jpegxl-rs/evidence.akr b/.akr/records/jpegxl-rs/evidence.akr index 7ca2ee13..777cc6f9 100644 --- a/.akr/records/jpegxl-rs/evidence.akr +++ b/.akr/records/jpegxl-rs/evidence.akr @@ -5072,6 +5072,54 @@ record jpegxl-rs.evidence.phase9-quant-scheduling-speed-2026-08-15/1 : evidence """ } +record jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 : evidence { + title "Fused-render 12 MP memory: peak 2,008,408 KiB under the 2 GiB ceiling" + state verified + result pass + method command + observed_at git:c23bc009ae6cd1a288e1922a60fe534dc711324c + command "python3 .agent/scratch/pqc-metric-cuts-20260825/rss_12mp.py" + summary """ + Balanced q85 on the locked 12 MP anchor after the fused render: peak RSS 2,007,060-2,008,408 KiB over three serialized capped runs, under the 2,097,152 KiB ceiling; output bytes unchanged (1,315,649). Report in kept scratch pqc-metric-cuts-20260825/rss-12mp.json. + """ +} + +record jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1 : evidence { + title "Fused-render production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical" + state verified + result pass + method command + observed_at git:c23bc009ae6cd1a288e1922a60fe534dc711324c + command "python3 .agent/scratch/pqc-metric-cuts-20260825/identity_scatter.py" + summary """ + After the fused depth-quantized probe render, the full locked production-identity matrix is 52/52 byte-identical (threads 1/4, AVX2 on/off) and the 6-cell A/B against the pre-change 1fc3ca7 reference binary is byte-identical across threads 1/4/8 and AVX2 off; the classifier's per-sample equivalence with encode-then-round-trip is additionally pinned by the fused_depth_levels_match_the_srgb_round_trip unit test. Reports in kept scratch pqc-metric-cuts-20260825 (round 2; round-1 reports preserved as *-round1). + """ +} + +record jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25/1 : evidence { + title "Fused-render wall anchors: 4.3 MP at 2.50x (8t), 2.0x target still unmet" + state verified + result fail + method command + observed_at git:c23bc009ae6cd1a288e1922a60fe534dc711324c + command "python3 .agent/scratch/pqc-metric-cuts-20260825/wall_scatter.py" + summary """ + Interleaved matched-score timing after the fused render: quality/rate 3.06x at 4 threads (load_1m 1.6-1.9) and 2.50x at 8 (load 4.0-4.2) on the 4.3 MP anchor; 4.65x at 4 threads on 12 MP (load 1.9-3.7); the 12 MP 8-thread schedule cell read 3.54x under ambient load 4.0-5.4 and is not representative — quiet manual 12 MP 8-thread quality runs measured 2.39-2.53 s against the schedule's 2.79 s median. Down from 3.40x/2.76x and 5.07x/3.50x before the fusion the same day. The 2.0x wall-anchors target remains unmet on both anchors. Report in kept scratch pqc-metric-cuts-20260825/wall-scatter.json (round 1 preserved as wall-scatter-round1.json). + """ +} + +record jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1 : evidence { + title "Fused-render workspace gates: fmt, clippy -D warnings, release tests all pass" + state verified + result pass + method command + observed_at git:c23bc009ae6cd1a288e1922a60fe534dc711324c + command "cd JPXL && set -o pipefail && cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace --release" + summary """ + One strictly chained run at the fused-render tree: cargo fmt --all --check, clippy --workspace --all-targets -D warnings, and the release workspace test suite (76 test groups) all pass, exit 0. + """ +} + record jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1 : evidence { title "Low-memory Balanced q85 path meets the 12 MP two-GiB RSS ceiling" state verified diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index 59f19ebe..df71138d 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -8270,7 +8270,7 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/7 : work { record jpegxl-rs.work.pqc-usable-efforts-cost/8 : work { title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" - state active + state superseded scope [ path "JPXL/crates/jpxl-encode-policy/src/quality.rs", path "JPXL/crates/jpxl-perceptual/src/**", @@ -8329,6 +8329,70 @@ record jpegxl-rs.work.pqc-usable-efforts-cost/8 : work { ] } +record jpegxl-rs.work.pqc-usable-efforts-cost/9 : work { + title "PQC usable efforts: reduce Fast/Balanced wall time and peak memory" + state active + scope [ + path "JPXL/crates/jpxl-encode-policy/src/quality.rs", + path "JPXL/crates/jpxl-perceptual/src/**", + path "JPXL/crates/jpxl-plan-render/src/**", + path "JPXL/crates/jpxl/src/lib.rs" + ] + intent """ + Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. + """ + note """ + 2026-08-25 direction (a), second round (commit c23bc00), now guided by a real cycle profile after the operator enabled perf: __powf_fma was 5.4% of cycles (a per-sample sRGB encode in the probe render whose result the quantizer immediately consumed, plus the 16-bit prep transfer), and the two-step linearize pass another ~3%. The probe render now produces depth-quantized linear planes directly: levels come from thresholds bisected over the f32 bit lattice against the actual linear_to_srgb->scale->round->clamp composition, guide-table accelerated and cached per bit depth; from_srgb16_with uses a 65536-entry transfer table. Exact: 6/6 A/B vs the pre-change binary, 52/52 locked matrix, a dedicated classifier-equivalence unit test. Quiet-host 12 MP 8-thread quality wall 2.39-2.53 s (2.62 s before the fusion, ~3.99 s before the day's first round). Interleaved ratios across the day: 4.3 MP 3.87x -> 3.40x -> 3.06x (4t) and 3.22x -> 2.76x -> 2.50x (8t); 12 MP 5.42x -> 5.07x -> 4.65x (4t); the round-2 12 MP 8t schedule cell was load-polluted (see the scratch README). Remaining hot leaves by cycle share: blur ~24%, EPF ~10%, pool ~7% (order-locked f64 summation, not vectorisable exactly), XYB convert ~7%, plan-phase quantize ~6%. The 4.3 MP 8-thread anchor now sits at 2.50x; the 2.0x target on both anchors still cannot be met by exact cuts alone (12 MP fixed costs dominate), so the live directions remain (a) further exact cuts with visibly diminishing headroom, (b) Contract-B-screened reduced-resolution navigation, (c) an operator renegotiation of the target; the check stays open rather than weakened unilaterally. + """ + acceptance { + check memory-12mp { + statement """ + Balanced q85 on the locked 12 MP anchor peaks at no more than 2.0 GB RSS, with the measurement serialized and process-capped. + """ + method observation + verified_by [ @jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 ] + } + check production-identity { + statement """ + Pure scorer/renderer/lifetime changes leave Fast and Balanced codestreams byte-identical across the locked identity cells, threads 1/4, and AVX2 on/off; any search-policy change instead passes the standing Contract B gate. + """ + method command + command "python3 .agent/scratch/pqc-metric-cuts-20260825/identity_scatter.py" + verified_by [ + @jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1 + ] + } + check wall-anchors { + statement """ + At matched achieved SSIMULACRA2, Balanced end-to-end wall is no more than 2.0x the rate path on both the 4.3 MP and 12 MP anchors; phase timings identify any remaining gap. + """ + method observation + } + check workspace-gates { + statement """ + The workspace release tests, clippy, and formatting gates pass. + """ + method command + command "cd JPXL && cargo test --workspace --release && cargo clippy --workspace --all-targets -- -D warnings && cargo fmt --all --check" + verified_by [ @jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1 ] + } + } + depends_on [ @jpegxl-rs.work.pqc-pr4-quality-navigator/1 ] + implements [ @jpegxl-rs.decision.perceptual-quality-contract/1 ] + part_of [ @jpegxl-rs.track.perceptual-quality-controller/1 ] + supersedes [ @jpegxl-rs.work.pqc-usable-efforts-cost/8 ] + supported_by [ + @jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25/1, + @jpegxl-rs.evidence.pqc-low-memory-wall-anchors-2026-08-23/1, + @jpegxl-rs.evidence.pqc-metric-cuts-wall-anchors-2026-08-25/1, + @jpegxl-rs.evidence.pqc-pr4-holdout-wall-reported-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-memory-12mp-2026-08-22/1, + @jpegxl-rs.evidence.pqc-pr4b-probe-speed-2026-08-22/1, + @jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25, + @jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25 + ] +} + record jpegxl-rs.work.publish-readme-benchmark-mit/1 : work { title "Publish-ready README, reproducible libjxl comparison, and MIT-only licensing" state completed diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index eb43733b..9cb56f64 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> @@ -49,7 +49,7 @@ Adopt the 2026-08-24 advisor memo's common-case one-shot design to close the ope ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/8` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` · **at risk** +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/9` · part of `@jpegxl-rs.track.perceptual-quality-controller/1` · **at risk** Profile and reduce the production Fast and Balanced perceptual path's full-frame render/metric allocation and rescue-probe cost. Work only on usable efforts: do not spend measurement time on the feature-gated Quality reference effort. Pure scorer, renderer, and lifetime changes must preserve Fast/Balanced codestream bytes; any deliberate search-policy change requires the standing Contract B screen. @@ -57,10 +57,10 @@ Profile and reduce the production Fast and Balanced perceptual path's full-frame | Check | Method | Verdict | | --- | --- | --- | -| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-memory-12mp-2026-08-25/1` | -| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-production-identity-2026-08-25/1` | +| `memory-12mp` | observation | **satisfied** by `@jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1` | +| `production-identity` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1` | | `wall-anchors` | observation | not satisfied — no evidence | -| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-metric-cuts-workspace-gates-2026-08-25/1` | +| `workspace-gates` | command | **satisfied** by `@jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1` | > **At risk** at depth 1 via `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.). See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory). diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 50aaf4d3..398f947b 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> @@ -822,7 +822,7 @@ The current low-memory implementation completed the 27 formerly missing Quality Interleaved matched-score timing (wall_current.py recipe: one warm-up per kind, 10-run schedule, 5 GiB address-space cap, host state per run) on a quiet host with the current release binary: 4.3 MP anchor quality/rate 2.021/0.521 s = 3.88x at 4 threads and 1.824/0.566 s = 3.22x at 8; 12 MP anchor 5.464/1.007 s = 5.42x at 4 threads and 3.988/0.938 s = 4.25x at 8. The 4-thread rate median (1.007 s) reproduces the 2026-08-23 baseline exactly, so the comparable quiet-host series at 12 MP/4t is 8.00x (2026-08-23) -> 5.42x (after the one-shot promotion cut probes 5->3); the 2.46x recorded on 2026-08-25 divided by an inflated ~1.59 s rate denominator measured under load and is not comparable. Phase attribution (12 MP, 8 threads): plan 366 ms, render_metric 1985 ms (three pixel probes at 642-697 ms each), entropy 79 ms, emit 99 ms, two exact prices 89 ms each; ~1.4 s of the 3.99 s wall is outside the controller (source PPM load, metric reference precompute, feature extraction, output IO). Consequence: the quality path's fixed costs excluding all scored probes (~1.83 s at 8 threads) are already 1.95x the rate path, and a perfect single-probe controller would land near 2.7x, so the wall-anchors 2.0x acceptance cannot be met by search improvements alone; it requires reducing per-pixel render+metric cost ~2-3x (score-identity risk) or renegotiating the target. Separately, the band-parallel varblock scatter is exact but wall-neutral at the 12 MP anchor: render_metric 2043/2168 ms (pre-change binary) vs 2107/2057 ms (post), and the 4 MP varblock stage is 45 ms at 4 threads in both trees, so the earlier ~50-100 ms/probe scatter estimate was high. Raw runs, traces, and comparisons are in the kept scratch entry pqc-scatter-20260825 (wall-scatter.json, trace-*.jsonl). -> **Stale** — `watches "JPXL/crates/jpxl-perceptual/src/**"` was matched by `c0253b31`, which touched `JPXL/crates/jpxl-perceptual/src/blur.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#quiet-host-wall-anchors-12-mp-542x-4t--425x-8t-the-quality-paths-fixed-costs-alone-sit-near-2x-so-the-20x-target-is-unreachable-by-search-improvements). +> **Stale** — `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `c23bc009`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#quiet-host-wall-anchors-12-mp-542x-4t--425x-8t-the-quality-paths-fixed-costs-alone-sit-near-2x-so-the-20x-target-is-unreachable-by-search-improvements). ### Pre-optimization encode wall-time ladder (release jpxl bench) @@ -4923,6 +4923,42 @@ On the pinned unseen 12 MP photo, finer deterministic quantization scheduling re - `completed` `@jpegxl-rs.work.arch-phase9-quant-scheduling/1` — check `speed` +### Fused-render 12 MP memory: peak 2,008,408 KiB under the 2 GiB ceiling + +`verified` · `@jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1` + +Balanced q85 on the locked 12 MP anchor after the fused render: peak RSS 2,007,060-2,008,408 KiB over three serialized capped runs, under the 2,097,152 KiB ceiling; output bytes unchanged (1,315,649). Report in kept scratch pqc-metric-cuts-20260825/rss-12mp.json. + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/9` — check `memory-12mp` + +### Fused-render production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical + +`verified` · `@jpegxl-rs.evidence.pqc-fused-render-production-identity-2026-08-25/1` + +After the fused depth-quantized probe render, the full locked production-identity matrix is 52/52 byte-identical (threads 1/4, AVX2 on/off) and the 6-cell A/B against the pre-change 1fc3ca7 reference binary is byte-identical across threads 1/4/8 and AVX2 off; the classifier's per-sample equivalence with encode-then-round-trip is additionally pinned by the fused_depth_levels_match_the_srgb_round_trip unit test. Reports in kept scratch pqc-metric-cuts-20260825 (round 2; round-1 reports preserved as *-round1). + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/9` — check `production-identity` + +### Fused-render wall anchors: 4.3 MP at 2.50x (8t), 2.0x target still unmet + +`verified` · `@jpegxl-rs.evidence.pqc-fused-render-wall-anchors-2026-08-25/1` + +Interleaved matched-score timing after the fused render: quality/rate 3.06x at 4 threads (load_1m 1.6-1.9) and 2.50x at 8 (load 4.0-4.2) on the 4.3 MP anchor; 4.65x at 4 threads on 12 MP (load 1.9-3.7); the 12 MP 8-thread schedule cell read 3.54x under ambient load 4.0-5.4 and is not representative — quiet manual 12 MP 8-thread quality runs measured 2.39-2.53 s against the schedule's 2.79 s median. Down from 3.40x/2.76x and 5.07x/3.50x before the fusion the same day. The 2.0x wall-anchors target remains unmet on both anchors. Report in kept scratch pqc-metric-cuts-20260825/wall-scatter.json (round 1 preserved as wall-scatter-round1.json). + +### Fused-render workspace gates: fmt, clippy -D warnings, release tests all pass + +`verified` · `@jpegxl-rs.evidence.pqc-fused-render-workspace-gates-2026-08-25/1` + +One strictly chained run at the fused-render tree: cargo fmt --all --check, clippy --workspace --all-targets -D warnings, and the release workspace test suite (76 test groups) all pass, exit 0. + +**Verifies** + +- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/9` — check `workspace-gates` + ### Low-memory Balanced q85 path meets the 12 MP two-GiB RSS ceiling `verified` · `@jpegxl-rs.evidence.pqc-low-memory-12mp-2026-08-23/1` @@ -4993,7 +5029,7 @@ Balanced q85 on the locked 12 MP anchor after the exact metric-path cost cuts: p **Verifies** -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `memory-12mp` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `memory-12mp` ### Metric-cuts production identity: 52/52 matrix and 6/6 pre-change A/B byte-identical @@ -5003,7 +5039,7 @@ After the exact metric-path cost cuts, the full locked production-identity matri **Verifies** -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `production-identity` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `production-identity` ### Metric-cuts wall anchors: ratios cut ~9-19 percent, 2.0x target still unmet @@ -5019,7 +5055,7 @@ One strictly chained run at the metric-cuts tree: cargo fmt --all --check, clipp **Verifies** -- `active` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `workspace-gates` +- `superseded` `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — check `workspace-gates` ### One-shot falsification screen: fails on corpus coverage, transform features validated diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index baafe458..0e6ba67c 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index 56b5e731..f9164912 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 66c922e6..5e6d06e8 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 2b6ce88e..d7316787 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> @@ -301,7 +301,7 @@ What should not be trusted without re-checking: records the build flagged `stale `verified` · `@jpegxl-rs.observation.pqc-wall-quiet-host-attribution-2026-08-25/1` · observation · **stale** · [Quiet-host wall anchors: 12 MP 5.42x (4t) / 4.25x (8t); the quality path's fixed costs alone sit near 2x, so the 2.0x target is unreachable by search improvements](CURRENT-STATE.md#quiet-host-wall-anchors-12-mp-542x-4t--425x-8t-the-quality-paths-fixed-costs-alone-sit-near-2x-so-the-20x-target-is-unreachable-by-search-improvements) -**Cause** — `watches "JPXL/crates/jpxl-perceptual/src/**"` was matched by `c0253b31`, which touched `JPXL/crates/jpxl-perceptual/src/blur.rs`. +**Cause** — `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `c23bc009`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`. ### Phase Q9 fixes Windows AVX2/fallback cube-root determinism without moving AVX2 production hashes @@ -349,7 +349,7 @@ What should not be trusted without re-checking: records the build flagged `stale ### PQC usable efforts: reduce Fast/Balanced wall time and peak memory -`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/8` · work · **depth 1** · [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) +`active` · `@jpegxl-rs.work.pqc-usable-efforts-cost/9` · work · **depth 1** · [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) **Via** `supported_by` → `@jpegxl-rs.observation.pqc-large-frame-render-parallel-2026-08-25/1` (stale: `watches "JPXL/crates/jpxl-plan-render/src/lib.rs"` was matched by `b0c128c7`, which touched `JPXL/crates/jpxl-plan-render/src/lib.rs`.) diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index c078624a..c6aad0d2 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:c5469183a640fdfb5dd28a9a6034632a0c2f9e378d5469f0dd6a6c1dd597d679 + source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d tool: akr 0.3.3 --> @@ -360,7 +360,7 @@ Deliver the perceptual quality contract of jpegxl-rs.decision.perceptual-quality **Work items** - `active` [One-shot program: shadow-first common-case one-shot quality controller](ACTIVE-WORK.md#one-shot-program-shadow-first-common-case-one-shot-quality-controller) `@jpegxl-rs.work.pqc-one-shot-controller/3` -- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/8` — **at risk** +- `active` [PQC usable efforts: reduce Fast/Balanced wall time and peak memory](ACTIVE-WORK.md#pqc-usable-efforts-reduce-fastbalanced-wall-time-and-peak-memory) `@jpegxl-rs.work.pqc-usable-efforts-cost/9` — **at risk** ### VarDCT encoder (M1-M8) From 05733e9b32cec8e460e552fea5f01c20c87ca941 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Wed, 26 Aug 2026 15:12:49 +0800 Subject: [PATCH 23/27] feat(jpxl-encode, jpxl): EXIF container box and colour-space signalling Add the two metadata seams the archive pipeline needs: - append_exif writes a Part 2 Exif box (LBox + "Exif" + u32 tiff_header_offset + raw TIFF payload) after the codestream boxes; the facade exposes Encoder::with_exif, which validates the TIFF byte-order header and implies the container. - ColourSpace {Srgb, LinearSrgb, DisplayP3, Rec2020} threads through EncodeOptions into the Annex E ColourEncoding bundle (Table E.1 non-default sequence). Lossless Modular signals colour declaratively; lossy VarDCT rejects non-sRGB with a typed Unsupported error because the XYB transform and the metric are defined on sRGB input. - Facade wrapping is unified in finish(); the rgb8/gray8 paths no longer widen u8 samples through an intermediate Vec<u16>. Round-trip tests cover the box walk, all four colour spaces through the decoder, the greyscale guard, and TIFF-header rejection. - jpegxl-rs.work.pipeline-metadata-seam new -> proposed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsAR4R1kGCRk8spmhQZMC2 AKR-Change: chg-2985272a860c003a AKR-Work: jpegxl-rs.work.pipeline-metadata-seam AKR-Graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd AKR-Tree: 7985656e58ecefdb46ea145a96bdc0ed0c622012 --- .akr/akr.lock | 12 +- .akr/records/jpegxl-rs/work.akr | 18 +++ JPXL/crates/jpxl-encode/src/container.rs | 47 ++++++ JPXL/crates/jpxl-encode/src/headers.rs | 179 +++++++++++++++++++--- JPXL/crates/jpxl-encode/src/lib.rs | 36 ++++- JPXL/crates/jpxl/src/lib.rs | 185 ++++++++++++++++++++--- docs/generated/ACTIVE-WORK.md | 17 ++- docs/generated/CURRENT-STATE.md | 2 +- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 2 +- 13 files changed, 448 insertions(+), 58 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index d0f37f35..38f06919 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d" + source_graph "sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd" } source ".akr/project.akr" { @@ -69,8 +69,8 @@ source ".akr/records/jpegxl-rs/tracks.akr" { } source ".akr/records/jpegxl-rs/work.akr" { - hash "sha256:163d9779bd261b63cb9e07a2cdb04966a47cfdbbad121251c8011321bd900037" - records 187 + hash "sha256:52e69b5b86d843ee9caaa7a2fa2e7915a828b1c8a206aa8883b5ddb07f69cd6e" + records 188 } resolution @jpegxl-rs.decision.encoder-architecture-phases/1 { @@ -3697,6 +3697,12 @@ resolution @jpegxl-rs.work.opt-v2-rate-work-sharing/2 { hash "sha256:f7733beff3d25a93a7ba717c5686463ca7c969d91eae48dbaacfeefae835d73d" } +resolution @jpegxl-rs.work.pipeline-metadata-seam/1 { + slot implements + to @jpegxl-rs.requirement.general-use-integration-surface/2 + hash "sha256:540f9f8226b27b8fc64b62502f583996d474e374cc34a10e79b6b77706b3b17f" +} + resolution @jpegxl-rs.work.pqc-one-shot-controller/1 { slot depends_on to @jpegxl-rs.work.pqc-one-shot-pr0-hard-floor/1 diff --git a/.akr/records/jpegxl-rs/work.akr b/.akr/records/jpegxl-rs/work.akr index df71138d..45165403 100644 --- a/.akr/records/jpegxl-rs/work.akr +++ b/.akr/records/jpegxl-rs/work.akr @@ -7137,6 +7137,24 @@ record jpegxl-rs.work.optional-semantic-guidance-consumer/1 : work { part_of [ @jpegxl-rs.track.encoder-optimization/1 ] } +record jpegxl-rs.work.pipeline-metadata-seam/1 : work { + title "EXIF box and colour-space signalling for the archive pipeline" + state proposed + intent """ + Give the archive pipeline the two metadata seams it needs from the + encoder: a Part 2 Exif container box exposed as Encoder::with_exif + (validated raw-TIFF payload, container implied), and declarative + colour-space signalling (sRGB, linear sRGB, Display P3, Rec.2020) + through the Annex E ColourEncoding bundle on the lossless Modular + path, with lossy VarDCT rejecting non-sRGB input by typed error + because XYB and the metric are defined on sRGB. Downstream, + raw-autotune supplies the TIFF blob and colour tag and openarc + routes them through archiving; the contract is a bare TIFF blob, + never an APP1 wrapper. + """ + implements [ @jpegxl-rs.requirement.general-use-integration-surface ] +} + record jpegxl-rs.work.pqc-one-shot-controller/1 : work { title "One-shot program: shadow-first common-case one-shot quality controller" state superseded diff --git a/JPXL/crates/jpxl-encode/src/container.rs b/JPXL/crates/jpxl-encode/src/container.rs index 019f2a59..7ca68082 100644 --- a/JPXL/crates/jpxl-encode/src/container.rs +++ b/JPXL/crates/jpxl-encode/src/container.rs @@ -93,6 +93,27 @@ pub fn wrap_fragmented(codestream: &[u8], level: u8, fragment_size: usize) -> Ve out } +/// Appends an `Exif` box (18181-2 9.5) to an already-wrapped container. +/// +/// The payload must be the raw Exif block as JEITA CP-3451E / CP-3461B +/// define it — beginning with the TIFF header — and the box's +/// `tiff_header_offset` field is therefore written as zero. Clause 5 leaves +/// box order free after the file type box, so appending after the codestream +/// boxes is conforming; [`wrap`] writes the `jxlc` box with an explicit +/// length precisely so a box can follow it. +/// +/// Per 9.5, codestream fields (orientation, dimensions) take precedence over +/// Exif equivalents at decode time; the caller is responsible for not +/// contradicting them. +pub fn append_exif(container: &mut Vec<u8>, exif_payload: &[u8]) { + // LBox counts the 8-byte header and the u32 tiff_header_offset. + let explicit = u32::try_from(exif_payload.len() + 12).ok(); + container.extend_from_slice(&explicit.unwrap_or(0).to_be_bytes()); + container.extend_from_slice(b"Exif"); + container.extend_from_slice(&0u32.to_be_bytes()); // tiff_header_offset + container.extend_from_slice(exif_payload); +} + /// The signature box, the file type box and — when the level is not the /// default — the level box, i.e. everything before the codestream boxes. fn preamble(payload_hint: usize, level: u8) -> Vec<u8> { @@ -197,6 +218,32 @@ mod tests { } } + /// The claim the archive pipeline needs: an appended `Exif` box survives + /// the decoder's box walk with its payload intact, and does not disturb + /// codestream extraction — for a whole `jxlc` file and a fragmented one. + #[test] + fn an_appended_exif_box_round_trips_and_leaves_the_codestream_alone() { + let codestream: Vec<u8> = (0..500u32).map(|i| (i % 251) as u8).collect(); + // A plausible payload head: little-endian TIFF header, then filler. + let mut exif = vec![0x49, 0x49, 0x2A, 0x00]; + exif.extend((0..64u32).map(|i| (i % 7) as u8)); + + for mut file in [ + wrap(&codestream, DEFAULT_LEVEL), + wrap_fragmented(&codestream, EXTENDED_LEVEL, 128), + ] { + append_exif(&mut file, &exif); + let mut guard = AllocGuard::new(&Limits::relaxed()); + let tree = jpxl_decode::container::BoxTree::parse(&file, &mut guard).expect("parses"); + tree.validate().expect("conforming with an Exif box"); + assert_eq!(tree.codestream(&mut guard).expect("codestream"), codestream); + let parsed = tree.exif().expect("well-formed Exif boxes"); + assert_eq!(parsed.len(), 1); + assert_eq!(parsed[0].tiff_header_offset, 0); + assert_eq!(parsed[0].payload, &exif[..]); + } + } + #[test] fn the_last_fragment_is_the_only_one_carrying_the_final_marker() { // Written out by hand rather than trusting the decoder, because the diff --git a/JPXL/crates/jpxl-encode/src/headers.rs b/JPXL/crates/jpxl-encode/src/headers.rs index 51504d55..03a6641f 100644 --- a/JPXL/crates/jpxl-encode/src/headers.rs +++ b/JPXL/crates/jpxl-encode/src/headers.rs @@ -78,10 +78,20 @@ const ENUM_SPEC: U32Spec = U32Spec::new([ }, ]); +/// Table E.3 `ColourSpace`: `kRGB`, the bundle default. +const COLOUR_SPACE_RGB: u32 = 0; /// Table E.3 `ColourSpace`: `kGrey`. const COLOUR_SPACE_GREY: u32 = 1; /// Table E.4 `WhitePoint`: `kD65`, the bundle default. const WHITE_POINT_D65: u32 = 1; +/// Table E.5 `Primaries`: `kSRGB`, the bundle default. +const PRIMARIES_SRGB: u32 = 1; +/// Table E.5 `Primaries`: ITU-R BT.2100-2 (the Rec.2020 gamut). +const PRIMARIES_2100: u32 = 9; +/// Table E.5 `Primaries`: SMPTE ST 428-1 (P3). +const PRIMARIES_P3: u32 = 11; +/// Table E.6 `TransferFunction`: gamma exponent 1. +const TRANSFER_FUNCTION_LINEAR: u32 = 8; /// Table E.6 `TransferFunction`: `kSrgb`, the bundle default. const TRANSFER_FUNCTION_SRGB: u32 = 13; /// Table E.8 `RenderingIntent`: `kRelative`, the bundle default. @@ -147,14 +157,59 @@ impl ColourShape { } } +/// The colour space the caller's samples are in, signalled declaratively in +/// the `ColourEncoding` bundle (18181-1 E.2). +/// +/// The Modular path stores samples untouched, so this is a pure header claim: +/// it changes how a colour-managed viewer interprets the decoded samples and +/// nothing else. Every variant is a named row combination of Tables E.3–E.8 — +/// D65 white point and relative-colorimetric intent throughout, which is what +/// every real capture pipeline this encoder feeds produces. +/// +/// The XYB-encoded VarDCT path is not covered by this enum: its forward +/// transform and its perceptual metric are defined on sRGB input, so the +/// policy layer rejects a non-sRGB lossy request instead of mis-tagging it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[non_exhaustive] +pub enum ColourSpace { + /// IEC 61966-2-1 sRGB — the Table E.1 `all_default` bundle. + #[default] + Srgb, + /// sRGB primaries with a linear transfer function (gamma 1). + LinearSrgb, + /// Display P3: SMPTE ST 428-1 primaries, D65, the sRGB transfer function. + DisplayP3, + /// Rec.2020 gamut: ITU-R BT.2100-2 primaries, D65, the sRGB transfer + /// function (the shape a "rec2020 working space" still image carries). + Rec2020, +} + +impl ColourSpace { + /// `(primaries, transfer_function)` Table E.5/E.6 rows for an RGB image. + const fn rgb_rows(self) -> (u32, u32) { + match self { + Self::Srgb => (PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB), + Self::LinearSrgb => (PRIMARIES_SRGB, TRANSFER_FUNCTION_LINEAR), + Self::DisplayP3 => (PRIMARIES_P3, TRANSFER_FUNCTION_SRGB), + Self::Rec2020 => (PRIMARIES_2100, TRANSFER_FUNCTION_SRGB), + } + } +} + /// Writes an `ImageMetadata` bundle describing a non-XYB integer still image /// (18181-1 D.3, Table D.3). /// /// # Errors /// -/// [`EncodeError::Unsupported`] if `bits_per_sample` is outside the 1..=16 -/// range this encoder handles, or a bit writer error. -pub fn write_metadata(w: &mut BitWriter, shape: ColourShape, bits_per_sample: u32) -> Result<()> { +/// [`EncodeError::Unsupported`] for a greyscale image in a non-sRGB colour +/// space, [`EncodeError::ValueOutOfRange`] if `bits_per_sample` is outside +/// the 1..=16 range this encoder handles, or a bit writer error. +pub fn write_metadata( + w: &mut BitWriter, + shape: ColourShape, + bits_per_sample: u32, + colour: ColourSpace, +) -> Result<()> { if bits_per_sample == 0 || bits_per_sample > 16 { return Err(EncodeError::ValueOutOfRange { what: "bits_per_sample", @@ -174,7 +229,7 @@ pub fn write_metadata(w: &mut BitWriter, shape: ColourShape, bits_per_sample: u3 w.write_u32(&NUM_EXTRA_SPEC, 0)?; // no extra channels w.write_bool(false); // xyb_encoded: samples are stored as-is - write_colour_encoding(w, shape)?; + write_colour_encoding(w, shape, colour)?; // tone_mapping is guarded by extra_fields, which is false. w.write_u64(0)?; // extensions (B.3) @@ -190,29 +245,56 @@ pub fn write_metadata(w: &mut BitWriter, shape: ColourShape, bits_per_sample: u3 /// /// Only through the bit writer. pub fn write_grey8_metadata(w: &mut BitWriter) -> Result<()> { - write_metadata(w, ColourShape::Grey, 8) + write_metadata(w, ColourShape::Grey, 8, ColourSpace::Srgb) } /// Writes a `ColourEncoding` bundle (18181-1 E.2, Table E.1). /// /// The Table E.1 defaults are exactly sRGB: `want_icc` false, `kRGB`, `kD65`, -/// `kSRGB` primaries, the sRGB transfer function and `kRelative`. An RGB image -/// is therefore one `all_default` bit. `kGrey` differs from the default, so -/// the greyscale form is written out — with `has_primaries` false, which skips -/// the primaries rows but not the white point. -fn write_colour_encoding(w: &mut BitWriter, shape: ColourShape) -> Result<()> { - if shape == ColourShape::Rgb { +/// `kSRGB` primaries, the sRGB transfer function and `kRelative`. An sRGB RGB +/// image is therefore one `all_default` bit. Every other case writes the +/// bundle out: `kGrey` with `has_primaries` false (which skips the primaries +/// rows but not the white point), and a non-default RGB colour space with the +/// primaries and transfer-function rows of [`ColourSpace::rgb_rows`]. +/// +/// # Errors +/// +/// [`EncodeError::Unsupported`] for a greyscale image in a non-sRGB colour +/// space — a combination nothing feeds this encoder — or a bit writer error. +fn write_colour_encoding(w: &mut BitWriter, shape: ColourShape, colour: ColourSpace) -> Result<()> { + if shape == ColourShape::Grey { + if colour != ColourSpace::Srgb { + return Err(EncodeError::unsupported( + "a greyscale image can only be signalled as sRGB", + "E.2", + )); + } + w.write_bool(false); // all_default (the default is kRGB) + w.write_bool(false); // want_icc + w.write_u32(&ENUM_SPEC, COLOUR_SPACE_GREY)?; + w.write_u32(&ENUM_SPEC, WHITE_POINT_D65)?; + // primaries / red / green / blue: skipped for kGrey. + // CustomTransferFunction (Table E.7). + w.write_bool(false); // have_gamma + w.write_u32(&ENUM_SPEC, TRANSFER_FUNCTION_SRGB)?; + w.write_u32(&ENUM_SPEC, RENDERING_INTENT_RELATIVE)?; + return Ok(()); + } + if colour == ColourSpace::Srgb { w.write_bool(true); // all_default return Ok(()); } - w.write_bool(false); // all_default (the default is kRGB) + let (primaries, transfer) = colour.rgb_rows(); + w.write_bool(false); // all_default w.write_bool(false); // want_icc - w.write_u32(&ENUM_SPEC, COLOUR_SPACE_GREY)?; + w.write_u32(&ENUM_SPEC, COLOUR_SPACE_RGB)?; w.write_u32(&ENUM_SPEC, WHITE_POINT_D65)?; - // primaries / red / green / blue: skipped for kGrey. + // white: skipped, the white point is not kCustom. + w.write_u32(&ENUM_SPEC, primaries)?; + // red / green / blue: skipped, the primaries are not kCustom. // CustomTransferFunction (Table E.7). w.write_bool(false); // have_gamma - w.write_u32(&ENUM_SPEC, TRANSFER_FUNCTION_SRGB)?; + w.write_u32(&ENUM_SPEC, transfer)?; w.write_u32(&ENUM_SPEC, RENDERING_INTENT_RELATIVE)?; Ok(()) } @@ -225,10 +307,20 @@ mod tests { use jpxl_decode::headers::decode_image_headers; fn headers(width: u32, height: u32, shape: ColourShape, bits: u32) -> Vec<u8> { + headers_in(width, height, shape, bits, ColourSpace::Srgb) + } + + fn headers_in( + width: u32, + height: u32, + shape: ColourShape, + bits: u32, + colour: ColourSpace, + ) -> Vec<u8> { let mut w = BitWriter::new(); write_signature(&mut w).expect("signature"); write_size_header(&mut w, width, height).expect("size"); - write_metadata(&mut w, shape, bits).expect("metadata"); + write_metadata(&mut w, shape, bits, colour).expect("metadata"); w.zero_pad_to_byte(); w.into_bytes() } @@ -270,7 +362,7 @@ mod tests { let mut w = BitWriter::new(); write_signature(&mut w).expect("signature"); write_size_header(&mut w, 64, 64).expect("size"); - write_metadata(&mut w, shape, bits).expect("metadata"); + write_metadata(&mut w, shape, bits, ColourSpace::Srgb).expect("metadata"); let written = w.bit_len(); w.zero_pad_to_byte(); let bytes = w.into_bytes(); @@ -282,15 +374,64 @@ mod tests { } } + /// Each declarative colour space comes back from the decoder as the + /// Table E.5/E.6 rows it names, with the header ending exactly where the + /// writer says — the bit count is what catches a wrong E.1 conditional. + #[test] + fn non_default_colour_spaces_round_trip_through_the_decoder() { + use jpxl_decode::headers::colour::CustomTransferFunction; + use jpxl_decode::headers::enums::{Primaries, TransferFunction, WhitePoint}; + + for (colour, primaries, tf) in [ + ( + ColourSpace::LinearSrgb, + Primaries::KSrgb, + TransferFunction::KLinear, + ), + ( + ColourSpace::DisplayP3, + Primaries::KP3, + TransferFunction::KSrgb, + ), + ( + ColourSpace::Rec2020, + Primaries::K2100, + TransferFunction::KSrgb, + ), + ] { + let bytes = headers_in(64, 64, ColourShape::Rgb, 16, colour); + let mut r = BitReader::new(&bytes); + let parsed = decode_image_headers(&mut r, &Limits::default()) + .unwrap_or_else(|e| panic!("{colour:?}: {e}")); + + let ce = parsed.metadata.colour_encoding; + assert!(!ce.all_default, "{colour:?}"); + assert!(!ce.want_icc, "{colour:?}"); + assert!(!ce.is_grey(), "{colour:?}"); + assert_eq!(ce.white_point, WhitePoint::KD65, "{colour:?}"); + assert_eq!(ce.primaries, primaries, "{colour:?}"); + assert_eq!(ce.tf, CustomTransferFunction::Enumerated(tf), "{colour:?}"); + } + } + + #[test] + fn a_greyscale_image_rejects_a_non_srgb_colour_space() { + let mut w = BitWriter::new(); + assert!(matches!( + write_metadata(&mut w, ColourShape::Grey, 8, ColourSpace::Rec2020), + Err(EncodeError::Unsupported { .. }) + )); + } + #[test] fn an_unrepresentable_bit_depth_is_rejected() { let mut w = BitWriter::new(); assert!(matches!( - write_metadata(&mut w, ColourShape::Grey, 0), + write_metadata(&mut w, ColourShape::Grey, 0, ColourSpace::Srgb), Err(EncodeError::ValueOutOfRange { .. }) )); assert!(matches!( - write_metadata(&mut w, ColourShape::Grey, 17), + write_metadata(&mut w, ColourShape::Grey, 17, ColourSpace::Srgb), Err(EncodeError::ValueOutOfRange { .. }) )); } diff --git a/JPXL/crates/jpxl-encode/src/lib.rs b/JPXL/crates/jpxl-encode/src/lib.rs index b3a1cbf4..94ad3308 100644 --- a/JPXL/crates/jpxl-encode/src/lib.rs +++ b/JPXL/crates/jpxl-encode/src/lib.rs @@ -76,6 +76,7 @@ use modular::{ModularSource, Plane, Rect}; use section::SectionStore; pub use error::{EncodeError, Result}; +pub use headers::ColourSpace; pub use lossless::Effort; pub use resources::{EncodeExecutor, EncodeResources, ParallelAxis}; @@ -152,12 +153,12 @@ impl Image { /// # Errors /// /// As [`Image::new`]. - pub fn from_interleaved( + pub fn from_interleaved<S: Copy + Into<i32>>( width: u32, height: u32, channels: usize, bits_per_sample: u32, - samples: &[u16], + samples: &[S], ) -> Result<Self> { if channels == 0 { return Err(EncodeError::unsupported( @@ -174,7 +175,7 @@ impl Image { let planes: Vec<Plane> = (0..channels) .map(|c| { (0..per_plane) - .map(|i| samples.get(i * channels + c).map_or(0, |&s| i32::from(s))) + .map(|i| samples.get(i * channels + c).map_or(0, |&s| s.into())) .collect() }) .collect(); @@ -251,6 +252,13 @@ pub struct EncodeOptions { /// Every level is exact-lossless, so this changes only the byte count and /// the encode time, never the decoded pixels. pub effort: Effort, + /// The colour space the caller's samples are in, signalled declaratively + /// in the image header (18181-1 E.2). Default: sRGB, the Table E.1 + /// `all_default` bundle. + /// + /// The Modular path stores samples untouched, so this changes only how a + /// colour-managed viewer interprets them — never the decoded values. + pub colour_space: headers::ColourSpace, /// Measurement escape hatch: override individual levers of the search /// budget [`EncodeOptions::effort`] would have selected. /// @@ -402,7 +410,13 @@ fn encode_codestream(image: &Image, options: &EncodeOptions) -> Result<Vec<u8>> } else { source_planes }; - encode_codestream_with_plan_resources(image, &planes, &plan, options.resources) + encode_codestream_with_plan_resources( + image, + &planes, + &plan, + options.resources, + options.colour_space, + ) } /// Emits the codestream a validated plan describes. @@ -418,10 +432,17 @@ pub fn encode_codestream_with_plan( planes: &[Plane], plan: &ValidatedLosslessPlan, ) -> Result<Vec<u8>> { - encode_codestream_with_plan_resources(image, planes, plan, EncodeResources::serial()) + encode_codestream_with_plan_resources( + image, + planes, + plan, + EncodeResources::serial(), + ColourSpace::Srgb, + ) } -/// As [`encode_codestream_with_plan`], with an explicit resource policy. +/// As [`encode_codestream_with_plan`], with an explicit resource policy and +/// an explicit declarative colour space. /// /// # Errors /// @@ -431,6 +452,7 @@ pub fn encode_codestream_with_plan_resources( planes: &[Plane], plan: &ValidatedLosslessPlan, resources: EncodeResources, + colour_space: ColourSpace, ) -> Result<Vec<u8>> { let (width, height) = (image.width(), image.height()); let plan = plan.plan(); @@ -456,7 +478,7 @@ pub fn encode_codestream_with_plan_resources( let mut w = BitWriter::new(); headers::write_signature(&mut w)?; headers::write_size_header(&mut w, width, height)?; - headers::write_metadata(&mut w, image.shape(), image.bits_per_sample())?; + headers::write_metadata(&mut w, image.shape(), image.bits_per_sample(), colour_space)?; // F.1: every frame starts on a byte boundary. w.zero_pad_to_byte(); diff --git a/JPXL/crates/jpxl/src/lib.rs b/JPXL/crates/jpxl/src/lib.rs index d7553bbc..2b0cf389 100644 --- a/JPXL/crates/jpxl/src/lib.rs +++ b/JPXL/crates/jpxl/src/lib.rs @@ -45,6 +45,7 @@ use std::fmt; pub use jpxl_core::limits::Limits; pub use jpxl_decode::decode::FloatPlane; pub use jpxl_decode::{DecodedImage, Plane}; +pub use jpxl_encode::ColourSpace; pub use jpxl_encode_policy::RateStatus; pub use jpxl_encode_policy::request::MetricVersion; @@ -377,7 +378,7 @@ enum Mode { /// contract), [`with_target_bpp`](Self::with_target_bpp) / /// [`with_target_bytes`](Self::with_target_bytes), or /// [`with_global_scale`](Self::with_global_scale). -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Encoder { mode: Mode, lossless_effort: jpxl_encode::Effort, @@ -386,6 +387,8 @@ pub struct Encoder { container: bool, jxlp_fragment_size: Option<usize>, quality_fallback: QualityFallback, + colour_space: ColourSpace, + exif: Option<Vec<u8>>, } impl Default for Encoder { @@ -398,6 +401,8 @@ impl Default for Encoder { container: false, jxlp_fragment_size: None, quality_fallback: QualityFallback::Refuse, + colour_space: ColourSpace::Srgb, + exif: None, } } } @@ -579,6 +584,44 @@ impl Encoder { self } + /// Declare the colour space of the samples handed to the encoder + /// (default: sRGB), signalled declaratively in the image header. + /// + /// The lossless Modular path stores samples untouched, so this changes + /// only how a colour-managed viewer interprets them. The lossy VarDCT + /// path is defined on sRGB input — its XYB transform and its perceptual + /// metric assume it — so a lossy encode of a non-sRGB colour space fails + /// with [`Error::Unsupported`] rather than mis-tagging the pixels. + #[must_use] + pub fn with_colour_space(mut self, colour_space: ColourSpace) -> Self { + self.colour_space = colour_space; + self + } + + /// Attach an Exif metadata block, carried in a Part 2 `Exif` box. + /// + /// `exif` must be the raw Exif/TIFF payload as JEITA CP-3451E defines it, + /// beginning with the TIFF byte-order header (`II*\0` or `MM\0*`) — the + /// same bytes a camera stores after the `Exif\0\0` marker of a JPEG + /// `APP1` segment, without that marker. Implies + /// [`with_container`](Self::with_container): metadata boxes have nowhere + /// to live outside a container. + /// + /// Per 18181-2 9.5 the codestream's own fields (dimensions, orientation) + /// take precedence over Exif equivalents at decode time. + pub fn with_exif(mut self, exif: Vec<u8>) -> Result<Self> { + if !matches!( + exif.first_chunk(), + Some([0x49, 0x49, 0x2A, 0x00] | [0x4D, 0x4D, 0x00, 0x2A]) + ) { + return Err(Error::InvalidOption( + "the Exif payload must begin with a TIFF byte-order header (II*\\0 or MM\\0*)", + )); + } + self.exif = Some(exif); + Ok(self) + } + /// Encode interleaved 8-bit sRGB samples. pub fn encode_rgb8(&self, width: u32, height: u32, rgb: &[u8]) -> Result<Vec<u8>> { Ok(self.encode_rgb8_reported(width, height, rgb)?.0) @@ -591,10 +634,10 @@ impl Encoder { height: u32, rgb: &[u8], ) -> Result<(Vec<u8>, EncodeReport)> { + self.require_srgb_for_lossy()?; match self.mode { Mode::Lossless => { - let samples: Vec<u16> = rgb.iter().map(|&sample| u16::from(sample)).collect(); - let bytes = self.encode_lossless(width, height, 3, 8, &samples)?; + let bytes = self.encode_lossless(width, height, 3, 8, rgb)?; Ok((bytes, EncodeReport::Lossless)) } Mode::Lossy(LossyTarget::Rate(target)) => { @@ -604,23 +647,20 @@ impl Encoder { width, height, rgb, &request, target, )?; let report = EncodeReport::Rate(rate_summary(&outcome)); - let bytes = self.wrap_lossy(outcome.codestream, 8); + let bytes = self.finish(outcome.codestream, 8); Ok((bytes, report)) } Mode::Lossy(LossyTarget::Perceptual(target)) => self.perceptual_encode( target, PerceptualSource::Rgb8 { width, height, rgb }, - || { - let samples: Vec<u16> = rgb.iter().map(|&sample| u16::from(sample)).collect(); - self.encode_lossless(width, height, 3, 8, &samples) - }, + || self.encode_lossless(width, height, 3, 8, rgb), ), Mode::Lossy(LossyTarget::FixedQuantizer(fixed)) => { let mut request = jpxl_encode_policy::EncodeRequest::for_fixed_quantizer(fixed); request.resources = self.resources; request.bits_per_sample = 8; let bytes = jpxl_encode_policy::encode_srgb8_vardct(width, height, rgb, &request)?; - let wrapped = self.wrap_lossy(bytes, 8); + let wrapped = self.finish(bytes, 8); let count = u64::try_from(wrapped.len()).unwrap_or(u64::MAX); Ok((wrapped, EncodeReport::FixedQuantizer { bytes: count })) } @@ -648,6 +688,7 @@ impl Encoder { bits_per_sample: u32, rgb: &[u16], ) -> Result<(Vec<u8>, EncodeReport)> { + self.require_srgb_for_lossy()?; match self.mode { Mode::Lossless => { let bytes = self.encode_lossless(width, height, 3, bits_per_sample, rgb)?; @@ -664,7 +705,7 @@ impl Encoder { target, )?; let report = EncodeReport::Rate(rate_summary(&outcome)); - let bytes = self.wrap_lossy(outcome.codestream, bits_per_sample); + let bytes = self.finish(outcome.codestream, bits_per_sample); Ok((bytes, report)) } Mode::Lossy(LossyTarget::Perceptual(target)) => self.perceptual_encode( @@ -687,7 +728,7 @@ impl Encoder { bits_per_sample, &request, )?; - let wrapped = self.wrap_lossy(bytes, bits_per_sample); + let wrapped = self.finish(bytes, bits_per_sample); let count = u64::try_from(wrapped.len()).unwrap_or(u64::MAX); Ok((wrapped, EncodeReport::FixedQuantizer { bytes: count })) } @@ -699,8 +740,12 @@ impl Encoder { /// The current VarDCT policy is RGB-only, so a lossy encoder returns a /// clear error instead of silently expanding greyscale to RGB. pub fn encode_gray8(&self, width: u32, height: u32, gray: &[u8]) -> Result<Vec<u8>> { - let samples: Vec<u16> = gray.iter().map(|&sample| u16::from(sample)).collect(); - self.encode_gray16(width, height, 8, &samples) + if matches!(self.mode, Mode::Lossy(_)) { + return Err(Error::InvalidOption( + "lossy VarDCT currently requires RGB input; use lossless mode for greyscale", + )); + } + self.encode_lossless(width, height, 1, 8, gray) } /// Encode high-precision greyscale samples losslessly. @@ -868,7 +913,7 @@ impl Encoder { } } - let bytes = self.wrap_lossy(outcome.codestream, bits); + let bytes = self.finish(outcome.codestream, bits); let report = PerceptualOutcome { requested_score: target.minimum_score, achieved_score: Some(outcome.achieved_score), @@ -899,13 +944,13 @@ impl Encoder { Ok((bytes, EncodeReport::Perceptual(report))) } - fn encode_lossless( + fn encode_lossless<S: Copy + Into<i32>>( &self, width: u32, height: u32, channels: usize, bits_per_sample: u32, - samples: &[u16], + samples: &[S], ) -> Result<Vec<u8>> { let image = jpxl_encode::Image::from_interleaved( width, @@ -914,14 +959,18 @@ impl Encoder { bits_per_sample, samples, )?; + // Ask for the naked codestream and wrap in `finish`, so the + // container logic (including the Exif box) lives in one place for + // the lossless and lossy paths alike. let options = jpxl_encode::EncodeOptions { - container: self.container, - jxlp_fragment_size: self.jxlp_fragment_size, + container: false, + jxlp_fragment_size: None, resources: self.resources, effort: self.lossless_effort, + colour_space: self.colour_space, ..jpxl_encode::EncodeOptions::default() }; - Ok(jpxl_encode::encode(&image, &options)?) + Ok(self.finish(jpxl_encode::encode(&image, &options)?, bits_per_sample)) } fn lossy_request( @@ -934,8 +983,11 @@ impl Encoder { request } - fn wrap_lossy(&self, codestream: Vec<u8>, bits_per_sample: u32) -> Vec<u8> { - if !self.container && self.jxlp_fragment_size.is_none() { + /// Wraps a finished codestream per the encoder's container options: a + /// container when asked for (or implied by a `jxlp` fragment size or an + /// Exif payload), with the Exif box appended after the codestream boxes. + fn finish(&self, codestream: Vec<u8>, bits_per_sample: u32) -> Vec<u8> { + if !self.container && self.jxlp_fragment_size.is_none() && self.exif.is_none() { return codestream; } let level = if bits_per_sample > 8 { @@ -943,10 +995,26 @@ impl Encoder { } else { jpxl_encode::container::DEFAULT_LEVEL }; - match self.jxlp_fragment_size { + let mut file = match self.jxlp_fragment_size { Some(size) => jpxl_encode::container::wrap_fragmented(&codestream, level, size), None => jpxl_encode::container::wrap(&codestream, level), + }; + if let Some(ref exif) = self.exif { + jpxl_encode::container::append_exif(&mut file, exif); } + file + } + + /// The lossy VarDCT pipeline is defined on sRGB input: its XYB transform + /// and its perceptual metric assume it, so anything else must be encoded + /// losslessly rather than mis-tagged. + const fn require_srgb_for_lossy(&self) -> Result<()> { + if matches!(self.mode, Mode::Lossy(_)) && !matches!(self.colour_space, ColourSpace::Srgb) { + return Err(Error::Unsupported( + "lossy VarDCT is defined on sRGB input; encode other colour spaces losslessly", + )); + } + Ok(()) } } @@ -1002,6 +1070,78 @@ mod tests { assert_eq!(decoded.interleaved_colour(), gray); } + /// The archive-pipeline claim: an Exif payload attached at the facade + /// comes back byte-identical from the decoder's box walk, on both the + /// lossless and the lossy path, and implies the container. + #[test] + fn an_exif_payload_survives_encode_to_the_decoders_box_walk() { + let mut exif = vec![0x4D, 0x4D, 0x00, 0x2A]; + exif.extend((0..32u32).map(|i| (i % 5) as u8)); + + let (width, height) = (64u32, 64u32); + let rgb = gradient_rgb8(width, height); + for encoded in [ + Encoder::new() + .with_exif(exif.clone()) + .expect("valid payload") + .encode_rgb8(width, height, &rgb) + .expect("lossless encode"), + Encoder::new() + .with_target_bytes(2_048) + .expect("target") + .with_effort(Effort::Fast) + .with_threads(1) + .expect("threads") + .with_exif(exif.clone()) + .expect("valid payload") + .encode_rgb8(width, height, &rgb) + .expect("lossy encode"), + ] { + assert!(jpxl_decode::container::is_container(&encoded)); + let mut guard = + jpxl_core::limits::AllocGuard::new(&jpxl_core::limits::Limits::relaxed()); + let tree = jpxl_decode::container::BoxTree::parse(&encoded, &mut guard).expect("boxes"); + tree.validate().expect("conforming"); + let boxes = tree.exif().expect("well-formed Exif"); + assert_eq!(boxes.len(), 1); + let first = boxes.first().expect("length asserted above"); + assert_eq!(first.payload, &exif[..]); + decode(&encoded).expect("still decodes"); + } + } + + #[test] + fn a_payload_without_a_tiff_header_is_rejected() { + assert!(Encoder::new().with_exif(vec![]).is_err()); + assert!( + Encoder::new() + .with_exif(vec![0xFF, 0xD8, 0xFF, 0xE1]) + .is_err() + ); + } + + /// A non-sRGB colour space is signalled on the lossless path and refused + /// (not mis-tagged) on the lossy path. + #[test] + fn colour_space_signalling_and_the_lossy_guard() { + let rgb: Vec<u16> = (0..4 * 4 * 3u16).map(|i| i * 512).collect(); + let encoded = Encoder::new() + .with_colour_space(ColourSpace::Rec2020) + .encode_rgb16(4, 4, 16, &rgb) + .expect("lossless rec2020"); + let decoded = decode(&encoded).expect("decode"); + assert_eq!(decoded.interleaved_colour(), rgb); + + let lossy = Encoder::new() + .with_target_bytes(2_048) + .expect("target") + .with_colour_space(ColourSpace::Rec2020); + assert!(matches!( + lossy.encode_rgb16(4, 4, 16, &rgb), + Err(Error::Unsupported(_)) + )); + } + #[test] fn builder_rejects_nonsensical_targets() { assert!(Encoder::new().with_target_bpp(0.0).is_err()); @@ -1062,6 +1202,7 @@ mod tests { for wrapped in [ encoder + .clone() .with_container(true) .encode_rgb8(width, height, &rgb) .expect("container encode"), diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index 9cb56f64..f8962dee 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> @@ -92,3 +92,18 @@ through Opt-P in after-order, supported by the static advisor assessment and the optimization decisions and gates. **Plan of record for** [Encoder optimization pass](ROADMAP.md#encoder-optimization-pass) `@jpegxl-rs.track.encoder-optimization/1` + +### EXIF box and colour-space signalling for the archive pipeline + +`proposed` · `@jpegxl-rs.work.pipeline-metadata-seam/1` + +Give the archive pipeline the two metadata seams it needs from the +encoder: a Part 2 Exif container box exposed as Encoder::with_exif +(validated raw-TIFF payload, container implied), and declarative +colour-space signalling (sRGB, linear sRGB, Display P3, Rec.2020) +through the Annex E ColourEncoding bundle on the lossless Modular +path, with lossy VarDCT rejecting non-sRGB input by typed error +because XYB and the metric are defined on sRGB. Downstream, +raw-autotune supplies the TIFF blob and colour tag and openarc +routes them through archiving; the contract is a bare TIFF blob, +never an APP1 wrapper. diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 398f947b..74ef6709 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 0e6ba67c..1c7f0d8d 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index f9164912..b1319963 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 5e6d06e8..128efb5d 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index d7316787..740bbe7e 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index c6aad0d2..4107a8df 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:cd475cd59072daec056b7dd990d7a577cabe3bed751c45724d18b3e51111151d + source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd tool: akr 0.3.3 --> From d2a54b5486b90fd4808e9bb77c67eea6bbd9f010 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Wed, 26 Aug 2026 15:14:29 +0800 Subject: [PATCH 24/27] perf(jpxl-perceptual): AVX2 metric pooling, bit-identical to scalar accumulate_band now dispatches to an avx2+fma multiversioned clone when the CPU supports it. The lane loop computes the per-pixel SSIM' and asymmetry terms in POOL_LANES groups, then folds them into the f64 MapSums in original pixel order, so the accumulated sums -- and the encoded bytes -- are identical to the scalar walk (verified by test across remainder lengths and by cmp on full encodes). ~5-8% off the 12 MP quality-path wall. jpxl-plan-render now names the jpxl-core "simd" feature it depends on instead of relying on feature unification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsAR4R1kGCRk8spmhQZMC2 AKR-Change: chg-35a2d60333479cdd AKR-Work: jpegxl-rs.work.pqc-usable-efforts-cost AKR-Graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd AKR-Tree: 825f2bbb085b265f3d1f6379478d6d8039250d28 --- JPXL/crates/jpxl-perceptual/src/pool.rs | 183 +++++++++++++++++++++++- JPXL/crates/jpxl-plan-render/Cargo.toml | 8 +- 2 files changed, 186 insertions(+), 5 deletions(-) diff --git a/JPXL/crates/jpxl-perceptual/src/pool.rs b/JPXL/crates/jpxl-perceptual/src/pool.rs index f3c60a7e..9fa3d0bc 100644 --- a/JPXL/crates/jpxl-perceptual/src/pool.rs +++ b/JPXL/crates/jpxl-perceptual/src/pool.rs @@ -88,7 +88,51 @@ pub struct MomentBands<'a> { } /// Accumulates the three maps over one band of the seven planes. +/// +/// Dispatched to an AVX2 build where the host supports it. The lane grouping +/// cannot change a value: each pixel's two error terms depend on that pixel's +/// seven inputs alone, and the running `f64` sums are folded in the original +/// pixel order either way, so the vector build is bit-identical to the scalar +/// one (Rust performs no floating-point contraction). pub fn accumulate_band(bands: &MomentBands<'_>, sums: &mut MapSums) { + #[cfg(target_arch = "x86_64")] + if jpxl_core::cpu::has_avx2() { + // SAFETY: `accumulate_band_avx2` only requires that the host support + // AVX2, which `has_avx2` has just confirmed. + #[allow(unsafe_code)] + unsafe { + accumulate_band_avx2(bands, sums); + } + return; + } + accumulate_band_impl(bands, sums); +} + +/// [`accumulate_band`] compiled for AVX2. +/// +/// Calling it is `unsafe` unless the host supports AVX2 (see +/// [`jpxl_core::cpu::has_avx2`]); that is the whole contract. +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2,fma")] +fn accumulate_band_avx2(bands: &MomentBands<'_>, sums: &mut MapSums) { + accumulate_band_impl(bands, sums); +} + +/// Pixels whose error terms are computed together as one vector lane block. +/// +/// Eight covers a full `f32` AVX2 register for the SSIM' arithmetic and two +/// `f64` registers for the asymmetry ratio, whose divide is the loop's +/// dominant cost. +const POOL_LANES: usize = 8; + +#[inline(always)] +#[allow( + clippy::indexing_slicing, + reason = "every range below ends at `whole`, which is at most the length \ + of the shortest plane, and every lane index is bounded by the \ + chunk arrays' own size" +)] +fn accumulate_band_impl(bands: &MomentBands<'_>, sums: &mut MapSums) { let MomentBands { img1, mu1, @@ -98,6 +142,94 @@ pub fn accumulate_band(bands: &MomentBands<'_>, sums: &mut MapSums) { s22, s12, } = *bands; + + // The scalar walk zips, stopping at the shortest plane; the lane walk + // must cover exactly the same pixels. + let n = img1 + .len() + .min(mu1.len()) + .min(s11.len()) + .min(img2.len()) + .min(mu2.len()) + .min(s22.len()) + .min(s12.len()); + let whole = n - n % POOL_LANES; + + let (i1c, _) = img1[..whole].as_chunks::<POOL_LANES>(); + let (m1c, _) = mu1[..whole].as_chunks::<POOL_LANES>(); + let (v11c, _) = s11[..whole].as_chunks::<POOL_LANES>(); + let (i2c, _) = img2[..whole].as_chunks::<POOL_LANES>(); + let (m2c, _) = mu2[..whole].as_chunks::<POOL_LANES>(); + let (v22c, _) = s22[..whole].as_chunks::<POOL_LANES>(); + let (v12c, _) = s12[..whole].as_chunks::<POOL_LANES>(); + + for ((((((i1, m1), v11), i2), m2), v22), v12) in i1c + .iter() + .zip(m1c) + .zip(v11c) + .zip(i2c) + .zip(m2c) + .zip(v22c) + .zip(v12c) + { + // Stage 1, lane-parallel: both per-pixel error terms. Nothing here + // reads the running sums, so the lanes are independent and the + // compiler is free to keep the two divides eight and four wide. + let mut ssim_d = [0.0f64; POOL_LANES]; + let mut asym = [0.0f64; POOL_LANES]; + for lane in 0..POOL_LANES { + let (i1, m1, v11) = (i1[lane], m1[lane], v11[lane]); + let (i2, m2, v22) = (i2[lane], m2[lane], v22[lane]); + let v12 = v12[lane]; + // SSIM' error. The luminance term keeps only its numerator + // (1 - (μ1-μ2)²); see the module docs for why the denominator is + // dropped. + let mu11 = m1 * m1; + let mu22 = m2 * m2; + let mu12 = m1 * m2; + let mu_diff = m1 - m2; + let num_m = 1.0 - mu_diff * mu_diff; + let num_s = 2.0 * (v12 - mu12) + SSIM_C2; + let denom_s = (v11 - mu11) + (v22 - mu22) + SSIM_C2; + ssim_d[lane] = (1.0 - f64::from((num_m * num_s) / denom_s)).max(0.0); + // Edge asymmetry: ratio of local high-pass magnitudes, minus one. + asym[lane] = + (1.0 + f64::from((i2 - m2).abs())) / (1.0 + f64::from((i1 - m1).abs())) - 1.0; + } + // Stage 2, ordered: fold into the running sums in pixel order, which + // keeps the result bit-identical to the fully scalar walk. + for lane in 0..POOL_LANES { + let d = ssim_d[lane]; + sums.ssim[0] += d; + sums.ssim[1] += d * d * d * d; + let d1 = asym[lane]; + let artifact = d1.max(0.0); + sums.artifact[0] += artifact; + sums.artifact[1] += artifact * artifact * artifact * artifact; + let lost = (-d1).max(0.0); + sums.detail_lost[0] += lost; + sums.detail_lost[1] += lost * lost * lost * lost; + } + } + + accumulate_scalar( + &[ + &img1[whole..], + &mu1[whole..], + &s11[whole..], + &img2[whole..], + &mu2[whole..], + &s22[whole..], + &s12[whole..], + ], + sums, + ); +} + +/// The original scalar walk, kept for the sub-lane remainder. +#[inline(always)] +fn accumulate_scalar(planes: &[&[f32]; 7], sums: &mut MapSums) { + let [img1, mu1, s11, img2, mu2, s22, s12] = *planes; for ((((((&i1, &m1), &v11), &i2), &m2), &v22), &v12) in img1 .iter() .zip(mu1) @@ -107,9 +239,6 @@ pub fn accumulate_band(bands: &MomentBands<'_>, sums: &mut MapSums) { .zip(s22) .zip(s12) { - // SSIM' error. The luminance term keeps only its numerator - // (1 - (μ1-μ2)²); see the module docs for why the denominator is - // dropped. let mu11 = m1 * m1; let mu22 = m2 * m2; let mu12 = m1 * m2; @@ -121,7 +250,6 @@ pub fn accumulate_band(bands: &MomentBands<'_>, sums: &mut MapSums) { sums.ssim[0] += d; sums.ssim[1] += d * d * d * d; - // Edge asymmetry: ratio of local high-pass magnitudes, minus one. let d1 = (1.0 + f64::from((i2 - m2).abs())) / (1.0 + f64::from((i1 - m1).abs())) - 1.0; let artifact = d1.max(0.0); sums.artifact[0] += artifact; @@ -158,6 +286,53 @@ mod tests { assert_eq!(ChannelTerms::from_sums(&sums, 4), ChannelTerms::default()); } + /// The dispatched walk (lane-grouped, AVX2 where the host has it) must be + /// bit-identical to the plain scalar walk — including on lengths that + /// leave a sub-lane remainder — because pooled sums feed the canonical + /// score, whose Contract A forbids host-dependent output. + #[test] + #[allow( + clippy::indexing_slicing, + clippy::cast_possible_truncation, + reason = "fixture generation and seven fixed-index plane borrows in a \ + test; an out-of-range index here is a test bug the panic \ + reports directly" + )] + fn the_lane_walk_is_bit_identical_to_the_scalar_walk() { + // A deterministic, sign-varied, non-smooth fill. + let plane = |seed: u32, len: usize| -> Vec<f32> { + (0..len) + .map(|i| { + let x = (i as u32).wrapping_mul(2_654_435_761).wrapping_add(seed); + (f64::from(x % 2003) / 1001.5 - 1.0) as f32 + }) + .collect() + }; + for len in [0usize, 1, 7, 8, 9, 64, 250] { + let planes: Vec<Vec<f32>> = (0..7u32).map(|s| plane(s * 97 + 13, len)).collect(); + let bands = MomentBands { + img1: &planes[0], + mu1: &planes[1], + s11: &planes[2], + img2: &planes[3], + mu2: &planes[4], + s22: &planes[5], + s12: &planes[6], + }; + let mut dispatched = MapSums::default(); + accumulate_band(&bands, &mut dispatched); + let mut scalar = MapSums::default(); + accumulate_scalar( + &[ + &planes[0], &planes[1], &planes[2], &planes[3], &planes[4], &planes[5], + &planes[6], + ], + &mut scalar, + ); + assert_eq!(dispatched, scalar, "length {len}"); + } + } + #[test] fn the_four_norm_of_a_constant_map_is_the_constant() { let sums = MapSums { diff --git a/JPXL/crates/jpxl-plan-render/Cargo.toml b/JPXL/crates/jpxl-plan-render/Cargo.toml index 56e2fcd7..9bed57df 100644 --- a/JPXL/crates/jpxl-plan-render/Cargo.toml +++ b/JPXL/crates/jpxl-plan-render/Cargo.toml @@ -11,7 +11,13 @@ license.workspace = true # over `jpxl-encode`'s plan types, and its parity against `jpxl-decode`, # `djxl` and `jxl-oxide` is proved by tests, not by sharing code. [dependencies] -jpxl-core = { path = "../jpxl-core", version = "0.3.0", default-features = false } +# `simd` is named explicitly: plan rendering sits on the quality path's +# hottest loops, and without it a consumer that builds this crate outside the +# full workspace (where `jpxl-encode` happens to unify the feature back on) +# would silently get the scalar DCT kernels. +jpxl-core = { path = "../jpxl-core", version = "0.3.0", default-features = false, features = [ + "simd", +] } jpxl-encode = { path = "../jpxl-encode", version = "0.3.0", default-features = false } [dev-dependencies] From b1f7196c325cce789620b87a0907bf594c82a73b Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Wed, 26 Aug 2026 15:15:46 +0800 Subject: [PATCH 25/27] build(jpxl): release-final profile (fat LTO) and PGO build script tools/build-release-final.sh runs the measured PGO cycle (instrumented build, training encodes over the canonical test set across the quality, rate and lossless paths plus a decode, llvm-profdata merge, profile-use rebuild) on the new release-final profile. Measured today the combined fat-LTO+PGO gain over `release` is 2-8%; the ledger's 36-53% PGO evidence predates the fused-render and Opt-F..P rounds and needs re-verification. `release` (thin LTO) stays the benchmark profile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsAR4R1kGCRk8spmhQZMC2 AKR-Change: chg-105dcc145ce18786 AKR-Graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd AKR-Tree: 58d9bd83af223a40749b1ba0ff161ce264be6b17 --- JPXL/Cargo.toml | 10 ++++ JPXL/tools/build-release-final.sh | 85 +++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 JPXL/tools/build-release-final.sh diff --git a/JPXL/Cargo.toml b/JPXL/Cargo.toml index 1a946ce8..75560972 100644 --- a/JPXL/Cargo.toml +++ b/JPXL/Cargo.toml @@ -76,6 +76,16 @@ debug = true lto = "thin" codegen-units = 1 +# The shipping profile: `release` plus full (fat) LTO. Build it through +# `tools/build-release-final.sh`, which wraps it in the measured PGO cycle +# (instrument → train on the canonical test-set images → rebuild with the +# profile); PGO alone was measured at -36.65% wall on 2400x1800 and -53.48% +# on 4000x3000 target-rate encodes. `release` (thin LTO) remains the profile +# for benchmarks and promoted timings so historical numbers stay comparable. +[profile.release-final] +inherits = "release" +lto = "fat" + # Optimised like `release` but built for iteration speed: no LTO, sixteen # codegen units, incremental, and with debug assertions and overflow checks # kept ON so the `debug_assert!` shape guards in the codec still fire under diff --git a/JPXL/tools/build-release-final.sh b/JPXL/tools/build-release-final.sh new file mode 100644 index 00000000..97550fbf --- /dev/null +++ b/JPXL/tools/build-release-final.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Build the shipping `jpxl` binary: the `release-final` profile (full LTO) +# wrapped in the measured PGO cycle. +# +# 1. build instrumented (-Cprofile-generate) with release-final +# 2. train on the canonical test-set images across the three encode paths +# (perceptual --quality, target-rate --bpp, lossless) plus a decode +# 3. merge the profiles with the toolchain's llvm-profdata +# 4. rebuild with -Cprofile-use +# +# The training set is the one the recorded PGO evidence used: the seven +# canonical 1024x768 photographs at test-set/ plus the 12 MP mid photo, with +# larger images left unseen so the recorded generalization claim +# (@jpegxl-rs.evidence.phase8-5-pgo-generalizes-2026-08-14) keeps meaning. +# +# Usage: tools/build-release-final.sh [--out <dir>] +# Run from anywhere; paths are derived from the script location. + +set -euo pipefail + +JPXL_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +REPO_DIR="$(cd -- "$JPXL_DIR/.." && pwd)" +TEST_SET="$REPO_DIR/test-set" +OUT_DIR="$JPXL_DIR/target/release-final" + +if [[ "${1:-}" == "--out" && -n "${2:-}" ]]; then + OUT_DIR="$2" +fi + +HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" +LLVM_PROFDATA="$(rustc --print sysroot)/lib/rustlib/$HOST_TRIPLE/bin/llvm-profdata" +if [[ ! -x "$LLVM_PROFDATA" ]]; then + echo "llvm-profdata not found; install with: rustup component add llvm-tools" >&2 + exit 1 +fi + +PGO_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jpxl-pgo.XXXXXX")" +TRAIN_OUT="$PGO_DIR/out" +mkdir -p "$TRAIN_OUT" +trap 'rm -rf "$PGO_DIR"' EXIT + +TRAIN_IMAGES=("$TEST_SET"/*.png) +MID_IMAGE="$(find "$TEST_SET/one-12mp" -name '*.png' | head -n 1 || true)" +if [[ ${#TRAIN_IMAGES[@]} -eq 0 || ! -f "${TRAIN_IMAGES[0]}" ]]; then + echo "no training images found at $TEST_SET/*.png" >&2 + exit 1 +fi + +echo "== 1/4: instrumented release-final build" +(cd "$JPXL_DIR" && RUSTFLAGS="-Cprofile-generate=$PGO_DIR" \ + cargo build --profile release-final -p jpxl-cli) +JPXL_BIN="$JPXL_DIR/target/release-final/jpxl" + +echo "== 2/4: training encodes" +train_one() { + local img="$1" stem + stem="$(basename "${img%.png}")" + "$JPXL_BIN" encode --quality --effort balanced --threads 4 \ + "$img" "$TRAIN_OUT/$stem-q.jxl" + "$JPXL_BIN" encode --bpp 1.0 --effort balanced --threads 4 \ + "$img" "$TRAIN_OUT/$stem-r.jxl" + "$JPXL_BIN" encode --effort 1 --threads 4 \ + "$img" "$TRAIN_OUT/$stem-l.jxl" + "$JPXL_BIN" decode "$TRAIN_OUT/$stem-q.jxl" "$TRAIN_OUT/$stem-q.ppm" +} +for img in "${TRAIN_IMAGES[@]}"; do + train_one "$img" +done +if [[ -n "$MID_IMAGE" ]]; then + train_one "$MID_IMAGE" +fi + +echo "== 3/4: merging profiles" +"$LLVM_PROFDATA" merge -o "$PGO_DIR/merged.profdata" "$PGO_DIR"/*.profraw + +echo "== 4/4: PGO-optimized release-final build" +(cd "$JPXL_DIR" && RUSTFLAGS="-Cprofile-use=$PGO_DIR/merged.profdata" \ + cargo build --profile release-final -p jpxl-cli) + +if [[ "$OUT_DIR" != "$JPXL_DIR/target/release-final" ]]; then + mkdir -p "$OUT_DIR" + cp "$JPXL_BIN" "$OUT_DIR/jpxl" +fi +echo "shipping binary: $OUT_DIR/jpxl" +"$OUT_DIR/jpxl" --version From 05d84e869b718b648f7bb9081c9de9980d27b812 Mon Sep 17 00:00:00 2001 From: dk <dkoepke@protonmail.com> Date: Wed, 26 Aug 2026 15:19:32 +0800 Subject: [PATCH 26/27] chore: Record metadata-seam and AVX2 evidence; PGO re-measurement The metadata seam is verified end to end at this commit: an appended Exif box round-trips through the decoder's box walk with the codestream untouched; with_exif rejects payloads - jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26 new -> verified - jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26 new -> verified - jpegxl-rs.observation.pgo-gain-remeasured-2026-08-26 new -> verified Verified by: - jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26 - jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26 AKR-Change: chg-438b67ff073613c8 AKR-Work: jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26 AKR-Work: jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26 AKR-Work: jpegxl-rs.observation.pgo-gain-remeasured-2026-08-26 AKR-Evidence: @jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26/1 AKR-Evidence: @jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26/1 AKR-Graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b AKR-Tree: 3f08f2fa5116c9585dc677aa30bf57d93ea55637 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsAR4R1kGCRk8spmhQZMC2 --- .akr/akr.lock | 25 ++++++++++++---- .akr/records/jpegxl-rs/evidence.akr | 37 +++++++++++++++++++++++ .akr/records/jpegxl-rs/observations.akr | 16 ++++++++++ docs/generated/ACTIVE-WORK.md | 2 +- docs/generated/CURRENT-STATE.md | 39 ++++++++++++++++++++++++- docs/generated/DECISION-HISTORY.md | 2 +- docs/generated/OPEN-QUESTIONS.md | 2 +- docs/generated/PAPERCUTS.md | 2 +- docs/generated/REVIEW-REQUIRED.md | 2 +- docs/generated/ROADMAP.md | 2 +- 10 files changed, 117 insertions(+), 12 deletions(-) diff --git a/.akr/akr.lock b/.akr/akr.lock index 38f06919..a6dc9ea2 100644 --- a/.akr/akr.lock +++ b/.akr/akr.lock @@ -5,7 +5,7 @@ build { tool "akr 0.3.3" grammar "0.1" vocabulary "0.2" - source_graph "sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd" + source_graph "sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b" } source ".akr/project.akr" { @@ -29,8 +29,8 @@ source ".akr/records/jpegxl-rs/decisions.akr" { } source ".akr/records/jpegxl-rs/evidence.akr" { - hash "sha256:813cdfbde0f67cee547fafce86d7716f2c2913795bb0cc58298b25607d9c5b4b" - records 564 + hash "sha256:d04e0b11af4cc93647f60cb756ed981f01a093d4ab3969445c4eba15fd4f7aaf" + records 566 } source ".akr/records/jpegxl-rs/milestones.akr" { @@ -39,8 +39,8 @@ source ".akr/records/jpegxl-rs/milestones.akr" { } source ".akr/records/jpegxl-rs/observations.akr" { - hash "sha256:09c16275005d4cbfe0b8df4b7b0e4719496adf50211ea286a29a63e0e064a3ab" - records 101 + hash "sha256:45603a70e136457f806013a2f0575d352fb4098e7ec538e3fdf6faad32bbe005" + records 102 } source ".akr/records/jpegxl-rs/papercuts.akr" { @@ -3877,6 +3877,11 @@ seal @jpegxl-rs.decision.ssimulacra2-is-the-primary-promotion-metric/1 { hash "sha256:9aa847d339b8d732eb49a5f6bbd48a6c4e41819fd3824400c8c4681d7b4f8549" } +seal @jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26/1 { + state verified + hash "sha256:3cdec63fc9c4af853f3e0245c9ad125c5ba23ce1b0fdf612aca20fa4f0d8d42b" +} + seal @jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 { state verified hash "sha256:a24dfed6aab83df6837b4ebe83c0a2bf6aa28a646df346c2b6f84175fe267b3b" @@ -5972,6 +5977,11 @@ seal @jpegxl-rs.evidence.phase9-quant-scheduling-speed-2026-08-15/1 { hash "sha256:cb264f0ebd981e140e9b4a3dac7845d50ea72489e8117e6198a957e436099375" } +seal @jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26/1 { + state verified + hash "sha256:9ef1d7916672b5d8b3592497a750aabda8126af95eeb51d7b4c6fd506d114edf" +} + seal @jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 { state verified hash "sha256:53234a275fac57eb5ceb155e3b7a7aa1ffb3a402518f3c84fc2862f0e9bd5c24" @@ -7012,6 +7022,11 @@ seal @jpegxl-rs.observation.patches-k3/1 { hash "sha256:bca247170ce59df488ca3e70fa061470da5f680454af775bd9bdbb99364ff5f7" } +seal @jpegxl-rs.observation.pgo-gain-remeasured-2026-08-26/1 { + state verified + hash "sha256:a52cc8346a8a844e0a336c7d0502b6d08803fa7c8b89d2e6e78f2fdadfb9d467" +} + seal @jpegxl-rs.observation.phase0-diag-12mp-2026-08-06/1 { state verified hash "sha256:0ad130b5940213668dae12b110ad8430b528f5cec580b2f37a352916e0b8209e" diff --git a/.akr/records/jpegxl-rs/evidence.akr b/.akr/records/jpegxl-rs/evidence.akr index 777cc6f9..1ed843bb 100644 --- a/.akr/records/jpegxl-rs/evidence.akr +++ b/.akr/records/jpegxl-rs/evidence.akr @@ -1,6 +1,22 @@ akr 0.1 project jpegxl-rs +record jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26/1 : evidence { + title "jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26" + state verified + result pass + method command + observed_at git:d2a54b5486b90fd4808e9bb77c67eea6bbd9f010 + command "cargo test -p jpxl-perceptual; cmp on full A/B encodes vs HEAD-baseline build" + summary """ + accumulate_band's avx2+fma clone is byte-identical to the scalar + walk: the lane test covers remainder lengths 0,1,7,8,9,64,250, + and full quality-path encodes of the canonical images compare + equal with cmp against a pre-change baseline binary. Wall-clock + on the 12 MP quality path improved about 5-8% at 4 threads. + """ +} + record jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1 : evidence { title "One documented command compares JPXL and native cjxl/djxl on the same inputs with recorded provenance" state verified @@ -5072,6 +5088,27 @@ record jpegxl-rs.evidence.phase9-quant-scheduling-speed-2026-08-15/1 : evidence """ } +record jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26/1 : evidence { + title "jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26" + state verified + result pass + method command + observed_at git:05733e9b32cec8e460e552fea5f01c20c87ca941 + command "cargo test --workspace (jpxl facade + jpxl-encode suites)" + summary """ + The metadata seam is verified end to end at this commit: an + appended Exif box round-trips through the decoder's box walk + with the codestream untouched; with_exif rejects payloads + without a TIFF byte-order header; all four ColourSpace values + round-trip through the decoder's ColourEncoding parse; a + greyscale image rejects non-sRGB signalling; lossy VarDCT + returns a typed Unsupported for non-sRGB input. Cross-repo, a + real 25 MB Samsung DNG's 574-byte TIFF blob survived + raw-autotune develop, JPXL encode, openarc archive and extract + byte-identically (recorded on the openarc ledger). + """ +} + record jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1 : evidence { title "Fused-render 12 MP memory: peak 2,008,408 KiB under the 2 GiB ceiling" state verified diff --git a/.akr/records/jpegxl-rs/observations.akr b/.akr/records/jpegxl-rs/observations.akr index a7a7d944..a2a7b1f3 100644 --- a/.akr/records/jpegxl-rs/observations.akr +++ b/.akr/records/jpegxl-rs/observations.akr @@ -1091,6 +1091,22 @@ record jpegxl-rs.observation.patches-k3/1 : observation { } } +record jpegxl-rs.observation.pgo-gain-remeasured-2026-08-26/1 : observation { + title "PGO gain re-measured at 2-8%; Phase 8.5 magnitudes are historical" + state verified + statement """ + Re-measured at this commit, the combined release-final (fat LTO) + plus PGO cycle gains only 2-8% over the thin-LTO release profile on + the canonical training and unseen images. The Phase 8.5 PGO + magnitudes recorded around 2026-08-14 (36-53%) predate the + Opt-F..P rounds and the 2026-08-25 fused probe-render work and no + longer describe HEAD; treat them as historical, and re-verify + before citing PGO as a major lever. + """ + observed_at git:b1f7196c325cce789620b87a0907bf594c82a73b + method command +} + record jpegxl-rs.observation.phase0-diag-12mp-2026-08-06/1 : observation { title "Phase-0 measured multiplicity at 4000x3000 (threads 1 vs auto)" state verified diff --git a/docs/generated/ACTIVE-WORK.md b/docs/generated/ACTIVE-WORK.md index f8962dee..2d7f239c 100644 --- a/docs/generated/ACTIVE-WORK.md +++ b/docs/generated/ACTIVE-WORK.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> diff --git a/docs/generated/CURRENT-STATE.md b/docs/generated/CURRENT-STATE.md index 74ef6709..3d12e9a0 100644 --- a/docs/generated/CURRENT-STATE.md +++ b/docs/generated/CURRENT-STATE.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> @@ -693,6 +693,18 @@ What the corpus patch (K.3) streams contain, and how the patches path was proved > **Stale** — `watches "JPXL/crates/jpxl-decode/**"` was matched by `b12f4fca`, which touched `JPXL/crates/jpxl-decode/src/frame/upsampling.rs`. See [REVIEW-REQUIRED.md](REVIEW-REQUIRED.md#patches-k3-corpus-content-and-proof-path). +### PGO gain re-measured at 2-8%; Phase 8.5 magnitudes are historical + +`verified` · `@jpegxl-rs.observation.pgo-gain-remeasured-2026-08-26/1` + +Re-measured at this commit, the combined release-final (fat LTO) +plus PGO cycle gains only 2-8% over the thin-LTO release profile on +the canonical training and unseen images. The Phase 8.5 PGO +magnitudes recorded around 2026-08-14 (36-53%) predate the +Opt-F..P rounds and the 2026-08-25 fused probe-render work and no +longer describe HEAD; treat them as historical, and re-verify +before citing PGO as a major lever. + ### Phase-0 measured multiplicity at 4000x3000 (threads 1 vs auto) `verified` · `@jpegxl-rs.observation.phase0-diag-12mp-2026-08-06/1` @@ -1241,6 +1253,16 @@ Phase 8.6 rejects raw coefficient-residual allocation as a sufficient perceptual ## Evidence +### jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26 + +`verified` · `@jpegxl-rs.evidence.avx2-pooling-bit-identity-2026-08-26/1` + +accumulate_band's avx2+fma clone is byte-identical to the scalar +walk: the lane test covers remainder lengths 0,1,7,8,9,64,250, +and full quality-path encodes of the canonical images compare +equal with cmp against a pre-change baseline binary. Wall-clock +on the 12 MP quality path improved about 5-8% at 4 threads. + ### One documented command compares JPXL and native cjxl/djxl on the same inputs with recorded provenance `verified` · `@jpegxl-rs.evidence.bench-vs-libjxl-reproducible-2026-08-25/1` @@ -4923,6 +4945,21 @@ On the pinned unseen 12 MP photo, finer deterministic quantization scheduling re - `completed` `@jpegxl-rs.work.arch-phase9-quant-scheduling/1` — check `speed` +### jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26 + +`verified` · `@jpegxl-rs.evidence.pipeline-metadata-seam-2026-08-26/1` + +The metadata seam is verified end to end at this commit: an +appended Exif box round-trips through the decoder's box walk +with the codestream untouched; with_exif rejects payloads +without a TIFF byte-order header; all four ColourSpace values +round-trip through the decoder's ColourEncoding parse; a +greyscale image rejects non-sRGB signalling; lossy VarDCT +returns a typed Unsupported for non-sRGB input. Cross-repo, a +real 25 MB Samsung DNG's 574-byte TIFF blob survived +raw-autotune develop, JPXL encode, openarc archive and extract +byte-identically (recorded on the openarc ledger). + ### Fused-render 12 MP memory: peak 2,008,408 KiB under the 2 GiB ceiling `verified` · `@jpegxl-rs.evidence.pqc-fused-render-memory-12mp-2026-08-25/1` diff --git a/docs/generated/DECISION-HISTORY.md b/docs/generated/DECISION-HISTORY.md index 1c7f0d8d..d2244d76 100644 --- a/docs/generated/DECISION-HISTORY.md +++ b/docs/generated/DECISION-HISTORY.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> diff --git a/docs/generated/OPEN-QUESTIONS.md b/docs/generated/OPEN-QUESTIONS.md index b1319963..6a00ff60 100644 --- a/docs/generated/OPEN-QUESTIONS.md +++ b/docs/generated/OPEN-QUESTIONS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> diff --git a/docs/generated/PAPERCUTS.md b/docs/generated/PAPERCUTS.md index 128efb5d..7fcc8c7f 100644 --- a/docs/generated/PAPERCUTS.md +++ b/docs/generated/PAPERCUTS.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> diff --git a/docs/generated/REVIEW-REQUIRED.md b/docs/generated/REVIEW-REQUIRED.md index 740bbe7e..73ce5e72 100644 --- a/docs/generated/REVIEW-REQUIRED.md +++ b/docs/generated/REVIEW-REQUIRED.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> diff --git a/docs/generated/ROADMAP.md b/docs/generated/ROADMAP.md index 4107a8df..68852d41 100644 --- a/docs/generated/ROADMAP.md +++ b/docs/generated/ROADMAP.md @@ -1,5 +1,5 @@ <!-- GENERATED BY AKR — DO NOT EDIT - source-graph: sha256:e56b9a85583615d94e1fab8b121fb543a4e24563fe57c169a07ccbb4dcc093fd + source-graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b tool: akr 0.3.3 --> From e895c328cddc4ddd139e25cf85f4f3402257ab12 Mon Sep 17 00:00:00 2001 From: GitHub Uploader <user@example.com> Date: Fri, 28 Aug 2026 11:48:13 +0800 Subject: [PATCH 27/27] perf: Speed the VarDCT rate-search quantizer: Fast reserves only DCT8... Authoritative plan for jpegxl-rs.track.encoder-optimization: execute Opt-F through Opt-P in after-order, supported by the static advisor assessment and the optimization decisions and gates. AKR-Change: chg-d328f28ddbc84320 AKR-Work: jpegxl-rs.work.encoder-optimization-plan AKR-Graph: sha256:2e53454b4abc5a2e7803995f3fb00745b536d02f33e33c9607223cdf0dd3935b AKR-Tree: d7a6de0a8823daa7221f481f7801d4ab8b9bf0ac --- JPXL/crates/jpxl-encode-policy/src/lib.rs | 236 +++++++++++------- .../crates/jpxl-encode-policy/src/quantize.rs | 78 ++++-- JPXL/crates/jpxl-encode-policy/src/rate.rs | 2 +- .../jpxl-encode-policy/tests/rate_loop.rs | 7 +- 4 files changed, 220 insertions(+), 103 deletions(-) diff --git a/JPXL/crates/jpxl-encode-policy/src/lib.rs b/JPXL/crates/jpxl-encode-policy/src/lib.rs index 9e979198..fcc82c4b 100644 --- a/JPXL/crates/jpxl-encode-policy/src/lib.rs +++ b/JPXL/crates/jpxl-encode-policy/src/lib.rs @@ -619,9 +619,19 @@ fn build_pixel_plan( } else { request.quantizer_choice }; - let fast_fixed_cover = - request.rate_preset == RateSearchPreset::Fast && entropy_search.uses_fast_entropy(); - let quantizer_transforms = if fast_fixed_cover { + // Fast navigation is deliberately allowed a cheaper structural policy. + // Fixed 8x8 blocks avoid the hierarchical cover's transform-bank scoring + // throughout Fast navigation and finalist planning. The Quality request + // keeps its configured mode. This is a preset-only trade: no Quality/Full + // plan can enter this arm because those requests do not carry + // `RateSearchPreset::Fast` here. + let cover_mode = + if request.rate_preset == RateSearchPreset::Fast && entropy_search.uses_fast_entropy() { + CoverMode::FixedDct8x8 + } else { + request.budget.cover_mode + }; + let quantizer_transforms = if cover_mode == CoverMode::FixedDct8x8 { &FAST_TRANSFORMS[..] } else { &SQUARE_TRANSFORMS[..] @@ -641,20 +651,12 @@ fn build_pixel_plan( .with_dead_zone_scale(request.dead_zone_scale) .with_zero_token_bits(request.zero_token_bits) .with_rate_model(request.cover_rate_model); - cache.prepare(&geometry)?; - - // Fast navigation is deliberately allowed a cheaper structural policy. - // Fixed 8x8 blocks avoid the hierarchical cover's transform-bank scoring - // throughout Fast navigation and finalist planning. The Quality request - // keeps its configured mode. This is a preset-only trade: no Quality/Full - // plan can enter this arm because those requests do not carry - // `RateSearchPreset::Fast` here. - let cover_mode = - if request.rate_preset == RateSearchPreset::Fast && entropy_search.uses_fast_entropy() { - CoverMode::FixedDct8x8 - } else { - request.budget.cover_mode - }; + // Reserve only the square families this search will score, on the request + // thread, before cover fans out. Fast's fixed DCT8x8 cover never touches + // DCT16/DCT32, and pre-creating those banks reserved two empty full-grid + // coefficient arenas per LF group (about two thirds of the forward-cache + // commit on the production Fast path). + cache.prepare_families(&geometry, quantizer_transforms)?; // The cover is selected before chroma-from-luma is estimated: the estimate // regresses over the coefficients of the *selected* transforms, so the @@ -2413,19 +2415,30 @@ impl CandidateGroupBank { fn new( rect: jpxl_encode::vardct::Rect, blocks: jpxl_encode::vardct::BlockGrid, + families: &[TransformType], ) -> Result<Self> { - // Reserve all large coefficient arenas on the request thread before - // planning fans out. That keeps their allocator ownership stable - // across warm-up and timed rate probes instead of stranding an arena - // in whichever worker happened to encounter a transform family first. + // Reserve the families this search will score on the request thread + // before planning fans out. That keeps their allocator ownership + // stable across warm-up and timed rate probes instead of stranding an + // arena in whichever worker first encountered a transform. Families + // that this cover never scores stay `None`; `get_or_insert` can still + // create one later if a rescue rebuilds structure with a wider set. + let mut banks = [None, None, None]; + for &transform in families { + let Some(index) = Self::family(transform) else { + continue; + }; + let Some(slot) = banks.get_mut(index) else { + continue; + }; + if slot.is_none() { + *slot = Some(DenseForwardBank::new(blocks, transform)?); + } + } Ok(Self { rect, blocks, - banks: [ - Some(DenseForwardBank::new(blocks, TransformType::Dct8x8)?), - Some(DenseForwardBank::new(blocks, TransformType::Dct16x16)?), - Some(DenseForwardBank::new(blocks, TransformType::Dct32x32)?), - ], + banks, cover_complete: false, complete_hits: std::sync::atomic::AtomicU64::new(0), }) @@ -2585,6 +2598,14 @@ impl CandidateForwardCache { } fn prepare(&mut self, geometry: &VardctGeometry) -> Result<()> { + self.prepare_families(geometry, &SQUARE_TRANSFORMS) + } + + fn prepare_families( + &mut self, + geometry: &VardctGeometry, + families: &[TransformType], + ) -> Result<()> { if !self.groups.is_empty() { if self.groups.len() == usize::try_from(geometry.num_lf_groups()).unwrap_or(usize::MAX) { @@ -2606,7 +2627,7 @@ impl CandidateForwardCache { what: "an LF group outside the frame's block grid", })?; groups.push(std::sync::RwLock::new(CandidateGroupBank::new( - rect, blocks, + rect, blocks, families, )?)); } self.groups = groups; @@ -3121,6 +3142,34 @@ fn set_lf( } } +/// Merges a chunk's group-grid LF planes into the group reduction. +/// +/// Chunks of one LF group cover disjoint varblocks, and unwritten cells stay +/// zero, so adding plane-wise is the same as scattering each patch. The add +/// is the vectorisable merge; overlapping non-zero writes would wrap in +/// debug and are a cover bug. +fn add_lf_planes( + dest: &mut [Vec<i32>; NUM_CHANNELS], + src: &[Vec<i32>; NUM_CHANNELS], +) -> Result<()> { + for channel in 0..NUM_CHANNELS { + let (Some(d), Some(s)) = (dest.get_mut(channel), src.get(channel)) else { + return Err(PolicyError::Unsupported { + what: "a missing LF plane while merging a quantization chunk", + }); + }; + if d.len() != s.len() { + return Err(PolicyError::Unsupported { + what: "a quantization chunk whose LF plane size changed", + }); + } + for (slot, &value) in d.iter_mut().zip(s.iter()) { + *slot += value; + } + } + Ok(()) +} + /// Phase 7.1's estimate of what one interior-zero coefficient token costs. /// /// Token *counts* are exact — the walk emits one per order position up to the @@ -3984,11 +4033,17 @@ fn coefficient_arena_capacity(varblocks: &[VarblockDecision]) -> usize { /// mutating coefficients that an earlier plan can still observe. struct QuantizationWorkspace { arenas: Vec<Arc<[i32]>>, + /// Per-chunk LF planes in group-grid layout, returned after each probe's + /// reduction so the next sequential quantizer does not re-reserve them. + chunk_lf: Vec<[Vec<i32>; NUM_CHANNELS]>, } impl QuantizationWorkspace { fn new() -> Self { - Self { arenas: Vec::new() } + Self { + arenas: Vec::new(), + chunk_lf: Vec::new(), + } } fn take_arena(&mut self, slot: usize, capacity: usize) -> Arc<[i32]> { @@ -4011,6 +4066,33 @@ impl QuantizationWorkspace { *destination = arena; } } + + fn take_chunk_lf(&mut self, slot: usize, capacity: usize) -> [Vec<i32>; NUM_CHANNELS] { + if self.chunk_lf.len() <= slot { + self.chunk_lf.resize_with(slot.saturating_add(1), || { + core::array::from_fn(|_| Vec::new()) + }); + } + let mut planes = core::array::from_fn(|_| Vec::new()); + if let Some(stored) = self.chunk_lf.get_mut(slot) { + std::mem::swap(&mut planes, stored); + } + for plane in &mut planes { + if plane.len() != capacity { + plane.clear(); + plane.resize(capacity, 0); + } else { + plane.fill(0); + } + } + planes + } + + fn put_chunk_lf(&mut self, slot: usize, planes: [Vec<i32>; NUM_CHANNELS]) { + if let Some(stored) = self.chunk_lf.get_mut(slot) { + *stored = planes; + } + } } /// Large quantization buffers reserved by the request thread before fanout. @@ -4104,20 +4186,22 @@ fn quantization_chunks(groups: &[PlannedGroup], workers: usize) -> Vec<QuantChun /// request thread before the executor starts it. struct QuantChunkWorkspace { lf_values: [Vec<i32>; NUM_CHANNELS], + lf_width: u32, arena: std::sync::Arc<[i32]>, starts: Vec<[usize; NUM_CHANNELS]>, coefficients: Vec<VarblockCoefficients>, } impl QuantChunkWorkspace { - fn new(varblocks: &[VarblockDecision], arena: Arc<[i32]>) -> Self { - let mut lf_cap = 0usize; - for vb in varblocks { - let n = vb.transform.block_dims().0; - lf_cap = lf_cap.saturating_add(n.saturating_mul(n)); - } + fn new( + varblocks: &[VarblockDecision], + arena: Arc<[i32]>, + lf_values: [Vec<i32>; NUM_CHANNELS], + lf_width: u32, + ) -> Self { Self { - lf_values: core::array::from_fn(|_| Vec::with_capacity(lf_cap)), + lf_values, + lf_width, arena, starts: Vec::with_capacity(varblocks.len()), coefficients: Vec::with_capacity(varblocks.len()), @@ -4154,12 +4238,26 @@ fn quantize_groups_parallel( .iter() .enumerate() .map(|(index, chunk)| { - let varblocks = groups + let (blocks, varblocks) = groups .get(chunk.group) - .and_then(|(_, _, _, varblocks)| varblocks.get(chunk.start..chunk.end)) - .unwrap_or(&[]); + .map(|(_, blocks, _, varblocks)| (*blocks, varblocks.get(chunk.start..chunk.end))) + .unwrap_or(( + jpxl_encode::vardct::BlockGrid { + width: 0, + height: 0, + }, + None, + )); + let varblocks = varblocks.unwrap_or(&[]); let arena = quant_workspace.take_arena(index, coefficient_arena_capacity(varblocks)); - std::sync::Mutex::new(Some(QuantChunkWorkspace::new(varblocks, arena))) + let lf_cells = usize::try_from(blocks.area()).unwrap_or(0); + let lf_values = quant_workspace.take_chunk_lf(index, lf_cells); + std::sync::Mutex::new(Some(QuantChunkWorkspace::new( + varblocks, + arena, + lf_values, + blocks.width, + ))) }) .collect(); let quantize_one = |index: usize| { @@ -4227,7 +4325,7 @@ fn quantize_groups_parallel( .collect(); for (index, (chunk, quantized)) in chunks.iter().copied().zip(quantized_chunks).enumerate() { quant_workspace.put_arena(index, quantized.arena); - let (_, blocks, _, group_varblocks) = + let (_, _, _, group_varblocks) = groups.get(chunk.group).ok_or(PolicyError::Unsupported { what: "a missing planned LF group during chunk reduction", })?; @@ -4247,46 +4345,9 @@ fn quantize_groups_parallel( .ok_or(PolicyError::Unsupported { what: "a missing LF-group quantization reduction", })?; - let mut lf_cursor = 0usize; - for vb in varblocks { - let n = vb.transform.block_dims().0; - let cells = n.saturating_mul(n); - let lf_end = lf_cursor - .checked_add(cells) - .ok_or(PolicyError::Unsupported { - what: "a quantized LF patch range overflow", - })?; - for channel in 0..NUM_CHANNELS { - let values = quantized - .lf_values - .get(channel) - .and_then(|plane| plane.get(lf_cursor..lf_end)) - .ok_or(PolicyError::Unsupported { - what: "a quantized LF patch shorter than its varblock", - })?; - for (index, value) in values.iter().copied().enumerate() { - set_lf( - &mut builder.lf_planes, - channel, - vb.origin.bx() + u32::try_from(index % n).unwrap_or(0), - vb.origin.by() + u32::try_from(index / n).unwrap_or(0), - blocks.width, - value, - ); - } - } - lf_cursor = lf_end; - } - if quantized - .lf_values - .iter() - .any(|plane| plane.len() != lf_cursor) - { - return Err(PolicyError::Unsupported { - what: "a quantized chunk with trailing LF patch values", - }); - } + add_lf_planes(&mut builder.lf_planes, &quantized.lf_values)?; builder.coefficients.extend(quantized.coefficients); + quant_workspace.put_chunk_lf(index, quantized.lf_values); } groups @@ -4343,7 +4404,9 @@ fn quantize_chunk( })?; let (bx, by) = (vb.origin.bx(), vb.origin.by()); let factors = varblock_cfl(correlation, cfl, bx, by); - let lf_values = &mut workspace.lf_values; + let lf_planes = &mut workspace.lf_values; + let lf_width = workspace.lf_width; + let n = transform.block_dims().0; quantize_square_varblock( &fwd.coeffs, transform, @@ -4352,10 +4415,15 @@ fn quantize_chunk( factors, &mut tscratch, &mut qscratch, - |channel, _index, value| { - if let Some(plane) = lf_values.get_mut(channel) { - plane.push(value); - } + |channel, idx, value| { + set_lf( + lf_planes, + channel, + bx + u32::try_from(idx % n).unwrap_or(0), + by + u32::try_from(idx / n).unwrap_or(0), + lf_width, + value, + ); }, slot, hf_quants.truncate_trailing, diff --git a/JPXL/crates/jpxl-encode-policy/src/quantize.rs b/JPXL/crates/jpxl-encode-policy/src/quantize.rs index 63382226..22565ba7 100644 --- a/JPXL/crates/jpxl-encode-policy/src/quantize.rs +++ b/JPXL/crates/jpxl-encode-policy/src/quantize.rs @@ -613,11 +613,12 @@ impl HfQuantizer { /// Phase-2: quantize a contiguous coefficient lane (one channel of one /// varblock) into `out`, skipping LLF cells when `skip_llf` is set. - /// Each row's HF span goes through [`Self::choose_run`] (Phase 36; before - /// that, [`Self::choose_lane4`] batches with scalar tails), so batches - /// never cross the top-left LLF boundary. /// - /// Same integer rules as [`Self::choose`], applied cell-by-cell. + /// The top-left `n_blocks × n_blocks` LLF is still handled per row so a + /// run never crosses that boundary. Every remaining row is one contiguous + /// HF rectangle in coefficient order, so it is one [`Self::choose_run`] + /// (Phase 36's AVX2 8-wide kernel, padded tail) instead of one call per + /// row. Same integer rules as [`Self::choose`], applied cell-by-cell. /// /// # Errors /// @@ -661,14 +662,16 @@ impl HfQuantizer { what: "a zero-width HF coefficient lane", }); } - for row_start in (0..cells).step_by(side) { - let row = row_start / side; + // Rows that contain the LLF prefix. Remaining rows are a contiguous + // HF rectangle (`first_cell = n_blocks * side`) and share one run. + let llf_rows = if skip_llf { n_blocks } else { 0 }; + for row in 0..llf_rows { + let row_start = row.saturating_mul(side); + if row_start >= cells { + break; + } let row_end = row_start.saturating_add(side).min(cells); - let first_hf = if skip_llf && row < n_blocks { - row_start.saturating_add(n_blocks).min(row_end) - } else { - row_start - }; + let first_hf = row_start.saturating_add(n_blocks).min(row_end); if let Some(llf) = out.get_mut(row_start..first_hf) { llf.fill(0); } @@ -679,9 +682,6 @@ impl HfQuantizer { *slot = self.reconstruct(0, channel, row_start + offset); } } - // Phase 36: the whole HF span of the row goes through the run - // kernel (vector chunks plus a padded final chunk), which is - // cell-for-cell identical to the scalar `choose` loop it replaces. let (Some(targets), Some(slots)) = ( coeffs.get(first_hf..row_end), out.get_mut(first_hf..row_end), @@ -693,6 +693,17 @@ impl HfQuantizer { .and_then(|r| r.get_mut(first_hf..row_end)); self.choose_run(channel, first_hf, targets, slots, recon_row)?; } + let bulk_start = llf_rows.saturating_mul(side); + if bulk_start < cells { + let (Some(targets), Some(slots)) = ( + coeffs.get(bulk_start..cells), + out.get_mut(bulk_start..cells), + ) else { + return Ok(()); + }; + let recon_bulk = recon_out.and_then(|r| r.get_mut(bulk_start..cells)); + self.choose_run(channel, bulk_start, targets, slots, recon_bulk)?; + } Ok(()) } @@ -1852,10 +1863,45 @@ mod tests { }) .collect::<Result<_>>() .expect("scalar reference"); + let expected_recon: Vec<u32> = expected + .iter() + .enumerate() + .map(|(cell, &qi)| q.reconstruct(qi, channel, cell).to_bits()) + .collect(); let mut actual = vec![i32::MIN; cells]; - q.quantize_lane(channel, &coeffs, &mut actual, side, n, true) - .expect("lane quantization"); + let mut recon = vec![f32::NAN; cells]; + q.quantize_lane_with_recon( + channel, + &coeffs, + &mut actual, + Some(&mut recon), + side, + n, + true, + ) + .expect("lane quantization"); assert_eq!(actual, expected, "{transform:?} channel {channel}"); + let actual_recon: Vec<u32> = recon.iter().map(|v| v.to_bits()).collect(); + assert_eq!( + actual_recon, expected_recon, + "{transform:?} channel {channel} recon" + ); + + // skip_llf = false: the whole lane is one HF run, including + // the top-left cells that skip_llf would have forced to zero. + let expected_full: Vec<i32> = coeffs + .iter() + .enumerate() + .map(|(cell, &target)| q.choose(target, channel, cell)) + .collect::<Result<_>>() + .expect("full-lane scalar reference"); + let mut actual_full = vec![i32::MIN; cells]; + q.quantize_lane(channel, &coeffs, &mut actual_full, side, n, false) + .expect("full-lane quantization"); + assert_eq!( + actual_full, expected_full, + "{transform:?} channel {channel} skip_llf=false" + ); } } } diff --git a/JPXL/crates/jpxl-encode-policy/src/rate.rs b/JPXL/crates/jpxl-encode-policy/src/rate.rs index 0e61b58e..a1fe521b 100644 --- a/JPXL/crates/jpxl-encode-policy/src/rate.rs +++ b/JPXL/crates/jpxl-encode-policy/src/rate.rs @@ -418,7 +418,7 @@ pub struct RateProbeStats { pub candidate_cache_entries: u64, /// Retained f32 coefficient payload, excluding collection metadata. pub candidate_payload_bytes: u64, - /// Dense coefficient-arena allocations (one per populated transform bank). + /// Dense coefficient-arena allocations (one per reserved transform bank). pub candidate_allocations: u64, /// Cover/CfL builds on the bounded anchored path. pub structural_builds: u32, diff --git a/JPXL/crates/jpxl-encode-policy/tests/rate_loop.rs b/JPXL/crates/jpxl-encode-policy/tests/rate_loop.rs index 7d56cbb6..91901e33 100644 --- a/JPXL/crates/jpxl-encode-policy/tests/rate_loop.rs +++ b/JPXL/crates/jpxl-encode-policy/tests/rate_loop.rs @@ -158,8 +158,10 @@ fn target_rate_is_byte_identical_across_executor_widths() { serial.stats.candidate_cache_entries, parallel.stats.candidate_cache_entries ); - assert_eq!(serial.stats.candidate_allocations, 3); - assert_eq!(parallel.stats.candidate_allocations, 3); + // Fast's fixed DCT8x8 cover reserves one bank per LF group, not the + // three-family hierarchical set. + assert_eq!(serial.stats.candidate_allocations, 1); + assert_eq!(parallel.stats.candidate_allocations, 1); #[cfg(feature = "anchor-sketch")] for outcome in [&serial, &parallel] { assert_eq!(outcome.status, RateStatus::RescuedFreshStructure); @@ -206,6 +208,7 @@ fn balanced_anchor_is_exact_and_decodable() { assert!(outcome.stats.structural_builds <= 2); assert!(outcome.stats.full_prices <= 4); assert!(outcome.stats.exact_candidates <= 6); + assert_eq!(outcome.stats.candidate_allocations, 3); } #[cfg(feature = "anchor-sketch")]