|
|
||
|---|---|---|
| _tess_tmp | ||
| scratch_py | ||
| static | ||
| templates | ||
| .gitignore | ||
| CROP_STRATEGY.md | ||
| EDGE_CASES.txt | ||
| Extractor_Guide.docx | ||
| HOW_IT_WORKS.docx | ||
| PROCESS_AND_PROMPTS.md | ||
| README.md | ||
| _aj_base.log | ||
| _aj_val.log | ||
| _aj_val2.log | ||
| _aj_val3.log | ||
| _aj_val4.log | ||
| _aj_val5.log | ||
| _cls.txt | ||
| _cmp_mc.py | ||
| _dump_all.py | ||
| _gap_probe.py | ||
| _jg_base.log | ||
| _jg_val.log | ||
| _jg_val2.log | ||
| _jg_val3.log | ||
| _jg_val4.log | ||
| _jg_val5.log | ||
| _jg_val6.log | ||
| _jg_val7.log | ||
| _jg_val8.log | ||
| _jg_val8b.log | ||
| _lc3.txt | ||
| _lencheck.txt | ||
| _lencheck2.txt | ||
| _lines.py | ||
| _nt4.txt | ||
| _nt_base.log | ||
| _nt_val.log | ||
| _nt_val2.log | ||
| _nt_val3.log | ||
| _nt_val4.log | ||
| _nt_val5.log | ||
| _nt_val6.log | ||
| _nt_val7.log | ||
| _nt_val8.log | ||
| _nta.log | ||
| _nta2.log | ||
| _nta3.log | ||
| _nta4.log | ||
| _orphan_check.py | ||
| _partition_debug.py | ||
| _probe71_tmp.py | ||
| _probe_above.py | ||
| _probe_bars.py | ||
| _probe_dl.py | ||
| _probe_fake.py | ||
| _probe_fake2.py | ||
| _probe_fix.py | ||
| _probe_fix2.py | ||
| _r65.txt | ||
| _r121.txt | ||
| _rSAK.txt | ||
| _replay_adilabad.py | ||
| _replay_all.py | ||
| _replay_crop_all.py | ||
| _replay_dl9.py | ||
| _replay_hstrip.py | ||
| _replay_jangaon.py | ||
| _replay_namaste.py | ||
| _replay_pg1.py | ||
| _replay_pg23.py | ||
| _replay_political.py | ||
| _replay_rescue.py | ||
| _replay_sakshi.py | ||
| _replay_thresh045.py | ||
| _sak_base.log | ||
| _sak_fix2.log | ||
| _sak_fix3.log | ||
| _sak_fix4.log | ||
| _sak_fix5.log | ||
| _sak_fix6.log | ||
| _sak_fix7.log | ||
| _sak_fixABC.log | ||
| _sak_newrun.log | ||
| _sak_newrun2.log | ||
| _sak_newrun3.log | ||
| _sak_newrun4.log | ||
| _sak_oldrun2.log | ||
| _sak_oldrun3.log | ||
| _sak_oldrun4.log | ||
| _sak_val7.log | ||
| _sim71.py | ||
| _sim_base.log | ||
| _testall.txt | ||
| _testcls.py | ||
| _testcls.txt | ||
| _trace76.py | ||
| _trace76b.py | ||
| _vwalls_test.py | ||
| app.py | ||
| claude_ocr.py | ||
| crop_political_articles.py | ||
| extractor.py | ||
| generate_pdf.py | ||
| political_filter.py | ||
| requirements.txt | ||
| run_pipeline.py | ||
| smart_extractor.py | ||
| test_padding.py | ||
| test_pdf.py | ||
README.md
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
# 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
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.01–0.05 per page.
To enable, set the environment variable before running:
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
python app.py
Then open http://localhost:5000.
How to use
-
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.
-
Click "Start extraction". A background job kicks off and the page redirects to a job view that polls for status.
-
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
- The cropped image (
-
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
titleregions as article starts - Use the
datelinefield 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.
- Column partition — regions clustered by
leftcoordinate. - Within each column:
titleregion OR dateline match starts a new article; everything else attaches to the current one.
To tune the rules:
- Adjust
DATELINE_REfor 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 80–95% 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.