Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c72e86e
feat: implement schema and backend for document processing
bartrosa May 3, 2026
206edc3
chore: update dependencies in project configuration
bartrosa May 3, 2026
717afc0
chore: update CHANGELOG with new features for upcoming release
bartrosa May 3, 2026
38c9f41
feat: implement device detection and hashing functionality
bartrosa May 3, 2026
dc9c0d1
feat: add sample PDF fixture for testing document processing
bartrosa May 3, 2026
846807d
feat: enhance project configuration and dependencies
bartrosa May 3, 2026
d023955
feat: update CHANGELOG and README with new features and usage examples
bartrosa May 3, 2026
3d26cbe
feat: add sanity check script and disk caching for document processing
bartrosa May 3, 2026
f2bb336
feat: enhance documentation and add sanity check directory
bartrosa May 3, 2026
8fa8516
feat: update project dependencies and enhance .gitignore
bartrosa May 3, 2026
def2f41
feat: add evaluation command and metrics for OmniDocBench
bartrosa May 3, 2026
6b364a3
feat: add OmniDocBench evaluation documentation and baseline results
bartrosa May 3, 2026
99f8f39
feat: update project dependencies and enhance evaluation capabilities
bartrosa May 3, 2026
9928e1c
feat: enhance evaluation features and documentation for OmniDocBench
bartrosa May 3, 2026
94d79cb
feat: enhance evaluation metrics and CLI options for OmniDocBench
bartrosa May 3, 2026
ae8bb24
feat: add diagnostic scripts for OmniDocBench evaluation
bartrosa May 3, 2026
4038393
chore: update .gitignore and enhance evaluation documentation
bartrosa May 3, 2026
878afef
feat: add VLM support to Docling backend and CLI
bartrosa May 3, 2026
bdaccaa
feat: add discovery scripts for VLM pipeline
bartrosa May 3, 2026
8e1a428
feat: add new evaluation documentation for OmniDocBench
bartrosa May 3, 2026
60a8cc8
feat: enhance Docling with VLM support and update documentation
bartrosa May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
run: uv run mypy src/

- name: Pytest
run: uv run pytest --cov --cov-report=term-missing
run: uv run pytest -m "not slow" --cov --cov-report=term-missing
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,9 @@ cython_debug/
marimo/_static/
marimo/_lsp/
__marimo__/

# Local document samples (keep only README + .gitkeep)
data/*
!data/README.md
!data/.gitkeep
eval/results/
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- (eval) Ground-truth markdown rebuilt to mirror OmniDocBench `tools/json2md.py` (PR #7b).

### Added

- (docling) `enable_vlm` on `DoclingBackend` — `VlmPipeline` + preset `granite_docling` for formula-friendly parsing (opt-in, PR #8).
- CLI `bigos parse --vlm` / `bigos eval --vlm` (Docling only, PR #8).
- `docs/eval/docling-vlm-api.md` — how VLM is wired in Docling 2.92.x (PR #8).
- OmniDocBench baseline **academic_paper v4** (`baseline-omnidocbench-academic_paper-v4.{md,json}`) with VLM; v1–v3 unchanged (PR #8).

### Changed

- Backend version string includes `+vlm` when VLM is enabled so disk cache keys stay disjoint from standard Docling runs (PR #8).

- (eval) Per-category metric breakdown on `EvalReport` and in markdown output (PR #7b).
- CLI `--gt-strategy` (`legacy` | `json2md`, default `json2md`) for OmniDocBench GT assembly (PR #7b).
- `docs/eval/json2md-reference.md` — reference behaviour from official `json2md.py` (PR #7b).
- OmniDocBench baselines **v3** (`baseline-*-v3.{md,json}`); v1/v2 retained for comparison (PR #7b).

- Pydantic v2 schemas: Source, Block, Document (PR #2)
- Backend Protocol abstraction (PR #2)
- Markdown and JSON export for Document (PR #2)
- DoclingBackend wrapping docling.DocumentConverter (PR #3)
- CLI: `bigos parse <path>` with --format/--output (PR #3)
- Cross-platform device detection (CUDA/MPS/CPU) (PR #3)
- Generated test fixtures: simple text, table, Polish (PR #3)
- DiskCache for parsed Documents (PR #4)
- CLI flags --no-cache, --cache-dir (PR #4)
- scripts/sanity_check.py for batch testing on user docs (PR #4)
- Eval harness: char_error_rate, teds (S-TEDS), edit_distance metrics (PR #5)
- OmniDocBench v1.5 integration via HuggingFace dataset repo + manifest JSON (PR #5)
- CLI: `bigos eval --benchmark=omnidocbench` (PR #5)
- Baseline results for `tables` and `academic_paper` subsets (PR #5)
- NED (normalized edit distance) metric (PR #6)
- Text normalization (markdown stripping, NFKC, whitespace) (PR #6)
- `compute_text_diagnostic()` with full per-sample diagnostic fields (PR #6)
- `--dump-dir` flag for per-sample debugging JSON (PR #6)
- Schema inspection script `scripts/inspect_omnidocbench.py` (PR #6)
- Re-baseline with corrected metrics (`baseline-*-v2`) (PR #6)
- `sanity_check.py`: previews, `_summary.md`, `_polish_check.json` (PR #6)

## [0.0.1.dev0] - 2026-05-03

### Added
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,58 @@ Early PoC — APIs unstable.
uv sync && uv run pytest
```

## Quick example

```bash
uv pip install -e ".[docling]"
bigos parse contract.pdf --format=md
```

## Supported formats

Currently PDF via Docling. More backends planned.

## Sanity-checking on your own documents

Drop PDFs in `data/`, then:

```bash
uv run python scripts/sanity_check.py data/ --output-md-dir=data/_parsed/
```

This parses every PDF, prints per-file stats, and writes markdowns to `data/_parsed/`. Open them in VS Code to visually verify quality.

## Evaluation

Install optional dependencies including eval extras (see `pyproject.toml`), then run OmniDocBench with optional per-sample dumps:

```bash
uv sync --all-extras
uv run bigos eval --benchmark=omnidocbench --subset=tables \
--max-samples=20 --dump-dir=eval/results/dumps/tables/
# Heavy mathematics PDFs / OmniDocBench academic_paper with Granite-Docling VLM:
# uv run bigos eval --benchmark=omnidocbench --subset=academic_paper --vlm ...
# Compare against older baselines that used text-only GT:
# uv run bigos eval ... --gt-strategy=legacy
```

Inspect individual samples:

```bash
cat eval/results/dumps/tables/SAMPLE_ID.json | jq .
```

**OmniDocBench academic_paper (subset, 20 próbek, json2md GT):**

| | v3 (standard Docling) | v4 (`--vlm`) |
|--|--:|--:|
| Mean NED | 0.783 | 0.524 |
| Mean len(pred)/len(gt) | 0.26 | 1.02 |
| Σ pred / Σ GT formuł | 0 / 248 | 98 / 248 (~40%) |

Szczegóły: `eval/results/comparison-v3-vs-v4-academic.md`, baseline `baseline-omnidocbench-academic_paper-v4.{md,json}`.

Baseline results (current GT = json2md-aligned): `eval/results/baseline-*-v3.md` (tables + starsze subsety); academic z VLM: **v4** powyżej. Starsze baseline (legacy GT): `baseline-*-v2.md`. See `docs/eval/metrics-explained.md`,
`docs/eval/omnidocbench.md`, `docs/eval/docling-vlm-api.md`, and `docs/eval/json2md-reference.md`.

[![CI](https://github.com/OWNER/REPO/actions/workflows/ci.yml/badge.svg)](https://github.com/OWNER/REPO/actions/workflows/ci.yml)
Empty file added data/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# data/

This directory is for your local document samples used in sanity checks. Files here are gitignored — do not commit private documents.

Recommended workflow:

```bash
# drop 5-10 representative PDFs into this directory, then:
uv run python scripts/sanity_check.py data/ --output-md-dir=data/_parsed/
```

Open `data/_parsed/*.md` in VS Code and visually verify quality.
142 changes: 142 additions & 0 deletions docs/eval/diagnosis-2025.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Diagnostyka len_ratio i NED w eval OmniDocBench

## TL;DR

Na podstawie dumpów JSON, statystyk długości oraz histogramu `layout_dets` z manifestu **OmniDocBench.json** (pierwsze 100 wpisów): **tekst referencyjny używany w evalu składa się głównie z pól `text` w detekcjach**, podczas gdy w danych oficjalnych **tabele żyją w polu `html`**, a **izolowane równania w polu `latex`** (bez `text`). W efekcie GT jest **celowo krótki** względem pełnej treści strony, którą Docling składa w markdown (w tym duże tabele HTML). To tłumaczy **średni stosunek `len(pred)/len(gt)` rzędu 34× (tables)** i **NED ~0,93** przy prawie identycznym NED „stripped” — problem nie jest wyłącznie nagłówkami Markdown. **Hipoteza z PR #6 jest potwierdzona:** `_gt_text_from_layout` **nie reprezentuje pełnej treści strony** w sensie porównywalnym z `export_markdown()`. Proponowany następny krok to **PR-B**: rozszerzyć budowę GT o `html` (tabele), `latex` (formuły), dopasować nazwy kategorii do schematu (`text_block`, `title`, …) oraz ewentualnie ścieżkę jak w `tools/json2md.py` z repozytorium OmniDocBench.

## Obserwacje surowe

### 1. Statystyki długości

Źródło: `eval/results/diagnosis/02_length_stats.txt` (20 próbek na subset).

**Subset `tables-v2`:**

| Pole | min | mediana | średnia | max |
|-----------|-----|---------|---------|-----|
| len_pred | 596 | 1490 | 1746 | 5761 |
| len_gt | 17 | 47 | 110 | 620 |
| ratio | 2 | 29 | 34 | 110 |

**Subset `academic-v2`:**

| Pole | min | mediana | średnia | max |
|-----------|-----|---------|---------|-----|
| len_pred | 161 | 850 | 1084 | 3668 |
| len_gt | 12 | 290 | 789 | 3404 |
| ratio | 0 | 2 | 20 | 97 |

Uwaga: dla części stron academic GT zawiera długi blok LaTeX w polu `text` (dowód), wtedy `len_gt > len_pred` i ratio &lt; 1 — stąd szeroki rozstrzał ratio na tym subsecie.

### 2. Per-sample ratio vs NED (tables — 5 najwyższych ratio)

Z pełnej listy posortowanej rosnąco po `ratio`, **najwyższe** wartości to m.in.:

| ID (skrót) | ratio | len_gt | len_pred | NED |
|------------|-------|--------|----------|-----|
| …b527edec… | 109.6 | 17 | 1863 | ~0.994 |
| …c7792da7… | 67.7 | 25 | 1693 | ~0.989 |
| …9edf7687… | 57.5 | 19 | 1092 | ~0.986 |
| …3c690b07… | 51.0 | 34 | 1733 | ~0.991 |
| …9477c155… | 46.1 | 23 | 1061 | ~0.987 |

Im krótszy `len_gt`, tym częściej ekstremalny ratio przy normalnej długości predykcji OCR.

### 3. Side-by-side — głęboki sample (medianowy ratio, tables)

**Sample:** `page-28c45f5f-7e0d-464a-89ec-8de3a4abb927.png` (najbliższy medianie ratio ≈ 29,2; źródło: `03_deep_dive.txt`).

**GT (pierwsze ~300 znaków, zapis w dumpie = pełny „tekstowy” GT dla metryki):**

> `J. Cardiovasc. Dev. Dis. 2025, 12, 13`
> `Table 1. Classification of congenital coronary abnormalities.`

**PRED (początek preview 500 znaków — reszta strony to dalszy artykuł + struktura):**

> `recommendations ofthe Cardiological Organizational CommitteeforSportsEligibility (COCIS) [1,2].`
> `# Background`
> `Theprevalence of CAAsremainsunclear...`

W dumpie: `len_gt=100`, `len_pred=2944`, `n_tables_gt=1`, `n_tables_pred=1`, **TEDS (S-TEDS) ~0,14** — struktura tabeli mocno się różni. **GT nie zawiera treści tabeli jako tekstu** (w manifestcie jest w `html`, nie w polu sklejanym do `_gt_text_from_layout`).

### 4. Kategorie `layout_dets` w manifeście

Źródło: `eval/results/diagnosis/05_our_logic.txt`, histogram po **pierwszych 100 wpisach** manifestu:

- **`equation_isolated`**: 888 detekcji, **0 z `text`**, **888 z `latex`**.
- **`text_block`**: 847 / 847 z `text`.
- **`table`**: 2 detekcje, **0 z `text`**, **2 z `html`**.
- **`title`**, **`header`**, **`page_number`**, **`figure_caption`**, **`code_txt`**, itd.

Wniosek: **znacząca część sygnału GT (LaTeX, HTML tabel) nie wchodzi do łańcucha złożonego wyłącznie z `det["text"]`.**

### 5. Obecna logika `_gt_text_from_layout`

Źródło: wycinek z `src/bigos/eval/omnidocbench.py` w `05_our_logic.txt`:

- Najpierw zbierane są detekcje z `category_type` ∈ `_TEXT_CATEGORIES` (m.in. `text`, `paragraph_title`, … — **nie** `text_block` ani `title` w tej nazwie) **albo** pusty typ.
- Jeśli nic — **fallback**: każdy blok z niepustym `text`.

**Co jest pomijane w praktyce:** bloki **bez** `text`, a z **`html`** lub **`latex`** (tabele, typowe równania `equation_isolated`). To dokładnie pokrywa się z histogramem.

Dodatkowo schemat OmniDocBench używa nazwy **`text_block`**, podczas gdy kod oczekuje m.in. **`text`** — pierwsza ścieżka często nie trafia; **fallback ratuje zwykły tekst** z `text_block`, ale **nie ratuje** treści z `html`/`latex`.

### 6. Logika OmniDocBench (reference repo)

Repozytorium sklonowane do `/tmp/omnidocbench-ref` (sieć OK).

W **`tools/json2md.py`** (fragment konceptualny — jak buduje się „pełniejszy” MD z JSON):

- Dla `category_type == 'table'`: zapis **`item[table_format]`** (domyślnie `html`).
- Dla bloków z `text`: normalizacja i zapis tekstu (np. `title` → nagłówek `#`).
- W gałęzi `elif item.get('html')` / `elif item.get('latex')`: zapis **`html`** lub **`latex`**.

Oficjalny **end-to-end** (`src/dataset/end2end_dataset.py`) ocenia osobno bloki (tekst, formuły, tabele, reading order) — **nie** redukuje całej strony do jednego łańcucha z samych pól `text`.

Szczegóły dopasowań: `src/core/matching/match*.py`, metryki: `src/metrics/cal_metric.py`, `table_metric.py` (TEDS z treścią komórek), itd. — pełna lista trafień w `eval/results/diagnosis/04_reference.txt`.

## Diagnoza

**Opcja A — GT jest niepełne względem tego, co user porównuje z pred (najbardziej uzasadniona):**

- **Dowód ilościowy:** histogram pokazuje masowe **`latex` bez `text`** oraz **`html` bez `text`**.
- **Dowód jakościowy:** sample medianowy — GT to dwie linijki nagłówka/tabeli, pred to długi artykuł + tabela HTML w markdown; **niedopasowanie długości jest oczekiwane**, jeśli GT nie zawiera treści tabeli ani akapitów z `text_block` w jednym spójnym „full page string” albo jeśli chunk GT jest tylko legendą tabeli.

**Opcja B — pred zbyt obszerne:** częściowo (Docling produkuje pełny OCR strony), ale **to nie wyjaśnia** samego braku treści tabeli w GT tekstowym; **page breaków `---`** w preview nie dominuje jako główna przyczyna ratio 34×.

**Opcja C (inne):** Kodowanie / język — **wtórne**; dla academic część GT to już długi LaTeX w `text`, wtedy ratio spada — zgodne z obserwacją dwóch reżimów na jednym benchu.

**Wybór:** **Opcja A** jako główna, z elementami **B** (pred naturalnie dłuższy od „samej legendy”).

## Proponowany fix (na przyszły PR — nie wykonany w tej diagnostyce)

1. **Zsynchronizować nazwy kategorii** z manifestem: `text_block`, `title`, `header`, `figure_caption`, `code_txt`, `page_number`, … (mapowanie 1:1 zamiast `text` / `paragraph_title`).
2. **Dla `table`:** do łańcucha GT dodać **tekst wyekstrahowany z `html`** (np. strip tagów + normalizacja spacji) albo surowy HTML — zgodnie z tym, co ma być porównywane z pred (uwaga: wtedy porównanie z MD Doclinga może wymagać tej samej normalizacji co w `normalize_text`).
3. **Dla `equation_isolated` / `equation_inline`:** dodać **`latex`** (po oczyszczeniu delimiterów) do GT tekstowego **albo** wyłączyć te bloki z „jednego stringa” i oceniać osobno (jak OmniDocBench).
4. **Sortowanie:** kontynuować po `order`; obsłużyć `order is None` (jak w próbkach `equation_semantic`).
5. **Opcjonalnie:** scalanie **truncated** z `extra["relation"]` jak w `json2md.py` — jeśli brakuje kontekstu w prostym konkatenacji.

## Spodziewany efekt po fixie

- **`len_ratio`** na subsetcie **tables** powinien **spaść wyraźnie** (z ~34 średnio w stronę **~1,5–4** jako luźny zakres — zależnie od tego, czy HTML tabeli jest liczony jako znaki vs markdown Doclinga); dokładna wartość zależy od normalizacji.
- **NED** powinno **spaść**, jeśli GT i pred pokrywają ten sam zbiór semantyczny; jeśli po fixie NED pozostaje **wysokie**, kolejny podejrzany to **jakość OCR / kolejność czytania**, nie sama długość GT.

## Następne kroki

1. **Diagnoza jest wystarczająco pewna na PR-B** — implementacja rozszerzonego GT + test regresji na kilku dumpach (porównanie `len_gt` przed/po).
2. Jeśli po PR-B NED nadal ~0,8+ na tables: **diagnostyka OCR** (fragmenty vs pełna strona) lub **alignment** z oficjalnym pipeline (oddzielne metryki tekst/tabela/formuła).
3. Jeśli zespół chce **ściśle** odwzorować leaderboard: rozważyć **eksport pred do Markdown per strona** i uruchomienie **oficjalnego evaluatora** OmniDocBench zamiast jednego stringa textowej edycji.

---

## Załączniki (wygenerowane komendami)

| Plik | Opis |
|------|------|
| `eval/results/diagnosis/01_dumps_inspection.txt` | 5 dumpów × 2 subsety |
| `eval/results/diagnosis/02_length_stats.txt` | Rozkłady + medianowy sample |
| `eval/results/diagnosis/03_deep_dive.txt` | Pełny deep-dive medianowego sample (tables) |
| `eval/results/diagnosis/04_reference.txt` | Przeszukanie `/tmp/omnidocbench-ref` |
| `eval/results/diagnosis/05_our_logic.txt` | Kod `_gt_text_from_layout` + histogram |

Skrypty: `scripts/diagnose/01_read_dumps.py` … `05_our_gt_logic.py`.
67 changes: 67 additions & 0 deletions docs/eval/docling-vlm-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Docling 2.92.x: VLM pipeline (Granite-Docling)

## Faktyczne API w tej wersji

Odkryte skryptem `scripts/discover/01_docling_vlm_api.py` (wynik: `eval/results/discovery/01_docling_vlm_api.txt`).

- **`VlmPipeline`**: `docling.pipeline.vlm_pipeline.VlmPipeline` — konstruktor
`(pipeline_options: VlmPipelineOptions)`.
- **`VlmPipelineOptions`**: `docling.datamodel.pipeline_options.VlmPipelineOptions` — m.in.
`accelerator_options`, `enable_remote_services`, `vlm_options` (typ `VlmConvertOptions` |
legacy `InlineVlmOptions` / `ApiVlmOptions`), `generate_page_images` (domyślnie włączone
pod VLM), `force_backend_text`.
- **Presety VLM**: `VlmConvertOptions.from_preset(name)` — ten sam mechanizm co w oficjalnym
CLI (`docling convert --pipeline vlm --vlm-model ...`). Preset domyślny w CLI:
`granite_docling` (Granite-Docling VLM, ~256M).
- **DocumentConverter**: `FormatOption` w `docling.document_converter` ma pole
`pipeline_cls: Type[BasePipeline]`. Dla VLM oficjalny wzorzec (z
`docling/cli/main.py`, gałąź `ProcessingPipeline.VLM`):

```python
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import VlmConvertOptions, VlmPipelineOptions
from docling.document_converter import DocumentConverter, ImageFormatOption, PdfFormatOption
from docling.pipeline.vlm_pipeline import VlmPipeline

pipeline_options = VlmPipelineOptions(enable_remote_services=False)
pipeline_options.vlm_options = VlmConvertOptions.from_preset("granite_docling")

pdf_format_option = PdfFormatOption(
pipeline_cls=VlmPipeline,
pipeline_options=pipeline_options,
)

converter = DocumentConverter(
format_options={
InputFormat.PDF: pdf_format_option,
InputFormat.IMAGE: ImageFormatOption(
pipeline_cls=VlmPipeline,
pipeline_options=pipeline_options,
),
},
)
```

(`ImageFormatOption` + `ImageDocumentBackend` jest wymagane dla `InputFormat.IMAGE`; sam
`PdfFormatOption` na obrazkach jest przestarzały i wywołuje ostrzeżenie.)

W `bigos` do `VlmPipelineOptions` przekazywane jest także `accelerator_options` (urządzenie
z `detect_device()`), spójnie ze ścieżką standardową.

## Różnice względem `StandardPdfPipeline`

- **Standard** (`PdfFormatOption` bez nadpisania `pipeline_cls`): `StandardPdfPipeline` +
layout, OCR, tabele, opcjonalne enrich (w tym `do_formula_enrichment` — inna ścieżka niż
pełny VLM).
- **VLM**: jedna ścieżka wizyjno-językowa na obrazach stron; formuły mogą pojawić się jako
`FormulaItem`, `TextItem` z etykietą `FORMULA`, lub LaTeX w `TextItem` (stąd heurystyka w
`DoclingBackend`).

## Jeśli Docling zmieni API

1. Uruchom ponownie `uv run python scripts/discover/01_docling_vlm_api.py`.
2. Sprawdź **upstream** `docling/cli/main.py` — gałąź `pipeline == ProcessingPipeline.VLM`.
3. Dostosuj `_make_converter` w `src/bigos/backends/docling.py` oraz ten dokument.

Oficjalna dokumentacja pakietu: [Docling](https://docling-project.github.io/docling/)
(oraz release notes dla Twojej wersji na PyPI).
Loading
Loading