feat(classify): gate halftone-scan to a peak band (MK-7) #28

Merged
nrupard merged 3 commits from feat/classify-halftone-ceiling-MK-7 into main 2026-05-21 18:01:30 +02:00
Member

What

MK-7: gate halftone-scan to a peak BAND instead of just a floor. Adds a configurable halftone_max ceiling; the halftone score is now soft_ge(peak, halftone_min) * soft_le(peak, halftone_max) * low_sat. Scoring change only, no change to the autocorrelation detector.

Changes

  • halftone_max field in FeatureThresholds + classify.toml, same XDG override path as the rest.
  • Halftone scoring uses both floor and ceiling.
  • Synthetic halftone_dots fixture previously autocorrelated at exactly 1.0 (a digital screen, not a scan) which the ceiling correctly rejects. It now jitters a fraction of dot cells so the peak lands at ~0.80, inside the realistic 0.70-0.95 band, and still classifies as halftone-scan (conf >= 0.5).
  • New test ceiling_rejects_perfect_periodic_grid: the un-jittered grid (peak ~1.0) is kept out of halftone-scan.

Acceptance status

  • halftone_max loads from classify.toml alongside halftone_min.
  • Halftone scoring uses both floor and ceiling.
  • Synthetic classifies_halftone still passes (now with conf >= 0.5).
  • Signature-scanned PNGs stop classifying as halftone-scan — blocked on fixtures (see below).
  • Real halftone fixtures classify as halftone-scan with conf >= 0.5 — blocked on fixtures.
  • Real-halftone unit test under tests/fixtures/classify/halftone-scan/blocked on fixtures.

Blocked / needs input

The default band (0.55..0.95) is a placeholder. The observed signature-scan peak from MK-6 validation was 0.641, which is above the 0.55 floor, so the current band does not yet reject it. Excluding it needs the floor raised (~0.68), but the real-halftone low-end peak is unknown without the user's fixtures, so I have not guessed the final values.

To finish: please drop the real halftone scan(s) (newspaper/magazine) and the signature-scanned PNG into the repo. I will measure their peaks, set the band to admit real halftone and reject the signature, and add the checked-in fixture test. Tuning lands as a follow-up commit on this branch.

🤖 Generated with Claude Code

## What MK-7: gate `halftone-scan` to a peak BAND instead of just a floor. Adds a configurable `halftone_max` ceiling; the halftone score is now `soft_ge(peak, halftone_min) * soft_le(peak, halftone_max) * low_sat`. Scoring change only, no change to the autocorrelation detector. ## Changes - `halftone_max` field in `FeatureThresholds` + `classify.toml`, same XDG override path as the rest. - Halftone scoring uses both floor and ceiling. - Synthetic `halftone_dots` fixture previously autocorrelated at exactly 1.0 (a digital screen, not a scan) which the ceiling correctly rejects. It now jitters a fraction of dot cells so the peak lands at ~0.80, inside the realistic 0.70-0.95 band, and still classifies as halftone-scan (conf >= 0.5). - New test `ceiling_rejects_perfect_periodic_grid`: the un-jittered grid (peak ~1.0) is kept out of halftone-scan. ## Acceptance status - [x] `halftone_max` loads from `classify.toml` alongside `halftone_min`. - [x] Halftone scoring uses both floor and ceiling. - [x] Synthetic `classifies_halftone` still passes (now with conf >= 0.5). - [ ] Signature-scanned PNGs stop classifying as halftone-scan — **blocked on fixtures** (see below). - [ ] Real halftone fixtures classify as halftone-scan with conf >= 0.5 — **blocked on fixtures**. - [ ] Real-halftone unit test under `tests/fixtures/classify/halftone-scan/` — **blocked on fixtures**. ## Blocked / needs input The default band (0.55..0.95) is a placeholder. The observed signature-scan peak from MK-6 validation was 0.641, which is **above** the 0.55 floor, so the current band does not yet reject it. Excluding it needs the floor raised (~0.68), but the real-halftone low-end peak is unknown without the user's fixtures, so I have not guessed the final values. To finish: please drop the real halftone scan(s) (newspaper/magazine) and the signature-scanned PNG into the repo. I will measure their peaks, set the band to admit real halftone and reject the signature, and add the checked-in fixture test. Tuning lands as a follow-up commit on this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(classify): gate halftone-scan to a peak band with halftone_max ceiling (MK-7)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 17s
a1b50bc76a
Add a configurable `halftone_max` ceiling so the halftone-scan class fires only when the autocorrelation peak falls inside the [halftone_min, halftone_max] band, not merely above a floor. The floor rejects photos and gradients (peak < 0.3); the ceiling curves the score back down past true screened-print levels, so a flawless digital periodic pattern (peak ~1.0) is no longer mistaken for a scanned halftone. Scoring change only: the autocorrelation detector is untouched.

`halftone_max` loads from classify.toml alongside `halftone_min`, with the same XDG override path. Default band is 0.55..0.95, documented as a starting point pending real halftone fixtures.

The synthetic `halftone_dots` test fixture previously autocorrelated at exactly 1.0 (a digital screen, not a scan), which the ceiling correctly rejects. It now jitters a fraction of dot cells so the peak lands at ~0.80, in the realistic 0.70-0.95 band a real halftone scan produces, and still classifies as halftone-scan with confidence >= 0.5. A new test asserts the perfect (un-jittered, peak ~1.0) grid is kept out of halftone-scan by the ceiling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test(classify): add synthetic line-art rejection, raise floor to 0.70 (MK-7)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 19s
571d2e6b47
Cover the line-art / signature acceptance criterion synthetically, without needing real image fixtures. Adds a `line_hatching` generator (signature-style strokes) whose autocorrelation peak lands ~0.69, the mid-range that mis-classified as halftone during MK-6 validation (observed 0.641 for a real signature). `line_art_is_not_halftone` asserts every pitch variant stays below the floor and out of halftone-scan.

Raises `halftone_min` from 0.55 to 0.70 so the band rejects this line-art range (and the observed 0.641 signature) while still admitting the synthetic halftone (peak 0.80) as halftone-scan with confidence >= 0.5. The 0.70 floor matches the bottom of the true-screened-print range in the MK-7 data table.

Real-image tuning (a checked-in newspaper/magazine fixture) remains a follow-up; the synthetic tests now exercise the full floor+ceiling band.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(classify): widen ceiling scale, de-fragilize halftone tests (MK-7 review)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 17s
Create release / Create release from merged PR (pull_request) Has been skipped
9d4f489a8d
Address review of the halftone band:

The ceiling now uses its own `soft_scale_halftone_max` (0.10) instead of reusing the tight floor scale (0.04). With one shared tight scale, both sigmoids eroded the band edges and a legitimately strong halftone just below `halftone_max` got its score halved. The wider ceiling scale keeps true high-periodicity print scoring fully while still curving down past ~1.0 digital-perfect patterns.

Test cleanup: `classifies_halftone` no longer asserts a thin `conf >= 0.5` margin on the synthetic (that bound is scoped to real fixtures in the acceptance criteria, and the synthetic margin was fragile); `line_art_is_not_halftone` drops the over-constrained `peak < halftone_min` assertion and keeps the real intent (`class != HalftoneScan`), and hoists `cfg()` out of the loop. Doc comment corrected to the generator's actual ~0.69 peak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nrupard deleted branch feat/classify-halftone-ceiling-MK-7 2026-05-21 18:01:31 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/monkey!28
No description provided.