camelot-dev / camelot-dev/camelot

Vector-graphics engine for Lattice (skip rasterize → OpenCV)

Open
#763 2 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
3.8k
Forks
546
Avg merge
3d 17h
Merged PRs (30d)
3

Description

**Background.** The Lattice flavor today recovers a table's ruled-line grid via a raster pipeline:

1. `ImageConversionBackend.convert(pdf_path, png_path, resolution=300)` — rasterize the page (pdfium / ghostscript / poppler)
2. `adaptive_threshold(png_path)` — binarize
3. `find_lines(threshold, direction='horizontal', ...)` — `cv2.findContours` on a morphologically-prepped mask
4. `find_contours(vertical_mask, horizontal_mask)` — table-bbox candidates from the joined mask

For PDFs with *native vector lines* (the common case — anything generated by a layout engine like LibreOffice, LaTeX, Word, Quarto, financial-report tools, etc.), every step after #1 is reconstructing information the PDF already carries as exact vector coordinates.

**Idea (originally raised by @bosd).** Add a second `engine='vector'` for Lattice that walks the playa layout tree directly:

| playa object | Carries |
|---|---|
| `LTLine` | One stroked segment with exact float `(x0, y0, x1, y1)` |
| `LTRect` | A rectangle — table cell grids are often drawn as a stack of `re` operators |
| `LTCurve` | General path; covers `re` plus the occasional diagonal/curved decoration |

So `find_lines(direction='horizontal')` becomes "iterate `page.layout`, keep LTLines with `y0 == y1`" plus "treat each LTRect's top/bottom edges as horizontal lines". Same for vertical via `x0 == x1` + side edges. The output is the same `[(x1, y1, x2, y2), …]` list that drives `find_contours` and downstream cell assignment.

**Advantages.**

- **Much faster.** The image-conversion backend is the heaviest non-network step in Lattice; skipping it (plus `adaptive_threshold`, plus `cv2.findContours`) on a typical multi-page report is a 10-50× speedup per page.
- **Pixel-perfect.** No 300dpi quantization, no morphological dilate/erode tuning, no `iterations` / `erode_iterations` / `line_scale` parameters that exist purely to undo raster artefacts. (Those params keep working for the raster engine.)
- **Lighter install.** Users on `engine='vector'` don't need OpenCV or any image-conversion backend at all. Could be repackaged as `camelot-py[lattice-vector]` vs `camelot-py[cv]`.
- **Better small-detail handling.** Lines thinner than ~2px at 300dpi (which raster path can drop entirely) come through unchanged.

**Limitations.**

- **Scanned PDFs have no native vectors.** Page is an embedded LTImage; nothing to walk. Vector engine must fail back to raster (or be skipped via auto-detect).
- **Some PDFs draw "lines" as thin filled regions / shaded paths.** Less common but worth a fallback.
- **Diagonal table dividers** (rare; some legal docs) aren't easily classified as h/v — vector engine can detect them explicitly and pass to a separate code path or simply ignore.

**Design sketch.**

\`\`\`python
def find_lines_from_layout(
layout, page_dimensions, direction="horizontal", angle_tol=0.5
):
\"\"\"Walk an LTPage/LTContainer, return ruled lines in PDF coords.\"\"\"
# ... LTLine, LTRect/edge expansion, LTCurve-as-path inspection
return processed_mask_or_None, lines # same shape as find_lines

# Lattice signature gains:
def __init__(self, ..., engine="raster"):
# 'raster' (default) — current OpenCV pipeline, unchanged.
# 'vector' — find_lines_from_layout, no image conversion.
# 'auto' — probe layout for LTLines; if none → fall back to raster.
\`\`\`

**Staged plan.**

- **Stage 1**: `find_lines_from_layout` helper + unit tests against fixtures with known vector lines (e.g. `tests/files/foo.pdf`). No Lattice integration yet — pure function.
- **Stage 2**: Lattice `engine='vector'` plumbing + integration tests across the existing lattice fixtures. Bench raster vs vector per fixture.
- **Stage 3**: `engine='auto'` heuristic + docs section in `advanced.rst`.

Tracking issue. Stage-1 POC PR coming next.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the existing Lattice find_lines and the Stage 1 plan, then inspect tests/files/foo.pdf for vector-line fixtures. Implement and test the standalone find_lines_from_layout behavior for LTLine and LTRect inputs, preserving the stated line-list shape; Lattice integration, auto-detection, benchmarking, and advanced.rst belong to later stages.

Written by the indexing model from the issue text.

Assessment

Tech stack
opencv, python
Domain
backend, data
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
40/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.