Skip to content

Add the eval page (contracts section 3), checked against OpenCV and the runner - #20

Open
06pratyush wants to merge 4 commits into
mainfrom
eval-page
Open

06pratyush wants to merge 4 commits into
mainfrom
eval-page

Conversation

@06pratyush

Copy link
Copy Markdown
Collaborator

The eval page that docs/contracts.md, section 3 asks of the app, so eval/run_detect.py (#10) can run the real detector instead of the stub.

What is in it

  • app/eval.html and app/src/eval.ts set window.solidMask:
    • decode(url): fetch, then the app's own decode() (createImageBitmap with imageOrientation: "from-image").
    • detectFaces(image, options): the app's detect.ts.
    • render(image, masks): draws the given masks (already padded) and encodes exactly as the app saves. The drawing moves out of main.ts into paint() in mask.ts, which the page and render() both call. render() rounds each mask outward to whole pixels and clips it with maskFor(mask, 0, …), as section 2 (v0.2) says. With the app's own masks, its file is byte-identical to the app's saved masked-1.jpg in all three engines.
    • info(): { ortVersion: "1.30.0", backend: "wasm", threads: 1, modelSha256 }. modelSha256 is the hash of the model as loaded: detect.ts now records it when it reads the file.
  • window.solidMask appears once the model has loaded and compiled, so the runner's wait covers it and its first image is timed like the rest.
  • A page that Vite reloads because a source file changed refuses to start. Without this, a run could go on under code that changed in the middle of it, all recorded under one git commit.
  • npm run dev serves the page at http://localhost:5173/eval.html, the address eval/README.md gives, and now fails rather than move if 5173 is taken. The page has the app's Content Security Policy.
  • The deployed build leaves the page out: Vite builds only index.html, and a test checks that no eval code is in app/dist.

Tests

tests/app/eval-page.spec.ts drives the page on the dev server, as the runner does (the image served on the page's own origin, the runner's options), in Chromium, Firefox and WebKit.

  • Golden detection. The 7 boxes scoring 0.10 or more must match OpenCV 4.13's FaceDetectorYN on the same model:

    • within 0.5 px and 0.0005 of score in Chromium;
    • within 2 px in Firefox and WebKit, since WebKit on macOS decodes JPEG with ImageIO.

    The reference comes from tests/reference/opencv_yunet.py, with OpenCV pinned, and its output is committed, so you can rerun it. Swapping the colour channels makes the test fail: the face is still found, 93 px lower, so the privacy suite wouldn't notice. Nothing guarded the equivalence with OpenCV before; the review of the eval PRs flagged this.

  • A photo stored turned, with the EXIF Orientation 8 the runner adds for Rotation 90, decodes upright with the face within 2 px.

  • render() and info(). render() covers the face and writes a file with nothing but the picture, and info() returns exactly the expected values.

  • A reloaded page refuses to start.

Playwright now starts the dev server too (port 4402), next to the static server for the production build.

Checked with the eval code

I ran eval/run_detect.py --page http://127.0.0.1:5173/eval.html and eval/score.py on a two-image stand-in manifest made from the public-domain test photo, one copy as stored and one stored turned 90°. No Open Images image was used.

  • Run line: the model's SHA-256, ORT 1.30.0, wasm and 1 thread.
  • Faces: the same face on both copies (within 0.3 px), each fully covered and found, with no false masks.
  • Timing: median 333 ms per 12 MP image, max 395 ms. The first image no longer carries the model's compile time.

For Shashwat

  • Detect reloads in the runner. run_detect.py could also stop when the page navigates mid-run (page.on('framenavigated')). The page now refuses to start after a reload, so the run fails on its next image anyway, but the runner would then say why.
  • Tiling and YOLO5Face. The runner's --tiles and --model yolo5face still stop at detectFaces, which supports only the single setting and YuNet so far. Tiling comes next, for part B's tiled setting.

/code-review and two independent reviewers looked at this before I opened it, and all their findings are fixed.

🤖 Generated with Claude Code

06pratyush and others added 3 commits October 28, 2026 16:43
app/eval.html gives eval/run_detect.py the app's own decoding, detection,
masking and encoding, as docs/contracts.md, section 3 defines them:
decode(url), detectFaces(image, options), render(image, masks) and
info(). The page has the app's Content Security Policy. npm run dev
serves it, and the deployed build leaves it out (Vite builds only
index.html).

render() encodes exactly as the app saves: the drawing moves out of
main.ts into paint() in mask.ts, which the page and render() both call,
and render() rounds the given masks outward to whole pixels and clips
them with maskFor(mask, 0, ...), as section 2 says.

tests/app/eval-page.spec.ts drives the page on the dev server as the
runner does. On the test photo the face matches OpenCV's FaceDetectorYN
on the same model within 0.5 px and 0.0005 of score, with the same 15
boxes at 0.05 or more, in Chromium, Firefox and WebKit. Swapping the
colour channels makes it fail (the face is still found, with its box
93 px lower and 2 boxes instead of 15), a regression the privacy tests
would miss. It also checks render(), info(), and that no eval code is
in app/dist.

Run against the page, Shashwat's run_detect.py and score.py give the
same face on the photo as stored and turned 90 degrees, both fully
covered and found, with no false masks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on a knife-edge

The eval-page test now also serves the photo stored turned with EXIF
Orientation 8, as run_detect.py serves turned images, and checks it
decodes upright with the face within 2 px of OpenCV's. The box count is
compared at score 0.10 (7 boxes; the lowest scores 0.1037), not at 0.05,
where one box sat 0.0003 above the cut and OpenCV's own count differs by
an edge box. The dev server's webServer allows 180 s, for a cold model
download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the review of the eval page:
- info().modelSha256 is now the SHA-256 of the model as loaded, which
  detect.ts records when it reads the file, not the expected constant.
- window.solidMask appears once the model has loaded and compiled, so
  the runner's wait covers it and its first image is timed like the rest
  (on the stand-in run: max 395 ms against a median of 333 ms).
- A page Vite reloaded because a file changed refuses to start, so a
  run fails instead of going on under code that changed mid-run.
- npm run dev keeps port 5173 (strictPort), where the eval docs say the
  page is.

The OpenCV boxes now come from tests/reference/opencv_yunet.py, which
writes opencv_yunet.txt with OpenCV pinned (4.13.0.92); the test reads
that file and compares all 7 boxes scoring 0.10 or more, not just the
count. The 0.5 px bound applies in Chromium, the runner's browser;
Firefox and WebKit get 2 px, since WebKit on macOS decodes JPEG with
ImageIO.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the conflict in docs/ai-use.md, where main added the rows for
#15, #17, #18 and #19 and this branch has its own: keeps all five,
main's rows first. The merge brings in only eval/ and docs changes;
nothing under app/ or tests/ changes.

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

This branch has not been deployed

No deployments
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