Commit graph

26 commits

Author SHA1 Message Date
LLLin000
d50ea4631b fix: final cleanup before merge — stale test, helper, RGB safety
1. Stale test: test_extract_objects_renders_same_page_once_for_multiple_crops
   now asserts render_pdf_page_cached is NOT called (new PageRenderContext path)
   and pages/page_001.jpg is NOT created.

2. Extract _resolve_object_crop_pdf_path() helper with 4-case test.
   Phase 4 now uses it instead of inline fallback logic.
   Test locks in: Phase 1 resolved path beats stale meta['source_pdf'].

3. PageRenderContext: use fitz.csRGB in get_pixmap() for safe CMYK/n>3 handling.
   Removed fragile pix.n mode detection.

4. Added test_extract_and_write_objects_with_use_disk_page_cache_false_and_valid_pdf
2026-07-05 21:36:17 +08:00
LLLin000
643052489a fix: determinism + correctness fixes before further optimization
6 fixes bundled:

1. Cleanup: remove accidental empty file '6s}'

2. Rebuild determinism: add 'use_disk_page_cache' gate to _crop_asset_from_pdf.
   Rebuild (use_disk_page_cache=False) never reads or writes pages/page_XXX.jpg.
   All rendering goes through PageRenderContext in-memory.
   Legacy callers keep backward-compat behavior (default True).

3. PageRenderContext safety:
   - Only use when page_width>0 and page_height>0 and not rotation_deg
   - Fix Pixmap.n mode detection (L/RGB/RGBA -> convert to RGB)
   - Fix Image.Resampling name bug (Image -> PILImage)
   - Rotated crops always fall back to direct PDF clip path

4. Phase 1+2a merged traversal: always extract PDF lines on every page,
   regardless of whether span_metadata already exists. Fixes figure inventory
   data gaps when pages have pre-existing span coverage.

5. Phase 4: use resolved source_pdf_path from Phase 1, not ocr_meta['source_pdf']
   fallback (which may be stale or missing).

6. Added 5 determinism tests
2026-07-05 21:32:05 +08:00
LLLin000
b977adc1d8 perf: PageRenderContext — in-memory page rendering for Phase 4a, no disk writes
Add PageRenderContext class that renders each page once into memory
(PIL Image via get_pixmap + frombytes), shared across all crop tasks
in a single rebuild session. No full-page JPG is written to disk.

- _crop_asset_from_pdf: new fast path (page_render_context kwarg)
- Three task functions pass context through
- extract_and_write_objects opens PDF once, creates context, closes after
- Cold Phase 4a: 4.0s → 1.24s (60p, 30 unique pages, 42 crops)
- Full cold rebuild: 10.9s → 7.17s
2026-07-05 21:13:29 +08:00
LLLin000
c34ebbce81 feat: rebuild speed-up with parallel rebuild, threaded asset cropping, maintenance refactor, embed progress, CLI docs 2026-07-05 19:43:07 +08:00
LLLin000
c42da204bb fix: expand crop bbox to include figure_inner_text (figures show their own labels)
figure_inner_text role means the text IS part of the figure content
(forest plot y-axis labels, nomogram variable names, panel markers).
The earlier fix listed the text as a separate below the image — that
was wrong: the text already lives inside the cropped image when the
crop bbox covers it.

Now extract_and_write_objects expands the crop bbox to include all
figure_inner_text blocks owned by the figure (via _object_owner_id,
same page). The cropped jpg contains the labels; render only emits
image + Legend.

Verified: M84CTEM9 figure_a001 (forest plot) now crops to 787x363
(previously 444x358 — missed the y-axis labels); figure_a002
(nomogram) still includes its variable labels. 105 focused tests pass.
2026-07-04 19:54:40 +08:00
LLLin000
34827f2af0 fix: merge figure_inner_text into figure render output
figure_inner_text role has been recognized (side-adjacent + contained
passes) but its blocks were dropped from body AND silently dropped from
figure render output — they had nowhere to go. The owner contract
(_object_owner_id) was already stamped on the block; the renderer
just never consumed it.

Three changes:
- extract_and_write_objects now accepts structured_blocks and matches
  figure_inner_text blocks to their owner figure via _object_owner_id
- render_figure_object_markdown merges the inner text as figure body
  (no separate Note section — figure_inner_text IS the figure content)
- tag_figure_contained_text now stamps _object_owner_id on contained
  blocks claimed via the matched-figure path, so contained text also
  flows back to its owning figure (previously only side-adjacent was
  bound)

Rebuild ordering fixed too: apply_object_writebacks now runs BEFORE
write_figure_inventory so claims land in the persisted inventory.

Tested: M84CTEM9 figure_a001.md now includes the forest plot y-axis
variable labels (Age/SPADI/Sex/...) as figure content; body no longer
leaks them; 105 focused tests pass.
2026-07-04 19:49:38 +08:00
LLLin000
86e0d1419b feat: rotate rendered table images for rotated (landscape) tables
37LK5T97 Tables 1-3 span dir=[0,-1] (rotated 90° content on portrait page).
The table body AND caption are both rotated — the rendered JPEG showed
vertical text.

Fix:
- Add _table_has_rotated_content() in ocr_tables.py — checks asset
  span_metadata dir to detect rotated tables, returns 270° correction
- In build_table_inventory, compute union render_bbox (caption+asset)
  and store render_rotation_deg in the table entry
- In ocr_objects.py table render loop, use render_bbox + rotation_deg
  when available, passing rotation_deg to _crop_asset_from_pdf

Result:
  Tables 1-5 (rotated): 1908x2858 -> 2858x1908 (270° corrected, readable)
  Table 6 (normal): 987x191 -> unchanged (no rotation_deg)
428 regression tests pass.
2026-07-02 15:01:04 +08:00
LLLin000
1cff29ff01 fix: high-quality rotated figure renders with correct coordinate mapping
Two bugs fixed in _crop_asset_from_pdf for rotated vector figures:

1. Coordinate mismatch: bbox values are in OCR coordinates (2x PDF), but
   PyMuPDF get_pixmap(clip=...) expects PDF user-space coordinates.
   Added per-page scale conversion using page_width/page_height vs PDF
   page rect ratio. Without this, the clip started ~52pt right of the
   actual chart, cutting off the left edge.

2. Triple quality loss: removed stale _apply_rotation_if_needed inner
   function (PIL save+reopen). Replaced with 4x zoom render + single-pass
   PIL rotate from pix.tobytes('png'). No JPEG intermediary, one rotation
   interpolation instead of two. 4.7x pixel improvement.

Resolution quality:
   Before (cached page path):  ~862x1309 px  (1.1M px)
   After  (4x pixmap + PIL):  2618x1914 px  (5.0M px, 4.5x)

428 regression tests pass.
2026-07-02 03:05:28 +08:00
LLLin000
d41712d7bc fix: normalize rotated figure captions before normal matching
- preserve PyMuPDF dir/wmode in span_metadata and bump span backfill version
- promote rotated vision_footnote figure descriptions into normal legend prematch
- normalize rotated caption/asset regions before score_figure_match
- carry rotation_correction_deg into matched figures and cropped renders
- use LANCZOS resampling on rotated figure crops to avoid aliasing
- keep synthetic fallback as last resort, reusing the same rotation metadata
2026-07-02 02:28:56 +08:00
Research Assistant
d4aa1b1037 fix: reuse shared PDF access during object extraction 2026-06-22 22:16:24 +08:00
Research Assistant
eb070ae318 fix: project table note bands through object and render 2026-06-20 02:19:23 +08:00
Research Assistant
765cdf91f2 fix: carry owned table notes through rebuild outputs 2026-06-19 23:34:01 +08:00
Research Assistant
63b56689c6 feat: tighten OCR figure validation matching 2026-06-09 21:13:53 +08:00
Research Assistant
c036ef7670 fix(ocr): align object filenames with unresolved cluster IDs 2026-06-08 12:14:15 +08:00
Research Assistant
463cd4d7b1 fix: references, figure matching, and OCR structural fixes
- Fix references zone detection without heading (backward reference fallback)
- Fix reference_item silently dropped in _reorder_tail_run_fifo when no heading
- Fix figure proximity matching: use nearest legend regardless of column order
- Fix figure numbering in extract_and_write_objects: use actual figure_id
- Fix body→reference_item rescue: require text starts with numeric ref pattern
- Fix tail reading order rebuilt after block normalization
- A8E7SRVS: corrected figure-legend pairing on page 5
- CAQNW9Q2: references now render without heading
- K7R8PEKW: adjusted body retention threshold
2026-06-07 22:27:58 +08:00
Research Assistant
06d7abb2fa feat: unify OCR math normalization across render and objects 2026-06-06 17:02:01 +08:00
Research Assistant
463852462f fix: stop legend-only figures from using fake unmatched assets 2026-06-06 16:08:26 +08:00
Research Assistant
7c278fbc60 fix(ocr): properly cluster multi-panel figures — role assignment, cluster bbox crop, render visibility 2026-06-06 10:08:42 +08:00
Research Assistant
c235dbdc68 feat(ocr): journal layout generalization, OCR diagnostics, and debug scripts 2026-06-06 00:46:49 +08:00
Research Assistant
d52ea37e92 feat(ocr): tail regime remediation + style-aware heading profiles + boundary detection
- Task: Bidirectional body/backmatter boundary detection (forward/backward spine + tail spread reconciliation)
- Task: Style-aware heading profiles from PDF span_metadata (extract, cluster, disambiguate)
- Task: Lock tests for tail-candidate overreach, cross-page continuation, style-aware heading detection
- 296/296 tests pass, real-paper 7C8829BD verified
2026-06-05 22:47:13 +08:00
Research Assistant
cff4107633 fix: stabilize object notes, render heading sanity, health/index detection 2026-06-05 15:13:33 +08:00
Research Assistant
0d9725808f fix: strengthen tests, clean object-note format, fix assets_path compat 2026-06-05 15:04:21 +08:00
Research Assistant
e7c942f68b fix: correct title via Zotero frontmatter, anchor figure/table placement, fix page markers and wikilinks 2026-06-05 13:50:26 +08:00
Research Assistant
0fa6dd19a9 fix: correct OCR object asset wikilinks for Obsidian 2026-06-05 13:35:52 +08:00
Research Assistant
d25dd1857a chore: remove unused import and stray blank line in OCR modules 2026-06-05 12:10:53 +08:00
Research Assistant
1de9cc281b feat: emit OCR figure and table objects 2026-06-05 00:43:39 +08:00