Initial commit

This commit is contained in:
Deep Koluguri 2026-06-12 09:21:14 -04:00
commit 900bdcc302
192 changed files with 17935 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# Virtual environment
venv/
.venv/
# Python cache
__pycache__/
*.py[cod]
*.egg-info/
# Pipeline outputs (large, regenerated each run)
output/
uploads/
# Input newspaper PDFs (large binary data)
*.pdf
# Scratch / diagnostic preview images
_*.png
pg-*.jpg
q-*.jpg
gp-*.jpg
gq-*.jpg
# Generated HTML guides
Extractor_Guide.html
HOW_IT_WORKS.html
# Secrets / API keys (never commit)
.keyenv
.env
.env.*
# OS / editor cruft
~$*
.DS_Store
*.swp
.idea/
.vscode/

515
CROP_STRATEGY.md Normal file
View File

@ -0,0 +1,515 @@
# Article-Cropping Strategy & Learnings (self-improving loop)
Living doc for the geometric article-cropping in `smart_extractor.py`
(`cluster_all_article_blocks` → `crop_all_article_blocks`). Updated each loop iteration.
## Validation rules (NEVER violate)
- **Never run `smart_extractor.py`** — it calls the paid Claude API. Validate ONLY with the
geometric replay scripts: `_replay_all.py` (AJ), `_replay_namaste.py` (NT Siddipet),
`_replay_jangaon.py` (NT Jangaon). These run the full cluster chain with NO API.
- **Measure read-only BEFORE editing** (blast-radius of any new rule), then **re-validate after**.
- **Baselines** (block counts) — keep stable unless an improvement is intentional & documented:
- AJ: 12 / 18 / 14 / **21** (pg4 20→21: the ';'-delimiter fix recovered a real గజ్వేల్ dateline R12)
- NT Siddipet: **11 / 23 / 20 / 25** (pg2 22→23: అంగన్‌వాడీ R127 split off the ప్రజావాణి section R110;
pg3 19→20: పర్యావరణ R60 split off; both are the Type-2 paragraph_title-anchor split)
- NT Jangaon: pg1 → **6**, pg2 → **18** (pg1 7→6: orphan photo R25 bound into its article;
pg2 Type-2 splits: R65→3 కలెక్టర్/సుందరయ్య/పోస్టుమార్టం, R64→2 జాతీయరహదారి/ఉపసర్పంచుల, R97→2
గుండెచికిత్స/మృతుడిపరామర్శ; + L89 court notice, F44 furniture)
- Sakshi Jangaon: pg1 → **22**, pg2 → **20** (new paper, no line separators; all orphan PHOTOS
bound to articles — 0 image orphans remain)
## Layout model (from the line-pattern study)
- **NT (Namaste) = boxed grid.** ~139 horizontal rules/4pp, many ~2-column wide; long vertical
column dividers. ~50% of articles are ruled on 34 sides. Boundaries are largely *drawn*.
- **AJ (Andhra Jyothi) = whitespace layout.** Few rules (~69/4pp, mostly ≤0.6-col caption
underlines). Most articles ruled on 02 sides. Boundaries come from column gutters +
grow-right-to-dead-end + headline/dateline structure. Lines are only *confirmation* in AJ.
- **Rule width classifies horizontal lines:** ≤0.6 col = caption underline (NEVER a boundary);
~1 col = single-col article edge / box edge; ~2 col = article boundary (NT workhorse);
≥2.5 col = section/banner divider.
- **Vertical rules:** long (≥0.5 page H) = column divider / article wall; short = quote-box edge.
## Why earlier runs failed (root causes found)
1. **Over-merge from clustering + containment-merge.** Multi-column articles whose datelines sit
only in the left column get over-extended upward; a separate dated article then falls *inside*
the box and is absorbed → 2 articles in one crop (JG R28, R64). Fixed by **header-split**.
2. **Banner over multiple sub-stories** not cropped as one rectangle, or its sub-stories
double-cropped → **section-banner rectangle** + **containment-merge**.
3. **L-shaped articles** (one column longer): rectangular crop's empty corner = a *different*
article masked white → looks like white space (JG H28). Generic ragged-bottom fix REJECTED —
it would also move AJ R48/R53 (currently fine). Still open; needs a tighter signal.
4. **Top-of-column banner with no anchor** falls into the article below → page-tall crop (JG R2).
Fixed by **ceiling-trim**.
5. **Body-less header strips** (doc_title or quote-box paragraph_title) cropped alone, split from
body → **body-less header merge** (H and now P).
## Strategies implemented (all gated + measured to touch only defective blocks)
- **Banner rectangle** (H & P, ≥1.6-col header, ≥2 cols, sure bottom boundary): crop whole rect.
- **Section-banner rectangle** (doc_title ≥2.5 col with ≥2 headed sub-stories): crop banner-top →
next spanning full-width separator; absorb sub-blocks. (Only JG R67 across all runs.)
- **Containment-merge** (block ≥90% inside a larger one → fold in). Fixes split columns,
roundup sub-stories, orphan tails.
- **Header-split** (H block with a dateline >20px ABOVE its headline → split at headline).
Only JG R28, R64.
- **Ceiling-trim** (DL block, leading furniture >300px above the dateline → drop). Only JG R2.
- **Body-less header merge** (H or P, no text, sits ≤90px above a text block, ≥60% overlap → fold).
H: NT R65→R73. P (iter-1): NT R133→R132, JG R52→R4.
- **Right-edge → page** (rightmost-column block → extend right edge to page-content edge).
Touches only rightmost blocks (AJ 4/6/3/4, NT 2/3/3/8), ≤15px; interior untouched.
- **Drop classifieds** (`drop_classifieds_regions`, runs right after `drop_masthead_regions`).
Classifieds = the "Classified Ads / phone-number listing" columns = page FURNITURE, not news.
Left in, a neighbour article's column-fold grows down into them (JG R65 swallowed the whole
left column to page-bottom). A region is classifieds when (only TALL regions ≥400px are
OCR-scanned) its OCR text carries a classifieds keyword (`classified`/`contact`) **OR** ≥5
phone-like `\d{6,}` runs (a news article rarely has >3). OCR is **half-res `eng`-only** — the
phone-runs + English keyword are the real signals, so this is ~2× faster than full-res tel+eng
and gives the IDENTICAL drop set on the corpus. Court/legal notices are intentionally NOT
dropped (user: classifieds ONLY) — they are carved into their own section instead (next).
- **Carve court/legal notices** (`tag_legal_notices` + a carve pass in `cluster_all_article_blocks`).
Court summons / name-change & matrimonial petitions / "by order of the court" notices are page
furniture too — but, per the brief, KEPT and cropped as their OWN section (kind **`L`**), not
dropped and not absorbed into a neighbour (JG R65 swallowed a court summons in its left column).
`tag_legal_notices` half-res-`eng`-OCRs single-column text/paragraph_title regions and tags
`r['_legal']='core'|'support'` on a legal-boilerplate keyword (CORE: hon'ble, in the court of,
by order of, plaintiff/defendant, petitioner/respondent, summons, o.s.no, civil judge … ;
SUPPORT: advocate, whereas, vakalat). The clusterer then groups adjacent tagged regions and
carves a cluster into its own LOCKED `L` block **only when** (1) ≥2 tagged regions incl. ≥1
CORE, (2) they all sit in ONE host block, and (3) that host is WIDE (>1100px → masked). Gate 3
is the safety rule: a wide masked host whites the carved area out (no duplication), whereas a
NARROW host is cropped verbatim, so carving out of it would duplicate the notice — those are
left alone (e.g. NT pg3's petition notice straddling the narrow locked #75 + open #62 is NOT
carved, so the NT baseline is untouched). The carved `L` block shows in BOTH the per-article
crops and the debug boundary map (the carve lives inside `cluster_all_article_blocks`, which
every consumer re-runs).
- **Type-2 split** (split pass in `cluster_all_article_blocks`, after the legal carve). TWO
layouts look like one big box but need OPPOSITE treatment (AJ/Namaste = ruled papers):
- **Type 1** — sub-stories under ONE header with a COMMON BOTTOM rule → the rule + wide banner
already made it a `_raw_rect` section (banner/section pass) → crop as ONE rectangle. NEVER split.
- **Type 2** — independent articles stacked one below another, EACH with its own doc_title +
its own dateline, NO common bottom rule (JG R65: కలెక్టర్/సుందరయ్య/పోస్టుమార్టం) → over-merge →
SPLIT into one article per headline (members assigned to the nearest headline at/above them).
Discriminator = the `_raw_rect` flag (set ⇒ sure bottom rule ⇒ Type 1). The split anchors are
headlines that **OWN** a dateline (each dateline assigned to the nearest headline above it) — a
single article with a headline + a sub-headline sharing ONE dateline has only ONE owning
headline, so it is NOT split (this fixed the AJ R50/R57 & R35/R79 false splits). Anchors must be
vertically stacked ≥120px apart (not two columns headed side-by-side). NOTE for line-less papers
(Sakshi): the `_raw_rect` bottom-rule signal won't exist — a different bottom cue would be needed.
## Iteration log
- **iter-1:** body-less header merge extended to `paragraph_title` → NT R133→R132, JG R52→R4
(title-only fragments fixed). AJ untouched; NT pg4 26→25, JG pg1→9.
- **iter-2:** drop graphic-label orphans (pure-text O block ≥80% inside a FOREIGN image) →
JG R34 dropped (label inside banner image). AJ/NT 0 drops; JG pg1 9→8.
- **iter-3:** orphan-TEXT absorb (fold pure-text orphan UP into clear above-article, gap≤80,
no down-folds) — TRIED & **REVERTED**. Only match was NT pg4 R75→R132, a CLASSIFIEDS
"change of name" notice; could not verify R75 belongs to that ad vs a separate notice →
merging distinct classifieds is a regression risk. Deferred. (AJ/JG had 0 matches anyway.)
## Assessment after iter-3 — safe heuristics largely exhausted
The remaining backlog items are **risk-bearing or need human judgment**, not safe heuristics:
- Orphan PHOTOS (JG R25, AJ R12/R26): binding a standalone photo to "the right" article is
ambiguous (far gaps, multiple candidates).
- Orphan TEXT in classifieds/notices (NT R75, etc.): can't tell same-ad vs separate-ad.
- JG front-page over-merges (R64/R65): dense multi-headline roundups; further splitting is
complex and could fragment legit roundups.
- JG H28 L-shape white space: a generic ragged-bottom split also moves AJ R48/R53 (fine now).
Recommendation: pause autonomous loop; these need a human call per case. Resume with a
specific directive if desired.
## Open defect backlog (prioritized for next iterations)
1. **Orphan photo/text blocks (kind O)** — REMAINING: AJ pg1 R12 (photo+cap+text), pg1 R76
(text), pg3 R32 (text), pg3 R26 (img+text); NT pg3 R7 (text tail, 36px below H98),
pg4 R75 (text, 14px below P132); JG pg1 R25 (img-only photo). [R34 done in iter-2.]
Next safest: fold orphan TEXT into the article directly above/below within a small gap
(NT R75→P132 gap14, R7→H98 gap36) — measure first (risk: wrong-article merge).
Harder: bind orphan IMAGE (R25) to caption+nearest article.
2. **JG front-page over-merges** R65 (4DL), R64 (residual 2DL below headline) — dense section
roundups; may need below-headline multi-headline split.
3. **JG H28 L-shape white space** — needs a signal that distinguishes it from AJ R48/R53 (which
are fine). Candidate: only when the empty corner is masked-out *foreign* article AND the
block's tail is a single-column sidebar with its own sub-head/byline → split the sidebar.
## Hard limitation: undetected headlines (OCR gap, not a cropping bug)
On some pages PaddleOCR detects **no doc_title** for the top articles (JG pg1: only 3 doc_titles
total, ALL in the bottom half; top-half headlines `భానుడి భగభగ` / Indiramma / కరీమాబాద్ were
missed entirely). A geometric crop cannot include a headline that has no region. Symptoms:
DL-anchored crops with the heading missing (JG a01_DL3, a03_DL4) and lead photos orphaned from
their headless article (JG a02_O25). The REAL pipeline recovers these via the paid Claude
"catch by eye" pass (pipeline log: "N article(s) Claude must catch by EYE — no headline box").
The geometric `all_article_crops` (real run AND replay) will lack them. The only geometric fix
is a NEW pixel-level headline-band detector (find the large-type band above a headless dateline)
— substantial + risky for AJ/NT baselines; deferred unless explicitly requested.
## Replay validation cheatsheet
```
python3 _replay_all.py # AJ → all_article_crops_final
python3 _replay_namaste.py # NT Siddipet → all_article_crops_final
python3 _replay_jangaon.py # NT Jangaon → all_article_crops_review
```
Read-only block inspection: replicate the chain (dedup → merge_stacked → drop_masthead →
promote_paragraph_titles → mark_bullet_titles → find_article_starts_by_dateline →
[probe cluster → recover_orphan_text_headlines → re-detect] → cluster_all_article_blocks).
## Partition passes added (post iter-2) — all gated + validated
- **Seed/lock confident cells** — a block is `_locked` (confident) when it's a single complete
article (exactly 1 dateline + a headline) OR fully boxed (≥3 ruled sides). Over-merges stay open.
- **Step 4 — grow OPEN cells into gaps** (bounded by walls/cells/frame; locked never move;
strict guard: never increase overlap with a locked cell). AJ/NT: 0 open∩locked overlaps.
- **Step 5 — containing-assignment** — every unassigned region inside a cell's box joins it
(no box growth). Cleared NT pg4's 11 section-absorbed regions; counts unchanged.
- **No-overlap reconciliation** — trims a 0-member over-reaching box back to its members.
- **Lead-photo rebind (caption-anchored)** — a PHOTO→CAPTION→HEADLINE stack = lead photo; move
the photo+caption down into the headline's block. Fixes JG a06_P37/maize R29 + AJ pg4/NT lead
photos. The caption requirement excludes the H93 false positive (photo at an article's bottom).
- **Horizontal-line LENGTH FLOOR (`_lines.separator_barriers`)** — a horizontal rule counts as an
article separator only if width ≥ **0.6× column** (median text width). Drops short
caption/decorative underlines. NOTE: ≥1.0× is TOO aggressive (a single-column article's own
boundary is <1 col 1.0 merged NT pg4 R121); 0.6× merges ZERO articles. Caption-vs-separator
is still decided primarily by "what's directly above the line" (photo/caption) — length is a
secondary floor. A rule below a caption ≈ caption width = caption underline (dropped); only a
rule much WIDER than the caption above it (≥1.5×) is kept as a boundary.
## Tried & REVERTED (kept for the record)
- **Orphan-TEXT absorb** — only match was a NT classifieds box (R75/R132); couldn't verify
same-ad vs separate-ad → regression risk. Deferred.
- **Ragged-bottom sidebar split** — landed for JG H28 (split the `రాత్రికి రాత్రికే` sidebar so the
L-shape white space cleared); later the lead-photo/grow cascade made H28 tile both quote boxes
with no white space anyway. (Gate: main part must stay multi-column → AJ R53 never split.)
- **Below-divider cut** (cut content sitting below a vertical divider's END out of the right-column
article above — JG DL2 holding the maize wide photo R30). Detection is SAFE (straddle gate ⇒
only JG R2; AJ/NT untouched), but the repair destabilised JG (merged into wrong neighbour R49;
emptied DL2 regrew). Deferred — needs correct merge-target (the dateline governing the band) +
a "don't regrow a just-cut block" guard.
## RUN-WIDE VALIDATION (all 7 output runs, geometric, no API) — 2026-06-09
Ran the full cluster chain on every run in `output/` and checked counts + 2+dateline over-merges.
- **AJ Siddipet ×3** (incl. 2 never-loaded runs): **12 / 18 / 14 / 20 on all three, 0 over-merges.**
- **NT Siddipet ×3** (incl. 2 never-loaded runs): **11 / 22 / 19 / 25 on all three.**
- **NT Jangaon**: 7 / 12.
- **Every `2+dateline` block is an intended ROUNDUP** (verified): NT pg2 [110,127], pg3 [54,60,62],
pg4 [67,121] (R121 = a `raw_rect` section: headline + 2 dated sub-stories + photos, same accepted
pattern as R67), JG [64,65] (front-page section roundups).
- **New runs match the baselines exactly** ⇒ rules generalise (not overfit). No crashes, no new
over-merges, no merged articles from the length floor.
Conclusion: the horizontal-line + caption handling and the partition/lead-photo passes are
validated across the whole corpus.
## Classifieds-drop validation (end-to-end replay, no API) — 2026-06-09
Wired `drop_classifieds_regions` into the pipeline (after `drop_masthead_regions`) + all 3 replay
scripts, then re-ran every replay:
- **AJ: 12 / 18 / 14 / 20** — baseline EXACT, **0 classifieds dropped** (no false positives).
- **NT Siddipet: 11 / 22 / 19 / 25** — baseline EXACT; roundups [110,127] / [54,60,62] /
[67,121] all intact; 0 classifieds dropped.
- **NT Jangaon 130635: pg1 = 7, pg2 = 13** (was 12). The only intended change: R65 shrank from
`[113,1958,1977,6279]` (page bottom, 4 datelines, swallowing the phone-listing classifieds) to
`[113,1958,1977,5113]` (3-DL front-page roundup). Classifieds R87 (`y51436267`, phones=8)
dropped; the freed left-column content below becomes its own block (12→13). a06_H65 renders as
a bounded roundup, no longer page-tall over the classifieds.
- **OCR cost addressed:** detector now OCRs each tall (≥400px) region at **half-res `eng`-only**
(was full-res `tel+eng`) — ~2× faster per call, verified to produce the IDENTICAL drop set
(R87 still phones=8/kw=True; 6 sampled news bodies still 0/False).
- NOTE: R65 still includes a *court/legal* notice stacked in its left column. Per the user's
"classifieds ONLY" directive this is intentionally left; separating court notices is deferred.
## Court/legal-notice carve validation (end-to-end replay, no API) — 2026-06-09
Follow-up directive: "include court/legal notices as box/section in populating the crops + debug
img." Added `tag_legal_notices` (pipeline + 3 replays, after classifieds) + the carve pass in
`cluster_all_article_blocks`, then re-ran every replay:
- **JG 130635 pg2: 13 → 14 crops.** The court summons (R51/R89/R101/R108/R93) was carved out of
R65 into a new **`a08_L89`** block `[118,2887,595,4080]` (5 regs) — a clean, complete notice
("IN THE COURT OF THE HONBLE JUNIOR CIVIL JUDGE … BY ORDER OF THE HON'BLE COURT … ADVOCATE").
R65 dropped 30→25 members and **whites the carved area out** (verified in `a06_H65` — no
duplication). The debug boundary map shows it as **LOCK #89 [L] (5)**, nested in #65. pg1 = 7.
- **AJ: 12 / 18 / 14 / 20** — baseline EXACT, 0 legal carves.
- **NT Siddipet: 11 / 22 / 19 / 25** — baseline EXACT, 0 legal carves; roundups intact. NT pg3 has
a real petition notice (R47/R79/R89) but it straddles the NARROW locked #75 + open #62, so Gate 3
(wide-host-only) correctly SKIPS it → baseline untouched.
- Detection sweep (read-only) across the corpus: legal keywords flagged ONLY the JG court notice
and the NT pg3 petition; **0 false positives on any AJ/NT news region.**
- COST: `tag_legal_notices` OCRs ~4070 single-column text regions/page (the expensive part).
Acceptable for the paid pipeline (Claude vision dominates), but a candidate for a cheaper
pre-gate (e.g. skip dateline-anchored regions) if it becomes a bottleneck.
- DEFERRED (harder, needs the seed-before-clustering model): a legal notice that straddles a
NARROW straight-cropped article (NT pg3) — to carve it cleanly the host article's box must be
shrunk away from the notice, which can't be done by masking alone.
## Type-2 split validation (end-to-end replay, no API) — 2026-06-09
Directive: Type-1 (shared header + common bottom rule) = ONE rectangle (fine); Type-2 (stacked
articles, own headline+dateline each, no common rule) = SEPARATE articles. Added the split pass.
- **JG 130635 pg2: 14 → 16 crops.** R65 (the კალెక్టర్ over-merge) split into 3 clean articles:
R65 కలెక్టర్ `[113,1958,1975,2859]`, R86 సుందరయ్య `[123,2894,1977,4292]`, R112 పోస్టుమార్టం
`[133,4325,1975,5113]`. Court notice R89 [L] still its own section. pg1 = 7.
- **AJ: 12 / 18 / 14 / 20** — baseline EXACT (after the dateline-OWNERSHIP fix; the first cut wrongly
split AJ pg1 R50/R57 and pg3 R35/R79 — headline+subhead sharing one dateline — 13/15, now fixed).
- **NT Siddipet: 11 / 22 / 19 / 25** — baseline EXACT. The 2+dl roundups [110,127]/[54,60,62] are
single-header-multi-dateline (Type 1) so they have <2 dateline-owning headlines NOT split;
[67,121] are `_raw_rect` → excluded. All correctly preserved.
- KNOWN minor issue: R65's left column held a furniture stack (court notice + classifieds logo +
a small job ad + a గమనిక notice). The court notice is carved (L89) and the main classifieds
(R87) dropped, but the leftover logo (R78 image) bleeds into R86's crop and the job ad + గమనిక
bleed into R112's crop (they're <400px / images, below the classifieds-drop gate). Secondary
cleanup — extend furniture detection if needed. [RESOLVED below: furniture section.]
## Furniture section (Type-2 split) — column-aware member assignment — 2026-06-09
Directive: when splitting a Type-2 stack, LEAVE classifieds / notices / unrelated content OUT of
the article crops, but KEEP that section visible on the debug empty-canvas map. Implemented
**geometrically (no OCR)** inside the split: each member is assigned to the nearest headline
at/above it ONLY IF it also sits within that headline's COLUMN x-span (±0.3 col). A member that
lands in a headline's y-band but a DIFFERENT column is unrelated content sharing the page — it
falls out as LEFTOVER and is grouped into a `_furniture` section block. Furniture blocks are:
(1) EXCLUDED from article crops — `crop_all_article_blocks` skips saving them, and because each
article sub-block's bbox is rebuilt from its in-column members only, the furniture column isn't
even inside the article rectangle; (2) DRAWN on the debug boundary map as a hatched GREY
"FURNITURE #id" box; (3) NEVER saved as a crop. The split fires only on JG R65, so this touches
ONLY that page — AJ/NT untouched.
- **JG pg2:** R65 → R65 కలెక్టర్ + R86 సుందరయ్య (x682, was x123 w/ the classifieds logo) + R112
పోస్టుమార్టం (x682, clean) + R89 [L] court notice + **F44 furniture** [123,4139,626,5113]
(classifieds logo + job ad + గమనిక — debug-only, not cropped). 16 saved crops; boundary map shows
the FURNITURE box. a09_H86 & a13_H112 verified clean (no furniture bleed).
- **AJ 12/18/14/20** and **NT 11/22/19/25** — baselines EXACT.
## Type-2 split — paragraph_title anchors + ';' dateline + section discriminator — 2026-06-09
Two more JG pg2 over-merges were reported (R97 = గుండె చికిత్స + మృతుడి పరామర్శ; R64 = జాతీయ రహదారి +
ఉపసర్పంచుల చెక్ పవర్). Three root causes, three fixes:
1. **2nd headline detected as `paragraph_title`, not `doc_title`** (R58, R84) → my split's anchors
were doc_title-only. FIX: anchors = `doc_title` OR `paragraph_title`, with COLUMN-AWARE dateline
ownership (each dateline → nearest headline above WHOSE x-span covers it; a caption in another
column can't steal it). Captions/sub-heads still own no dateline ⇒ never an anchor.
2. **2nd dateline's colon OCR'd as a semicolon** (R97: 'చిల్పూరు, మే 19 ;') → `_dateline_in_text`
required ':'. FIX: accept ';' too (guards: digit + short prefix + no sentence punctuation).
3. **R64 was `_raw_rect`** (banner-rectangle extended the wide headline to the next banner, NO
spanning rule) so the split skipped it. FIX: the split's Type-1 exclusion is now the `_section`
flag (set ONLY by the section-banner pass, which requires a real spanning bottom rule), not the
coarse `_raw_rect` flag. R64 (no rule) → splits; R67/R121 (real section) → stay one rectangle.
- **JG pg2 16→18:** R64→R64+R84, R97→R97+R58 (verified: each crop is one complete article).
- Side effects, all VERIFIED correct (same rule, genuinely distinct articles — not fragmentation):
NT pg2 22→23 (అంగన్‌వాడీ R127 off the ప్రజావాణి section), NT pg3 19→20 (పర్యావరణ R60 off), AJ pg4
20→21 (గజ్వేల్ R12 — a real dateline the ';' fix recovered; an existing pass then split it).
R110 (ప్రజావాణి) stayed whole as a `_section` ⇒ the discriminator protects real sections.
## Orphan-PHOTO binding — an image may never stand alone — 2026-06-09
RULE (user): an article may be orphan, but an IMAGE may not — every photo must belong to some
article. A final pass in `cluster_all_article_blocks` binds each ORPHAN-PHOTO block (no
doc_title/paragraph_title headline AND no dateline; image-only, OR image + caption only, OR
image-area ≥50%) to the best NEWS article (furniture/legal blocks are excluded as targets): the
article whose top is just BELOW the photo in the same column (lead photo), else just above, else
the nearest column-overlapping article. A TEXT/body orphan is LEFT ALONE.
- **Sakshi pg1 23→22, pg2 27→20:** every orphan photo bound (R28→R78 lead photo verified — the
'collector inspecting BL-1' photo + caption now sit in R78; pg2 R38/R42/R48/R52/R60/R63 all
bound). **0 image orphans remain** on either page. The text orphan R18 (pg1) correctly stays.
- **JG pg1 7→6:** orphan photo R25 bound into its article.
- **AJ 12/18/14/21** and **NT 11/23/20/25** — UNCHANGED (no qualifying image orphans on those).
- NOTE: Sakshi itself has rough article structure (headlines/datelines under-detected, no rules) —
some bound photos land on a column-overlapping article that may need per-case review; but the
rule (no floating image) now holds. Deeper Sakshi clustering is a separate task.
- **Respect horizontal separators in the photo bind (refinement):** a photo binds to an ADJACENT
article (gap ≤200px) even if a rule sits between them — that rule is the article's own headline
border and the photo is its lead photo (JG R28→R78, 7px gap). But a FAR fallback bind is REJECTED
if a horizontal rule LONGER than a caption (>0.6 col) lies between the photo and the article —
the user's cross-paper line rule. This kept the Sakshi pg2 "student photo" R52 (top of page) from
binding down through two FULL-WIDTH rules + a page of white space into R118 (the భక్తులకు/
Kaleshwaram story); R52 now binds to R127 in its own band, and `a08_H118` is clean. Verified:
Sakshi pg1 22 / pg2 20 (0 image orphans); AJ 12/18/14/21, NT 11/23/20/25, JG 6/18 all unchanged.
## Sakshi page-1 deep fix round — 2026-06-10 (visual review of all 22 pg1 crops → 14 BAD)
A crop-by-crop visual audit of Sakshi JG pg1 found 14/22 BAD crops. Root causes & fixes, ALL
Sakshi-gated (AJ 12/18/14/21, NT 11/23/20/25, JG 6/18 re-validated EXACT after every change):
- **Masthead drop for Sakshi** (`drop_masthead_regions`, `paper=="sakshi"` branch, every page):
drop every region that ends ABOVE the topmost headline-type region (sanity: that line must be
in the top 30%). pg1: weather box R64, photo-brief R62/R59, nameplate R63, and the page-date
strip R71 — which OCR'd as a DATELINE and seeded a fake DL71 article. pg2: R68/R71/R84/R104.
- **`detect_sakshi_headline_bands`** (called after `promote_paragraph_titles`, pipeline + replay)
— the big one. Sakshi's display headlines are often NOT boxed by PaddleOCR at all. Two passes:
1. **WIDEN**: a doc_title grows sideways over adjacent UNCOVERED large-ink columns — its
un-boxed kicker half (R52 'అనూహ్య స్పందన' + red kicker 'రిబేట్ ఆఫర్‌కు'). Widest titles first;
extensions join the coverage map so a narrow neighbour can't steal a wide headline's kicker.
Ink within 25px of a foreign region's edge is the neighbour's own glyph overflow — ignored.
2. **SYNTHESIZE**: an uncovered big-type band (55280px tall, ≥0.75 col wide, ink 560%, text
following below, never above the first real headline) becomes a synthetic `doc_title`
(`_synth_band`) anchor. Found BOTH missing pg1 banners (lift-1 'జూలై 31...' R99 and SIR
'మ్యాపింగ్‌లో జిల్లాకు అగ్రస్థానం' R100) and pg2's గమనిక box R139. CRITICAL detail: two
headlines can share pixel rows with only a ~50px gutter (same size as a word gap) — a gap
splits a band run ONLY if it contains a known text-column LEFT edge (≥35px wide); plain
gap-size segmentation merges both banners into one anchor (measured: pg1 12 crops, 45-member
monster).
- **Box-edge rule disarm** (`_is_box_edge` in `_barrier_between`, Sakshi only): a horizontal rule
with a PARALLEL partner 80400px away (≥70% overlap) is a quote/bullet-box border, never a story
separator. Without it the lift-1 bullet-box borders fenced the article's own columns off.
- **Photo-bottom border disarm** (`_in_photo`, Sakshi only): a rule within 14px BELOW an image,
horizontally inside the photo span ±25px, is the photo's frame/caption rule (PaddleOCR's box
ends a few px above the printed border, so the interior test missed it). Without it R61/R46/R23
(lift-1 caption + col-3 body) fell into the story below.
- **Caption never a headline line** (headline-stack recovery, all papers — baselines unaffected):
a figure_title directly under an image (≤90px) is a CAPTION; stop the stack there. Without it
the synth SIR headline pulled the Indiramma photo's caption R69 down as a "wrapped line".
- **No lead-photo rebind into a `_synth_band` headline**: a synth band's lead photo is BELOW it by
construction; a photo+caption ABOVE it belongs to the story above (R36/R69 stay with R77).
- **Continuation-column fold** (Sakshi only, end of clusterer): a TEXT-only orphan block directly
beside an article block with the SAME vertical band (top ±90px, ≥80% overlap, gap ≤80px, no
vertical rule between) is the other column of the same box → merge (pg1 R18 → R79; the text even
continues mid-word 'విద్యా సంస్థ|లకు' across the two).
- **Stray-image re-home** (Sakshi only): an image member vertically DETACHED from the rest of its
block but fully inside ANOTHER article's box moves there. A 3-column synth banner column-overlaps
everything below it, so R50's tiny సాక్షి-ఎఫెక్ట్ clipping R44 (whose own narrow headline does
NOT column-overlap it) had fallen to the banner 1100px above.
- **Narrow-crop masking** (`crop_all_article_blocks`, all papers): narrow (≤1100px) crops now white
out regions OWNED BY OTHER blocks (gpad=2) before the ink-trim — kills the sliced-glyph slivers
at crop edges. Un-owned ink is left alone (it may be the crop's own un-boxed headline). Members
are repasted, so nothing of the article itself can be erased. Counts unchanged on all papers.
- **Review overlays**: `crop_all_article_blocks(..., overlay=True)` (used by `_replay_sakshi.py`)
draws member boxes on the review crops — blue=anchor, magenta=synth band, green=member.
- **RESULT (Sakshi JG pg1): 22 → 16 crops, every reduction a genuine reunification.** Visual
re-audit of all 16: 14 GOOD / 2 MINOR / 0 BAD (was 8 GOOD / 14 BAD). The rebate feature package
(was shredded into P94+H52+DL71+DL11 with sliced letters) is ONE crop; lift-1 and SIR-mapping
(whose headlines have NO region) are complete articles anchored by synth headlines; H77 is
exactly the Indiramma story; H54/H78/H55 no longer hold neighbours' content. pg2: 20 → 17
(top photo-gallery orphans folded into their stories; not yet visually audited).
- **NEW Sakshi baselines: pg1 → 16, pg2 → 17.**
- Known remaining (MINOR): a01_H55 loses the unboxed last line of its మ్యూజియాలజీ sub-brief
(no region covers it; the next block's white-out grazes it); the EAPCET ad image R33 sits
mid-article in H55 (it IS physically inside the article band; ad detection deferred).
- Known remaining (pg2, PRE-EXISTING): the Type-2 split's column-aware leftover pass marks the
విద్యార్థిని ఆత్మహత్య article as FURNITURE (R4/R10 F) → it is NOT cropped at all. Present in the
pre-fix baseline too. Needs its own round. [RESOLVED in the pg2 round below.]
## Sakshi page-2 round — 2026-06-10 (visual audit of all pg2 crops; user-reported defects)
Fixes, validated after each change (AJ 12/18/14/21, NT 11/23/20/25 incl. 2+dl=[110]-only on pg2,
JG 6/18 — all EXACT):
- **a04_H118 body truncated at the column boundary** (col5 ends mid-word 'నిర్మా-', col6 holds
'ణం…'): the col6 body R20 was stolen by the headless dateline R14's GROW-UP, which climbed PAST
the R14 story's own subhead R121 because `_is_caption` lets a 197px two-paragraph text through
(≤200px + a photo 82px above). FIX (all papers, baselines unchanged): grow-up STOPS after
claiming a `paragraph_title` — the heading IS the article's top; nothing above it is ours.
- **విద్యార్థిని ఆత్మహత్య never cropped** (whole story = furniture): its heading R88 IS detected
(paragraph_title) but the R41 Type-2 split's anchors don't span col4 → all 8 regions fell out
as LEFTOVER → furniture. FIX (all papers; gates keep AJ/NT/JG exact): a leftover group that
LEADS with its own COLUMN-WIDE heading (≥0.6 of the group width — a narrow WANTED label stays
furniture) and has body text → emit as an ARTICLE (kind P), not furniture.
- **a13_H93/'విషాదయాత్ర' cut mid-sentence, its tail + tributes in the girl's crop**: the col4
leftover zone holds TWO stacked items (girl's story y3658-4643, శ్రీనివాస్ tail y4723-5938) with
a separator rule at y4679 between them. FIX (Sakshi only): (1) leftover clustering never merges
across a separator rule; (2) a heading-less leftover group whose band MATCHES an adjacent-column
split article (top ±90px, ≥80% overlap, gap ≤80px) is that article's column CONTINUATION → its
members merge there (the tail's text literally resumes H93's "...అయితే వైద్యులు" mid-sentence).
ORDER MATTERS: the wide-heading→ARTICLE check runs FIRST, else the girl's story (gap-close to
H99's band) band-attaches into H99. Both passes are Sakshi-gated: un-gated they re-shuffled an
NT pg2 leftover into R105 (2+dl-blocks [110]→[110,105]) — caught by the 2+dl indicator and
re-gated.
- **a03_P129/a05_H91: dead farmer's portrait R47 + caption R115 stuck at the briefs column's
bottom, white hole in his own story's crop**: the stray-image re-home pass couldn't see R47 as
detached because its caption R115 sat below it inside the same block. FIX: re-home treats an
image + its caption (figure_title ≤90px below, ≥40% overlap) as ONE UNIT — excluded from the
envelope test and moved together (→ H91, inside whose box they sit).
- **గమనిక notice box synthesized as a headline** (R139, its own bogus crop): a filled red header
measures ink 0.51; real Sakshi display headlines measure 0.30-0.35. FIX: synth band ink ceiling
0.60 → 0.45; the notice text returns to the classifieds-column furniture (not cropped).
- **RESULT (Sakshi JG pg2): 20 → 17 crops.** Visual audit of all 17: 11 GOOD / 4 MINOR / 2 BAD →
after the H93/P88 boundary fix the BAD pair re-audited GOOD. The H118 two-column body, the
విద్యార్థిని article (new crop P88), H91's portrait, and the P129 briefs column all verified.
- Known remaining (pg2, magazine-style kids/summer feature band only): H82/H127 share the gallery
collage and H82's story conclusion ('–దుగ్గొండి' sign-off + 'నీతి' moral box) renders inside
H127's crop — a collage-page attribution problem, low priority for the news pipeline. The
classifieds crops (P136/P111) bundle neighbouring ads and clip one ref number (MINOR).
## POLITICAL crops now BLOCK-SNAP to the geometric blocks — 2026-06-10
`crop_political_articles` used to re-derive each Claude-selected article's rectangle from the raw
member_region_ids union plus ~400 lines of parallel ad-hoc geometry (banner walls, foreign-headline
clips, floors, grow-right) — none of which carried the geometric-crop fixes. Now it BLOCK-SNAPS:
- It clusters the page once (`cluster_all_article_blocks`) and renders every block crop via
`crop_all_article_blocks` into a temp dir — the exact validated pipeline (furniture drop,
un-boxed headline recovery, neighbour masking, box-edge rules, continuation folds).
- Each political article is mapped to the geometric block owning the MAJORITY (≥50%) of its
member regions; that block's crop is shipped as `article.png` (`method:
"smart_extractor_blocksnap"`, info.json carries the block's full member list).
- A block already claimed by an earlier article on the page is not re-used (no duplicate crops);
any article that fails to map falls back to the legacy path unchanged.
- The pipeline call passes `dateline_starts` + `sep_lines` through so the clustering matches the
all_article_crops output exactly.
- VALIDATION (no API — `_replay_political.py` replays saved `page_NNN.political_result.json`
member lists through the new path): Sakshi JG pg1 2/2 snapped (the rebate package + DL5, both
verified visually as the block-quality crops); AJ Siddipet pg2/3/4 4/4 snapped (10-13 of 12-13
member votes, incl. the R48/R33 banner blocks); NT Jangaon pg1 2/2 snapped (13/13, 4/4). ZERO
legacy fallbacks needed on the corpus.
- NOTE: replayed member lists come from OLD paid runs (stale region understanding) — e.g. the
Sakshi 'Transfers delayed!' entry maps to the rebate block because its old members [66,29] sit
there now. On the next paid run Claude selects members against the CURRENT regions (incl. the
synthesized Sakshi headlines, which are in regions.json and the prompt's region list), so the
member→block vote will be consistent end-to-end.
## New-run defects round (Sakshi_Jangaon District_20260513, first PAID run on new code) — 2026-06-10
User-reported bad crops on the fresh paper; three more Sakshi-gated passes (validated: old-run
Sakshi pg1 16 / pg2 17 UNCHANGED; AJ 12/18/14/21, NT 11/23/20/25 [110], JG 6/18 all EXACT):
- **pg2 a02_H106 was a 695×5055 column strip** (statue story + 4000px of masked white):
TWO causes. (a) The dance-photo caption R104 (page top) fell down-column into H106 —
its photo R44 lived in another block. FIX: **stray caption re-home** — a figure_title member
moves to the block that owns the image it abuts (±90px above/below, with **-30px overlap
tolerance**: PaddleOCR's photo box overlaps the caption strip by a few px, R104 vs R44 = -11px,
which defeated a 0-gap test on the first attempt). Donor block shrinks back to its members.
(b) The aviation story's heading is a `paragraph_title` 1200px below the statue story's
doc_title in-column → never promoted → whole stack merged. FIX: **stacked-story heading
promotion** — a pt ≥0.55 col wide with content directly beneath (≤150px) and the nearest
in-column base anchor **>900px** above becomes an anchor. The gate was first 600px and broke
the old-run rebate package (its sub-head R94 sits 614px under the widened R52 headline and
re-split, whiting R88's box over the stats infographic — caught by re-validating the old run);
900px keeps feature sub-heads folded and still promotes genuinely stacked stories.
- **pg2 a08_H100 stretched ~900px up with a foreign fragment above its own headline** (the NPDCL
story's tail folded down-column into H100). FIX: **stray text re-home** — a plain-text member
of an H block lying entirely ABOVE the anchor headline's top is never that article's content
(kickers/decks are title-typed); move it to the smallest other article box containing it.
- Verified visually: new-run a10_H106 (639×1326, exactly the statue story), a09_H100 (complete,
no stray top), old-run a02_H52 (infographic numbers 15,941/5.79/11,376/3.72/1,00,20,087 intact).
- New-run pages have NOT had a full visual audit (only the reported crops + block maps checked);
pg1 = 14 crops / pg2 = 16 on replay.
## NT Adilabad flood-special front page — NT rule split + late structural cut — 2026-06-10
NamastheTelangana_Adilabad_20260528 pg1 produced 5 page-scale OVERLAPPING mega-blocks. This page
is DETECTION-LIMITED at the source: 65 regions only, 4 doc_titles for ~8 stories, and BOTH big
feature headlines ('రెక్కల కష్టం.. నీళ్లపాలు', 'వడ్ల తరలింపు ఎక్కడిదాకా?') are INSIDE PaddleOCR
image boxes — invisible to every geometric pass incl. the synth-band detector (coverage test).
With anchors missing, the image-must-belong bind + grow/merge built page-tall blocks. Fixes:
- **NT horizontal-rule split enabled** (was Sakshi-only): same machinery, but only at rules
≥1.8 col (per the line study a ≥2-col rule is an article boundary on NT; the boxed grid's
~1-col caption/box rules must not cut). _replay_adilabad.py added (NTA_RUN env overridable).
- **NT LATE structural cut** (end of clusterer, after grow/merge/photo binds): the mid-pipeline
split runs BEFORE the passes that built the mega-block, so DL5 [668,901,4084,6302] was never
re-checked against the 4.4-col rule at y=4764 (and at mid-split time the bottom band's R30
headline wasn't yet a member, so the new-start gate failed). Final blocks (not locked/raw_rect/
section/furniture) are re-cut at ≥1.8-col rules with the underline guard; a rule ≥2.2 col cuts
even with no detected start below. DL5 → DL56 (the whole flood feature, verified coherent) +
H30 ('తడిసిన వడ్లు కొనాలి' bottom band, verified clean). pg1 5 → 6 crops.
- VALIDATION: NT Siddipet 11/23/20/25 EXACT (2+dl [110],[54,62],[67,121]); NT Jangaon 6/18 EXACT;
Adilabad pg2 31 crops unchanged (no shredding). AJ/Sakshi untouched (gate).
- HONEST REMAINDER: H44 still spans the page top (left story + photo row + nameplate share the
band; the 60%-block-span guard correctly refuses partial rules on a page-wide block) and the
H59/DL9 boxes still overlap neighbours. Only a better layout model (headline bands inside
photo boxes) or the paid pipeline's Claude-vision pass can fully fix this page. NT masthead
drop never fired here (nameplate is INSIDE image R46, no header region) — benign, the strip
rides atop the photo-row crop.
## ⚠ REPLAY-TOOLING BUG (found 2026-06-10, fixed): _ACTIVE_PAPER was never set for NT
`_replay_jangaon.py` / `_replay_namaste.py` never set `se._ACTIVE_PAPER`, so every prior "NT
validated EXACT" run measured with ALL paper-gated clustering passes OFF while the real pipeline
runs them ON. (AJ has no gated passes, Sakshi's replay always set it — only NT was affected.)
Both scripts now set it; `_replay_jangaon.py` takes JG_RUN env (default = the 130635 run; the
user-facing run of the same paper is ..._20260608_124720). FIRST true gated-NT measurement:
NT Siddipet 11/23/20/25 + 2+dl [110],[54,62],[67,121] EXACT (gated passes are no-ops there ✓),
JG both runs 6/18, Adilabad 6/31.
## NT Jangaon pg1 (user-reported: a01_DL3 white holes, a04_H28 over-merge, a05_DL2 foreign photo)
Root cause is the same family as Sakshi/Adilabad: THREE un-detected display headlines (brush-font
'భానుడి భగభగ', script 'నమ్మిండు..', DL2's heading). The heat feature's lower content (vendor +
ice-cream photos, captions, continuation column) folded DOWN to the 'ఆలస్యంపై రైతుల ఆగ్రహం'
headline 2200px below (nearest-anchor metric); the white holes in a01_DL3 were those stolen
regions masked out. Fixes (gated `in ("sakshi","namaste_telangana")` — AJ untouched):
- **Re-home family enabled for NT** (stray image+caption unit, stray caption, above-own-headline
members). The above-headline re-home is GENERALISED from text-only to text/image/figure_title
(title-typed members stay — kickers/decks are legitimate above a headline).
- **Synth headline bands enabled for NT — FRONT PAGE ONLY** (`n == 1`): inner NT pages are dense
and well-detected; un-gated synth split the protected Siddipet pg4 roundups (25→28, caught).
Band height cap 280→400 (tall display/script faces; ink-fraction still rejects photos).
- RESULT (JG 124720 pg1, counts unchanged at 6): DL3 = the COMPLETE heat feature (mem 8→13, no
white holes, verified visually); H28 starts AT its own headline [y3089, was 838] = exactly the
ఆగ్రహం article (verified); DL2 = clean single-column committee article, its foreign tractor
photo+caption re-homed to R29's procurement story (verified).
- Residual: DL2 remains heading-less (its display heading has no region and the synth band gates
didn't admit it) — hard-limitation class; the heading pixels sit above the crop.

266
EDGE_CASES.txt Normal file
View File

@ -0,0 +1,266 @@
================================================================================
TELUGU POLITICAL-ARTICLE EXTRACTOR - EDGE CASES, BUGS & RULES
================================================================================
Pipeline: PDF -> PaddleOCR layout regions -> Claude grouping
(identify_and_group_political_articles) -> deterministic geometric
safeguards in crop_political_articles() -> per-page crops ->
political_articles.json. Separator/line + page-bottom detection
lives in _lines.py (classical CV, no ML, no API).
This document lists every edge case / bug / rule handled so far, plus the ones
still in progress. Each point: PROBLEM -> RULE/FIX -> STATUS -> VERIFICATION.
Papers seen (identified by page-1 pixel height):
6422 = namaste_telangana (no printer calibration strip)
6549 / 6833 = andhra_jyothi (CMYK calibration strip at bottom)
7040-7060 = sakshi (CMYK calibration strip at bottom)
================================================================================
1. GROW-RIGHT TO THE BOUNDING VERTICAL WALL [FIXED]
================================================================================
PROBLEM:
An under-grouped article was cropped too NARROW - its body clearly continued
into the next column to the right, but Claude grouped only the left column, so
the crop stopped mid-article.
Example: run 20260602_093820 p003_a003 "రైతన్న లోడెత్తేదెలా?".
RULE / FIX (in crop_political_articles, after the Rule-B floor block):
- Detect a long vertical separator line (len >= 600 px) that sits to the RIGHT
of the current crop (x > rx2 + 10) and overlaps the article vertically
(> 50% y-overlap). That line is the article's right "wall".
- Absorb every text / image / figure_title / paragraph_title region whose
center-x falls between the current right edge (rx2) and the wall.
- Push the right edge out to the wall: rx2 = wall_x, right_cap = wall_x.
- The padding step clamps x2 = min(x2, right_cap) so it never overshoots.
WIDTH-CAP GUARD (prevents over-grab):
too_far = (wall_x - rx2) > max(art_w, 1)
If the wall is farther away than the article's own width, it is a page-margin
rule swallowing a NEIGHBOUR, not the article's wall -> do NOT grow.
Also bail if a FOREIGN doc_title (a different story's headline) lies inside the
strip we are about to absorb.
Regression that drove this guard: run 20260601_234709 p007_a001 grew from
[77,634,1035,1609] to [77,634,4127,2581], eating neighbour "భూముల విలువ పెంపు".
STATUS: Fixed & committed (HEAD 2345453). Prints "➡️ Grew ...".
================================================================================
2. ROBUST JSON PARSING OF CLAUDE'S GROUPING RESPONSE [FIXED]
================================================================================
PROBLEM:
One page's grouping response was empty / truncated / fence-wrapped, and the
fragile parse of content[0].text crashed the WHOLE run:
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Example: a run crashed on page 10.
RULE / FIX (smart_extractor.py):
- New helper _extract_json_obj(text): strips ```json fences / prose, extracts
the outermost {...} object, raises ValueError on empty/garbage.
- Gather text from ALL response content blocks (not just block 0).
- max_tokens raised to 8192 to avoid truncated JSON.
- On (ValueError, json.JSONDecodeError): save page_NNN.badresponse.txt for
debugging, then RETRY once in strict mode ("Return ONLY the raw JSON ...").
- If the retry also fails: return {"political_articles": [], "rejected": []}
so that ONE bad page degrades gracefully instead of killing the run.
(json.JSONDecodeError is a subclass of ValueError - both are caught.)
STATUS: Fixed.
================================================================================
3. RULE-B OVER-FIRE: in-article subhead wrongly treated as a new headline [FIXED]
================================================================================
PROBLEM:
Rule B floors a crop at the next headline that starts a DIFFERENT story in the
same column. But a paragraph_title that is really an IN-ARTICLE byline /
subhead (narrow, with body text continuing below it) was being treated as a
new-article headline, so the crop was floored far too high and dropped 9 of the
article's member regions.
Example: run 20260603_112014 p003_a001 "తీరని కొనుగోళ్ల వెతలు" - paragraph_title
R79 [1193,5226,2035,5271] (an internal subhead) floored the crop at y5226.
RULE / FIX (Rule B block in crop_political_articles):
- A doc_title floors the crop UNCONDITIONALLY (a real new story headline).
- A paragraph_title floors ONLY if there is a HORIZONTAL SEPARATOR RULE
directly above it (a printed line above a heading => genuine new section).
Implemented via _rule_above(): a detected h-line whose y is between the
primary headline bottom and the candidate top, overlapping > 40% of the
candidate's width.
- R79 has NO rule above -> no longer floors (correct).
VERIFICATION (offline, no API):
- 20260603_112014 p003_a001 now keeps all members; bbox grew correctly down.
- 20260602_093820 R81 [1162,1879,2063,1926] HAS a rule above (y1707,
overlap 787) -> STILL floors correctly (legitimate case preserved).
- 20260601_234709 known-good crops unaffected.
STATUS: Fixed.
NOTE / FOLLOW-UP: the cross-run downward-growth sweep (>250 px) flagged a few
crops that grew DOWN after this change (they were previously floored by a
paragraph_title without a rule above). Largest legit one is the 112014 fix
itself. Others to re-confirm are NOT over-merges (swallowing a separate article
below):
20260602_081720 p004_a001 DOWN+1951
20260531_231056 p002_a004/a005/a006, p002_a001
20260601_005917 p002_a006
20260531_205325 p002_a005
[STILL TO VALIDATE]
================================================================================
4. PAGE-BOTTOM PRINTER CALIBRATION STRIP DETECTION [FIXED - needs wiring]
================================================================================
PROBLEM:
Andhra Jyothi & Sakshi print a CMYK colour-calibration strip (registration
dots + grey bars) just above the bottom paper margin. Article crops whose
member regions reach the page bottom (esp. footer-type regions) bleed this
strip of coloured dots into the article image.
RULE / FIX (_lines.py: detect_content_bottom):
Returns the y at the TOP of the calibration band (clamp crops to it), or the
full height H when there is no strip (nothing to clamp).
Algorithm:
a. Search the bottom search_frac (12%) of the page.
b. Per-row colour fraction col_frac (saturated CMYK: sat>55 & max>80) and ink
fraction; the strip = the bottom-most run of rows whose col_frac >= 0.06.
c. Require a WHITE PAPER MARGIN below the band down to the page edge
(>=90% blank) - a real strip is followed by margin.
d. Walk UP from the band bottom while still coloured, tolerating small white
gaps between dot rows (<=12 px).
e. Guard max_band (160 px): if the band is too tall it is fused with a photo
-> bail (return H).
f. PEAK-COLOUR DISCRIMINATOR (key false-positive fix): require the band's
peak col_frac >= 0.15. Real registration strips are dense multi-hue CMYK
dots peaking 0.22-0.45; a single-hue coloured ad/notice box peaks ~0.11.
FALSE POSITIVE that drove the peak-colour gate:
namaste 20260603_113847 p004 has a GREEN-bordered notice box near the bottom
(col peak 0.113) embedded in article text. An earlier "white gutter above"
gate rejected it but WRONGLY rejected real AJ strips on busy pages (p3/p6 had a
photo extending into the gutter). The peak-colour gate (0.15) cleanly
separates: namaste box 0.11 -> reject; AJ 0.25 / Sakshi 0.22 / AJ-6549 0.45 ->
accept.
VERIFICATION (offline):
AJ-6833 -> clamps ~6674-6687 (drop ~150)
AJ-6549 -> clamps ~6377-6401 (drop ~150-172) (was a false NEGATIVE before)
Sakshi -> clamps ~6720-6721 (drop ~332)
namaste -> H (no clamp) (correct - no strip; green box not clamped)
AJ pages with a full-bleed bottom photo -> H (acceptable: no clean strip).
STATUS: Detector fixed & verified across all 4 papers.
PENDING: wire into crop_political_articles as a per-page y2 clamp
(y2 = min(y2, content_bottom), computed once per page); then re-verify
112014 p003_a001 no longer includes calibration dots + regression-check.
================================================================================
5. MULTI-COLUMN BANNER HEADLINE = ONE ARTICLE SPANNING ALL ITS COLUMNS
[FIXED]
================================================================================
PROBLEM:
When a single wide headline (banner) spans MULTIPLE columns, everything beneath
it (down to its bottom rule) is ONE article - even though it contains several
internal sub-headlines / sub-sections and photos. The foreign-headline clip
(Rule A) and Rule B wrongly treat those internal sub-headlines as new stories
and truncate the crop, so the banner article comes out severely under-cropped.
Example: namaste 20260603_113847 p004 article 1
"రైతులను వీడని అన్‌లోడింగ్ సమస్య"
Banner headline id=64 [1264,423,3500,628] (width 2236 = 0.53 of page,
crosses the internal vertical rule at x=2388 -> multi-column banner).
Bounding vertical walls: x=1232 (left), x=3545 (right).
Bottom rule: full-banner-width h-line at y=2756 (x[1268..3512], len 2244).
Correct crop ~= [1232, 423, 3545, 2756].
Current (buggy) crop = [1264,423,3500,2183] - cut off ~570 px too short and
not extended to the side walls.
RULE (to implement):
- BANNER TEST: an article's top headline (doc_title) is a banner if it CROSSES
at least one internal vertical separator line (i.e. it spans >1 column).
(Equivalently width >= ~0.4 of page width.)
- For a banner article:
* Horizontal extent = bounded by the vertical separator lines just LEFT and
just RIGHT of the headline (left_wall .. right_wall).
* Bottom = the LOWEST/own bottom horizontal rule that spans most of the
banner width (x-overlap > ~0.6 of banner width) and lies below the
headline -> the first such full-width rule below the grouped content.
* Crop = [left_wall, headline_top-pad, right_wall, bottom_rule].
* SKIP the internal foreign-headline clip and Rule B floor for this article
(its sub-headlines are part of it).
- "If the multi-column header HAS a bottom line, crop to it; if it does NOT,
fall back to the previous (member-union + Rule A/B) logic." (user's words)
IMPLEMENTED (crop_political_articles, banner_box branch):
- BANNER TEST: topmost member doc_title width >= 0.40 * page_width.
- left_wall / right_wall = vertical separator lines reaching alongside the
headline, just left of / right of it (fallback to member edge if none).
- bottom_rule = the first horizontal line BELOW the grouped content whose
x-span overlaps > 60% of the banner width (the >60% width test rejects short
caption/photo underlines - see point 6). Require bottom_rule > headline_top+200.
- Only when BOTH walls-ish extent AND a bottom_rule exist do we set
banner_box = [left_wall, headline_top, right_wall, bottom_rule]; this OVERRIDES
the per-column clip / Rule-B floor / grow-right results and restores the full
member set. No bottom rule -> banner_box stays None -> normal logic (fallback).
VERIFICATION (offline, namaste 20260603_113847 p4 art1):
- Banner fired: x[1232..3545] y[423..2756] -> padded crop [1217,408,3560,2771].
- Visual check: crop now contains the full headline + every column beneath it
down to the bottom rule (previously cut at y~2183).
- Regression: banner does NOT fire on normal single-column-headline articles
(threshold 0.40*pw); other pages' crops unchanged.
STATUS: Fixed & verified offline.
================================================================================
6. HORIZONTAL RULE WITH TEXT/IMAGE DIRECTLY ABOVE IT = UNDERLINE, NOT SEPARATOR
[PARTIALLY APPLIED - new rule, affects points 3, 4, 5]
================================================================================
PROBLEM:
Not every horizontal printed line separates two articles. A line that has text
or an image DIRECTLY ABOVE it is an UNDERLINE - a caption underline beneath a
photo, or a decorative underline under a heading. Using such a line as an
article boundary (a Rule-B "rule above", or a banner BOTTOM rule, or a page
separator) is wrong.
Examples (user-supplied): photo captions like "జక్కల్ శివారులో నిర్మించిన ..."
and "నర్సాపూర్‌లో మాట్లాడుతున్న మాజీ మంత్రి ..." each sit ABOVE a thin rule -
those rules underline the caption, they do not divide articles.
RULE (to implement):
When deciding whether a horizontal line is an ARTICLE SEPARATOR (for Rule B's
"rule above" check, for the banner bottom rule in point 5, and anywhere a
h-line is used as a boundary):
- Reject the line if the tight band immediately ABOVE it is mostly INK
(text) or is an image/figure region -> it is an underline, not a separator.
- A genuine article separator sits in a whitespace gutter (paper above AND
below), OR has a NEW headline starting just below it.
(This is analogous to the existing _isolated() whitespace-gutter test in
_lines.py used for separator-line detection.)
APPLIED SO FAR:
- Banner bottom rule (point 5): only horizontal lines spanning > 60% of the
banner width qualify, which already excludes short caption/photo underlines
(those are < ~0.5 column wide).
- Note: _lines.detect_separator_lines() already runs the _isolated() gutter
test, so the h-lines fed to Rule B / banner are whitespace-gutter lines, not
arbitrary text strokes.
STILL TO DO:
- Add an explicit "ink/image directly above" rejection to Rule-B's
_rule_above() so a caption underline beneath a photo can never corroborate a
paragraph_title floor, even if a future detector change lets one through.
STATUS: Partially applied (via width test + existing gutter detection);
explicit ink-above guard for Rule B still to add.
================================================================================
CROSS-CUTTING NOTES / CONVENTIONS
================================================================================
- BILLING/SECURITY CONSTRAINT: do NOT call the Claude APIs while iterating. All
verification is done OFFLINE via fixture replay of saved page PNGs +
regions.json + cached per-page political_result.json.
- Calibration strip is NOT an OCR region; crops only reach it via member-region
bboxes (especially footer/header/number page-furniture regions) or padding.
Consider dropping footer/header/number members from crops as well.
- Test harnesses (in /tmp): replay_crop.py, replay_run.py, replay_regress.py
(new-vs-stored bbox diff), sweep_floor.py (flags downward growth >250 px),
test_bottom.py / test_all.py (detect_content_bottom across papers).
================================================================================
can u creat img with boundaris of ech articl in th  empty whit pg wit diffrnt colr ech articl so tht i can see if thy r overlppng r ?

BIN
Extractor_Guide.docx Normal file

Binary file not shown.

BIN
HOW_IT_WORKS.docx Normal file

Binary file not shown.

296
PROCESS_AND_PROMPTS.md Normal file
View File

@ -0,0 +1,296 @@
# Telugu Newspaper Political Article Extractor
## Complete Process & Prompts Reference
---
## STEP-BY-STEP PROCESS
### Step 1: Upload PDF
- User uploads Telugu newspaper PDF on localhost:5000
- File: `app.py` (Flask web server)
- Cost: Free
### Step 2: PDF to Page Images
- PyMuPDF converts each PDF page to high-res PNG (4200 x 7400 pixels)
- File: `extractor.py``render_pdf_pages()`
- Output: `output/<job_id>/pages/page_001.png`, `page_002.png`, etc.
- Cost: Free (local)
### Step 3: Layout Detection
- PaddleOCR LayoutDetection scans each page image
- Finds pixel-accurate bounding boxes for every text block, headline, image, table
- Each region gets: ID, type (text/title/image/header/etc.), bbox [x1,y1,x2,y2], confidence score
- File: `extractor.py``detect_regions()`
- Output: `output/<job_id>/pages/page_001.regions.json`
- Debug: `output/<job_id>/pages/page_001_regions_debug.png` (visual bounding boxes)
- Cost: Free (local)
- Typically finds 70-130 regions per page
### Step 4: Article Grouping (Claude API)
- Sends page thumbnail image + all region IDs/coordinates to Claude
- Claude groups regions into complete articles (which text blocks belong to the same news story)
- File: `extractor.py``_group_with_claude()`
- Model: claude-opus-4-7
- Cost: 1 API call per page
- Output: `output/<job_id>/pages/page_001.articles.json`
### Step 5: Crop ALL Articles
- Each grouped article is cropped into its own PNG using PaddleOCR's precise bounding boxes
- Every article gets a unique ID: p001_a001, p001_a002, etc.
- File: `extractor.py``crop_articles()`
- Cost: Free (local)
### Step 6: Political Filter (Claude API)
- Sends page image + list of article IDs (with headlines and positions) to Claude
- Claude evaluates each article with Q1/Q2 questions
- Returns only the politically significant IDs
- File: `extractor.py``_filter_articles_with_claude()`
- Model: claude-opus-4-7
- Cost: 1 API call per page
- Output: `output/<job_id>/filter_reasoning_page1.json`, `political_articles.json`
### Step 7: Keep or Delete
- Articles Claude flagged as significant: kept, updated with political analysis
- All other articles: deleted from output folder
- Cost: Free (local)
### Step 8: Generate Outputs
- `opposition_daily_brief.txt` — readable brief ranked by priority
- `political_articles.json` — structured data
- `all_political_images/` — all kept images in one folder
- `political_articles.pdf` — all images in single PDF
- Cost: Free (local)
### Step 9 (Optional): Text Extraction
- Run `python claude_ocr.py` separately
- Sends each kept article image to Claude Vision API
- Extracts accurate Telugu text in proper reading order
- File: `claude_ocr.py`
- Cost: 1 API call per article
---
## ALTERNATIVE: Smart Extractor (ONE Claude call per page)
File: `smart_extractor.py`
### Steps:
1. PDF → page images (same as above)
2. PaddleOCR → all regions with precise boxes (same as above)
3. ONE Claude call: sends page image + all region IDs → Claude identifies political articles AND groups their region IDs → returns only political groups
4. Crop using PaddleOCR's precise boxes for only the political articles
5. Generate outputs
### Difference from app.py:
- app.py: 2 Claude calls per page (grouping + filtering)
- smart_extractor.py: 1 Claude call per page (combined)
---
## GROUPING PROMPT (Step 4)
Model: claude-opus-4-7
```
You group newspaper page regions into INDIVIDUAL articles. Each news story is a SEPARATE article.
You receive:
1. A thumbnail of the full page (for spatial context).
2. A JSON list of regions on the page. Each has:
- id (int)
- type ("title", "text", "figure", "table", etc.)
- bbox [left, top, right, bottom] in pixels (top-left origin)
- top_text: a Tesseract OCR snippet from the top of text regions
- dateline: if non-null, the region starts an article body (strong signal)
CRITICAL RULES — BE CONSERVATIVE, SPLIT MORE:
1. A `title` or `doc_title` region ALWAYS starts a NEW article. Every title = separate article.
2. A region with a non-null `dateline` ALWAYS starts a new article body.
3. Text regions attach to the NEAREST title above them IN THE SAME COLUMN ONLY.
4. Newspapers have COLUMNS. A region in column 1 NEVER belongs to an article in column 2, even if vertically close.
5. When in doubt, SPLIT into separate articles rather than merge. Too many small articles is better than one giant article containing multiple stories.
6. An article should typically have 1-5 regions. If an article has more than 6 regions, you are probably merging multiple stories — split them.
7. A banner title spanning multiple columns owns ONLY the text directly below it, not the entire page.
8. Types "header", "footer", "page_number" — make these their own separate article.
9. Figures/images attach to the nearest title above them in THEIR column only.
10. NEVER merge two articles that have different headlines or topics into one.
Return ONLY valid JSON, no prose, no markdown fences:
{
"articles": [
{
"article_id": 1,
"title_region_id": <int or null>,
"member_region_ids": [<int>, ...],
"headline_hint": "<short string or null>",
"dateline": "<string or null>"
}
]
}
Order articles top-to-bottom, then left-to-right by topmost member region.
Every region (except headers/footers) appears in exactly one article.
AIM FOR 15-25 articles per page for a typical Telugu newspaper front page.
```
---
## POLITICAL FILTER PROMPT (Step 6)
Model: claude-opus-4-7
```
You are a senior opposition party strategist in Telangana state, India.
I have already cropped the following articles from this newspaper page:
[article IDs with headlines and positions]
For EACH article, ask yourself these TWO questions:
QUESTION 1: "Does this article describe actual DAMAGE, LOSS, DEATH, CORRUPTION, or SUFFERING happening to people in Telangana? And can the government (state OR central) be BLAMED for it — either for causing it, failing to prevent it, or failing to respond to it?"
- The article must describe a NEGATIVE EVENT that has already occurred — not a future plan, not an announcement, not a statistic.
- There must be a VICTIM — someone who died, lost money, was cheated, is suffering, or was harmed.
- The government (Telangana state OR central/BJP) can be blamed — for causing it, failing to prevent it, or failing to respond to it.
- Central government failures also qualify IF the article describes actual harm to Telangana citizens (e.g., NEET paper leak harming Telangana students, central policy causing job losses in Telangana).
- If the article describes something POSITIVE (new scheme, development, achievement) → answer is NO.
- If the article describes a PLAN or ANNOUNCEMENT (not something bad that happened) → answer is NO.
- If there is NO victim and NO damage described → answer is NO.
- RESPONSIBILITY CHECK: Ask "WHO caused this damage?" If the damage was caused by nature (fire, flood, lightning), individual negligence (reckless driving, personal accident), criminals (theft, murder), or private parties — and the government had NO role in causing or preventing it — answer is NO. Only answer YES if the government is CLEARLY responsible through its policy, negligence, corruption, or failure to act.
- If YES → proceed to Question 2
- If NO → REJECT this article immediately
QUESTION 2: "Can the opposition party DIRECTLY USE this article to attack the Telangana government in a press conference, assembly session, or public rally?"
- If YES → SELECT this article
- If NO → REJECT this article
BOTH answers must be YES to select. If either answer is NO, reject.
DEDUPLICATION: If two or more articles cover the SAME issue or event (e.g., same farmer protest, same accident, same scam), only SELECT the ONE with the most complete coverage or the larger article. REJECT the duplicates. Do not keep two articles about the same news story.
NOW go through EACH article ID I gave you. Ask Q1 and Q2 for each. Only select if BOTH are YES.
IMPORTANT: This application is for TELANGANA STATE politics only. Farmer issues are the MOST IMPORTANT — always select if farmers are suffering.
DO NOT SELECT — automatically REJECT:
- International news (wars, foreign affairs, immigration)
- Government achievements, new schemes, welfare announcements, development projects
- Newspaper masthead, headers, advertisements, classifieds
- Sports, entertainment, cinema, horoscopes
- General statistics without actual damage (birth rate, population, GDP)
- News about other states
- Resignations — ALWAYS REJECT unless the article explicitly contains words like corruption, scam, fired, forced out, or political pressure. A person resigning for personal, health, family, or workload reasons is NOT a government failure.
- PM Modi speeches or party meetings (unless specific policy failure hurting Telangana people)
- Individual accidents (road accidents, electric shocks, drownings, fire accidents) — these are NOT government failures, they can happen due to personal negligence or bad luck. Only select if there is a MASS disaster (10+ deaths) or CLEAR systemic government negligence (e.g., bridge collapse, building collapse, factory fire due to no safety inspections).
Return a JSON object with TWO lists:
1. "reasoning": for EVERY article (selected or not), explain your thinking:
- "id": the article ID
- "headline_english": English translation
- "q1": "YES" or "NO"
- "q1_reason": why (1 sentence)
- "q2": "YES" or "NO" (skip if q1 is NO)
- "q2_reason": why (1 sentence, skip if q1 is NO)
- "decision": "SELECT" or "REJECT"
2. "selected": only the articles where both Q1 and Q2 are YES:
- "id": the article ID
- "headline_english": English translation of the headline
- "q1_answer": your answer to Question 1 (1 sentence)
- "q2_answer": your answer to Question 2 (1 sentence)
- "attack_angle": what opposition should say in a press conference (1 sentence)
- "priority": "high" or "medium"
- "category": one of corruption, governance_failure, public_grievance, law_order, health, education, infrastructure, ruling_party_crisis, farmer_issues
Return ONLY valid JSON. No markdown. If no articles pass both questions, return {"reasoning": [...], "selected": []}
```
---
## SMART EXTRACTOR PROMPT (Combined — smart_extractor.py Step 3)
Model: claude-opus-4-7
```
You are a senior opposition party strategist in Telangana state, India.
I am showing you a Telugu newspaper page image. I have also detected {num_regions} text/image regions on this page using OCR layout detection. Each region has an ID and precise pixel coordinates.
REGIONS:
[list of all region IDs with types and bbox coordinates]
YOUR TASK: Look at the newspaper page and identify ONLY the politically significant articles. For each political article, tell me which region IDs belong to it so I can crop them together precisely.
For EACH article on the page, ask:
QUESTION 1: "Does this article describe actual DAMAGE, LOSS, DEATH, CORRUPTION, or SUFFERING happening to people in Telangana? And can the government (state OR central) be BLAMED for it?"
- Must be a NEGATIVE EVENT that already occurred — not a plan, announcement, or statistic.
- Must have a VICTIM — someone who died, lost money, was cheated, is suffering.
- Government (Telangana state OR central/BJP) can be blamed — for causing, failing to prevent, or failing to respond.
- Central government failures qualify IF actual harm to Telangana citizens.
- RESPONSIBILITY CHECK: If damage caused by nature, individual negligence, criminals, or private parties with NO government role → NO.
- If POSITIVE (new scheme, development, achievement) → NO.
- If PLAN or ANNOUNCEMENT → NO.
- If NO victim and NO damage → NO.
QUESTION 2: "Can the opposition party DIRECTLY USE this article to attack the government?"
Only include articles where BOTH answers are YES.
DEDUPLICATION: Same issue appearing in multiple places → group ALL related regions into ONE article.
TELANGANA STATE politics only. Farmer issues are MOST IMPORTANT.
DO NOT SELECT:
- International news, other states news
- Government achievements, new schemes, welfare announcements, development projects
- Newspaper masthead, headers, ads, classifieds
- Sports, entertainment, cinema, horoscopes
- General statistics without actual damage
- Resignations (unless corruption/scam/fired/forced out)
- PM Modi speeches (unless specific policy failure hurting Telangana)
- Individual accidents (road, electric shock, drowning, fire) — only if MASS disaster (10+ deaths) or CLEAR systemic government negligence
For each selected political article, return:
- "headline_telugu": exact Telugu headline from the page
- "headline_english": English translation
- "member_region_ids": list of region IDs that belong to this article (for precise cropping)
- "q1_reason": why this is government failure (1 sentence)
- "q2_reason": how opposition can use this (1 sentence)
- "attack_angle": what to say in press conference (1 sentence)
- "priority": "high" or "medium"
- "category": one of corruption, governance_failure, public_grievance, law_order, health, education, infrastructure, ruling_party_crisis, farmer_issues
Also return "rejected" list with brief reasoning for EVERY rejected article.
Return ONLY valid JSON:
{
"political_articles": [...],
"rejected": [
{"headline": "...", "reason": "..."}
]
}
If no political articles found, return {"political_articles": [], "rejected": [...]}
```
---
## FILES REFERENCE
| File | Purpose | Runs |
|------|---------|------|
| `app.py` | Flask web server, upload UI | `python app.py` |
| `extractor.py` | All processing logic (Steps 2-8) | Called by app.py |
| `smart_extractor.py` | Standalone CLI, combined approach | `python smart_extractor.py newspaper.pdf` |
| `claude_ocr.py` | Extract Telugu text from kept images | `python claude_ocr.py` |
| `political_filter.py` | Standalone political filter (backup) | `python political_filter.py` |
## API COSTS PER PAGE
| Approach | Claude Calls | What |
|----------|-------------|------|
| app.py | 2 per page | 1 grouping + 1 filtering |
| smart_extractor.py | 1 per page | Combined grouping + filtering |
| claude_ocr.py | 1 per article | Text extraction (optional) |

179
README.md Normal file
View File

@ -0,0 +1,179 @@
# Telugu Article Extractor
A local web app that takes a Telugu newspaper PDF and produces one PNG +
one TXT file per article.
## Pipeline
```
PDF
│ Stage 1 PyMuPDF → page_001.png (target: 4200×7400 px, fits aspect)
Pages
│ Stage 2 PaddleOCR PP-Structure → regions.json
│ (title / text / figure rectangles with bboxes)
Regions
│ Stage 3 Article grouping → articles.json
│ Primary: Claude API (spatial reasoning over regions,
│ sees page thumbnail + region JSON)
│ Fallback: column-aware + dateline-anchored Python rules
│ • title region starts a new article
│ • "<place>, మే <day> (ఆంధ్రజ్యోతి):" dateline
│ also starts a new article
│ • regions partitioned by column first to avoid
│ cross-column contamination
Articles (bboxes)
│ Stage 4 PIL crop → article.png per article
Article PNGs
│ Stage 5 Tesseract (lang=tel) → article.txt per article
Done. Browse in the UI, download all as zip.
```
## Setup
### 1. System dependencies
```bash
# Ubuntu / Debian
sudo apt update
sudo apt install -y tesseract-ocr tesseract-ocr-tel libgl1
```
`tesseract-ocr-tel` is the Telugu language data — required for the per-article
OCR step.
`libgl1` is needed by paddlepaddle on headless Linux.
### 2. Python environment
```bash
cd telugu_extractor
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
First run of paddlepaddle/paddleocr will download model weights (~400 MB)
into `~/.paddleocr/`. This happens once.
### 3. (Optional) Enable Claude grouping
Stage 3 (article grouping) works two ways:
- **Without an API key**: falls back to column-aware + dateline Python
rules. Works fine for tidy pages; struggles on banner headlines and
cross-column layouts.
- **With an API key**: Claude looks at a thumbnail of the page plus
the regions JSON and does the spatial reasoning. Much better on
complex layouts. Costs ~$0.010.05 per page.
To enable, set the environment variable before running:
```bash
export ANTHROPIC_API_KEY="your-api-key-here"
```
Get a key at https://console.anthropic.com/. If the variable isn't set
or the API call fails, the app silently falls back to the Python rules.
### 4. Run
```bash
python app.py
```
Then open <http://localhost:5000>.
## How to use
1. On the homepage, choose a PDF and pick a target render size.
Defaults: 4200 × 7400 px (≈ 600 DPI for a tabloid newspaper page).
Each page is scaled to fit *inside* this box preserving aspect.
2. Click "Start extraction". A background job kicks off and the page
redirects to a job view that polls for status.
3. When the job finishes, every article is shown as a card with:
- The cropped image (`article.png`)
- A link to its OCR'd text (`article.txt`)
- The detected dateline (e.g. `చిన్నగూడూరు, మే 15 (ఆంధ్రజ్యోతి):`)
if the grouper found one
- The first non-empty line of OCR text as a headline preview
4. Click "Download all (zip)" to grab everything in one file.
## What's in each job folder
```
output/<timestamp>/
├── input.pdf the uploaded PDF
├── meta.json job metadata
├── pages/
│ ├── page_001.png rendered page
│ ├── page_001.regions.json raw PaddleOCR layout output
│ └── page_001.articles.json grouping result
└── articles/
└── p001_a001/
├── article.png per-article crop
├── article.txt per-article OCR
└── info.json bbox + dateline + member regions
```
Everything is on disk and inspectable. If a crop looks wrong, you can
look at `pages/page_NNN.articles.json` to see exactly which regions
were grouped together.
## Tuning the grouping
Stage 3 has two implementations in `extractor.py`:
### Path A: Claude API (`_group_with_claude`)
The system prompt is in `CLAUDE_GROUPING_PROMPT`. It tells Claude:
- Use `title` regions as article starts
- Use the `dateline` field as a strong "this is an article body start" signal
- Respect column boundaries
- Handle banner headlines that span columns
- Pull quotes inside another article's bbox belong to that article
To tune, edit the prompt. To debug, look at `pages/page_NNN.articles.json`
and check the `grouped_by` field — `"claude"` means the API call worked,
`"rules"` means it fell back.
### Path B: Python rules (`_group_with_rules`)
Used when no API key is set or the API call fails.
1. **Column partition** — regions clustered by `left` coordinate.
2. **Within each column**: `title` region OR dateline match starts a
new article; everything else attaches to the current one.
To tune the rules:
- Adjust `DATELINE_RE` for different paper conventions.
- Adjust the column-clustering threshold in `_assign_columns`.
## Known limitations
- **L-shaped articles**: an article whose body wraps around a photo into
a non-rectangular shape can't be captured by one bbox. The current
pipeline produces the smallest rect that covers everything, which may
include a sliver of a neighboring article. You'd need a polygon-based
crop to fix that — out of scope here.
- **Cross-page continuations** (`మిగతా 3వ పేజీలో`): not stitched. Each
page is processed independently.
- **PaddleOCR layout model is trained mostly on English/Chinese
documents.** Expect 8095% of articles to be bounded correctly out of
the box. Edge cases are why the per-job folder keeps the raw
`regions.json` — so you can audit and tune.
## License & data
All processing is local. The app never sends your PDF or anything
extracted from it to a remote service. The PaddleOCR model weights are
downloaded once from PaddlePaddle's CDN on first run.

5
_aj_base.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

5
_aj_val.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

5
_aj_val2.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

5
_aj_val3.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

5
_aj_val4.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

5
_aj_val5.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 12 crops, 9 datelines | Headline-strip:2, Floor-trim:2, orphan overlap:2
page 2: 18 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2
page 3: 14 crops, 7 datelines | bullet titles flagged:1
page 4: 21 crops, 18 datelines | bullet titles flagged:1, Headline-strip:2, Block dateline split:2, Multi-column body:2
DONE -> output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_final

4
_cls.txt Normal file
View File

@ -0,0 +1,4 @@
R16 text bbox=[119, 616, 612, 1899] phones=3 kw=[] :: జనగామ, మే 19 (నమస్తే తెలంగాణ) : ఆల్‌ ఇండియా కెమిస్
R89 paragraph_title bbox=[121, 2887, 595, 2978] phones=0 kw=['in the court'] :: IN THE COURT OF THE HONBLE JUNIOR CIVIL JUDGE AT G
R93 text bbox=[174, 4010, 545, 4080] phones=1 kw=['advocate'] :: ADVOCATE - JANGAON Cell: 9848259371
R87 text bbox=[150, 5143, 604, 6267] phones=8 kw=['classified', 'contact'] :: For Classified Ads Please Contact... 9182777552 HA

34
_cmp_mc.py Normal file
View File

@ -0,0 +1,34 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
def blocks_for(pg, strip_mc):
png = D / f"page_{pg:03d}.png"
regs = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
regs = se.drop_masthead_regions(regs, "andhra_jyothi", pg, png)
ds = se.find_article_starts_by_dateline(regs, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regs)
if strip_mc:
bars = [{k: v for k, v in b.items() if k not in ("mc", "anchor")} for b in bars]
bl = se.cluster_all_article_blocks(regs, dateline_starts=ds, sep_lines=bars)
return {b["anchor_id"]: tuple(sorted(b["members"])) for b in bl}
for pg in (1, 2, 3, 4):
with contextlib.redirect_stdout(io.StringIO()):
cur = blocks_for(pg, False)
base = blocks_for(pg, True)
print(f"==== PAGE {pg} ====")
changed = False
for k in sorted(set(cur) | set(base)):
if cur.get(k) != base.get(k):
changed = True
print(f" anchor {k}: base={base.get(k)}")
print(f" curr={cur.get(k)}")
if not changed:
print(" (no change from mc clamp)")

18
_dump_all.py Normal file
View File

@ -0,0 +1,18 @@
import sys, json
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regs0 = json.loads((D / "page_001.regions.json").read_text())["regions"]
regs = se.drop_masthead_regions(regs0, "andhra_jyothi", 1, png)
ds = se.find_article_starts_by_dateline(regs, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regs)
bl = se.cluster_all_article_blocks(regs, dateline_starts=ds, sep_lines=bars)
allmem = set()
for b in sorted(bl, key=lambda b: (b["bbox"][1], b["bbox"][0])):
allmem |= set(b["members"])
print(f" {b['kind']:>1} anchor={b['anchor_id']:>3} bbox={b['bbox']} members={sorted(b['members'])}")
present = {r["id"] for r in regs if r.get("type") not in ("header","number")}
print("MISSING (not in any block):", sorted(present - allmem))

40
_gap_probe.py Normal file
View File

@ -0,0 +1,40 @@
import sys, json
sys.path.insert(0, "/Users/kranthikumar/Documents/telugu_extractor")
from pathlib import Path
import smart_extractor as se
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
def headline_above(regions, start_bboxes, body, max_gap):
cx = (body[0] + body[2]) / 2
cands = [r for r in regions
if (r.get("type") == "doc_title" or r.get("_promoted_from"))
and r["bbox"][0] <= cx <= r["bbox"][2]
and r["bbox"][3] <= body[1] + 40
and (body[1] - r["bbox"][3]) <= max_gap]
if not cands:
return None, None
h = max(cands, key=lambda r: r["bbox"][3])
gap = body[1] - h["bbox"][3]
for ob in start_bboxes:
if ob is body:
continue
ocx = (ob[0] + ob[2]) / 2
if h["bbox"][3] < ob[1] < body[1] and cx - 120 <= ocx <= cx + 120:
return None, gap
return h["id"], gap
for pg in (1, 2, 3, 4):
png = D / f"page_{pg:03d}.png"
regions = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
regions = se.drop_masthead_regions(regions, "andhra_jyothi", pg, png)
ds = se.find_article_starts_by_dateline(regions, str(png), "andhra_jyothi")
sb = [s["bbox"] for s in ds]
print(f"\n==== PAGE {pg} ====")
for s in ds:
rid = s["region_id"]
row = []
for g in (900, 1000, 1100, 1300):
hid, gap = headline_above(regions, sb, s["bbox"], g)
row.append(f"g{g}:R{hid}" )
print(f" DL R{rid:>3} gap_to_nearest_doc_title above -> " + " ".join(row))

36
_jg_base.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1483,838,4073,3081] 2590x2243 mem=13 {'text': 8, 'image': 3, 'figure_title': 2}
R 4 DL bbox=[105,846,1467,4295] 1362x3449 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,3089,3031,4921] 1533x1832 mem=13 {'text': 8, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R 2 DL bbox=[3039,3957,4081,6269] 1042x2312 mem=4 {'text': 2, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,3211,6283] 1711x1271 mem=5 {'doc_title': 1, 'text': 2, 'figure_title': 1, 'image': 1}
[Containment-merge] x4
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val2.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

38
_jg_val3.log Normal file
View File

@ -0,0 +1,38 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 78 F bbox=[123,4139,626,4285] 503x146 mem=1 {'image': 1} IMG-ONLY(no text) SINGLE-region
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 82 F bbox=[137,4423,618,4782] 481x359 mem=2 {'text': 1, 'paragraph_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R 44 F bbox=[133,4811,616,5113] 483x302 mem=1 {'text': 1} SINGLE-region
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val4.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val5.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val6.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val7.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=8 {'text': 7, 'image': 1}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,838,3031,4921] 1533x4083 mem=18 {'doc_title': 1, 'text': 9, 'image': 3, 'paragraph_title': 2, 'figure_title': 3}
R 2 DL bbox=[3039,3957,4081,5748] 1042x1791 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4087,1833] 1394x1428 mem=12 {'doc_title': 1, 'text': 7, 'image': 2, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=2 {'doc_title': 1, 'text': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,2023,2859] 1910x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[113,5731,3267,6318] 3154x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val8.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=13 {'text': 8, 'image': 3, 'figure_title': 2}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,3089,3031,4921] 1533x1832 mem=13 {'doc_title': 1, 'text': 8, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 2 DL bbox=[3092,3957,4079,4960] 987x1003 mem=1 {'text': 1} SINGLE-region
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=8 {'text': 3, 'image': 2, 'doc_title': 1, 'figure_title': 2}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4078,1567] 1385x1162 mem=11 {'doc_title': 1, 'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=3 {'text': 1, 'image': 1, 'doc_title': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260609_130635/all_article_crops_review

36
_jg_val8b.log Normal file
View File

@ -0,0 +1,36 @@
=== page 1: 6 crops, 6 datelines ===
R 3 DL bbox=[1497,137,4081,3023] 2584x2886 mem=13 {'text': 8, 'image': 3, 'figure_title': 2}
R 4 DL bbox=[105,145,1467,4295] 1362x4150 mem=15 {'text': 11, 'image': 2, 'paragraph_title': 2}
R 28 H bbox=[1498,3089,3031,4921] 1533x1832 mem=13 {'doc_title': 1, 'text': 8, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 2 DL bbox=[3092,3957,4079,4960] 987x1003 mem=1 {'text': 1} SINGLE-region
R 49 H RAW bbox=[106,4377,1429,6301] 1323x1924 mem=10 {'doc_title': 1, 'text': 5, 'image': 2, 'figure_title': 2}
R 29 H RAW bbox=[1500,5012,4081,6283] 2581x1271 mem=8 {'text': 3, 'image': 2, 'doc_title': 1, 'figure_title': 2}
[Containment-merge] x2
[Headline-merge] x2
[Banner rectangle] x4
=== page 2: 18 crops, 17 datelines ===
R109 H bbox=[119,393,612,1899] 493x1506 mem=2 {'doc_title': 1, 'text': 1}
R 79 H bbox=[2693,405,4078,1567] 1385x1162 mem=11 {'doc_title': 1, 'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 55 H RAW bbox=[679,415,2630,1919] 1951x1504 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 2, 'figure_title': 1}
R 77 H bbox=[2692,1594,4087,1903] 1395x309 mem=3 {'text': 1, 'image': 1, 'doc_title': 1}
R 14 DL bbox=[2045,1950,3265,3097] 1220x1147 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 65 H bbox=[113,1958,1975,2859] 1862x901 mem=8 {'text': 5, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 96 H bbox=[3326,1964,4087,3638] 761x1674 mem=4 {'doc_title': 1, 'text': 2, 'image': 1}
R 89 L bbox=[118,2887,595,4080] 477x1193 mem=5 {'text': 4, 'paragraph_title': 1}
R 86 H bbox=[682,2894,1977,4292] 1295x1398 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 2, 'doc_title': 1}
R 64 H bbox=[2046,3116,3263,3907] 1217x791 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 97 H bbox=[3324,3756,4087,4627] 763x871 mem=2 {'doc_title': 1, 'text': 1}
R 84 H bbox=[2046,3920,3264,4820] 1218x900 mem=6 {'text': 3, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 44 F bbox=[123,4139,626,5113] 503x974 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R112 H bbox=[682,4325,1975,5056] 1293x731 mem=5 {'text': 2, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 58 H bbox=[3322,4658,4087,5831] 765x1173 mem=2 {'doc_title': 1, 'text': 1}
R110 H bbox=[2044,4858,3265,5685] 1221x827 mem=7 {'doc_title': 1, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 75 H RAW bbox=[680,5078,1975,5692] 1295x614 mem=5 {'doc_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R 59 H RAW bbox=[680,5731,3267,6318] 2587x587 mem=7 {'doc_title': 2, 'text': 5}
R 7 DL bbox=[3308,5839,4087,6305] 779x466 mem=1 {'text': 1} SINGLE-region
[Containment-merge] x10
[Banner rectangle] x8
DONE -> output/NamastheTelangana_Jangaon_20260520_20260608_124720/all_article_crops_review

10
_lc3.txt Normal file
View File

@ -0,0 +1,10 @@
AJ pg1: 12 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
AJ pg2: 18 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
AJ pg3: 14 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
AJ pg4: 20 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
NT pg1: 11 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
NT pg2: 22 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
NT pg3: 19 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
NT pg4: 24 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
JG pg1: 7 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)
JG pg2: 12 blocks (baseline AJ 12/18/14/20 NT 11/22/19/25 JG 7/12)

10
_lencheck.txt Normal file
View File

@ -0,0 +1,10 @@
AJ pg1 pitch755 hbars10 base12 | thr0.6: 12 (drop6) | thr0.8: 12 (drop7) | thr1.0: 12 (drop7)
AJ pg2 pitch719 hbars19 base18 | thr0.6: 18 (drop5) | thr0.8: 18 (drop8) | thr1.0: 18 (drop9)
AJ pg3 pitch728 hbars15 base14 | thr0.6: 14 (drop7) | thr0.8: 13 (drop9) | thr1.0: 13 (drop9)
AJ pg4 pitch728 hbars25 base20 | thr0.6: 20 (drop11) | thr0.8: 20 (drop13) | thr1.0: 20 (drop13)
NT pg1 pitch536 hbars24 base11 | thr0.6: 11 (drop5) | thr0.8: 11 (drop8) | thr1.0: 11 (drop9)
NT pg2 pitch527 hbars47 base22 | thr0.6: 22 (drop7) | thr0.8: 22 (drop10) | thr1.0: 22 (drop15)
NT pg3 pitch525 hbars25 base19 | thr0.6: 19 (drop3) | thr0.8: 19 (drop4) | thr1.0: 19 (drop7)
NT pg4 pitch523 hbars43 base25 | thr0.6: 25 (drop5) | thr0.8: 25 (drop8) | thr1.0: 21 (drop25)
JG pg1 pitch601 hbars15 base7 | thr0.6: 7 (drop5) | thr0.8: 7 (drop6) | thr1.0: 7 (drop9)
JG pg2 pitch594 hbars17 base12 | thr0.6: 12 (drop0) | thr0.8: 12 (drop0) | thr1.0: 12 (drop9)

0
_lencheck2.txt Normal file
View File

416
_lines.py Normal file
View File

@ -0,0 +1,416 @@
"""Separator-rule detection for newspaper pages (classical CV, no ML / no API).
Detects the thin printed grey/black straight lines the paper uses to divide
stories, with a TEXT-ISOLATION filter to reject false positives from dense
Telugu text (a real rule sits in a whitespace gutter; a text stroke has ink
immediately above/below it).
Public:
detect_separator_lines(png_path) -> {"h": [...], "v": [...], "size": (W,H)}
each line is a dict: {x1,y1,x2,y2,len,gray} (gray = mean intensity).
"""
import cv2
import numpy as np
def _candidates(bw, horizontal, min_len, max_thick=55):
if horizontal:
k = cv2.getStructuringElement(cv2.MORPH_RECT, (min_len // 2, 1))
else:
k = cv2.getStructuringElement(cv2.MORPH_RECT, (1, min_len // 2))
opened = cv2.morphologyEx(bw, cv2.MORPH_OPEN, k, iterations=1)
# bridge small collinear gaps along the line direction
if horizontal:
opened = cv2.dilate(opened, cv2.getStructuringElement(cv2.MORPH_RECT, (25, 3)))
else:
opened = cv2.dilate(opened, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 25)))
cnts, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
out = []
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
if horizontal and w >= min_len and h <= max_thick:
out.append((x, y + h // 2, x + w, y + h // 2, w))
if (not horizontal) and h >= min_len and w <= max_thick:
out.append((x + w // 2, y, x + w // 2, y + h, h))
return out
def _isolated(gray, line, horizontal, ink_thresh=110, gap=(5, 16),
max_adjacent_ink=0.12):
"""A true separator sits in a whitespace gutter: the tight band on BOTH sides
(parallel to the line) is mostly paper. Returns (is_isolated, mean_gray)."""
H, W = gray.shape
x1, y1, x2, y2, ln = line
if horizontal:
seg = gray[y1, x1:x2]
lo, hi = gap
above = gray[max(0, y1 - hi):max(0, y1 - lo), x1:x2]
below = gray[min(H, y1 + lo):min(H, y1 + hi), x1:x2]
else:
seg = gray[y1:y2, x1]
lo, hi = gap
above = gray[y1:y2, max(0, x1 - hi):max(0, x1 - lo)]
below = gray[y1:y2, min(W, x1 + lo):min(W, x1 + hi)]
if seg.size == 0 or above.size == 0 or below.size == 0:
return False, 255
ink_a = float((above < ink_thresh).mean())
ink_b = float((below < ink_thresh).mean())
isolated = (ink_a <= max_adjacent_ink) and (ink_b <= max_adjacent_ink)
return isolated, float(seg.mean())
def detect_separator_lines(png_path, h_frac=0.045, v_frac=0.06):
img = cv2.imread(str(png_path))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
H, W = gray.shape
blur = cv2.GaussianBlur(gray, (3, 3), 0)
bw = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
h_min = int(h_frac * W)
v_min = int(v_frac * H)
h_out, v_out = [], []
for ln in _candidates(bw, True, h_min):
iso, g = _isolated(gray, ln, True)
if iso:
h_out.append({"x1": ln[0], "y1": ln[1], "x2": ln[2], "y2": ln[3],
"len": ln[4], "gray": round(g, 1)})
for ln in _candidates(bw, False, v_min):
iso, g = _isolated(gray, ln, False)
if iso:
v_out.append({"x1": ln[0], "y1": ln[1], "x2": ln[2], "y2": ln[3],
"len": ln[4], "gray": round(g, 1)})
return {"h": h_out, "v": v_out, "size": (W, H)}
def detect_content_bottom(png_path, search_frac=0.12, color_thr=0.06,
blank_color=0.02, blank_ink=0.035, peak_thr=0.15,
max_band=160, min_gap=25, min_margin=18):
"""Find the y below which a page holds only the printer's COLOUR-CALIBRATION
strip (the row of CMYK registration dots + grey bars) and the bottom paper
margin i.e. the bottom of real article content.
The calibration strip is a thin band of strongly-saturated colour dots near
the page bottom, isolated by a white paper margin BELOW it (down to the page
edge) and a white gap ABOVE it (separating it from article content). Crops
should be clamped to the returned y so the dots never bleed into an article.
Returns the y at the TOP of the calibration band, or the full page height H
when no such strip is found (nothing to clamp)."""
img = cv2.imread(str(png_path))
if img is None:
return None
H, W = img.shape[:2]
b, g, r = img[..., 0].astype(np.int16), img[..., 1].astype(np.int16), img[..., 2].astype(np.int16)
mx = np.maximum(np.maximum(r, g), b)
mn = np.minimum(np.minimum(r, g), b)
sat = mx - mn
colored = (sat > 55) & (mx > 80) # saturated colour (registration dots)
dark = mx < 110 # ink / dark
col_frac = colored.mean(axis=1) # per-row colour fraction
ink_frac = dark.mean(axis=1) # per-row ink fraction
blank = (col_frac < blank_color) & (ink_frac < blank_ink)
y0 = int(H * (1 - search_frac))
# The calibration strip is a band of strongly-coloured rows; identify it as the
# bottom-most run of rows whose colour fraction clears `color_thr`.
strip_rows = [y for y in range(y0, H) if col_frac[y] >= color_thr]
if not strip_rows:
return H # no coloured strip near the bottom
band_bottom = strip_rows[-1]
# A real calibration strip is followed by white paper margin to the page edge.
below = blank[band_bottom + 1:H]
if below.size < min_margin or float(below.mean()) < 0.9:
return H
# Walk up from band_bottom while still in the coloured band (tolerate the small
# white gaps between dot rows).
band_top = band_bottom
gap = 0
y = band_bottom - 1
while y > y0:
if col_frac[y] >= color_thr:
band_top = y
gap = 0
else:
gap += 1
if gap > 12:
break
y -= 1
if (band_bottom - band_top + 1) > max_band:
return H # too tall -> fused with a photo, bail
# A genuine printer calibration strip is a dense row of saturated CMYK
# registration dots: its peak per-row colour fraction is high (~0.22-0.45 across
# Andhra Jyothi / Sakshi). A single-hue coloured ad/notice box embedded in
# article text peaks far lower (~0.11) -> reject it as a false positive.
peak = float(col_frac[band_top:band_bottom + 1].max())
if peak < peak_thr:
return H
return band_top
def _hspan_overlap(ax1, ax2, bx1, bx2):
ix = max(0, min(ax2, bx2) - max(ax1, bx1))
return ix / max(1, min(ax2 - ax1, bx2 - bx1))
def _faint_rule_above(gray, hbbox, max_gap=170, min_gap=6, gray_lo=140, gray_hi=215,
cover=0.6, max_dark=0.06):
"""Find a faint GREY horizontal rule sitting in the whitespace directly above a
(multi-column) header. Such a light rule reads as background to the ink-based
Otsu detector in `detect_separator_lines`, so we look for a thin row that is
mostly uniform mid-grey across the header's width and is isolated by white paper
above and below. Returns the rule's y, or None. (Used only above multi-column
headers, where it marks the article's TOP boundary.)"""
H, W = gray.shape
hx1, hy, hx2 = hbbox[0], hbbox[1], hbbox[2]
y_hi = max(0, hy - min_gap)
y_lo = max(0, hy - max_gap)
for y in range(y_hi, y_lo, -1):
row = gray[y, hx1:hx2].astype(np.int16)
if row.size == 0:
continue
mid = float(((row >= gray_lo) & (row <= gray_hi)).mean())
dark = float((row < gray_lo).mean())
if mid >= cover and dark <= max_dark:
above = gray[max(0, y - 6):max(0, y - 2), hx1:hx2]
below = gray[min(H, y + 2):min(H, y + 6), hx1:hx2]
if (above.size and below.size
and float((above > gray_hi).mean()) > 0.85
and float((below > gray_hi).mean()) > 0.85):
return y
return None
def _faint_grey_band_above(gray, hbbox, max_gap=185, min_gap=6, max_thick=12,
gray_lo=135, gray_hi=232, cover=0.8, max_dark=0.08,
white_thr=235, white_cover=0.9):
"""Find a faint GREY horizontal rule (1-`max_thick` px thick) sitting in the
whitespace directly above a SINGLE-column header. Per the layout convention a
grey rule whose length matches the article/header width marks the boundary
between two stacked stories, so the next story's headline must not fold into
the article above the rule. Such light rules read as background to the ink-based
Otsu detector in `detect_separator_lines`, and `_faint_rule_above` only accepts
a 1-px isolated rule, so a thicker grey band is missed; here we group the band
and require it to span the header's width and be isolated by white paper above
and below. Returns the band's centre y, or None."""
H, W = gray.shape
hx1, hy, hx2 = int(hbbox[0]), int(hbbox[1]), int(hbbox[2])
y_hi = max(0, hy - min_gap)
y_lo = max(0, hy - max_gap)
cols = slice(hx1, hx2)
grey_rows = []
for y in range(y_lo, y_hi):
row = gray[y, cols].astype(np.int16)
if row.size == 0:
continue
mid = float(((row >= gray_lo) & (row <= gray_hi)).mean())
dark = float((row < gray_lo).mean())
if mid >= cover and dark <= max_dark:
grey_rows.append(y)
if not grey_rows:
return None
bands = []
s = p = grey_rows[0]
for y in grey_rows[1:]:
if y == p + 1:
p = y
else:
bands.append((s, p)); s = p = y
bands.append((s, p))
for top, bot in sorted(bands, key=lambda b: -b[1]): # nearest the header first
if bot - top + 1 > max_thick:
continue
above = gray[max(0, top - 4):top, cols]
below = gray[bot + 1:min(H, bot + 5), cols]
if (above.size and below.size
and float((above > white_thr).mean()) >= white_cover
and float((below > white_thr).mean()) >= white_cover):
return (top + bot) // 2
return None
def _faint_grey_band_below(gray, bbox, max_gap=150, min_gap=4, max_thick=12,
gray_lo=135, gray_hi=232, cover=0.8, max_dark=0.08,
white_thr=235, white_cover=0.9):
"""Mirror of `_faint_grey_band_above`, searching DOWNWARD: find the faint grey
rule that sits in the whitespace just below an article (within `max_gap` px of
its bottom edge) and spans its column width. That rule is the article's BOTTOM
boundary, so the crop can be cut precisely at it. Returns the band's top y (the
cut line), or None when no clean full-width grey rule is found below."""
H, W = gray.shape
bx1, by, bx2 = int(bbox[0]), int(bbox[3]), int(bbox[2])
y_lo = min(H, by + min_gap)
y_hi = min(H, by + max_gap)
cols = slice(bx1, bx2)
grey_rows = []
for y in range(y_lo, y_hi):
row = gray[y, cols].astype(np.int16)
if row.size == 0:
continue
mid = float(((row >= gray_lo) & (row <= gray_hi)).mean())
dark = float((row < gray_lo).mean())
if mid >= cover and dark <= max_dark:
grey_rows.append(y)
if not grey_rows:
return None
bands = []
s = p = grey_rows[0]
for y in grey_rows[1:]:
if y == p + 1:
p = y
else:
bands.append((s, p)); s = p = y
bands.append((s, p))
for top, bot in sorted(bands, key=lambda b: b[0]): # nearest below first
if bot - top + 1 > max_thick:
continue
# Skip the 1-px anti-aliased fade row hugging the rule before checking for
# the white paper gutter above and below it.
above = gray[max(0, top - 7):max(0, top - 2), cols]
below = gray[bot + 2:min(H, bot + 7), cols]
if (above.size and below.size
and float((above > white_thr).mean()) >= white_cover
and float((below > white_thr).mean()) >= white_cover):
return top
return None
def separator_barriers(png_path, regions, near=80, min_overlap=0.3,
title_types=("doc_title", "paragraph_title", "figure_title"),
multicol_frac=0.22):
"""Article-boundary rules: the horizontal printed lines that mark the END of a
story. Per the layout convention, a rule with NORMAL text (or whitespace) above
it separates two stacked articles, while a rule sitting right under a BOLD
headline/title is a decorative flourish and must NOT split anything.
We approximate 'bold above' by a title-type region (doc_title / paragraph_title
/ figure_title) whose bottom edge is within `near` px above the rule and that
horizontally overlaps it. Such rules are dropped; the rest are returned as
barriers {y, x1, x2} for the clusterer to forbid cross-line folding.
Additionally, for every MULTI-COLUMN header (a doc_title at least `multicol_frac`
of the page width), we look for a faint grey rule directly above it and add it as
a barrier spanning the header's width: that rule is the article's TOP boundary, so
nothing above it folds into the multi-column article below."""
res = detect_separator_lines(png_path)
# A real article separator sits in a WHITESPACE GUTTER between two stacked
# boxes; it never lands deep inside a body-text region. PaddleOCR's morphology
# occasionally mistakes a dense Telugu text stroke for a thin rule, producing a
# phantom line straight through a paragraph. Reject any detected rule whose
# midpoint falls well inside (margin-shrunk) a 'text' box it column-overlaps.
_text_boxes = [r["bbox"] for r in regions if r.get("type") == "text"]
def _in_text(y, lx1, lx2, margin=28):
mx = (lx1 + lx2) / 2
for tx1, ty1, tx2, ty2 in _text_boxes:
if (tx1 <= mx <= tx2
and ty1 + margin <= y <= ty2 - margin
and _hspan_overlap(lx1, lx2, tx1, tx2) >= min_overlap):
return True
return False
# Column width (median text-region width). A horizontal line counts as an article
# separator only when it spans most of a column. NOTE: a strict ≥1.0× column floor is
# too aggressive — a SINGLE-column article's own boundary rule is itself <1 column wide,
# so ≥1.0× drops real separators and merges articles (measured: NT pg4 R121 → 2 datelines).
# 0.6× column is the safe floor: it removes only the short caption / decorative underlines
# and merges NO articles on AJ / NT / JG.
_twid = sorted((r["bbox"][2] - r["bbox"][0]) for r in regions if r.get("type") == "text")
_pitch = _twid[len(_twid) // 2] if _twid else 0
_len_floor = 0.6 * _pitch
bars = []
for L in res["h"]:
y, lx1, lx2 = L["y1"], L["x1"], L["x2"]
if _in_text(y, lx1, lx2):
continue
if _pitch and (lx2 - lx1) < _len_floor:
continue # too short to be an article separator (caption/underline)
bold_above = False
title_w_above = 0
for r in regions:
b = r["bbox"]
if (b[3] <= y and (y - b[3]) <= near
and r.get("type") in title_types
and _hspan_overlap(lx1, lx2, b[0], b[2]) >= min_overlap):
bold_above = True
title_w_above = max(title_w_above, b[2] - b[0])
# A rule under a bold title is a decorative flourish ONLY when it merely
# underlines that title (rule width ≈ title width). A rule spanning much
# WIDER than the title above it runs across several columns — e.g. a photo
# caption (figure_title) sits above ONE column while the rule runs the FULL
# article width — so it is a genuine article boundary and must be kept.
if (not bold_above) or ((lx2 - lx1) > 1.5 * title_w_above):
bars.append({"y": y, "x1": lx1, "x2": lx2})
gray = cv2.cvtColor(cv2.imread(str(png_path)), cv2.COLOR_BGR2GRAY)
W = gray.shape[1]
_img_boxes = [r["bbox"] for r in regions if r.get("type") == "image"]
def _lead_photo_above(yb, hx1, hx2, gap=80):
"""A faint rule directly under the article's OWN lead photo is that photo's
caption underline, not the article's top boundary. If an image sits flush
(within `gap`px) above the rule and overlaps the header's width, the rule must
NOT bound the article otherwise the lead photo gets cut off."""
for ix1, iy1, ix2, iy2 in _img_boxes:
if (0 <= yb - iy2 <= gap
and _hspan_overlap(hx1, hx2, ix1, ix2) >= min_overlap):
return True
return False
for r in regions:
if r.get("type") != "doc_title":
continue
b = r["bbox"]
if (b[2] - b[0]) < multicol_frac * W:
continue # single-column header → skip
yb = _faint_rule_above(gray, b)
if yb is not None and not _lead_photo_above(yb, b[0], b[2]):
bars.append({"y": yb, "x1": b[0], "x2": b[2],
"mc": True, "anchor": r["id"]})
# Same rule for SINGLE-column headers: a faint grey rule spanning the header's
# width directly above it bounds the article ABOVE it, so the header (and its
# story) cannot fold upward across the rule. Plain barrier (no mc clamp).
for r in regions:
if r.get("type") not in ("doc_title", "paragraph_title"):
continue
b = r["bbox"]
if (b[2] - b[0]) >= multicol_frac * W:
continue # multi-column → handled above
yb = _faint_grey_band_above(gray, b)
if yb is not None and not _lead_photo_above(yb, b[0], b[2]):
bars.append({"y": yb, "x1": b[0], "x2": b[2]})
# Vertical column-divider rules. detect_separator_lines already requires each
# line to sit in a whitespace gutter (isolated on both sides), so photo/text
# internal strokes are filtered out. These fence rightward column-growth: an
# article must not grow across a printed vertical rule into the next column.
for L in res["v"]:
bars.append({"vert": True, "x": (L["x1"] + L["x2"]) // 2,
"y1": min(L["y1"], L["y2"]), "y2": max(L["y1"], L["y2"])})
return bars
def overlay(png_path, lines, out_path):
img = cv2.imread(str(png_path))
for L in lines["h"]:
cv2.line(img, (L["x1"], L["y1"]), (L["x2"], L["y2"]), (0, 0, 255), 6)
for L in lines["v"]:
cv2.line(img, (L["x1"], L["y1"]), (L["x2"], L["y2"]), (255, 0, 0), 6)
cv2.imwrite(str(out_path), img)
if __name__ == "__main__":
import sys
from pathlib import Path
p = Path(sys.argv[1])
res = detect_separator_lines(p)
print(f"{p.name} {res['size']} H={len(res['h'])} V={len(res['v'])}")
for L in sorted(res["h"], key=lambda d: d["y1"]):
print(f" H y={L['y1']:>5} x[{L['x1']:>4}..{L['x2']:>4}] len={L['len']:>4} gray={L['gray']}")
for L in sorted(res["v"], key=lambda d: d["x1"]):
print(f" V x={L['x1']:>5} y[{L['y1']:>4}..{L['y2']:>4}] len={L['len']:>4} gray={L['gray']}")
out = Path(sys.argv[2]) if len(sys.argv) > 2 else Path(f"/tmp/lines_{p.stem}.png")
overlay(p, res, out)
print("overlay:", out)

4
_nt4.txt Normal file
View File

@ -0,0 +1,4 @@
WITH ≥1col floor: 24 blocks
R67 H 4DL members=[5, 7, 28, 34, 42, 48, 50, 67, 68, 73, 74, 80, 100, 102, 107, 127, 135]
R121 H 2DL members=[12, 14, 21, 39, 59, 111, 116, 121]
anchors: R23,R67,R81,R85,R87,R89,R90,R91,R93,R94,R95,R96,R97,R98,R101,R103,R108,R118,R119,R121,R122,R128,R131,R132

5
_nt_base.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val2.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val3.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110, 105] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val4.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val5.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val6.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val7.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 28 crops, 25 datelines, 2+dl-blocks=[80] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

5
_nt_val8.log Normal file
View File

@ -0,0 +1,5 @@
page 1: 11 crops, 11 datelines, 2+dl-blocks=none | Caption bound:4, Headline-strip:8, Multi-column body:4
page 2: 23 crops, 22 datelines, 2+dl-blocks=[110] |
page 3: 20 crops, 21 datelines, 2+dl-blocks=[54, 62] |
page 4: 25 crops, 25 datelines, 2+dl-blocks=[67, 121] |
DONE -> output/NamastheTelangana_Siddipet_20260602_20260604_214937/all_article_crops_final

179
_nta.log Normal file
View File

@ -0,0 +1,179 @@
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 5 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL5 [668,901,4084,6302] 3416x5401 mem=22
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2

295
_nta2.log Normal file
View File

@ -0,0 +1,295 @@
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=21
DL56 [1265,901,4079,4724] 2814x3823 mem=11
DL9 [105,2334,1197,6302] 1092x3968 mem=5
H59 [112,2762,3229,4860] 3117x2098 mem=13
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
DONE -> output/NamastheTelangana_Adilabad_20260528_20260610_160056/all_article_crops_review

281
_nta3.log Normal file
View File

@ -0,0 +1,281 @@
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
=== page 2: 30 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,4979] 468x1493 mem=3
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [610,4170,1497,4966] 887x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
DL26 [154,4987,1498,5443] 1344x456 mem=3
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [114,5467,1498,6269] 1384x802 mem=3
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [590,5993,1498,6302] 908x309 mem=2
DONE -> output/NamastheTelangana_Adilabad_20260528_20260610_160056/all_article_crops_review

295
_nta4.log Normal file
View File

@ -0,0 +1,295 @@
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 1: 6 crops, 6 datelines ===
H44 [106,111,4084,3144] 3978x3033 mem=23
DL9 [105,2334,1200,6302] 1095x3968 mem=6
DL13 [1320,2761,4079,4724] 2759x1963 mem=14
H59 [115,3752,3228,4860] 3113x1108 mem=7
H41 [2577,4793,4084,6309] 1507x1516 mem=4
H30 [668,4829,3208,6302] 2540x1473 mem=11
─ NT late rule-split: R5 → 2 bands at structural rules [4764]
🖼 Orphan photo: R38 → article R5 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
=== page 2: 31 crops, 30 datelines ===
H80 [1833,465,3228,1814] 1395x1349 mem=6
H65 [3293,465,4079,987] 786x522 mem=2
H84 [106,501,1776,1820] 1670x1319 mem=6
H43 [3292,1014,4079,1562] 787x548 mem=2
DL9 [3292,1590,4079,2136] 787x546 mem=2
H78 [1838,1678,2517,1805] 679x127 mem=2
DL7 [104,1851,923,2566] 819x715 mem=3
H74 [981,1883,2350,3060] 1369x1177 mem=4
H72 [2408,1884,3227,2713] 819x829 mem=2
DL5 [3292,2167,4079,2759] 787x592 mem=2
DL23 [104,2596,922,2998] 818x402 mem=2
H58 [2408,2772,3226,3479] 818x707 mem=2
DL13 [3292,2787,4079,3333] 787x546 mem=2
DL20 [106,3050,923,3478] 817x428 mem=2
H60 [981,3117,2351,3479] 1370x362 mem=3
DL1 [3293,3382,4079,4190] 786x808 mem=2
P67 [131,3486,599,5211] 468x1725 mem=4
DL10 [1559,3528,3226,4291] 1667x763 mem=4
P63 [677,3558,1499,4144] 822x586 mem=2
H50 [678,4170,1497,4966] 819x796 mem=2
DL12 [3292,4217,4079,4815] 787x598 mem=2
H71 [1560,4365,3226,4958] 1666x593 mem=3
H51 [3292,4842,4079,5479] 787x637 mem=2
DL41 [1561,4960,3225,5110] 1664x150 mem=2
H54 [678,4987,1498,5443] 820x456 mem=2
H76 [1559,5171,3228,5881] 1669x710 mem=5
H53 [677,5467,1498,5964] 821x497 mem=2
H83 [3291,5509,4079,6303] 788x794 mem=3
H56 [1559,5944,3228,6302] 1669x358 mem=3
H46 [680,5993,1498,6302] 818x309 mem=2
O79 [152,6141,590,6257] 438x116 mem=1
🖼 Orphan photo: R90 → article R67 (an image must belong to an article)
DONE -> output/NamastheTelangana_Adilabad_20260528_20260610_160056/all_article_crops_review

21
_orphan_check.py Normal file
View File

@ -0,0 +1,21 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
for pg in (2, 3, 4):
png = D / f"page_{pg:03d}.png"
regs = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.drop_masthead_regions(regs, "andhra_jyothi", pg, png)
ds = se.find_article_starts_by_dateline(regs, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regs)
bl = se.cluster_all_article_blocks(regs, dateline_starts=ds, sep_lines=bars)
dl_ids = {s["region_id"] for s in ds}
n_dl = sum(1 for b in bl if any(m in dl_ids for m in b["members"]))
orph = [b for b in bl if b["kind"] == "O"]
print(f"==== PAGE {pg}: {len(bl)} blocks, {n_dl} dateline, {len(orph)} orphan ====")
for b in orph:
print(f" O anchor={b['anchor_id']} bbox={b['bbox']} members={sorted(b['members'])}")

86
_partition_debug.py Normal file
View File

@ -0,0 +1,86 @@
"""TEST (read-only): finalize article boundaries using the RIGHTMOST dead-end (nearest big
vertical wall to the right, else page edge) and the TOPMOST dead-end (masthead bottom, for
top-row articles). Draw both the original block boundary (thin gray) and the dead-end-snapped
boundary (thick colour) on a blank canvas, plus the big vertical walls (red) and the masthead
band. No smart_extractor change, no API. Test on 1-2 pages."""
import os, tempfile, json, io, contextlib, statistics
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import smart_extractor as se
from _lines import separator_barriers, detect_separator_lines
BIG_FRAC = 0.45
def big_walls(png):
res = detect_separator_lines(png); W, H = res["size"]
out = []
for L in res["v"]:
y1, y2 = min(L["y1"], L["y2"]), max(L["y1"], L["y2"])
if (y2 - y1) >= BIG_FRAC * H:
out.append({"x": (L["x1"]+L["x2"])//2, "y1": y1, "y2": y2})
return out, (W, H)
def masthead_bottom(png, raw, paper, pg, W, H):
# compute from RAW regions (before masthead removal) so the dropped band's bottom is visible
with contextlib.redirect_stdout(io.StringIO()):
dd = se.dedup_overlapping_regions([dict(r) for r in raw])
kept = se.drop_masthead_regions([dict(r) for r in dd], paper, pg, png)
drop_bottom = max((r["bbox"][3] for r in dd
if r["id"] not in {k["id"] for k in kept}), default=0)
res = detect_separator_lines(png)
wide = sorted([L for L in res["h"] if (L["x2"]-L["x1"]) >= 0.30*W
and L["y1"] < 0.20*H], key=lambda L: L["y1"])
top_rule = wide[0]["y1"] if wide else 0
return max(drop_bottom, top_rule)
def run(pdir, pg, paper, tag):
png = f"{pdir}/page_{pg:03d}.png"
raw = json.loads(Path(f"{pdir}/page_{pg:03d}.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.dedup_overlapping_regions(raw); regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, paper, pg, png); regs = se.promote_paragraph_titles(regs, png, paper); regs = se.mark_bullet_titles(regs, png)
ds = se.find_article_starts_by_dateline(regs, png, paper)
_p = se.cluster_all_article_blocks(regs, ds, sep_lines=separator_barriers(png, regs))
if se.recover_orphan_text_headlines(_p, regs, png): ds = se.find_article_starts_by_dateline(regs, png, paper)
bars = separator_barriers(png, regs)
blocks = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
walls, (W, H) = big_walls(png)
cright = max((r["bbox"][2] for r in regs), default=W)
mh = masthead_bottom(png, raw, paper, pg, W, H)
top_row_cut = mh + 320 # an article whose top is within this band is "top row"
def deadends(b):
x1, y1, x2, y2 = b["bbox"]; cx = (x1 + x2) / 2; bh = y2 - y1
# rightmost dead-end: nearest big wall right of the block centre that spans it; else page edge
rcand = [w["x"] for w in walls if w["x"] > cx
and max(0, min(y2, w["y2"]) - max(y1, w["y1"])) >= 0.4 * bh]
right = min(rcand) if rcand else cright
right = max(right, x2) # never shrink
# topmost dead-end: top-row article → up to masthead bottom
top = mh if (y1 <= top_row_cut and mh > 0) else y1
top = min(top, y1) # never shrink downward
return [x1, top, right, y2]
img = Image.new("RGB", (W, H), "white"); d = ImageDraw.Draw(img)
try: font = ImageFont.truetype("DejaVuSans-Bold.ttf", 30)
except Exception: font = ImageFont.load_default()
if mh > 4:
d.rectangle([0, 0, W, mh], fill=(232,232,232)); d.line([0,mh,W,mh], fill=(140,140,140), width=5)
d.text((14, max(2, mh//2-16)), "MASTHEAD (top dead-end)", fill=(110,110,110), font=font)
for w in walls:
d.line([w["x"], w["y1"], w["x"], w["y2"]], fill=(235,70,70), width=8)
pal = se._ARTICLE_COLORS
for i, b in enumerate(blocks):
ox1, oy1, ox2, oy2 = b["bbox"]; nx1, ny1, nx2, ny2 = deadends(b); c = pal[i % len(pal)]
d.rectangle([ox1, oy1, ox2, oy2], outline=(180,180,180), width=3) # original (gray)
d.rectangle([nx1, ny1, nx2, ny2], outline=c, width=8) # dead-end (color)
lab = f"#{b['anchor_id']} [{b['kind']}]"
d.rectangle([nx1+5, ny1+6, nx1+16+len(lab)*15, ny1+42], fill=c)
d.text((nx1+11, ny1+9), lab, fill="white", font=font)
out = f"{pdir}/page_{pg:03d}_partition_debug.png"; img.save(out)
print(f"{tag} pg{pg}: {len(blocks)} blocks, {len(walls)} walls, masthead_bottom={int(mh)} -> {out}")
run("output/NamastheTelangana_Siddipet_20260602_20260604_214937/pages", 2, "namaste_telangana", "NT")
run("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages", 1, "andhra_jyothi", "AJ")

26
_probe71_tmp.py Normal file
View File

@ -0,0 +1,26 @@
import sys, types; sys.modules.setdefault("fitz", types.ModuleType("fitz"))
import os, tempfile, json
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN = Path("output/Sakshi_Jangaon_District_20260520_20260609_210104")
png = str(RUN/"pages/page_001.png")
regions = json.loads((RUN/"pages/page_001.regions.json").read_text())["regions"]
regions = se.dedup_overlapping_regions(regions)
regions = se.merge_stacked_title_lines(regions, png)
regions = se.drop_masthead_regions(regions, "sakshi", 1, png)
regions = se.promote_paragraph_titles(regions, png, "sakshi")
dls = se.find_article_starts_by_dateline(regions, png, "sakshi")
seps = separator_barriers(png, regions)
blocks = se.cluster_all_article_blocks(regions, dls, sep_lines=seps)
rmap = {r['id']:r for r in regions}
for b in blocks:
if b['anchor_id']==71:
print("BLOCK:", {k:v for k,v in b.items() if k!='members'})
print("members:")
for m in sorted(b['members']+[b['anchor_id']], key=lambda m: rmap[m]['bbox'][1] if m in rmap else 0):
r = rmap.get(m)
print(" ", m, r['type'] if r else '?', r['bbox'] if r else '?')
json.dump({'blocks':[{'anchor':b['anchor_id'],'kind':b['kind'],'bbox':b['bbox'],'members':b['members']} for b in blocks]}, open('/tmp/blocks71.json','w'))

29
_probe_above.py Normal file
View File

@ -0,0 +1,29 @@
import sys, json
sys.path.insert(0, "/Users/kranthikumar/Documents/telugu_extractor")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
for pg in (1, 2, 3, 4):
png = D / f"page_{pg:03d}.png"
regions = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
regions = se.drop_masthead_regions(regions, "andhra_jyothi", pg, png)
ds = se.find_article_starts_by_dateline(regions, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regions)
blocks = se.cluster_all_article_blocks(regions, dateline_starts=ds, sep_lines=bars)
rmap = {r["id"]: r for r in regions}
print(f"\n==== PAGE {pg} ====")
for b in blocks:
anchor = rmap.get(b["anchor_id"])
if not anchor:
continue
atop = anchor["bbox"][1]
# members whose ENTIRE box is above the anchor headline top
above_mems = [m for m in b["members"]
if m != b["anchor_id"] and rmap[m]["bbox"][3] <= atop + 20]
if above_mems and b["kind"] == "H":
print(f" block H anchor={b['anchor_id']} (top y={atop}) has {len(above_mems)} member(s) ABOVE it:")
for m in above_mems:
rb = rmap[m]["bbox"]
print(f" R{m} {rmap[m]['type']:<14} y[{rb[1]}..{rb[3]}] x[{rb[0]}..{rb[2]}]")

21
_probe_bars.py Normal file
View File

@ -0,0 +1,21 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers, detect_separator_lines
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regs0 = json.loads((D / "page_001.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.drop_masthead_regions(regs0, "andhra_jyothi", 1, png)
res = detect_separator_lines(png)
print(f"=== {len(res['h'])} raw horizontal lines ===")
for L in sorted(res["h"], key=lambda d: d["y1"]):
print(f" H y={L['y1']:>5} x[{L['x1']:>4}..{L['x2']:>4}] len={L['len']:>4} gray={L['gray']}")
print("\n=== separator_barriers (after filters) ===")
bars = separator_barriers(str(png), regs)
for L in sorted(bars, key=lambda d: d["y"]):
print(f" bar y={L['y']:>5} x[{L['x1']:>4}..{L['x2']:>4}] mc={L.get('mc')}")

22
_probe_dl.py Normal file
View File

@ -0,0 +1,22 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers, detect_separator_lines
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regs0 = json.loads((D / "page_001.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.drop_masthead_regions(regs0, "andhra_jyothi", 1, png)
ds = se.find_article_starts_by_dateline(regs, str(png), "andhra_jyothi")
print(f"=== {len(ds)} dateline starts ===")
for s in sorted(ds, key=lambda s: s["bbox"][1]):
print(f" DL region={s['region_id']:>3} bbox={s['bbox']} headline={s.get('headline_region')}")
byid = {r["id"]: r for r in regs}
print("\n=== all regions sorted by y ===")
for r in sorted(regs, key=lambda r: (r["bbox"][1], r["bbox"][0])):
b = r["bbox"]
print(f" R{r['id']:>3} {r['type']:<16} bbox={b}")

38
_probe_fake.py Normal file
View File

@ -0,0 +1,38 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers, detect_separator_lines
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regs0 = json.loads((D / "page_001.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.drop_masthead_regions(regs0, "andhra_jyothi", 1, png)
byid = {r["id"]: r for r in regs}
# focus region 35 and 55
for rid in (35, 55):
if rid in byid:
r = byid[rid]
print(f"R{rid} type={r['type']} bbox={r['bbox']}")
else:
print(f"R{rid} NOT in regs (dropped?)")
print("\n-- all regions near x 150..750, y 1100..1600 --")
for r in sorted(regs, key=lambda r: r["bbox"][1]):
b = r["bbox"]
if b[1] < 1700 and b[3] > 1050 and b[0] < 760:
print(f" R{r['id']:>3} {r['type']:<16} bbox={b}")
print("\n-- raw horizontal lines near y=1375 --")
res = detect_separator_lines(png)
for L in sorted(res["h"], key=lambda d: d["y1"]):
if 1300 < L["y1"] < 1450:
print(f" H y={L['y1']} x[{L['x1']}..{L['x2']}] len={L['len']} gray={L['gray']}")
print("\n-- separator_barriers near y=1375 --")
bars = separator_barriers(str(png), regs)
for L in sorted(bars, key=lambda d: d["y"]):
if 1300 < L["y"] < 1450:
print(f" bar y={L['y']} x[{L['x1']}..{L['x2']}] mc={L.get('mc')}")

21
_probe_fake2.py Normal file
View File

@ -0,0 +1,21 @@
import sys, json, contextlib, io
sys.path.insert(0, ".")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regs0 = json.loads((D / "page_001.regions.json").read_text())["regions"]
with contextlib.redirect_stdout(io.StringIO()):
regs = se.drop_masthead_regions(regs0, "andhra_jyothi", 1, png)
ds = se.find_article_starts_by_dateline(regs, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regs)
bl = se.cluster_all_article_blocks(regs, dateline_starts=ds, sep_lines=bars)
print("bars near y=1375:", [b for b in bars if 1300 < b["y"] < 1450])
print()
dl_ids = {s["region_id"] for s in ds}
for b in sorted(bl, key=lambda b: (b["bbox"][1], b["bbox"][0])):
kind = b["kind"]
print(f" {kind} anchor={b['anchor_id']:>3} bbox={b['bbox']} members={sorted(b['members'])}")

41
_probe_fix.py Normal file
View File

@ -0,0 +1,41 @@
import os, tempfile, contextlib, io, json
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "sakshi"
RUN = Path("output/Sakshi_Jangaon_District_20260520_20260609_210104"); D = RUN / "pages"
png = str(D / "page_001.png")
regs = json.loads((D / "page_001.regions.json").read_text())["regions"]
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
regs = se.dedup_overlapping_regions(regs); regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, "sakshi", 1, png)
regs = se.drop_classifieds_regions(regs, png); regs = se.tag_legal_notices(regs, png)
regs = se.promote_paragraph_titles(regs, png, "sakshi")
regs = se.detect_sakshi_headline_bands(regs, png)
regs = se.mark_bullet_titles(regs, png)
ds = se.find_article_starts_by_dateline(regs, png, "sakshi")
bars = separator_barriers(png, regs)
_p = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
if se.recover_orphan_text_headlines(_p, regs, png):
ds = se.find_article_starts_by_dateline(regs, png, "sakshi")
blocks = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
WATCH = [44, 16, 36, 69, 61, 46, 23, 4, 74, 49, 17, 88, 33, 28, 3, 53, 56]
own = {}
for b in blocks:
for m in set(b["members"]) | {b["anchor_id"]}:
own.setdefault(m, []).append(f"{b['kind']}{b['anchor_id']}")
print("region owners:")
for w in WATCH:
print(f" R{w:>3} -> {own.get(w, ['(NONE)'])}")
print("\nhorizontal bars near lift-1 (y3300-4100) and R50 zone (y4350-4900):")
for L in bars:
if not L.get("vert") and "y" in L and (3300 <= L["y"] <= 4100 or 4350 <= L["y"] <= 4900):
print(f" y={L['y']} x={L['x1']}-{L['x2']} w={L['x2']-L['x1']}")
print("\ndateline regions:", sorted(s["region_id"] for s in ds))
print("\npipeline lines mentioning watched regions / passes:")
for l in buf.getvalue().splitlines():
if any(k in l for k in ("R44", "R69", "R61", "R46", "R23", "rebind", "Sakshi", "Orphan photo", "Masthead", "📐")):
print(" ", l.strip())

46
_probe_fix2.py Normal file
View File

@ -0,0 +1,46 @@
import os, tempfile, contextlib, io, json
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "sakshi"
RUN = Path("output/Sakshi_Jangaon_District_20260520_20260609_210104"); D = RUN / "pages"
png = str(D / "page_002.png")
regs = json.loads((D / "page_002.regions.json").read_text())["regions"]
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
regs = se.dedup_overlapping_regions(regs); regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, "sakshi", 2, png)
regs = se.drop_classifieds_regions(regs, png); regs = se.tag_legal_notices(regs, png)
regs = se.promote_paragraph_titles(regs, png, "sakshi")
regs = se.detect_sakshi_headline_bands(regs, png)
regs = se.mark_bullet_titles(regs, png)
ds = se.find_article_starts_by_dateline(regs, png, "sakshi")
bars = separator_barriers(png, regs)
_p = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
if se.recover_orphan_text_headlines(_p, regs, png):
ds = se.find_article_starts_by_dateline(regs, png, "sakshi")
blocks = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
rm = {r["id"]: r for r in regs}
print("BLOCKS:")
for b in sorted(blocks, key=lambda b: (b["bbox"][1], b["bbox"][0])):
x1, y1, x2, y2 = b["bbox"]
f = " FURN" if b.get("_furniture") else ""
print(f" {b['kind']}{b['anchor_id']:<4} [{x1},{y1},{x2},{y2}] mem={sorted(set(b['members']))}{f}")
WATCH = [20, 29, 34, 51, 3, 14, 31, 65, 87, 105, 121, 76, 8, 10, 36, 46, 81]
own = {}
for b in blocks:
tag = ("F" if b.get("_furniture") else b["kind"]) + str(b["anchor_id"])
for m in set(b["members"]) | {b["anchor_id"]}:
own.setdefault(m, []).append(tag)
print("\nWATCH owners:", {f"R{w}": own.get(w, "NONE") for w in WATCH})
print("\nbars y2380-2820 / y3400-3700 / y4640-4760:")
for L in bars:
if not L.get("vert") and "y" in L and (2380 <= L["y"] <= 2820 or 3400 <= L["y"] <= 3700 or 4640 <= L["y"] <= 4760):
print(f" y={L['y']} x={L['x1']}-{L['x2']} w={L['x2']-L['x1']}")
print("\ndatelines:", sorted(s["region_id"] for s in ds))
print("\npipeline lines:")
for l in buf.getvalue().splitlines():
if any(k in l for k in ("Type-2", "rule-split", "📐", "Masthead", "", "re-home", "📸", "Orphan photo", "carve", "furniture")):
print(" ", l.strip())

9
_r121.txt Normal file
View File

@ -0,0 +1,9 @@
R121 bbox=[2113, 2214, 3502, 3634] kind=H raw=True
R12 text y[1996,2719] x[2979,3480] DATELINE
R39 image y[2214,2622] x[2113,2939]
R111 figure_title y[2628,2725] x[2196,2856]
R121 doc_title y[2799,2898] x[2422,3493]
R59 image y[2902,3115] x[2981,3502]
R21 text y[2907,3621] x[2419,2945] DATELINE
R116 figure_title y[3123,3163] x[3113,3376]
R14 text y[3195,3622] x[2977,3502]

20
_r65.txt Normal file
View File

@ -0,0 +1,20 @@
page WxH=4200x6422; 12 blocks
R65 bbox=[113, 1958, 1977, 6279] kind=H raw=True locked=False #members=36
R65 datelines: [43, 47, 26, 21]
vertical walls: [(616, 4683, 6292), (141, 4683, 6292), (123, 4123, 6309), (637, 4122, 6309), (2015, 1949, 5689), (3300, 1929, 6308), (2669, 404, 1905)]
wide H-seps (>0.5W): [(1936, 114, 4091), (5709, 686, 3276)]
all block anchors+bbox:
R109 H bbox=[119, 393, 612, 1899] ndl=1
R79 H bbox=[2693, 405, 4087, 1833] ndl=1
R55 H bbox=[679, 415, 2630, 1919] ndl=1
R77 H bbox=[2692, 1594, 4087, 1903] ndl=1
R14 DL bbox=[2045, 1950, 3265, 3097] ndl=1
R65 H bbox=[113, 1958, 1977, 6279] ndl=4
R96 H bbox=[3326, 1964, 4087, 3638] ndl=1
R64 H bbox=[2046, 3116, 3264, 4832] ndl=2
R97 H bbox=[3322, 3756, 4087, 5831] ndl=1
R110 H bbox=[2044, 4858, 3265, 5685] ndl=1
R59 H bbox=[680, 5731, 3267, 6318] ndl=1
R7 DL bbox=[3308, 5839, 4087, 6305] ndl=1

0
_rSAK.txt Normal file
View File

39
_replay_adilabad.py Normal file
View File

@ -0,0 +1,39 @@
import os, tempfile, contextlib, io, json, shutil
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "namaste_telangana"
RUN = Path(os.environ.get("NTA_RUN", "output/NamastheTelangana_Adilabad_20260528_20260610_160056"))
D = RUN / "pages"; P = "namaste_telangana"
OUT = RUN / "all_article_crops_review"
if OUT.exists(): shutil.rmtree(OUT)
pages = sorted(int(p.stem.split("_")[1]) for p in D.glob("page_*.png"))
for pg in pages:
png = str(D / f"page_{pg:03d}.png")
regs = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
regs = se.dedup_overlapping_regions(regs); regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, P, pg, png); regs = se.drop_classifieds_regions(regs, png)
regs = se.tag_legal_notices(regs, png); regs = se.promote_paragraph_titles(regs, png, P)
if pg == 1: regs = se.detect_sakshi_headline_bands(regs, png) # NT: front-page only
regs = se.mark_bullet_titles(regs, png)
ds = se.find_article_starts_by_dateline(regs, png, P)
bars = separator_barriers(png, regs)
_p = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
if se.recover_orphan_text_headlines(_p, regs, png):
ds = se.find_article_starts_by_dateline(regs, png, P)
blocks = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
n = se.crop_all_article_blocks(png, regs, str(OUT), pg, dateline_starts=ds,
sep_lines=bars, overlay=True)
print(f"=== page {pg}: {n} crops, {len(ds)} datelines ===")
for b in sorted(blocks, key=lambda b: (b["bbox"][1], b["bbox"][0])):
x1, y1, x2, y2 = b["bbox"]
f = " FURN" if b.get("_furniture") else ""
print(f" {b['kind']}{b['anchor_id']:<4} [{x1},{y1},{x2},{y2}] {x2-x1}x{y2-y1} mem={len(b['members'])}{f}")
for l in sorted({l.strip() for l in buf.getvalue().splitlines()
if "rule-split" in l or "Masthead" in l or "Orphan photo" in l}):
print(" ", l)
print("DONE ->", OUT)

35
_replay_all.py Normal file
View File

@ -0,0 +1,35 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN=Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919"); D=RUN/"pages"
OUT=RUN/"all_article_crops_final"; PAPER="andhra_jyothi"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2,3,4):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"] # 0.5 saved
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs)
regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,PAPER,pg,png)
regs=se.drop_classifieds_regions(regs,png)
regs=se.tag_legal_notices(regs,png)
regs=se.promote_paragraph_titles(regs,png,PAPER)
regs=se.mark_bullet_titles(regs,png)
bars=separator_barriers(png,regs)
ds=se.find_article_starts_by_dateline(regs,png,PAPER)
_probe=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
if se.recover_orphan_text_headlines(_probe,regs,png): ds=se.find_article_starts_by_dateline(regs,png,PAPER)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
se.draw_boundary_map_debug(png,regs,D,pg,dateline_starts=ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
log=buf.getvalue()
tags={k:sum(1 for l in log.splitlines() if k in l) for k in
["bullet titles flagged","Headline-strip","Floor-trim","Block dateline split",
"Multi-column body","orphan overlap"]}
tags={k:v//2 if k in ("Headline-strip","Floor-trim","Multi-column body","Block dateline split","orphan overlap") else v for k,v in tags.items()}
print(f"page {pg}: {n} crops, {len(ds)} datelines | "+", ".join(f"{k}:{v}" for k,v in tags.items() if v))
print("DONE ->",OUT)

36
_replay_crop_all.py Normal file
View File

@ -0,0 +1,36 @@
"""Re-crop EVERY article on all 4 pages using the EXISTING smart_extractor code
(no Claude / no paid API). Runs the FULL pre-processing chain the real pipeline uses
(dedup -> merge stacked titles -> drop masthead -> promote paragraph-titles -> dateline
scan -> separators -> cluster/crop), so the replay crops match real output. Tesseract
temp files are redirected off /tmp so OCR can read them. Output goes to a fresh dir."""
import os, tempfile, json
tempfile.tempdir = os.path.abspath("_tess_tmp") # keep Tesseract off /tmp
os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919")
PAGES = RUN / "pages"
OUT = RUN / "all_article_crops_new" # fresh output dir
PAPER = "andhra_jyothi"
for pg in (1, 2, 3, 4):
png = str(PAGES / f"page_{pg:03d}.png")
regions = json.loads((PAGES / f"page_{pg:03d}.regions.json").read_text())["regions"]
# Full pre-processing chain (matches the real pipeline order):
regions = se.dedup_overlapping_regions(regions)
regions = se.merge_stacked_title_lines(regions, png)
regions = se.drop_masthead_regions(regions, PAPER, pg, png)
regions = se.promote_paragraph_titles(regions, png, PAPER) # paragraph_title -> doc_title
dateline_starts = se.find_article_starts_by_dateline(regions, png, PAPER)
sep_lines = separator_barriers(png, regions)
n = se.crop_all_article_blocks(png, regions, str(OUT), pg,
dateline_starts=dateline_starts, sep_lines=sep_lines)
print(f"page {pg}: {len(dateline_starts)} datelines -> {n} article crop(s) written")
print(f"\nCrops written under: {OUT}")

31
_replay_dl9.py Normal file
View File

@ -0,0 +1,31 @@
import sys, json, shutil, os
sys.path.insert(0, "/Users/kranthikumar/Documents/telugu_extractor")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regions = json.loads((D / "page_001.regions.json").read_text())["regions"]
regions2 = se.drop_masthead_regions(regions, "andhra_jyothi", 1, png)
ds = se.find_article_starts_by_dateline(regions2, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regions2)
blocks = se.cluster_all_article_blocks(regions2, dateline_starts=ds, sep_lines=bars)
dl_ids = {s["region_id"] for s in ds}
dl_anchors = {b["anchor_id"] for b in blocks if any(m in dl_ids for m in b["members"])}
print(f"{len(ds)} datelines -> {len(dl_anchors)} dateline blocks: {sorted(dl_anchors)}")
for b in sorted(blocks, key=lambda d: (d["bbox"][1], d["bbox"][0])):
if b["anchor_id"] in dl_anchors:
print(f" {b['kind']} anchor={b['anchor_id']:>3} bbox={b['bbox']} members={sorted(b['members'])}")
out = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/dateline_crops")
if out.exists():
shutil.rmtree(out)
out.mkdir(parents=True)
n = se.crop_all_article_blocks(str(png), regions2, str(out), 1,
dateline_starts=ds, sep_lines=bars,
only_anchor_ids=dl_anchors)
print(f"\ndateline crops written: {n}")
for f in sorted(os.listdir(out / "page_001")):
print(" ", f)

24
_replay_hstrip.py Normal file
View File

@ -0,0 +1,24 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN=Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919"); D=RUN/"pages"
OUT=RUN/"all_article_crops_hstrip"; PAPER="andhra_jyothi"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2,3,4):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"]
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs); regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,PAPER,pg,png); regs=se.promote_paragraph_titles(regs,png,PAPER)
bars=separator_barriers(png,regs)
ds=se.find_article_starts_by_dateline(regs,png,PAPER)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
ext=[l for l in buf.getvalue().splitlines() if "Headline-strip" in l]
print(f"page {pg}: {n} crops | {len(ext)} headline-strip extension(s)")
for l in ext: print(" ",l.strip())
print("DONE")

48
_replay_jangaon.py Normal file
View File

@ -0,0 +1,48 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "namaste_telangana" # gates paper-specific clustering passes (pipeline sets this too)
RUN=Path(os.environ.get("JG_RUN","output/NamastheTelangana_Jangaon_20260520_20260609_130635")); D=RUN/"pages"
OUT=RUN/"all_article_crops_review"; P="namaste_telangana"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"]
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs); regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,P,pg,png); regs=se.drop_classifieds_regions(regs,png); regs=se.tag_legal_notices(regs,png); regs=se.promote_paragraph_titles(regs,png,P)
if pg==1: regs=se.detect_sakshi_headline_bands(regs,png) # NT: front-page display headlines only
regs=se.mark_bullet_titles(regs,png)
ds=se.find_article_starts_by_dateline(regs,png,P)
bars=separator_barriers(png,regs)
_probe=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
if se.recover_orphan_text_headlines(_probe,regs,png): ds=se.find_article_starts_by_dateline(regs,png,P)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
se.draw_boundary_map_debug(png,regs,D,pg,dateline_starts=ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
log=buf.getvalue()
rm={r['id']:r for r in regs}; dlids={s['region_id'] for s in ds}
print(f"\n=== page {pg}: {n} crops, {len(ds)} datelines ===")
for b in sorted(blocks,key=lambda b:(b['bbox'][1],b['bbox'][0])):
x1,y1,x2,y2=b['bbox']; w,h=x2-x1,y2-y1
ndl=sum(1 for m in b['members'] if m in dlids)
types={}
for m in b['members']:
t=rm.get(m,{}).get('type','?'); types[t]=types.get(t,0)+1
ntext=types.get('text',0); nimg=types.get('image',0)
flags=[]
if ndl>=2: flags.append(f"{ndl}DL(roundup?)")
if ntext==0 and nimg>0: flags.append("IMG-ONLY(no text)")
if len(b['members'])==1: flags.append("SINGLE-region")
if b['kind']=='O': flags.append("ORPHAN")
print(f" R{b['anchor_id']:>3} {b['kind']} {'RAW' if b.get('_raw_rect') else ' '} "
f"bbox=[{x1},{y1},{x2},{y2}] {w}x{h} mem={len(b['members'])} {types} "
f"{' '.join(flags)}")
for key in ("Section banner","Containment-merge","Headline-merge","Banner rectangle"):
c=[l.strip() for l in log.splitlines() if key in l]
if c: print(f" [{key}] x{len(c)//2 if key in ('Headline-merge','Containment-merge','Section banner','Banner rectangle') else len(c)}")
print("\nDONE ->",OUT)

34
_replay_namaste.py Normal file
View File

@ -0,0 +1,34 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "namaste_telangana" # gates paper-specific clustering passes (pipeline sets this too)
RUN=Path("output/NamastheTelangana_Siddipet_20260602_20260604_214937"); D=RUN/"pages"
OUT=RUN/"all_article_crops_final"; P="namaste_telangana"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2,3,4):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"]
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs); regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,P,pg,png); regs=se.drop_classifieds_regions(regs,png); regs=se.tag_legal_notices(regs,png); regs=se.promote_paragraph_titles(regs,png,P)
if pg==1: regs=se.detect_sakshi_headline_bands(regs,png) # NT: front-page display headlines only
regs=se.mark_bullet_titles(regs,png)
ds=se.find_article_starts_by_dateline(regs,png,P)
bars=separator_barriers(png,regs)
_probe=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
if se.recover_orphan_text_headlines(_probe,regs,png): ds=se.find_article_starts_by_dateline(regs,png,P)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
se.draw_boundary_map_debug(png,regs,D,pg,dateline_starts=ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
log=buf.getvalue()
tags={k:sum(1 for l in log.splitlines() if k in l) for k in
["Caption bound","Caption orphan dropped","bullet titles flagged","Headline-strip",
"Floor-trim","Block dateline split","Multi-column body","orphan overlap"]}
tags={k:(v//2 if k in ("Headline-strip","Floor-trim","Multi-column body","Block dateline split","orphan overlap") else v) for k,v in tags.items()}
over=[b['anchor_id'] for b in blocks if len([m for m in b['members'] if m in {s['region_id'] for s in ds}])>=2]
print(f"page {pg}: {n} crops, {len(ds)} datelines, 2+dl-blocks={over or 'none'} | "+", ".join(f"{k}:{v}" for k,v in tags.items() if v))
print("DONE ->",OUT)

47
_replay_pg1.py Normal file
View File

@ -0,0 +1,47 @@
import sys, json
sys.path.insert(0, "/Users/kranthikumar/Documents/telugu_extractor")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
png = D / "page_001.png"
regions = json.loads((D / "page_001.regions.json").read_text())["regions"]
print(f"raw regions: {len(regions)}")
# 1) masthead drop
regions2 = se.drop_masthead_regions(regions, "andhra_jyothi", 1, png)
dropped = {r['id'] for r in regions} - {r['id'] for r in regions2}
print(f"masthead dropped ids: {sorted(dropped)} -> {len(regions2)} regions")
# 2) dateline starts
ds = se.find_article_starts_by_dateline(regions2, str(png), "andhra_jyothi")
print(f"dateline starts: {len(ds)}")
for s in ds:
print(f" DL region_id={s['region_id']} headline_id={s.get('headline_id')} bbox={s['bbox']}")
# 3) separator barriers
bars = separator_barriers(str(png), regions2)
print(f"separator barriers: {len(bars)}")
# 4) cluster
blocks = se.cluster_all_article_blocks(regions2, dateline_starts=ds, sep_lines=bars)
print(f"\nblocks: {len(blocks)}")
dl_ids = {s['region_id'] for s in ds}
for b in sorted(blocks, key=lambda d: (d['bbox'][1], d['bbox'][0])):
mem = b['members']
has_dl = any(m in dl_ids for m in mem)
print(f" block anchor={b['anchor_id']} kind={b['kind']} dl={has_dl} bbox={b['bbox']} members={sorted(mem)}")
# 5) render crops to /tmp for visual check
import shutil
out = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/all_article_crops_test")
if out.exists():
shutil.rmtree(out)
out.mkdir(parents=True)
n = se.crop_all_article_blocks(str(png), regions2, str(out), 1,
dateline_starts=ds, sep_lines=bars)
print(f"\ncrops written: {n} -> {out}/page_001")
import os
for f in sorted(os.listdir(out / "page_001")):
print(" ", f)

33
_replay_pg23.py Normal file
View File

@ -0,0 +1,33 @@
import sys, json
sys.path.insert(0, "/Users/kranthikumar/Documents/telugu_extractor")
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
D = Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919/pages")
for pg in (2, 3):
png = D / f"page_{pg:03d}.png"
regions = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
regions = se.drop_masthead_regions(regions, "andhra_jyothi", pg, png)
ds = se.find_article_starts_by_dateline(regions, str(png), "andhra_jyothi")
bars = separator_barriers(str(png), regions)
blocks = se.cluster_all_article_blocks(regions, dateline_starts=ds, sep_lines=bars)
dl_ids = {s['region_id'] for s in ds}
print(f"\n==== PAGE {pg} datelines={len(ds)} bars={len(bars)} blocks={len(blocks)} ====")
# each dateline must sit in a block that ALSO has a headline/title anchor
for s in ds:
rid = s['region_id']
owner = None
for b in blocks:
if rid in b['members'] or rid == b['anchor_id']:
owner = b; break
kind = owner['kind'] if owner else '??'
ndl = sum(1 for m in owner['members'] if m in dl_ids) if owner else 0
flag = ''
if owner is None:
flag = 'LOST'
elif kind == 'DL':
flag = 'headless(DL)'
elif ndl > 1:
flag = f'MERGED({ndl} datelines)'
print(f" DL R{rid} -> block anchor={owner['anchor_id'] if owner else None} kind={kind} {flag}")

53
_replay_political.py Normal file
View File

@ -0,0 +1,53 @@
import os, tempfile, contextlib, io, json, sys, shutil
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
# Replays the POLITICAL crop step (no API): saved political_result.json member ids
# + freshly recomputed region prep chain -> crop_political_articles (block-snap).
RUNS = [
("sakshi", Path("output/Sakshi_Jangaon_District_20260520_20260609_210104"), [1]),
("andhra_jyothi", Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919"), [2, 3, 4]),
("namaste_telangana", Path("output/NamastheTelangana_Jangaon_20260520_20260608_124720"), [1]),
]
for paper, RUN, pages in RUNS:
se._ACTIVE_PAPER = paper
D = RUN / "pages"
OUT = RUN / "articles_review"
if OUT.exists():
shutil.rmtree(OUT)
for pg in pages:
png = str(D / f"page_{pg:03d}.png")
prf = D / f"page_{pg:03d}.political_result.json"
if not prf.exists() or not os.path.exists(png):
continue
political = json.load(open(prf)).get("political_articles", [])
if not political:
continue
regs = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
regs = se.dedup_overlapping_regions(regs)
regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, paper, pg, png)
regs = se.drop_classifieds_regions(regs, png)
regs = se.tag_legal_notices(regs, png)
regs = se.promote_paragraph_titles(regs, png, paper)
if paper == "sakshi":
regs = se.detect_sakshi_headline_bands(regs, png)
regs = se.mark_bullet_titles(regs, png)
ds = se.find_article_starts_by_dateline(regs, png, paper)
bars = separator_barriers(png, regs)
_p = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
if se.recover_orphan_text_headlines(_p, regs, png):
ds = se.find_article_starts_by_dateline(regs, png, paper)
recs = se.crop_political_articles(png, political, regs, OUT, pg,
dateline_starts=ds, sep_lines=bars)
print(f"=== {RUN.name} page {pg}: {len(political)} political → {len(recs)} crops")
for l in buf.getvalue().splitlines():
if any(k in l for k in ("Block-snap", "Cropped:", "Clipped", "Floored", "Grew",
"Banner", "block-snap unavailable")):
print(" ", l.strip())
print("DONE")

25
_replay_rescue.py Normal file
View File

@ -0,0 +1,25 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN=Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919"); D=RUN/"pages"
OUT=RUN/"all_article_crops_rescue"; PAPER="andhra_jyothi"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2,3,4):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"] # 0.5 saved
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs); regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,PAPER,pg,png); regs=se.promote_paragraph_titles(regs,png,PAPER)
bars=separator_barriers(png,regs)
regs=se.rescue_headlines_above_photos(regs,png,PAPER,bars) # NEW
ds=se.find_article_starts_by_dateline(regs,png,PAPER)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
resc=[l for l in buf.getvalue().splitlines() if "HEADLINE RESCUE" in l]
print(f"page {pg}: {n} crops | {len(resc)} headline(s) rescued")
for l in resc: print(" ",l.strip())
print("DONE")

47
_replay_sakshi.py Normal file
View File

@ -0,0 +1,47 @@
import os,tempfile,contextlib,io,json,shutil
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
se._ACTIVE_PAPER = "sakshi" # gate Sakshi-specific clustering passes
RUN=Path(os.environ.get("SAK_RUN","output/Sakshi_Jangaon_District_20260520_20260609_210104")); D=RUN/"pages"
OUT=RUN/"all_article_crops_review"; P="sakshi"
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2):
png=str(D/f"page_{pg:03d}.png")
regs=json.loads((D/f"page_{pg:03d}.regions.json").read_text())["regions"]
buf=io.StringIO()
with contextlib.redirect_stdout(buf):
regs=se.dedup_overlapping_regions(regs); regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,P,pg,png); regs=se.drop_classifieds_regions(regs,png); regs=se.tag_legal_notices(regs,png); regs=se.promote_paragraph_titles(regs,png,P)
regs=se.detect_sakshi_headline_bands(regs,png)
regs=se.mark_bullet_titles(regs,png)
ds=se.find_article_starts_by_dateline(regs,png,P)
bars=separator_barriers(png,regs)
_probe=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
if se.recover_orphan_text_headlines(_probe,regs,png): ds=se.find_article_starts_by_dateline(regs,png,P)
blocks=se.cluster_all_article_blocks(regs,ds,sep_lines=bars)
se.draw_boundary_map_debug(png,regs,D,pg,dateline_starts=ds,sep_lines=bars)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars,overlay=True)
log=buf.getvalue()
rm={r['id']:r for r in regs}; dlids={s['region_id'] for s in ds}
print(f"\n=== page {pg}: {n} crops, {len(ds)} datelines ===")
img_orphans=[]
for b in sorted(blocks,key=lambda b:(b['bbox'][1],b['bbox'][0])):
x1,y1,x2,y2=b['bbox']; w,h=x2-x1,y2-y1
types={}
for m in b['members']:
t=rm.get(m,{}).get('type','?'); types[t]=types.get(t,0)+1
ntext=types.get('text',0); nimg=types.get('image',0)
is_art=any(rm.get(m,{}).get('type') in ('doc_title','paragraph_title') for m in b['members']) or any(m in dlids for m in b['members'])
flags=[]
if nimg>0 and not is_art: flags.append("IMG-ORPHAN?")
if b['kind']=='O': flags.append("ORPHAN")
if nimg>0 and not is_art and ntext==0: img_orphans.append(b['anchor_id'])
print(f" R{b['anchor_id']:>3} {b['kind']} bbox=[{x1},{y1},{x2},{y2}] {w}x{h} mem={len(b['members'])} {types} {' '.join(flags)}")
binds=sorted({l.strip() for l in log.splitlines()
if "Orphan photo" in l or "Masthead" in l or "📐" in l or "rule-split" in l})
print(f" >>> image-only orphans REMAINING: {img_orphans}")
for l in binds: print(f" {l}")
print("\nDONE ->",OUT)

24
_replay_thresh045.py Normal file
View File

@ -0,0 +1,24 @@
import os,tempfile,contextlib,io
tempfile.tempdir=os.path.abspath("_tess_tmp"); os.environ["TMPDIR"]=tempfile.tempdir
os.makedirs("_tess_tmp",exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
RUN=Path("output/AndhraJyothi_Siddipet District_20260602_20260603_144919"); D=RUN/"pages"
OUT=RUN/"all_article_crops_t045"; PAPER="andhra_jyothi"
import shutil
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1,2,3,4):
png=str(D/f"page_{pg:03d}.png")
with contextlib.redirect_stdout(io.StringIO()):
regs=se.detect_regions(png, threshold=0.45) # NEW cutoff
regs=se.dedup_overlapping_regions(regs)
regs=se.merge_stacked_title_lines(regs,png)
regs=se.drop_masthead_regions(regs,PAPER,pg,png)
regs=se.promote_paragraph_titles(regs,png,PAPER)
ds=se.find_article_starts_by_dateline(regs,png,PAPER)
bars=separator_barriers(png,regs)
n=se.crop_all_article_blocks(png,regs,str(OUT),pg,dateline_starts=ds,sep_lines=bars)
nreg=len(regs)
print(f"page {pg}: {nreg} regions, {len(ds)} datelines, {n} crops")
print("DONE ->",OUT)

78
_sak_base.log Normal file
View File

@ -0,0 +1,78 @@
=== page 1: 22 crops, 17 datelines ===
R 55 H bbox=[2796,291,4114,2002] 1318x1711 mem=8 {'image': 2, 'text': 5, 'doc_title': 1}
R 94 P bbox=[2113,299,2785,3703] 672x3404 mem=5 {'paragraph_title': 1, 'text': 4}
R 52 H bbox=[1080,371,2766,2350] 1686x1979 mem=6 {'doc_title': 1, 'image': 3, 'paragraph_title': 1, 'text': 1}
R 71 DL bbox=[769,913,1375,1781] 606x868 mem=3 {'text': 2, 'paragraph_title': 1}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 11 DL bbox=[694,1789,1440,2678] 746x889 mem=1 {'text': 1}
R 77 H bbox=[2793,1973,4114,3303] 1321x1330 mem=10 {'text': 6, 'image': 1, 'figure_title': 1, 'doc_title': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 66 P bbox=[694,2686,1391,3745] 697x1059 mem=4 {'paragraph_title': 1, 'text': 3}
R 51 P bbox=[1399,2686,2105,3458] 706x772 mem=3 {'paragraph_title': 1, 'text': 2}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 78 H bbox=[749,3466,2070,6049] 1321x2583 mem=11 {'text': 8, 'image': 1, 'figure_title': 1, 'doc_title': 1}
R 54 H bbox=[2793,3509,4114,6094] 1321x2585 mem=13 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 1}
R 4 DL bbox=[2113,3707,2752,4876] 639x1169 mem=2 {'text': 1, 'image': 1}
R 3 DL bbox=[750,3753,1388,4588] 638x835 mem=1 {'text': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=4 {'doc_title': 1, 'text': 3}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,1391,6637] 619x555 mem=2 {'paragraph_title': 1, 'text': 1}
R 18 O bbox=[1430,6086,2049,6635] 619x549 mem=1 {'text': 1} ORPHAN
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
🖼 Orphan photo: R28 → article R78 (an image must belong to an article)
🖼 Orphan photo: R28 → article R78 (an image must belong to an article)
🖼 Orphan photo: R28 → article R78 (an image must belong to an article)
🖼 Orphan photo: R28 → article R78 (an image must belong to an article)
=== page 2: 20 crops, 11 datelines ===
R 61 O bbox=[777,474,1506,2308] 729x1834 mem=8 {'image': 3, 'text': 3, 'figure_title': 2} IMG-ORPHAN? ORPHAN
R 82 H bbox=[695,478,2747,2308] 2052x1830 mem=10 {'text': 3, 'image': 4, 'figure_title': 2, 'doc_title': 1}
R 55 O bbox=[2549,479,2801,1028] 252x549 mem=2 {'image': 1, 'text': 1} IMG-ORPHAN? ORPHAN
R129 P bbox=[80,482,769,1053] 689x571 mem=2 {'paragraph_title': 1, 'text': 1}
R 72 H bbox=[2755,482,3519,2232] 764x1750 mem=8 {'image': 3, 'text': 3, 'doc_title': 1, 'figure_title': 1}
R127 H bbox=[3353,482,4115,2215] 762x1733 mem=9 {'text': 4, 'image': 3, 'figure_title': 1, 'doc_title': 1}
R 24 O bbox=[80,1061,687,2305] 607x1244 mem=4 {'image': 1, 'figure_title': 1, 'text': 2} IMG-ORPHAN? ORPHAN
R118 H bbox=[2795,2298,4115,2954] 1320x656 mem=4 {'doc_title': 1, 'text': 1, 'image': 2}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R113 H bbox=[72,2417,709,4641] 637x2224 mem=7 {'doc_title': 1, 'text': 3, 'image': 2, 'figure_title': 1}
R 14 DL bbox=[3475,2685,4115,3999] 640x1314 mem=5 {'text': 3, 'image': 1, 'paragraph_title': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 4 F bbox=[2002,3658,2752,5938] 750x2280 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 3}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,6617] 478x1638 mem=6 {'text': 5, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1217,5969,2752,6682] 1535x713 mem=4 {'text': 3, 'doc_title': 1}
>>> image-only orphans REMAINING: []
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
🖼 Orphan photo: R38 → article R72 (an image must belong to an article)
🖼 Orphan photo: R48 → article R127 (an image must belong to an article)
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
🖼 Orphan photo: R38 → article R72 (an image must belong to an article)
🖼 Orphan photo: R48 → article R127 (an image must belong to an article)
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
🖼 Orphan photo: R38 → article R72 (an image must belong to an article)
🖼 Orphan photo: R48 → article R127 (an image must belong to an article)
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

63
_sak_fix2.log Normal file
View File

@ -0,0 +1,63 @@
=== page 1: 19 crops, 16 datelines ===
R 55 H bbox=[2796,1049,4114,2002] 1318x953 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 52 H bbox=[664,1067,2749,2657] 2085x1590 mem=8 {'doc_title': 1, 'text': 3, 'image': 2, 'paragraph_title': 2}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 94 P bbox=[2113,1941,2755,3257] 642x1316 mem=4 {'paragraph_title': 1, 'text': 3}
R 77 H bbox=[2793,1973,4114,3315] 1321x1342 mem=8 {'text': 6, 'doc_title': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 66 P bbox=[693,2665,1391,3349] 698x684 mem=2 {'paragraph_title': 1, 'text': 1}
R 51 P bbox=[1399,2665,2105,3349] 706x684 mem=3 {'paragraph_title': 1, 'text': 2}
R100 H bbox=[2113,2860,4114,4876] 2001x2016 mem=10 {'doc_title': 1, 'text': 5, 'image': 3, 'figure_title': 1}
R 99 H bbox=[750,3357,2068,4588] 1318x1231 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 78 H bbox=[749,3891,2070,6049] 1321x2158 mem=10 {'doc_title': 1, 'text': 8, 'figure_title': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=4 {'doc_title': 1, 'text': 3}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [664, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 21 crops, 11 datelines ===
R 61 O bbox=[777,474,1506,2308] 729x1834 mem=8 {'image': 3, 'text': 3, 'figure_title': 2} IMG-ORPHAN? ORPHAN
R 82 H bbox=[695,478,2747,2308] 2052x1830 mem=10 {'text': 3, 'image': 4, 'figure_title': 2, 'doc_title': 1}
R 55 O bbox=[2549,479,2801,1028] 252x549 mem=2 {'image': 1, 'text': 1} IMG-ORPHAN? ORPHAN
R129 P bbox=[80,482,769,1053] 689x571 mem=2 {'paragraph_title': 1, 'text': 1}
R 72 H bbox=[2755,482,3519,2232] 764x1750 mem=8 {'image': 3, 'text': 3, 'doc_title': 1, 'figure_title': 1}
R127 H bbox=[3353,482,4115,2215] 762x1733 mem=9 {'text': 4, 'image': 3, 'figure_title': 1, 'doc_title': 1}
R 24 O bbox=[80,1061,687,2305] 607x1244 mem=4 {'image': 1, 'figure_title': 1, 'text': 2} IMG-ORPHAN? ORPHAN
R118 H bbox=[2795,2298,4115,2954] 1320x656 mem=4 {'doc_title': 1, 'text': 1, 'image': 2}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R113 H bbox=[72,2417,709,4641] 637x2224 mem=7 {'doc_title': 1, 'text': 3, 'image': 2, 'figure_title': 1}
R 14 DL bbox=[3475,2685,4115,3999] 640x1314 mem=5 {'text': 3, 'image': 1, 'paragraph_title': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 99 H bbox=[650,3656,2071,6145] 1421x2489 mem=10 {'image': 1, 'text': 7, 'doc_title': 1, 'paragraph_title': 1}
R 10 F bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 4 F bbox=[2002,4723,2752,5938] 750x1215 mem=4 {'text': 2, 'paragraph_title': 2}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1104,5969,2752,6682] 1648x713 mem=4 {'text': 3, 'doc_title': 1}
R139 H bbox=[614,6155,1168,6617] 554x462 mem=2 {'doc_title': 1, 'text': 1}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R139 doc_title [614, 6155, 1168, 6236] (un-boxed display headline band, ink=0.51)
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1104, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R72 (an image must belong to an article)
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R48 → article R127 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

54
_sak_fix3.log Normal file
View File

@ -0,0 +1,54 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 1}
R 52 H bbox=[664,1067,2755,3314] 2091x2247 mem=17 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 5}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [664, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2736] 646x2237 mem=8 {'paragraph_title': 1, 'image': 2, 'text': 3, 'figure_title': 2}
R118 H bbox=[2795,2298,4115,2954] 1320x656 mem=4 {'text': 1, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 14 DL bbox=[3475,2685,4115,3999] 640x1314 mem=5 {'text': 3, 'image': 1, 'paragraph_title': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[650,3656,2071,6145] 1421x2489 mem=10 {'image': 1, 'text': 7, 'doc_title': 1, 'paragraph_title': 1}
R 10 F bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 4 F bbox=[2002,4723,2752,5938] 750x1215 mem=4 {'text': 2, 'paragraph_title': 2}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1104,5969,2752,6682] 1648x713 mem=4 {'text': 3, 'doc_title': 1}
R139 H bbox=[614,6155,1168,6617] 554x462 mem=2 {'doc_title': 1, 'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline SYNTH: R139 doc_title [614, 6155, 1168, 6236] (un-boxed display headline band, ink=0.51)
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1104, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

54
_sak_fix4.log Normal file
View File

@ -0,0 +1,54 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 5}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2736] 646x2237 mem=8 {'paragraph_title': 1, 'image': 2, 'text': 3, 'figure_title': 2}
R118 H bbox=[2795,2298,4115,2954] 1320x656 mem=4 {'text': 1, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 14 DL bbox=[3475,2685,4115,3999] 640x1314 mem=5 {'text': 3, 'image': 1, 'paragraph_title': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 4 F bbox=[2002,3658,2752,5938] 750x2280 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 3}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=4 {'text': 3, 'doc_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,6145] 478x1166 mem=5 {'text': 4, 'paragraph_title': 1}
R 77 H bbox=[1147,5193,2752,6694] 1605x1501 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R139 H bbox=[614,6155,1168,6617] 554x462 mem=2 {'doc_title': 1, 'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline SYNTH: R139 doc_title [614, 6155, 1168, 6236] (un-boxed display headline band, ink=0.51)
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

52
_sak_fix5.log Normal file
View File

@ -0,0 +1,52 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 5}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2736] 646x2237 mem=8 {'paragraph_title': 1, 'image': 2, 'text': 3, 'figure_title': 2}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2002,3658,2752,5938] 750x2280 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 3}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,6617] 478x1638 mem=6 {'text': 5, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

52
_sak_fix6.log Normal file
View File

@ -0,0 +1,52 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 5}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2002,3658,2752,5938] 750x2280 mem=8 {'text': 4, 'image': 1, 'paragraph_title': 3}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,6617] 478x1638 mem=6 {'text': 5, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

55
_sak_fix7.log Normal file
View File

@ -0,0 +1,55 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'doc_title': 1, 'text': 9, 'image': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'doc_title': 1, 'text': 9, 'image': 2, 'paragraph_title': 5}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,2752,5939] 1536x1229 mem=10 {'text': 5, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,5185] 478x206 mem=1 {'text': 1}
R 92 F bbox=[653,5252,1127,5744] 474x492 mem=3 {'text': 2, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 90 F bbox=[685,5924,1099,6145] 414x221 mem=1 {'text': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
R110 F bbox=[668,6258,1106,6617] 438x359 mem=1 {'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

55
_sak_fixABC.log Normal file
View File

@ -0,0 +1,55 @@
=== page 1: 12 crops, 16 datelines ===
R 55 H bbox=[2796,1049,4114,2002] 1318x953 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 52 H bbox=[664,1067,2749,2657] 2085x1590 mem=8 {'doc_title': 1, 'text': 3, 'image': 2, 'paragraph_title': 2}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 94 P bbox=[2113,1941,2755,3257] 642x1316 mem=4 {'paragraph_title': 1, 'text': 3}
R 77 H bbox=[2793,1973,4114,3315] 1321x1342 mem=8 {'text': 6, 'doc_title': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 66 P bbox=[693,2665,1391,6678] 698x4013 mem=2 {'paragraph_title': 1, 'text': 1}
R 51 P bbox=[1399,2665,2105,6678] 706x4013 mem=3 {'paragraph_title': 1, 'text': 2}
R 99 H bbox=[749,2860,4114,6698] 3365x3838 mem=45 {'text': 30, 'image': 5, 'figure_title': 2, 'paragraph_title': 3, 'doc_title': 5}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R99 doc_title [763, 3353, 4097, 3452] (un-boxed display headline band, ink=0.32)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [664, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 21 crops, 11 datelines ===
R 61 O bbox=[777,474,1506,2308] 729x1834 mem=8 {'image': 3, 'text': 3, 'figure_title': 2} IMG-ORPHAN? ORPHAN
R 82 H bbox=[695,478,2747,2308] 2052x1830 mem=10 {'text': 3, 'image': 4, 'figure_title': 2, 'doc_title': 1}
R 55 O bbox=[2549,479,2801,1028] 252x549 mem=2 {'image': 1, 'text': 1} IMG-ORPHAN? ORPHAN
R129 P bbox=[80,482,769,1053] 689x571 mem=2 {'paragraph_title': 1, 'text': 1}
R 72 H bbox=[2755,482,3519,2232] 764x1750 mem=8 {'image': 3, 'text': 3, 'doc_title': 1, 'figure_title': 1}
R127 H bbox=[3353,482,4115,2215] 762x1733 mem=9 {'text': 4, 'image': 3, 'figure_title': 1, 'doc_title': 1}
R 24 O bbox=[80,1061,687,2305] 607x1244 mem=4 {'image': 1, 'figure_title': 1, 'text': 2} IMG-ORPHAN? ORPHAN
R118 H bbox=[2795,2298,4115,2954] 1320x656 mem=4 {'doc_title': 1, 'text': 1, 'image': 2}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=3 {'doc_title': 1, 'text': 2}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R113 H bbox=[72,2417,709,4641] 637x2224 mem=7 {'doc_title': 1, 'text': 3, 'image': 2, 'figure_title': 1}
R 14 DL bbox=[3475,2685,4115,3999] 640x1314 mem=5 {'text': 3, 'image': 1, 'paragraph_title': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 99 H bbox=[650,3656,2071,6145] 1421x2489 mem=10 {'image': 1, 'text': 7, 'doc_title': 1, 'paragraph_title': 1}
R 10 F bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,1963,5939] 747x1229 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 4 F bbox=[2002,4723,2752,5938] 750x1215 mem=4 {'text': 2, 'paragraph_title': 2}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 77 H bbox=[1104,5969,2752,6682] 1648x713 mem=4 {'text': 3, 'doc_title': 1}
R139 H bbox=[614,6155,1168,6617] 554x462 mem=2 {'doc_title': 1, 'text': 1}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R139 doc_title [614, 6155, 1168, 6236] (un-boxed display headline band, ink=0.51)
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1104, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R72 (an image must belong to an article)
🖼 Orphan photo: R42 → article R82 (an image must belong to an article)
🖼 Orphan photo: R48 → article R127 (an image must belong to an article)
🖼 Orphan photo: R52 → article R127 (an image must belong to an article)
🖼 Orphan photo: R60 → article R82 (an image must belong to an article)
🖼 Orphan photo: R63 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

41
_sak_newrun.log Normal file
View File

@ -0,0 +1,41 @@
=== page 1: 13 crops, 14 datelines ===
R 53 H bbox=[2792,1069,4115,3285] 1323x2216 mem=14 {'doc_title': 1, 'text': 9, 'image': 1, 'figure_title': 1, 'paragraph_title': 2}
R 46 H bbox=[684,1077,2784,3285] 2100x2208 mem=21 {'doc_title': 2, 'text': 11, 'image': 2, 'paragraph_title': 6}
R 66 H bbox=[99,1078,681,2654] 582x1576 mem=3 {'doc_title': 1, 'text': 1, 'paragraph_title': 1}
R 65 H bbox=[99,2678,680,4455] 581x1777 mem=6 {'text': 3, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 82 H bbox=[2114,3293,4115,5007] 2001x1714 mem=11 {'doc_title': 1, 'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 75 H bbox=[749,3323,2069,4792] 1320x1469 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 3 DL bbox=[98,4463,741,5703] 643x1240 mem=2 {'text': 1, 'image': 1}
R 47 H bbox=[749,4850,2368,5588] 1619x738 mem=6 {'doc_title': 1, 'text': 2, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 93 H bbox=[2791,5058,4115,6247] 1324x1189 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
R 55 H bbox=[2137,5113,2736,6130] 599x1017 mem=2 {'doc_title': 1, 'text': 1}
R 44 H bbox=[98,5711,681,6639] 583x928 mem=2 {'doc_title': 1, 'text': 1}
R100 H bbox=[750,5782,2072,6697] 1322x915 mem=5 {'doc_title': 1, 'text': 2, 'image': 2}
R 56 H bbox=[2564,6284,4115,6691] 1551x407 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline WIDEN: R100 [765, 6130, 1350, 6228] → [765, 6130, 1949, 6228] (un-boxed kicker ink alongside)
=== page 2: 16 crops, 15 datelines ===
R 61 H bbox=[931,490,4115,2536] 3184x2046 mem=32 {'text': 15, 'image': 8, 'doc_title': 2, 'paragraph_title': 5, 'figure_title': 2}
R106 H bbox=[2081,490,2753,5535] 672x5045 mem=6 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 2, 'paragraph_title': 1}
R105 P bbox=[78,498,898,1656] 820x1158 mem=5 {'paragraph_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R128 P bbox=[106,1664,860,2507] 754x843 mem=4 {'paragraph_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R 57 H bbox=[751,2579,2754,4161] 2003x1582 mem=10 {'doc_title': 1, 'text': 7, 'image': 1, 'paragraph_title': 1}
R 69 H bbox=[2794,2589,4115,3863] 1321x1274 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R121 DL bbox=[70,2805,709,3948] 639x1143 mem=6 {'text': 3, 'image': 1, 'paragraph_title': 1, 'figure_title': 1}
R100 H bbox=[752,3306,2070,5044] 1318x1738 mem=5 {'text': 4, 'doc_title': 1}
R 95 H bbox=[2795,3914,4115,4990] 1320x1076 mem=6 {'doc_title': 2, 'image': 1, 'text': 2, 'figure_title': 1}
R 87 DL bbox=[73,3974,709,5049] 636x1075 mem=5 {'text': 2, 'image': 2, 'figure_title': 1}
R 80 H bbox=[2814,5054,4115,6172] 1301x1118 mem=7 {'text': 4, 'image': 2, 'doc_title': 1}
R115 DL bbox=[94,5097,1161,6624] 1067x1527 mem=5 {'text': 4, 'image': 1}
R107 H bbox=[1199,5102,2071,6685] 872x1583 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 74 F bbox=[658,5350,1139,6624] 481x1274 mem=4 {'text': 4}
R 7 DL bbox=[2114,5559,2754,6684] 640x1125 mem=3 {'text': 1, 'image': 1, 'paragraph_title': 1}
R 90 H bbox=[2814,5760,3431,6635] 617x875 mem=4 {'doc_title': 1, 'text': 3}
R 76 H bbox=[3474,5970,4115,6634] 641x664 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R12 → 2 bands at rules [4170]
🖼 Orphan photo: R36 → article R87 (an image must belong to an article)
DONE -> output/Sakshi_Jangaon District_20260513_20260610_140735/all_article_crops_review

50
_sak_newrun2.log Normal file
View File

@ -0,0 +1,50 @@
=== page 1: 19 crops, 14 datelines ===
R 53 H bbox=[2792,1069,4115,3285] 1323x2216 mem=14 {'text': 9, 'image': 1, 'doc_title': 1, 'figure_title': 1, 'paragraph_title': 2}
R 46 H bbox=[684,1077,2784,2187] 2100x1110 mem=8 {'doc_title': 2, 'text': 4, 'image': 1, 'paragraph_title': 1}
R 84 P bbox=[1433,1077,2107,2755] 674x1678 mem=2 {'paragraph_title': 1, 'text': 1}
R 66 H bbox=[99,1078,681,2654] 582x1576 mem=3 {'doc_title': 1, 'text': 1, 'paragraph_title': 1}
R 45 P bbox=[689,2195,1425,3315] 736x1120 mem=2 {'paragraph_title': 1, 'text': 1}
R 72 P bbox=[2115,2195,2784,2915] 669x720 mem=5 {'paragraph_title': 1, 'text': 3, 'image': 1}
R 65 H bbox=[99,2678,680,4291] 581x1613 mem=4 {'text': 2, 'image': 1, 'doc_title': 1}
R 48 P bbox=[1433,2763,2107,3315] 674x552 mem=2 {'paragraph_title': 1, 'text': 1}
R 60 P bbox=[2115,2923,2784,3285] 669x362 mem=2 {'paragraph_title': 1, 'text': 1}
R 82 H bbox=[2114,3293,4115,5007] 2001x1714 mem=11 {'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1, 'doc_title': 1}
R 75 H bbox=[749,3323,2069,4792] 1320x1469 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 86 P bbox=[102,4299,741,4455] 639x156 mem=2 {'paragraph_title': 1, 'text': 1}
R 3 DL bbox=[98,4463,741,5703] 643x1240 mem=2 {'text': 1, 'image': 1}
R 47 H bbox=[749,4850,2368,5588] 1619x738 mem=6 {'doc_title': 1, 'text': 2, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 93 H bbox=[2791,5058,4115,6247] 1324x1189 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
R 55 H bbox=[2137,5113,2736,6130] 599x1017 mem=2 {'doc_title': 1, 'text': 1}
R 44 H bbox=[98,5711,681,6639] 583x928 mem=2 {'doc_title': 1, 'text': 1}
R100 H bbox=[750,5782,2072,6697] 1322x915 mem=5 {'doc_title': 1, 'text': 2, 'image': 2}
R 56 H bbox=[2564,6284,4115,6691] 1551x407 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline WIDEN: R100 [765, 6130, 1350, 6228] → [765, 6130, 1949, 6228] (un-boxed kicker ink alongside)
=== page 2: 19 crops, 15 datelines ===
R 61 H bbox=[931,490,4115,2536] 3184x2046 mem=32 {'text': 15, 'image': 8, 'doc_title': 2, 'paragraph_title': 5, 'figure_title': 2}
R127 P bbox=[2081,490,2754,4147] 673x3657 mem=3 {'paragraph_title': 1, 'text': 1, 'figure_title': 1}
R105 P bbox=[78,498,898,1518] 820x1020 mem=5 {'paragraph_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R121 DL bbox=[70,1526,743,3817] 673x2291 mem=4 {'text': 2, 'image': 1, 'figure_title': 1}
R128 P bbox=[106,1664,860,2507] 754x843 mem=4 {'paragraph_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R 57 H bbox=[754,2579,2751,3639] 1997x1060 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 69 H bbox=[2794,2589,4115,3863] 1321x1274 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 4 DL bbox=[751,3306,2071,4149] 1320x843 mem=3 {'text': 3}
R 81 P bbox=[75,3825,743,3966] 668x141 mem=2 {'paragraph_title': 1, 'text': 1}
R 95 H bbox=[2795,3914,4115,4990] 1320x1076 mem=6 {'doc_title': 2, 'image': 1, 'text': 2, 'figure_title': 1}
R 87 DL bbox=[73,3974,709,5049] 636x1075 mem=5 {'text': 2, 'image': 2, 'figure_title': 1}
R100 H bbox=[752,4203,2070,5044] 1318x841 mem=4 {'text': 3, 'doc_title': 1}
R106 H bbox=[2114,4209,2753,5535] 639x1326 mem=5 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 80 H bbox=[2814,5054,4115,6172] 1301x1118 mem=7 {'text': 4, 'image': 2, 'doc_title': 1}
R115 DL bbox=[94,5097,1161,6624] 1067x1527 mem=5 {'text': 4, 'image': 1}
R107 H bbox=[1199,5102,2071,6685] 872x1583 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 74 F bbox=[658,5350,1139,6624] 481x1274 mem=4 {'text': 4}
R 7 DL bbox=[2114,5559,2754,6684] 640x1125 mem=3 {'text': 1, 'image': 1, 'paragraph_title': 1}
R 90 H bbox=[2814,5760,3431,6635] 617x875 mem=4 {'doc_title': 1, 'text': 3}
R 76 H bbox=[3474,5970,4115,6634] 641x664 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R12 → 2 bands at rules [4170]
🖼 Orphan photo: R36 → article R87 (an image must belong to an article)
DONE -> output/Sakshi_Jangaon District_20260513_20260610_140735/all_article_crops_review

42
_sak_newrun3.log Normal file
View File

@ -0,0 +1,42 @@
=== page 1: 14 crops, 14 datelines ===
R 53 H bbox=[2792,1069,4115,3285] 1323x2216 mem=14 {'text': 9, 'image': 1, 'doc_title': 1, 'figure_title': 1, 'paragraph_title': 2}
R 46 H bbox=[684,1077,2784,3285] 2100x2208 mem=21 {'text': 11, 'image': 2, 'paragraph_title': 6, 'doc_title': 2}
R 66 H bbox=[99,1078,681,2654] 582x1576 mem=3 {'doc_title': 1, 'text': 1, 'paragraph_title': 1}
R 65 H bbox=[99,2678,680,4291] 581x1613 mem=4 {'text': 2, 'image': 1, 'doc_title': 1}
R 82 H bbox=[2114,3293,4115,5007] 2001x1714 mem=11 {'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1, 'doc_title': 1}
R 75 H bbox=[749,3323,2069,4792] 1320x1469 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 86 P bbox=[102,4299,741,4455] 639x156 mem=2 {'paragraph_title': 1, 'text': 1}
R 3 DL bbox=[98,4463,741,5703] 643x1240 mem=2 {'text': 1, 'image': 1}
R 47 H bbox=[749,4850,2368,5588] 1619x738 mem=6 {'doc_title': 1, 'text': 2, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 93 H bbox=[2791,5058,4115,6247] 1324x1189 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
R 55 H bbox=[2137,5113,2736,6130] 599x1017 mem=2 {'doc_title': 1, 'text': 1}
R 44 H bbox=[98,5711,681,6639] 583x928 mem=2 {'doc_title': 1, 'text': 1}
R100 H bbox=[750,5782,2072,6697] 1322x915 mem=5 {'doc_title': 1, 'text': 2, 'image': 2}
R 56 H bbox=[2564,6284,4115,6691] 1551x407 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline WIDEN: R100 [765, 6130, 1350, 6228] → [765, 6130, 1949, 6228] (un-boxed kicker ink alongside)
=== page 2: 16 crops, 15 datelines ===
R 61 H bbox=[931,490,4115,2536] 3184x2046 mem=32 {'text': 15, 'image': 8, 'doc_title': 2, 'paragraph_title': 5, 'figure_title': 2}
R106 H bbox=[2081,490,2753,5535] 672x5045 mem=6 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 2, 'paragraph_title': 1}
R105 P bbox=[78,498,898,1656] 820x1158 mem=5 {'paragraph_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R128 P bbox=[106,1664,860,2507] 754x843 mem=4 {'paragraph_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R 57 H bbox=[751,2579,2754,4161] 2003x1582 mem=11 {'text': 8, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 69 H bbox=[2794,2589,4115,3863] 1321x1274 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R121 DL bbox=[70,2805,709,3948] 639x1143 mem=6 {'text': 3, 'image': 1, 'paragraph_title': 1, 'figure_title': 1}
R 95 H bbox=[2795,3914,4115,4990] 1320x1076 mem=6 {'doc_title': 2, 'image': 1, 'text': 2, 'figure_title': 1}
R 87 DL bbox=[73,3974,709,5049] 636x1075 mem=5 {'text': 2, 'image': 2, 'figure_title': 1}
R100 H bbox=[752,4203,2070,5044] 1318x841 mem=4 {'text': 3, 'doc_title': 1}
R 80 H bbox=[2814,5054,4115,6172] 1301x1118 mem=7 {'text': 4, 'image': 2, 'doc_title': 1}
R115 DL bbox=[94,5097,1161,6624] 1067x1527 mem=5 {'text': 4, 'image': 1}
R107 H bbox=[1199,5102,2071,6685] 872x1583 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 74 F bbox=[658,5350,1139,6624] 481x1274 mem=4 {'text': 4}
R 7 DL bbox=[2114,5559,2754,6684] 640x1125 mem=3 {'text': 1, 'image': 1, 'paragraph_title': 1}
R 90 H bbox=[2814,5760,3431,6635] 617x875 mem=4 {'doc_title': 1, 'text': 3}
R 76 H bbox=[3474,5970,4115,6634] 641x664 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R12 → 2 bands at rules [4170]
🖼 Orphan photo: R36 → article R87 (an image must belong to an article)
DONE -> output/Sakshi_Jangaon District_20260513_20260610_140735/all_article_crops_review

42
_sak_newrun4.log Normal file
View File

@ -0,0 +1,42 @@
=== page 1: 14 crops, 14 datelines ===
R 53 H bbox=[2792,1069,4115,3285] 1323x2216 mem=14 {'text': 9, 'image': 1, 'doc_title': 1, 'figure_title': 1, 'paragraph_title': 2}
R 46 H bbox=[684,1077,2784,3285] 2100x2208 mem=21 {'text': 11, 'image': 2, 'paragraph_title': 6, 'doc_title': 2}
R 66 H bbox=[99,1078,681,2654] 582x1576 mem=3 {'doc_title': 1, 'text': 1, 'paragraph_title': 1}
R 65 H bbox=[99,2678,680,4291] 581x1613 mem=4 {'text': 2, 'image': 1, 'doc_title': 1}
R 82 H bbox=[2114,3293,4115,5007] 2001x1714 mem=11 {'text': 7, 'image': 1, 'figure_title': 1, 'paragraph_title': 1, 'doc_title': 1}
R 75 H bbox=[749,3323,2069,4792] 1320x1469 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 86 P bbox=[102,4299,741,4455] 639x156 mem=2 {'paragraph_title': 1, 'text': 1}
R 3 DL bbox=[98,4463,741,5703] 643x1240 mem=2 {'text': 1, 'image': 1}
R 47 H bbox=[749,4850,2368,5588] 1619x738 mem=6 {'doc_title': 1, 'text': 2, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 93 H bbox=[2791,5058,4115,6247] 1324x1189 mem=6 {'doc_title': 1, 'text': 3, 'image': 1, 'figure_title': 1}
R 55 H bbox=[2137,5113,2736,6130] 599x1017 mem=2 {'doc_title': 1, 'text': 1}
R 44 H bbox=[98,5711,681,6639] 583x928 mem=2 {'doc_title': 1, 'text': 1}
R100 H bbox=[750,5782,2072,6697] 1322x915 mem=5 {'doc_title': 1, 'text': 2, 'image': 2}
R 56 H bbox=[2564,6284,4115,6691] 1551x407 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline WIDEN: R100 [765, 6130, 1350, 6228] → [765, 6130, 1949, 6228] (un-boxed kicker ink alongside)
=== page 2: 16 crops, 15 datelines ===
R 61 H bbox=[931,490,4115,2536] 3184x2046 mem=33 {'text': 15, 'image': 8, 'doc_title': 2, 'paragraph_title': 5, 'figure_title': 3}
R105 P bbox=[78,498,898,1656] 820x1158 mem=5 {'paragraph_title': 1, 'image': 1, 'text': 2, 'figure_title': 1}
R128 P bbox=[106,1664,860,2507] 754x843 mem=4 {'paragraph_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R 57 H bbox=[751,2579,2754,4161] 2003x1582 mem=11 {'text': 8, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 69 H bbox=[2794,2589,4115,3863] 1321x1274 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R121 DL bbox=[70,2805,709,3948] 639x1143 mem=6 {'text': 3, 'image': 1, 'paragraph_title': 1, 'figure_title': 1}
R 95 H bbox=[2795,3914,4115,4990] 1320x1076 mem=6 {'doc_title': 2, 'image': 1, 'text': 2, 'figure_title': 1}
R 87 DL bbox=[73,3974,709,5049] 636x1075 mem=5 {'text': 2, 'image': 2, 'figure_title': 1}
R100 H bbox=[752,4203,2070,5044] 1318x841 mem=4 {'text': 3, 'doc_title': 1}
R106 H bbox=[2114,4209,2753,5535] 639x1326 mem=5 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1, 'paragraph_title': 1}
R 80 H bbox=[2814,5054,4115,6172] 1301x1118 mem=7 {'text': 4, 'image': 2, 'doc_title': 1}
R115 DL bbox=[94,5097,1161,6624] 1067x1527 mem=5 {'text': 4, 'image': 1}
R107 H bbox=[1199,5102,2071,6685] 872x1583 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 74 F bbox=[658,5350,1139,6624] 481x1274 mem=4 {'text': 4}
R 7 DL bbox=[2114,5559,2754,6684] 640x1125 mem=3 {'text': 1, 'image': 1, 'paragraph_title': 1}
R 90 H bbox=[2814,5760,3431,6635] 617x875 mem=4 {'doc_title': 1, 'text': 3}
R 76 H bbox=[3474,5970,4115,6634] 641x664 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R12 → 2 bands at rules [4170]
🖼 Orphan photo: R36 → article R87 (an image must belong to an article)
DONE -> output/Sakshi_Jangaon District_20260513_20260610_140735/all_article_crops_review

58
_sak_oldrun2.log Normal file
View File

@ -0,0 +1,58 @@
=== page 1: 19 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'text': 9, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2749,2854] 2061x1787 mem=9 {'doc_title': 1, 'text': 4, 'image': 2, 'paragraph_title': 2}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 94 P bbox=[2113,1941,2755,3257] 642x1316 mem=4 {'paragraph_title': 1, 'text': 3}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 66 P bbox=[749,2686,1391,3302] 642x616 mem=2 {'paragraph_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R 51 P bbox=[1399,2862,2105,3349] 706x487 mem=2 {'paragraph_title': 1, 'text': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,2752,5939] 1536x1229 mem=10 {'text': 5, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,5185] 478x206 mem=1 {'text': 1}
R 92 F bbox=[653,5252,1127,5744] 474x492 mem=3 {'text': 2, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 90 F bbox=[685,5924,1099,6145] 414x221 mem=1 {'text': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
R110 F bbox=[668,6258,1106,6617] 438x359 mem=1 {'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

55
_sak_oldrun3.log Normal file
View File

@ -0,0 +1,55 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'text': 9, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'text': 9, 'image': 2, 'paragraph_title': 5, 'doc_title': 1}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,2752,5939] 1536x1229 mem=10 {'text': 5, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,5185] 478x206 mem=1 {'text': 1}
R 92 F bbox=[653,5252,1127,5744] 474x492 mem=3 {'text': 2, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 90 F bbox=[685,5924,1099,6145] 414x221 mem=1 {'text': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
R110 F bbox=[668,6258,1106,6617] 438x359 mem=1 {'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

55
_sak_oldrun4.log Normal file
View File

@ -0,0 +1,55 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'text': 9, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'text': 9, 'image': 2, 'paragraph_title': 5, 'doc_title': 1}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,2752,5939] 1536x1229 mem=10 {'text': 5, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,5185] 478x206 mem=1 {'text': 1}
R 92 F bbox=[653,5252,1127,5744] 474x492 mem=3 {'text': 2, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 90 F bbox=[685,5924,1099,6145] 414x221 mem=1 {'text': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
R110 F bbox=[668,6258,1106,6617] 438x359 mem=1 {'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

55
_sak_val7.log Normal file
View File

@ -0,0 +1,55 @@
=== page 1: 16 crops, 16 datelines ===
R 55 H bbox=[2763,1049,4114,2732] 1351x1683 mem=12 {'text': 9, 'image': 1, 'doc_title': 1, 'paragraph_title': 1}
R 52 H bbox=[688,1067,2755,3314] 2067x2247 mem=17 {'text': 9, 'image': 2, 'paragraph_title': 5, 'doc_title': 1}
R 83 H bbox=[92,1079,686,2507] 594x1428 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'paragraph_title': 1}
R 72 H bbox=[92,2536,685,3404] 593x868 mem=2 {'doc_title': 1, 'text': 1}
R 77 H bbox=[2793,2740,4114,3303] 1321x563 mem=4 {'doc_title': 1, 'text': 1, 'image': 1, 'figure_title': 1}
R100 H bbox=[2113,3353,4113,4297] 2000x944 mem=7 {'doc_title': 1, 'text': 5, 'image': 1}
R 99 H bbox=[750,3357,2069,4605] 1319x1248 mem=8 {'doc_title': 1, 'text': 5, 'image': 1, 'figure_title': 1}
R 67 H bbox=[92,3432,686,4743] 594x1311 mem=3 {'doc_title': 1, 'text': 1, 'image': 1}
R 50 H bbox=[2112,4357,2752,5581] 640x1224 mem=5 {'text': 3, 'image': 1, 'doc_title': 1}
R 54 H bbox=[2793,4368,4114,6094] 1321x1726 mem=9 {'doc_title': 1, 'text': 6, 'image': 1, 'paragraph_title': 1}
R 78 H bbox=[749,4622,2070,6049] 1321x1427 mem=7 {'doc_title': 1, 'text': 6}
R 9 DL bbox=[92,4771,685,5982] 593x1211 mem=2 {'text': 1, 'paragraph_title': 1}
R 5 DL bbox=[2112,5609,2753,6686] 641x1077 mem=2 {'text': 1, 'paragraph_title': 1}
R 75 H bbox=[91,6012,688,6640] 597x628 mem=2 {'doc_title': 1, 'text': 1}
R 79 P bbox=[772,6082,2049,6637] 1277x555 mem=3 {'text': 2, 'paragraph_title': 1}
R 98 H bbox=[2793,6114,4114,6696] 1321x582 mem=3 {'doc_title': 1, 'text': 2}
>>> image-only orphans REMAINING: []
📐 Sakshi headline SYNTH: R100 doc_title [2126, 3353, 4097, 3452] (un-boxed display headline band, ink=0.35)
📐 Sakshi headline SYNTH: R99 doc_title [763, 3357, 2068, 3452] (un-boxed display headline band, ink=0.30)
📐 Sakshi headline WIDEN: R52 [1182, 1067, 2738, 1327] → [688, 1067, 2738, 1327] (un-boxed kicker ink alongside)
🗞️ Masthead: dropped 5 Sakshi top-band region(s) [59, 62, 63, 64, 71] (above first headline y=1049)
=== page 2: 17 crops, 11 datelines ===
R 82 H bbox=[777,474,2801,2212] 2024x1738 mem=20 {'text': 7, 'image': 8, 'figure_title': 4, 'doc_title': 1}
R127 H bbox=[2755,485,4086,2232] 1331x1747 mem=17 {'text': 8, 'image': 6, 'figure_title': 2, 'doc_title': 1}
R129 P bbox=[95,499,741,2209] 646x1710 mem=6 {'paragraph_title': 1, 'image': 1, 'text': 3, 'figure_title': 1}
R118 H bbox=[2795,2298,4115,2957] 1320x659 mem=5 {'text': 2, 'image': 2, 'doc_title': 1}
R 91 H bbox=[72,2313,707,3101] 635x788 mem=5 {'text': 2, 'image': 1, 'doc_title': 1, 'figure_title': 1}
R 41 H bbox=[753,2316,2753,3600] 2000x1284 mem=11 {'text': 9, 'doc_title': 1, 'image': 1}
R 3 DL bbox=[2761,2958,3467,4049] 706x1091 mem=3 {'text': 1, 'image': 1, 'figure_title': 1}
R 14 DL bbox=[3475,3022,4115,3999] 640x977 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R113 H bbox=[72,3129,709,4641] 637x1512 mem=5 {'doc_title': 1, 'text': 3, 'image': 1}
R 99 H bbox=[752,3656,2071,4649] 1319x993 mem=5 {'image': 1, 'text': 3, 'doc_title': 1}
R 88 P bbox=[2113,3658,2752,4643] 639x985 mem=4 {'text': 2, 'image': 1, 'paragraph_title': 1}
R 70 H bbox=[2760,4057,4115,5702] 1355x1645 mem=11 {'doc_title': 1, 'text': 9, 'image': 1}
R 93 H bbox=[1216,4710,2752,5939] 1536x1229 mem=10 {'text': 5, 'image': 1, 'doc_title': 1, 'paragraph_title': 2, 'figure_title': 1}
R136 P bbox=[95,4735,1161,5529] 1066x794 mem=3 {'paragraph_title': 1, 'text': 1, 'image': 1}
R 44 F bbox=[650,4979,1128,5185] 478x206 mem=1 {'text': 1}
R 92 F bbox=[653,5252,1127,5744] 474x492 mem=3 {'text': 2, 'paragraph_title': 1}
R111 P bbox=[120,5618,603,6628] 483x1010 mem=3 {'paragraph_title': 1, 'text': 2}
R 98 H bbox=[2798,5710,4115,6696] 1317x986 mem=6 {'doc_title': 1, 'text': 4, 'image': 1}
R 90 F bbox=[685,5924,1099,6145] 414x221 mem=1 {'text': 1}
R 77 H bbox=[1147,5969,2752,6682] 1605x713 mem=4 {'text': 3, 'doc_title': 1}
R110 F bbox=[668,6258,1106,6617] 438x359 mem=1 {'text': 1}
>>> image-only orphans REMAINING: []
─ Sakshi rule-split: R41 → 3 bands at rules [1066, 2252]
─ Sakshi rule-split: R74 → 2 bands at rules [1066]
📐 Sakshi headline WIDEN: R77 [1217, 5969, 2690, 6109] → [1147, 5969, 2690, 6109] (un-boxed kicker ink alongside)
🖼 Orphan photo: R38 → article R127 (an image must belong to an article)
🖼 Orphan photo: R61 → article R82 (an image must belong to an article)
🗞️ Masthead: dropped 4 Sakshi top-band region(s) [68, 71, 84, 104] (above first headline y=499)
DONE -> output/Sakshi_Jangaon_District_20260520_20260609_210104/all_article_crops_review

50
_sim71.py Normal file
View File

@ -0,0 +1,50 @@
"""Sandbox simulation of the Sakshi Jangaon page-1/2 crop run.
Telugu tesseract is unavailable here, so dateline starts are INJECTED with the
exact anchor set the real 20260609_210104 run found (DL anchors from the saved
crop filenames). Usage: python3 _sim71.py <tag>
Writes block table to stdout and crops to output/.../_sim_crops_<tag>/."""
import sys, types
sys.modules.setdefault("fitz", types.ModuleType("fitz"))
import os, tempfile, contextlib, io, json, shutil
tempfile.tempdir = os.path.abspath("_tess_tmp"); os.environ["TMPDIR"] = tempfile.tempdir
os.makedirs("_tess_tmp", exist_ok=True)
from pathlib import Path
import smart_extractor as se
from _lines import separator_barriers
TAG = sys.argv[1] if len(sys.argv) > 1 else "base"
RUN = Path("output/Sakshi_Jangaon_District_20260520_20260609_210104"); D = RUN / "pages"
OUT = RUN / f"_sim_crops_{TAG}"; P = "sakshi"
DL_IDS = {1: [71, 11, 4, 3, 9, 5], 2: []} # real run's headline-less datelines
if OUT.exists(): shutil.rmtree(OUT)
for pg in (1, 2):
png = str(D / f"page_{pg:03d}.png")
regs = json.loads((D / f"page_{pg:03d}.regions.json").read_text())["regions"]
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
regs = se.dedup_overlapping_regions(regs)
regs = se.merge_stacked_title_lines(regs, png)
regs = se.drop_masthead_regions(regs, P, pg, png)
regs = se.drop_classifieds_regions(regs, png)
regs = se.tag_legal_notices(regs, png)
regs = se.promote_paragraph_titles(regs, png, P)
regs = se.mark_bullet_titles(regs, png)
rm = {r["id"]: r for r in regs}
ds = [{"region_id": i, "dateline": "sim:", "bbox": rm[i]["bbox"],
"headline_region": None} for i in DL_IDS[pg] if i in rm]
bars = separator_barriers(png, regs)
blocks = se.cluster_all_article_blocks(regs, ds, sep_lines=bars)
n = se.crop_all_article_blocks(png, regs, str(OUT), pg,
dateline_starts=ds, sep_lines=bars)
print(f"=== page {pg}: {n} crops, {len(ds)} injected datelines ===")
for b in sorted(blocks, key=lambda b: (b["bbox"][1], b["bbox"][0])):
x1, y1, x2, y2 = b["bbox"]
types = {}
for m in b["members"]:
t = rm.get(m, {}).get("type", "?"); types[t] = types.get(t, 0) + 1
print(f" R{b['anchor_id']:>3} {b['kind']} bbox=[{x1},{y1},{x2},{y2}] "
f"{x2-x1}x{y2-y1} mem={len(b['members'])} {types}")
for l in buf.getvalue().splitlines():
if "Masthead" in l: print(" ", l.strip())
print("DONE ->", OUT)

0
_sim_base.log Normal file
View File

0
_tess_tmp/tess_14ktolcb Normal file
View File

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

0
_tess_tmp/tess_1511jiz9 Normal file
View File

View File

@ -0,0 +1,18 @@
ADEA ASEXE shotloo Sr crsrAs
Boas dof datwera, Ads Soseor
weak? Hiirtht dod Siro. Kew0e vOt
BORD arLBSCOS TS SUNG HOTS
SS SHS EH ert. SYS OED HU,
Brag SomoSodS Dweakl SASHyw
TY. 80 OY GSo DHWBwHS S%So Bra
oer BS BP wid Sard dren Sewn
Sndke dSKo Hnowt, & Ysos!
BraS Zodoh soerr Gardsivg
DUS HEH OZ dbx Som Soebrey
Perle SH Spor Sad Seared. ac
GAS SOShowsh dso SO advert
Sto, SEBS SrerS ZokoH Sodh ws
B%o BLISS SS SyPHHS Ser
UPSPAE SOY. wisp Ero HS Koha
SE SK0. Stot dSthen Kar aeh'B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

0
_tess_tmp/tess_1onw_da9 Normal file
View File

View File

@ -0,0 +1,12 @@
BS: GSS Sel Ste (ohowS)
Srghoh SES wQos*s eer wpideSo
& NODA. 2025 ws8 LeeS* Snifo Tox
©%8 S0508 ZHf6* ToXoT09 s0A8
SeghoF SQ erp, 90.59 wo Karciws
8008. 20m 72,194 0 LY soshoh
wokofire 408. warshdS(SgE ssp
espe) Soms 2,33,75 Tre, 31.93 wdor
608. vert & 3018 5 d45* HOR ay
Segboh SOB 3.214 Sow wonsen
weberthen BDar%.

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

0
_tess_tmp/tess_1sf6wilh Normal file
View File

View File

@ -0,0 +1,18 @@
BPSASOU: DeogS adFgroo wdhen L608
woxso maaan. oY SAijo mddos
Dio AGSL0B. wd Gesey wwe od:
BOE nO AE SHE wSsr¥o ¥Oy0N0
&. SiSHoE NDS Gadag we nod
CRE AHIYHasryon. wond Neds
PH SrSo nddo woo SASS ors
Gets. OY aro{go SpSaras Higher, OF
ROARS, DAS PASS, Rrcsorer Bo S*ar
doS HS Sy b3de Se, Smaysd
aPOE URES DS WES BdaryWS SEyhe
S850 wdgsore Boorres Ga GE & Hearrs
RS Yond OWGE doryim Sod. oHo8
wont DeiegS Soxseo ae ads, bf es
8, éé a ALa6, dé 25K4495 aod
Bo Exp 0% J actyso Sodd davrd) s08.
BaSSoo8 nado BE 4408. 3&% &%7.0

Some files were not shown because too many files have changed in this diff Show More