Skip to content

calibrate: refuse a fit that is not to a chart, and a chart off the frame - #43

Merged
widgetii merged 1 commit into
mainfrom
calibrate-refuse-nonchart
Sep 28, 2026
Merged

widgetii merged 1 commit into
mainfrom
calibrate-refuse-nonchart

Conversation

@widgetii

Copy link
Copy Markdown
Member

Refs #42. This fixes the two defects there. The third point, that the 30 s hold asks the user to confirm a picture they cannot see because the panel shows the RAW frame and not the live stream, is left open on the issue.

What was wrong

Calibrate solved and offered Apply to the camera for any 24 samples, chart or not. On a gk7205v200's plain, red-lit wall with no chart in view:

  • detectChart() reported 19 cells, with two corners outside the 1920×1080 frame.
  • Every quad measured on that wall solved at a mean ΔE2000 of 19–26, with coefficients up to ±20. The detected quad, the default middle third and a dragged area all did. Rows sum to exactly 1 by construction, so majestic's row-sum check accepted them.
  • Applied through POST /api/v1/config, the matrix turned the live picture into amplified noise. That matches the field report in Gk7201v200 support firmware#2234: "mosaic black and white tiled image".

The change

  • detectChart() returns null when any corner lies outside the frame.
  • solveFromPatches() throws when the mean ΔE2000 is over 12. The editor already shows solver errors on the panel, so the user sees the refusal and gets no Apply button.
frame mean ΔE2000
tests/chart-on-wood.dng, the real chart 4.77
a make-chart.mjs chart at the default corners 7.47
chart-on-wood.dng's wood, three quads 19.3 / 25.3 / 25.9
the camera's chartless wall, four quads 21.2 – 24.7

A coefficient-size bound was tried as well and dropped. The ΔE limit caught every case on its own, so there was nothing to justify the second number. The camera-side bound belongs in majestic.

Tests

  • smoke.mjs: a chart hanging off the bottom edge (18 cells, corners extrapolated to y=521 on a 480 frame) is not reported. The wood beside the real chart is refused, and the real chart still calibrates.
  • ui-check: the calibrate checks used to measure fixture.dng's middle third, which has no chart, and depended on getting a matrix back. They now measure a chart drawn under the default corners, carrying fixture.dng's ColorMatrix1 so the light is still named. make-chart.mjs and the check server pass the matrices through for that. A new check measures fixture.dng and expects the refusal on the panel, with no Apply button.

Every new check was seen failing with the fix reverted: 2 in smoke and 2 in ui-check. The full loop passes: node --check, build.sh, the dist/ diff, smoke.mjs and ui-check.mjs. dist/engine.wasm is unchanged, since engine.c is not touched.

…rame

Calibrate solved and offered "Apply to the camera" for any 24 samples,
chart or not. On a camera's plain red wall with no chart in view, the
detector reported 19 cells with two corners outside the 1920x1080 frame
(at 2170,1986 and 634,1095 of a lattice that starts at 946,230). Every
quad measured on that wall -- the detected one, the default middle third,
a dragged one -- solved at 19 to 26 mean dE2000, with coefficients up to
+-20 and rows summing to exactly 1, so the camera accepted it too. Applied
to a gk7205v200, it turned the live picture into amplified noise; a user
reported the same as "mosaic black and white tiled image"
(OpenIPC/firmware#2234).

- detectChart() returns null when any corner lies outside the frame.
  The cells past the edge have nothing under them to measure, and it is
  what the detector made of that wall.
- solveFromPatches() refuses a mean dE2000 over 12. The real chart in
  tests/chart-on-wood.dng fits at 4.77 and a make-chart.mjs chart at 7.47
  (drawn from 8-bit sRGB, scored against Lab); the wall and that frame's
  own wood fit at 19 to 26.

The UI checks measured fixture.dng's middle third, which has no chart in
it, and relied on getting a matrix back. They now measure a chart drawn
under the default corners, carrying fixture.dng's ColorMatrix1 so the
light is still named; and a new check measures fixture.dng and expects
the refusal on the panel with nothing to apply. make-chart.mjs and the
check server pass camera matrices through for that.

Every new check was seen failing with the fix reverted.

Refs #42
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Refuse off-frame charts and poor calibration fits

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Reject detected charts whose corners extend outside the frame.
• Refuse calibration fits above mean ΔE2000 12, preventing chartless samples from producing an
 applicable matrix.
• Test chart detection, solver refusal, and the Calibrate panel with representative frames.
Diagram

graph TD
  A["RAW frame"] --> B["Chart detection"] --> C{"Corners in frame?"}
  C -- Yes --> D["Patch sampling"] --> E["Calibration fit"] --> F{"Mean ΔE ≤ 12?"}
  A -- "Manual corners" --> D
  C -- No --> G["Refuse calibration"]
  F -- No --> G
  F -- Yes --> H["Apply offered"]
Loading
High-Level Assessment

Keep both guards: frame bounds reject unusable automatic detections, while fit quality also protects manually selected regions. A coefficient-size bound was considered but adds another threshold without covering a demonstrated gap.

Files changed (8) +133 / -15

Bug fix (4) +54 / -8
calibrate.jsDistribute the calibration fit-quality guard +17/-0

Distribute the calibration fit-quality guard

• Mirrors the source solver's mean ΔE2000 limit of 12 and its error for patches that do not fit a colour chart.

dist/calibrate.js

engine.jsDistribute the off-frame chart guard +10/-4

Distribute the off-frame chart guard

• Mirrors the source detector wrapper's rejection of chart corners outside frame bounds.

dist/engine.js

calibrate.jsRefuse poor chart fits before returning a matrix +17/-0

Refuse poor chart fits before returning a matrix

• Throws an explanatory error when the solved mean ΔE2000 exceeds 12, so chartless measurements cannot yield an applicable calibration.

src/calibrate.js

engine.jsDiscard detected charts extending beyond the frame +10/-4

Discard detected charts extending beyond the frame

• Returns null if any detected chart corner lies outside the image, rather than offering an extrapolated lattice for measurement.

src/engine.js

Tests (4) +79 / -7
ui-check.htmlCheck panel refusal and calibrate against an actual chart +34/-4

Check panel refusal and calibrate against an actual chart

• Adds a no-chart measurement check that expects a visible refusal and no Apply button. Existing successful-calibration checks now use a generated chart carrying camera colour-matrix metadata.

tests/ui-check.html

make-chart.mjsAllow generated charts to carry colour matrices +2/-2

Allow generated charts to carry colour matrices

• Passes optional camera colour matrices into generated DNG frames so UI checks can retain light estimation while measuring a chart.

tools/make-chart.mjs

smoke.mjsCover off-frame detection and off-chart solver refusal +39/-0

Cover off-frame detection and off-chart solver refusal

• Checks that a partially off-frame chart is not detected and wood beside a real chart is refused. It also confirms the real chart still calibrates.

tools/smoke.mjs

ui-check.mjsServe generated chart fixtures with camera matrices +4/-1

Serve generated chart fixtures with camera matrices

• Accepts optional colour matrices on the generated-chart endpoint and passes them to the DNG fixture generator.

tools/ui-check.mjs

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Incomplete charts are still reported 📎 Requirement gap ≡ Correctness
Description
detectChart() returns { corners, cells: found } after checking corner bounds, without requiring
found to equal 24. The underlying detector accepts as few as 18 distinct cells, so an incomplete
detection with in-frame corners reaches the editor as a found chart, where it is offered for
measurement with only a warning.
Code

src/engine.js[281]

+		return { corners, cells: found };
Evidence
The checklist requires 24 distinct patch cells before a detection is reported. The detector counts
unique cell coordinates but accepts counts of 18 or more; the changed return path applies only a
corner-bounds check, and the editor treats a shorter result as a found chart.

Reject invalid or non-chart detections
src/engine.c[1195-1216]
src/engine.js[269-281]
src/editor.js[2965-2985]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new bounds check still reports detections containing fewer than 24 distinct cells as charts.
## Fix Focus Areas
- src/engine.js[269-281]
- dist/engine.js[269-281]
## Recommended Fix
Return null unless the detector reports all 24 cells, in both the source and built copy. Add a test for an in-frame detection with fewer than 24 cells.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/engine.js
// solved from it wrecked the camera's picture (OpenIPC/raw-editor#42).
if (corners.some(([cx, cy]) => !(cx >= 0 && cy >= 0 && cx <= i.width && cy <= i.height)))
return null;
return { corners, cells: found };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Incomplete charts are still reported 📎 Requirement gap ≡ Correctness

detectChart() returns { corners, cells: found } after checking corner bounds, without requiring
found to equal 24. The underlying detector accepts as few as 18 distinct cells, so an incomplete
detection with in-frame corners reaches the editor as a found chart, where it is offered for
measurement with only a warning.
Agent Prompt
## Issue description
The new bounds check still reports detections containing fewer than 24 distinct cells as charts.

## Fix Focus Areas
- src/engine.js[269-281]
- dist/engine.js[269-281]

## Recommended Fix
Return null unless the detector reports all 24 cells, in both the source and built copy. Add a test for an in-frame detection with fewer than 24 cells.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@widgetii

Copy link
Copy Markdown
Member Author

Re the review's "incomplete charts are still reported" point: I'm not taking this one. Requiring all 24 cells would reject the tree's own real-chart fixture. tests/chart-on-wood.dng is a real chart and detects 20 cells, because the dark patches merge into the grey surround. smoke.mjs accepts >= 18 for exactly that reason, and the editor deliberately warns about missing cells rather than refusing.

What keeps a false detection from reaching a camera is the fit gate added here. The patches measured under it have to fit the chart (mean ΔE2000 ≤ 12), and the chartless wall fitted at 21–25 wherever the corners landed, including the quad the detector reported. Corners off the frame are refused because those cells can't be measured at all, which a partial but in-frame chart can.

@widgetii
widgetii merged commit b61a60f into main Sep 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant