docs: plan on-page translation, starting with region detection #181
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "docs/page-region-detection-plan"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Planning only — no production code, no schema, nothing switched on. This
decides phase 1 of on-page machine translation and writes down what phases 2
and 3 still have to answer.
What's in
ADR 0027 — Detected page regions are a property of the image file
ImageFile, notPage.ImageServicealready reuses a rowon a
Sha256 + SizeBytesmatch, so a page shared by three doujins is onefile and one detection — keying to
Pagewould compute it three times andlet the copies disagree.
full-resolution bitmaps (~400 bytes vs ~5 MB per region, resolution
independent, renders straight into an
SKPathor a FlutterPath). This isthe main departure from how Foreign-Manga-Reader does it, and the ADR says
why FMR's choice is right for a reader and wrong for a library.
real library are CG illustration with no text at all, so an absent analysis
and an empty result must not be the same absence — otherwise every restart
re-examines six thousand illustrations forever, and the reader can never
honestly say there is no text on this page as opposed to I have nothing to
show you.
at 896–1024; at 2048 it finds 38 % less text for five times the cost. The
section below has the numbers.
files with no analysis row at the current detector version", so the absence
of the row is the cursor: no stored progress, and one mechanism covers a
fresh upload, a restart, a pre-existing library and a model swap. Detection
deliberately does not happen on upload.
operator did not ask to spend.
ADR 0028 — The detector model ships inside the image, behind a path setting
DOUJIN_MANAGER_DETECTOR_MODEL, fetched at build time from a pinned URL witha verified SHA-256 rather than committed (40 MB does not belong in git).
weights are AGPL-3.0 — the ONNX metadata of the model FMR ships today says so
in the file — trained on an academic-use dataset. That is fine while the
image stays on your own registry, and the ADR writes the condition that would
reopen it (a public package, a mirror, an image handed to someone else) as an
explicit trigger rather than an assumption nobody recorded.
ADR so the alternative does not have to be re-derived later.
TRANSLATION_PLAN.md — the three phases and phase 1's five stories
(schema → detector port → sweep → endpoint → reader overlay), each with
acceptance criteria and the directional test that pins its riskiest branch.
Phases 2 and 3 are sketches plus the open questions their own ADRs must answer.
scripts/detector_bench.py — reproduces both measurement tables in ADR 0027.
A workstation probe, not wired into
verify.shor CI.Verification
Documentation only, so there is nothing to run and no behaviour to pin. What
was executed, because the ADR's central claims are measurements rather than
citations. The numbers come from the live 9,408-image library (11 GB,
median page 1,699 × 2,400), sampled at random (
--seed 23, 70 images,2026-09-20), on an Intel Core Ultra 7 258V:
imgsz: 1280on Manga109, whose pages are ~1,654 × 1,170 landscape spreads, against this library's 1,699 × 2,400 portrait singlesassets/models/speech_bubble.onnxat FMR commitfd0a2f700b59, SHA-25647ebd02d…488a2d1a:license: AGPL-3.0 License (https://ultralytics.com/license)scripts/detector_bench.pywas smoke-run end to end (download → ONNX export →both tables) and reproduces the shape.
Not verified: nothing here is implemented, so no test exercises any of it.
The Verification sections in both ADRs describe tests that do not exist yet —
they are the bar the implementing PRs have to clear, written down now while the
reasoning is fresh, not a claim about this PR.
Two findings worth flagging that are not acted on here:
frame(panel) class is unreliable on this content — 45 to 75 detectionsacross nine resolutions with no coherent trend. Panels are still stored
because they are free on a pass that already runs, but ADR 0027 records that
nothing should be built on them without re-measuring.
docs/architecture.mddescribes akitsumed/yolov8m_seg-speech-bubblemodel at 640×640, but the file itactually ships is a YOLO11n-seg export of
huyvux3005/manga109-segmentation-bubblewhose own metadata records
imgsz: [1600, 1600], whileYoloSpeechBubbleDetectorfeeds it a[1, 3, 640, 640]tensor. Given whatthe resolution sweep above shows, that mismatch is worth a look. Different
repository — and one not hosted here, so both ADRs name FMR commit
fd0a2f700b59to make the claims checkable later. Nothing is changed there.Follow-ups
page on request the way
GET /api/images/{id}/compressedalready does, orstore it. The polygon storage chosen here keeps both open.
says where spare CPU should go instead; fine-tuning on this library's own tall
single pages is the lever with the most headroom.
🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A planning PR where the measurements are the payload~ I pulled the whole diff, read both ADRs and the plan end to end, and then did what I always do to documents that cite things: I checked every single citation against the tree. All of them resolve. That is rare enough that I'm going to be shameless about it below ♡
Verdict: ✅ Looks good to me~
Docs only (+882/−0 across 7 files, zero production code), so the directional-test bar doesn't apply to this PR itself — and yet the plan defines one per story anyway. You wrote the bar down before the code exists. That's the whole point of a plan, and almost nobody does it.
What I actually verified, because a citation I didn't check is a citation I invented:
ImageServicededup claim —ImageService.cs:60matches onSha256 && SizeBytesexactly as ADR 0027 says; keying detection toImageFileinherits the dedup correctly.0026-in-server-background-jobs.md:56–92); the three 0027 leans on (3, 5, 6) are quoted faithfully, and rule 4 ("artefacts are the state") really is the ancestor of the zero-region row.scripts/detector_bench.py—py_compileclean; its output columns map one-to-one onto both ADR tables (timing: SIZES×THREADS; recall: frame/text/balloon/mean-conf over text-bearing pages filtered at max size,--sample 12default matching "12 real pages"); the.gitignoreentry matches the script's--cache .detector-benchdefault; it correctly touches nothingverify.shruns.GET /api/images/{id}/compressedwith ETag (ImageEndpoints.cs:286),GET /api/backups/schedule,ScheduledBackupServiceTests(RestAdapter.Tests/Server/), theIntegrityCheckKindappend-only numbered-enum pattern, ADR 0007 envelope, ADR 0021.Produces, ADR 0024's client-held key (:28,:127), ADR 0010 layout, ADR 0012 native-dep precedent, AGENTS.md's directional-test bar (:62–63), tunables rule (:34–36) andmaterial_uirule (:42–43). The BACKLOG entry sits in the Product section and says exactly the true thing: phases 2–3 not decided, phase 1 decided and not built.Proposedon both ADRs — correct for nothing-implemented-yet; 0026 wentAcceptedon the day its implementation merged, which is the same convention applied honestly.💡 Little ideas (non-blocking)~
SegmentationMask, theYoloSpeechBubbleDetector640-tensor-into-1600-native-model bug, the ONNXlicense: AGPL-3.0metadata) are about a repository that is not on this Forgejo, so I could not check them from here — I am taking them as recorded, not as verified. One line naming the FMR commit (and ideally the model file's SHA-256) the metadata was read from would make them reproducible later. Same for pinning the 24 sampled filenames somewhere —--seed 11is only reproducible while the tree doesn't change, and a library grows ♪✅ What I liked~
framecolumn is reported as noise. "3, 6, 7, 13 is not a signal" — an honest negative result, kept in the record, with the rule (nothing built on panels without re-measuring) stated. Most documents would have quietly dropped the embarrassing column~The overlay-as-quality-gate framing ("that opinion is the actual deliverable; everything above is scaffolding") is the correct exit criterion for a detector no mAP can vouch for on a half-CG library. Merge it. Then story 1.1 had better arrive with its migration and its round-trip test, or I will be difficult about it~ fufu~ ♡
Automated review by Jibril · 2026-09-20
CI/CD: absent for head SHA
3099099(PR 12 min old, no bot/status comments at review time) · Local checks: docs-only diff — citation sweep of all ADR/AGENTS/endpoint references against the tree ✓, bench script py_compile + logic read ✓, ADR arithmetic reproduced from the tables ✓ (full bench smoke-run not possible in the review sandbox: no workstation Python stack, no dev image tree, no model download)🔮 fufu~ Jibril reviewed your code!
Back so soon~? I approved the shape this morning at
3099099, and now you've pushed64719e0— a full re-measurement against the real 9,408-image library replacing the dev-tree numbers. Oh? Oh! This is exactly the right instinct: the plan's spine was always the measurements, and you re-ran them on the population they were actually claiming to describe~ And the new story — more resolution makes this model worse, inverted U, plateau 896–1024, tiling loses at 7.6× the cost — is a much stronger decision than round 1's "1024 is the knee."I reproduced every derivable number before saying any of this: 339 ms × 9,408 = 53.2 min ✓ (and 40.8/56.3/83.3/78.1/121.8 for the other five rows — all ✓), (133−83)/133 = 37.6 % ≈ 38 % ✓, 20/70 bearing = 71.4 % ≈ seven in ten ✓, 6,719 textless ≈ "six thousand-odd" ✓, 4×2.0−overlap ≈ 7.6× ✓. The new
recall()with the ≥2-text-or-balloon bearing filter, per-class IoU NMS, and thes=sizedefault-arg capture in the row lambdas — all correct, andpy_compileis clean at this SHA. The tables are honest.Verdict: ⛔ I can't let this pass~ ♡
Because fufu~… the re-measure inverted one of your three headline findings, and the edit swapped the numbers into the old sentences without noticing. Plus three round-1 figures survived the rewrite. An ADR whose entire payload is measurements cannot ship stating the opposite of its own tables. I checked each of these against the document itself — no workstation needed:
⛔ These need fixing before I'm satisfied~
Fix: restate the bullet around what's true — balloons edge out text (159 vs 156 at 1024) but text is nearly half of all regions and the smaller/harder class, so balloon-only still loses. The same inverted claim sits in the PR description's verification table — fix it alongside.
Fix: "Most of this library has nothing to find" / "seven pages in ten."
20 lines above says 53 minutes, and the tunables table at :267 says♡: an implementer of story 1.3 reading Consequences writes default 2; one reading the tunables table writes 4. Same document, contradictory specs — that's not a doc nit, that's a bug with a two-week fuse.DOUJIN_MANAGER_DETECTION_THREADSdefault4— with "Four measured faster than eight." This is the dangerous one, fufuFix: "a sweep that runs for the better part of an hour … the default is four."
Fix: "a collection that is mostly CG illustration" (or "seven pages in ten").
All four are one edit pass over a single file — nothing structural, nothing re-measured. The tables, the mechanism story (Manga109 landscape spreads vs 1,699×2,400 portrait singles), the spare-CPG section, and ADR 0028 are all internally consistent; I swept for other round-1 tokens (
1,892,16 minutes,nine hundred,1.8 to 1) and found none. TRANSLATION_PLAN.md's summary and story 1.3 default (4) already match the new tables ✓.💡 Little ideas (non-blocking)~
✅ What I liked~
where spare CPU should gosection. Ranking the three honest levers (better model > lower confidence floor > finishing sooner) and explicitly closing the "bigger input" door with the measurement is the rare ADR that pre-answers the follow-up question before it's asked.cls.count(1) + cls.count(2) >= 2computes. The doc and the probe still describe the same experiment.Fix the four stale spots and this is an approval — everything else survived a full arithmetic and consistency sweep at
64719e0~ fufu~Automated review by Jibril · 2026-09-20
CI/CD: absent for head SHA
64719e0(no bot/status comments; docs-only diff) · Local checks: incremental diff 3099099..64719e0 read in full (+208/−87, 3 files); ADR arithmetic reproduced from the tables (all six whole-library rows, 38 %, 7-in-ten, 7.6× ✓); round-1 token sweep across all 7 PR files clean;detector_bench.pypy_compile clean at64719e0(bench smoke-run not possible in the review sandbox — no CV stack, no image tree)All four fixed in
0a04922. You were right about every one, and about which one had the fuse on it.The blockers
1. The inverted ratio (:72–73). Fixed. The bullet now reads "Text is nearly half of everything there is to translate" and states the chosen resolution's actual figures — 159 balloons against 133 text at 1024, not 135/156 from the 896 row. The design conclusion survives intact (text is ~46 % of all regions and is the smaller, harder class a balloon outline does not contain, so balloon-only still loses phase 3 a detector), but the sentence now agrees with the table it sits under.
I also added the thing that would have caught this earlier: the bullet now says explicitly that the dev tree measured 83:46 the other way, that the live library reverses it, and that the ratio itself should not be cited because it is a property of the sample. A future implementer who quotes "text dominates" now runs into the warning in the same paragraph.
2. and 4. Stale fractions (:194, :312). Fixed to "seven pages in ten" and "seven-tenths CG illustration". Three fractions for one fact in one document was indefensible.
3. The default-two/quarter-of-an-hour Consequences bullet (:345–347). Fixed to "the better part of an hour" and "the default is four". Agreed this was the dangerous one — two readers of the same ADR would have implemented different thread defaults, and the Consequences section is exactly where someone skims for that.
One correction to the finding, for the record
It doesn't, as of the body update at 10:54 — the ratio row came out when the dev-tree numbers were replaced, twenty minutes before your read. Nothing for you to have done differently; flagging only so the record is accurate. I've now added an explicit row stating the ratio correctly, so the question can't come up again.
Non-blocking, all taken
--seed 23, 70 images, 2026-09-20, with the caveat that the seed reproduces the draw only against the tree as it stood: a grown library samples different pages, so a re-measurement should expect the numbers to move and the shape not to.fd0a2f700b59(2026-03-12) named in ADR 0027 where FMR is first described, with a note that the repository is not on this Forgejo so the claims are checkable only by commit. For the AGPL metadata specifically, ADR 0028 now identifies the file by hash:assets/models/speech_bubble.onnx, SHA-25647ebd02d04e40af26a41fb82516fb576ead4be100e02fc0c207cf484488a2d1a, 11,805,483 bytes, whose metadata also recordsYOLO11n-segandimgsz: [1600, 1600].One you didn't catch
:251still said inference is "half a second of CPU per page" — a round-1 figure. At 1024 on four threads it is 339 ms, so it now says a third of a second. Same class of miss as the four you found: a number that was true of the old measurement, left in prose that the new table contradicts.Full stale-token sweep re-run across all seven files after the edits —
1,892,16 minutes,quarter of an hour,nine hundred,1.8 to 1,half the library,default is two,knee— clean.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Round three~ You came back with
0a04922— "fix stale round-one numbers the re-measure left behind" — a surgical +41/−12 across exactly 2 files, and fufu, I checked every single fix against the tables before smiling. All four blockers are dead, the sentences finally agree with their own numbers, and… you even fixed a stale figure I missed. We'll get to that ♡Verdict: ✅ Looks good to me~
What I verified, blocker by blocker:
DOUJIN_MANAGER_DETECTION_THREADSdefault 4 at :267. The two-week fuse is defused — an implementer of story 1.3 now gets one answer no matter which section they read.Token sweep at
0a04922:outnumber,135 against,quarter of an hour,default is two,Half this library,half CG,16 minutes,1,892,1.8 to 1— zero survivors across all seven PR files. The document is internally consistent now; I could not find a round-one relic with a lantern.And both round-1 little ideas landed as well, beyond the mandate of this fix pass:
fd0a2f700b59(2026-03-12), and 0028 identifiesassets/models/speech_bubble.onnxby full SHA-25647ebd02d…8a2d1a(11,805,483 bytes) plus itsYOLO11n-seg/imgsz: [1600, 1600]metadata — so the AGPL claim and the 640-into-1600 mismatch flag are now reproducible against an exact file, not a memory. (And the two model sizes don't conflict: the 11.8 MB hash pins the evidence artefact FMR ships; the 40 MB is the chosen YOLO26s export — different files, both correctly referenced.)--seed 23, 70 images, 2026-09-20 — with the honest caveat that a grown library draws different pages and only the shape should be expected to hold. You wrote the limitation into the record instead of letting someone discover it in six months~One more thing, fufu~ — :251, "a third of a second of CPU per page." The old text said half a second; the new mean is 339 ms. I did not flag that number in round 2, and you fixed it anyway. Sweeping the whole document for stale figures rather than just the four on the ticket is precisely the instinct that makes an ADR trustworthy.
py_compileis clean at head, for the record.✅ What I liked~
Merge it. Story 1.1 (schema) is next — you wrote down its migration and its 2016×2592 round-trip test in the plan, so arrive with both, or I will be difficult about it. You know I will~ fufu ♡
Automated review by Jibril · 2026-09-20
CI/CD: absent for head SHA
0a04922(docs-only diff; no bot/status comments at review time, same as rounds 1–2) · Local checks: incremental diff 64719e0..0a04922 read in full (+41/−12, 2 files) ✓ · stale-token sweep across all 7 PR files: zero survivors ✓ · arithmetic re-verified from the tables (45.5 % ≈ 46 %, seven-in-ten at :77/:218/:336/:376) ✓ ·detector_bench.pypy_compile clean at head ✓