pdf2md-tool 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. pdf2md_tool-0.8.0/LICENSE +21 -0
  2. pdf2md_tool-0.8.0/PKG-INFO +254 -0
  3. pdf2md_tool-0.8.0/README.md +214 -0
  4. pdf2md_tool-0.8.0/pyproject.toml +76 -0
  5. pdf2md_tool-0.8.0/setup.cfg +4 -0
  6. pdf2md_tool-0.8.0/src/pdf2md/__init__.py +8 -0
  7. pdf2md_tool-0.8.0/src/pdf2md/_pix2tex_runner.py +45 -0
  8. pdf2md_tool-0.8.0/src/pdf2md/_profiles.py +130 -0
  9. pdf2md_tool-0.8.0/src/pdf2md/aggregate.py +407 -0
  10. pdf2md_tool-0.8.0/src/pdf2md/cli.py +930 -0
  11. pdf2md_tool-0.8.0/src/pdf2md/discovery.py +84 -0
  12. pdf2md_tool-0.8.0/src/pdf2md/executor.py +317 -0
  13. pdf2md_tool-0.8.0/src/pdf2md/extractors.py +241 -0
  14. pdf2md_tool-0.8.0/src/pdf2md/formula_cropper.py +337 -0
  15. pdf2md_tool-0.8.0/src/pdf2md/multi_roundtrip.py +272 -0
  16. pdf2md_tool-0.8.0/src/pdf2md/normalize.py +63 -0
  17. pdf2md_tool-0.8.0/src/pdf2md/optimize.py +392 -0
  18. pdf2md_tool-0.8.0/src/pdf2md/pdfs.py +184 -0
  19. pdf2md_tool-0.8.0/src/pdf2md/pixel_roundtrip.py +406 -0
  20. pdf2md_tool-0.8.0/src/pdf2md/provenance.py +174 -0
  21. pdf2md_tool-0.8.0/src/pdf2md/restructure.py +354 -0
  22. pdf2md_tool-0.8.0/src/pdf2md/roundtrip.py +197 -0
  23. pdf2md_tool-0.8.0/src/pdf2md/routing.py +467 -0
  24. pdf2md_tool-0.8.0/src/pdf2md/stats.py +780 -0
  25. pdf2md_tool-0.8.0/src/pdf2md/telemetry.py +314 -0
  26. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/PKG-INFO +254 -0
  27. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/SOURCES.txt +45 -0
  28. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/dependency_links.txt +1 -0
  29. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/entry_points.txt +2 -0
  30. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/requires.txt +15 -0
  31. pdf2md_tool-0.8.0/src/pdf2md_tool.egg-info/top_level.txt +1 -0
  32. pdf2md_tool-0.8.0/tests/test_aggregate.py +95 -0
  33. pdf2md_tool-0.8.0/tests/test_cli_smoke.py +88 -0
  34. pdf2md_tool-0.8.0/tests/test_executor.py +290 -0
  35. pdf2md_tool-0.8.0/tests/test_extractors.py +133 -0
  36. pdf2md_tool-0.8.0/tests/test_formula_cropper.py +141 -0
  37. pdf2md_tool-0.8.0/tests/test_multi_roundtrip.py +97 -0
  38. pdf2md_tool-0.8.0/tests/test_normalize.py +50 -0
  39. pdf2md_tool-0.8.0/tests/test_optimize.py +216 -0
  40. pdf2md_tool-0.8.0/tests/test_pdfs.py +68 -0
  41. pdf2md_tool-0.8.0/tests/test_pixel_roundtrip.py +223 -0
  42. pdf2md_tool-0.8.0/tests/test_provenance.py +67 -0
  43. pdf2md_tool-0.8.0/tests/test_restructure.py +113 -0
  44. pdf2md_tool-0.8.0/tests/test_roundtrip.py +74 -0
  45. pdf2md_tool-0.8.0/tests/test_routing.py +303 -0
  46. pdf2md_tool-0.8.0/tests/test_stats.py +183 -0
  47. pdf2md_tool-0.8.0/tests/test_telemetry.py +122 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leonardo Marques de Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,254 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdf2md-tool
3
+ Version: 0.8.0
4
+ Summary: Conversor PDF→MD CPU-first: roteamento por intent (rápido/qualidade), núcleo offline e round-trip mensurável
5
+ Author-email: Leonardo Marques de Souza <leonardo.marques.souza@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/LeoPR/pdf2md
8
+ Project-URL: Repository, https://github.com/LeoPR/pdf2md
9
+ Project-URL: Issues, https://github.com/LeoPR/pdf2md/issues
10
+ Keywords: pdf,markdown,pdf-to-markdown,ocr,latex,document-conversion,cli,round-trip
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: typer>=0.12
28
+ Requires-Dist: pymupdf>=1.24
29
+ Requires-Dist: pillow>=10
30
+ Requires-Dist: psutil>=5.9
31
+ Provides-Extra: rtpixel
32
+ Requires-Dist: numpy>=1.26; extra == "rtpixel"
33
+ Requires-Dist: scipy>=1.14; extra == "rtpixel"
34
+ Requires-Dist: scikit-image>=0.22; extra == "rtpixel"
35
+ Provides-Extra: ocr
36
+ Requires-Dist: pytesseract>=0.3; extra == "ocr"
37
+ Provides-Extra: all
38
+ Requires-Dist: pdf2md-tool[ocr,rtpixel]; extra == "all"
39
+ Dynamic: license-file
40
+
41
+ # pdf2md
42
+
43
+ Conversor PDF → Markdown que **mede cada extrator em vértices primitivos**
44
+ (velocidade, memória, VRAM, latência, qualidade por elemento, footprint de
45
+ instalação) e deixa um **roteador escolher o caminho mais barato que ainda
46
+ satisfaz o seu intent**. Em vez de "o melhor extrator universal", entrega a
47
+ decisão: `--rapido` usa um caminho CPU puro (offline, determinístico, ~0.02 s/pg);
48
+ `--qualidade` gasta GPU/marker só quando você pede e o host comporta.
49
+
50
+ O **núcleo roda em qualquer máquina** — só `pip`, sem GPU, sem modelos, sem rede.
51
+ As capacidades pesadas (marker/GPU, pix2tex, OCR, VLM) são **opcionais** e
52
+ detectadas em runtime por `pdf2md doctor`.
53
+
54
+ ```bash
55
+ pip install pdf2md-tool # núcleo CPU — nada externo
56
+ pdf2md convert paper.pdf --intent rapido
57
+ ```
58
+ > No PyPI o pacote chama-se **`pdf2md-tool`** (o slug `pdf2md` estava reservado por
59
+ > outra conta). O **comando** e o **import** continuam `pdf2md`. Do código-fonte:
60
+ > `git clone … && pip install -e .`.
61
+
62
+ ---
63
+
64
+ ## Quickstart sem GPU (o caminho portável)
65
+
66
+ ```bash
67
+ pip install pdf2md-tool
68
+ pdf2md doctor # o que você tem (core sempre OK; resto opcional)
69
+ pdf2md convert paper.pdf --intent rapido --out out/
70
+ ```
71
+
72
+ `--rapido` roteia para **pdftotext (PyMuPDF)**: prosa fiel, math como Unicode cru,
73
+ 0 VRAM, ~63 MB RAM, ~0.1 s de cold-start, determinístico. Não precisa instalar
74
+ mais nada. Para recuperar math em LaTeX, layout e tabelas, use `--qualidade` (que
75
+ quer marker/GPU) — `pdf2md doctor --intent qualidade` diz exatamente o que falta.
76
+
77
+ ---
78
+
79
+ ## Por que vale a pena — vértices medidos (RTX 3060, N pequeno)
80
+
81
+ | Vértice | Como o roteador fecha | Âncora medida |
82
+ |---|---|---|
83
+ | **Velocidade** | `--rapido`/`--low-resource`/`--indexacao` → pdftotext | **0.02 s/pg** vs marker 12.9 (~**630×**) |
84
+ | **RAM** | teto duro 160 MB em `--low-resource`; degrada, não estoura | pdftotext **63 MB** vs marker ~1500 (est.) |
85
+ | **CPU-only** | pipeline 100% CPU (pdftotext + cropper built-in + pix2tex) | math display **0.80** sem GPU |
86
+ | **Latência** | default sem warm-up; refiners caros só quando o intent paga | pdftotext **0.1 s** vs marker ~30 s (est.) |
87
+ | **Qualidade** | roteada por **sub-elemento** (prosa/math/matriz/logo) | prosa 0.95 · scan impresso WER 0.052 · matriz 0.50 ⚠️ |
88
+ | **Footprint** | core offline; pesado é opt-in (`doctor`) | core = só wheels do pyproject |
89
+
90
+ > **Escopo honesto:** perfis medidos em **1 host** (RTX 3060) e N pequeno; RAM/cold-start
91
+ > de marker/VLM são **estimados**. É "roteamento medido para este corpus", não benchmark
92
+ > universal. Ver [`docs/profiles/`](docs/profiles/) (os 7 perfis) e
93
+ > [`tickets/closed/T090…`](tickets/closed/T090_macro_intent_routing.md).
94
+
95
+ ---
96
+
97
+ ## Status
98
+
99
+ | Camada / capacidade | Estado | Notas |
100
+ |---|---|---|
101
+ | **Roteador macro-intent** `--rapido/--qualidade/--balanceado/--auto/--indexacao/--low-resource` | Estável (T090) | `route()` puro profile-driven; degradação honesta |
102
+ | **Extração CPU** pdftotext (PyMuPDF) + Tesseract (scan) | Estável | offline, determinístico |
103
+ | **Cropper de fórmula CPU + pix2tex** (math display → LaTeX) | Estável | cropper built-in; runtime pix2tex externo (torch) |
104
+ | **Extração GPU** marker-pdf (Surya+Texify) | Estável | venv próprio + GPU; math/layout nativo |
105
+ | **MD → PDF** pandoc + Chrome + KaTeX | Estável | |
106
+ | **Round-trip textual + multi-iteração** | Estável | validador sem ground-truth |
107
+ | **Pixel-roundtrip visual L0.5** (SSIM + align) | Estável | extra `[rtpixel]` |
108
+ | **Otimização adaptativa de imagens** | Estável | −38.6% em N&C, sem perda visual |
109
+ | **Telemetria por step + agregada** | Estável | `psutil` + `nvidia-smi` |
110
+ | Reconstrução vetorial de logos (VLM small-image) | Pesquisa | T180 (não promovido) |
111
+ | Vetorização SVG (potrace) · tabelas (TEDS) · cross-hardware | Pesquisa | T132 · BURACO #2 · T091 |
112
+
113
+ ---
114
+
115
+ ## Instalação
116
+
117
+ ```bash
118
+ git clone https://github.com/LeoPR/pdf2md && cd pdf2md
119
+ pip install -e . # núcleo CPU (typer, pymupdf, pillow, psutil)
120
+ pip install -e '.[rtpixel]' # + validador visual (numpy/scipy/scikit-image)
121
+ pip install -e '.[ocr]' # + wrapper pytesseract (engine é externo)
122
+ pip install -e '.[all]' # tudo que é pip-puro seguro
123
+ ```
124
+
125
+ **Capacidades externas (NÃO instaláveis por pip deste pacote — `pdf2md doctor` valida):**
126
+
127
+ | Capacidade | Como obter | Por que externo |
128
+ |---|---|---|
129
+ | **marker/GPU** (math+layout nativo) | venv próprio + `PDF2MD_MARKER` | conflito `pillow<11` + torch/CUDA |
130
+ | **pix2tex** (math→LaTeX CPU) | venv com torch + `PDF2MD_PIX2TEX_PYTHON` | torch é pesado/OS-específico |
131
+ | **tesseract** (OCR scan) | engine UB-Mannheim no PATH + extra `[ocr]` | binário de sistema |
132
+ | **pandoc + Chrome** (MD→PDF) | no PATH | binários de sistema |
133
+ | **ollama + gemma3/qwen** (logos) | daemon `:11434` + `ollama pull` | server + modelos fora do pip |
134
+
135
+ > `pip install pdf2md-tool[gpu]` **não existe de propósito**: marker fixa `Pillow<11` e é
136
+ > impossível co-instalar no mesmo ambiente. A interface honesta para a stack pesada é o
137
+ > `doctor`, não um extra pip. Dev roda a suíte com `uv sync --all-extras`.
138
+ >
139
+ > *Setup do autor (não é requisito):* venv junction em `Z:\venvs\pdf2md`; ver
140
+ > [`docs/reference/conventions.md`](docs/reference/conventions.md).
141
+
142
+ ---
143
+
144
+ ## Uso
145
+
146
+ ```
147
+ MACRO por intent (roteador T090 — escolhe a stack por host+doc)
148
+ pdf2md convert FILE.pdf --intent rapido # CPU puro, velocidade máxima
149
+ pdf2md convert FILE.pdf --intent qualidade # marker/GPU se houver; senão degrada
150
+ pdf2md convert FILE.pdf --intent auto # melhor stack que CABE no host
151
+ pdf2md convert FILE.pdf --intent indexacao # pass1 indexa tudo; pass2 enfileira math-heavy
152
+ pdf2md route FILE.pdf --intent qualidade # dry-run: mostra o pipeline (--execute roda)
153
+
154
+ LEGADO (mantido; --intent substitui)
155
+ pdf2md convert FILE.pdf --quick / --best
156
+
157
+ SUBCOMANDOS FINOS (controle granular / retomar pipeline parcial)
158
+ pdf2md extract · restruct · optimize · stats · rt · rt-multi · aggr · prov · norm · pdfs
159
+
160
+ META
161
+ pdf2md doctor [--intent N] # capabilities do host (+ o que um intent usaria aqui)
162
+ pdf2md version · help <cmd>
163
+ ```
164
+
165
+ ### Os 6 intents (resumo; detalhes em [how-to/escolher_intent](docs/how-to/escolher_intent.md))
166
+
167
+ | Intent | PRIMARY | Para quê |
168
+ |---|---|---|
169
+ | `--rapido` | pdftotext (mesmo com GPU) | indexar/pré-processar em massa; wall-time mínimo |
170
+ | `--low-resource` | pdftotext (teto RAM 160 MB) | máquinas magras; optimize desliga se estourar |
171
+ | `--indexacao` | pdftotext (pass1) + marker (pass2 enfileirável) | milhares de docs; pass2 só nos de perda recuperável |
172
+ | `--balanceado` (default) | marker se houver, senão pdftotext | uso geral |
173
+ | `--qualidade` | marker (degrada p/ pdftotext+pix2tex sem GPU) | máxima fidelidade; math LaTeX, layout |
174
+ | `--auto` | melhor que o host comporta | "faça a melhor coisa possível aqui" |
175
+
176
+ Sem GPU, `--qualidade` **degrada com aviso honesto** (`.rationale` vai pra proveniência),
177
+ nunca finge qualidade nem quebra em silêncio.
178
+
179
+ ---
180
+
181
+ ## Variáveis de ambiente
182
+
183
+ | Variável | Função |
184
+ |---|---|
185
+ | `PDF2MD_MARKER` | path do `marker_single` (sem fallback de máquina — **necessária** p/ usar marker) |
186
+ | `PDF2MD_PIX2TEX_PYTHON` | python de um venv com `pix2tex` (runtime math→LaTeX CPU) |
187
+ | `PDF2MD_TESSERACT` | path do `tesseract` (senão PATH → local padrão do SO) |
188
+ | `PDF2MD_PANDOC` / `PDF2MD_CHROME` | paths de pandoc / Chrome (senão PATH → local padrão do SO) |
189
+ | `PDF2MD_ZCACHE` / `PDF2MD_AULAQUANTUM` | raízes de corpus zcache / source privado (default = drives do autor) |
190
+
191
+ Descoberta (em `pdf2md/discovery.py`): `env → PATH (multi-nome, multi-SO) → local padrão
192
+ do SO → nome do comando`. Sem paths absolutos presos a uma máquina.
193
+
194
+ ---
195
+
196
+ ## Estrutura do repo
197
+
198
+ ```
199
+ pdf2md/
200
+ ├── src/pdf2md/ (lógica do pacote)
201
+ │ ├── cli.py CLI (macro --intent, route, doctor, subcomandos)
202
+ │ ├── routing.py ROTEADOR T090: route() puro + HostInfo/DocInfo + pass2_warranted
203
+ │ ├── _profiles.py MAPA: perfis medidos (route-relevant), dep-free
204
+ │ ├── executor.py executa o Pipeline que route() decide
205
+ │ ├── extractors.py PRIMARYs CPU: pdftotext + tesseract
206
+ │ ├── formula_cropper.py cropper de fórmula display CPU (built-in)
207
+ │ ├── discovery.py descoberta portável de ferramentas externas
208
+ │ ├── optimize.py otimização adaptativa de imagens
209
+ │ ├── telemetry.py INSTRUMENTO: wall/cpu/mem/gpu/io por step
210
+ │ ├── pixel_roundtrip.py validador visual L0.5 (extra [rtpixel])
211
+ │ ├── roundtrip.py · multi_roundtrip.py round-trip textual + estabilidade
212
+ │ ├── pdfs.py · restructure.py · normalize.py · provenance.py · stats.py · aggregate.py
213
+ │ └── _pix2tex_runner.py script no venv externo do pix2tex (subprocess)
214
+ ├── corpus/ dataset em 3 tiers (ver corpus/RIGHTS.md)
215
+ │ ├── examples/ excertos LIVRES commitados (prova pronta, sem baixar nada)
216
+ │ └── registry.py resolve(doc_id) → in-repo | zcache | private
217
+ ├── docs/ Diátaxis (tutorials/how-to/reference/explanation/profiles)
218
+ ├── lab/ bancada experimental (eNN por experimento)
219
+ └── tickets/ work items (open/closed/research) + INDEX.md
220
+ ```
221
+
222
+ PDFs pesados ficam **fora do repo** (zcache `Z:/caches/...` via `PDF2MD_ZCACHE`, ou sources
223
+ privados). O tier in-repo (`corpus/examples/`) tem só excertos livres pequenos.
224
+
225
+ ---
226
+
227
+ ## Resultados (medidos)
228
+
229
+ Round-trip e telemetria sobre **Nielsen & Chuang QCQI** (704 pág, RTX 3060):
230
+
231
+ | Métrica | Valor |
232
+ |---|---:|
233
+ | Round-trip textual (cap. 4, marker) | **95.09%** |
234
+ | Multi-iteração (paper) | drift 0.86% em 5 iter (**estável**) |
235
+ | Otimização de imagens (cap. 4) | 4.5 → 2.7 MB (**−38.6%**) |
236
+ | Extração full-doc (marker) | ~4145 s (~70 min) |
237
+
238
+ > Os números do N&C são **resultados derivados** (métricas, não reprodução da obra),
239
+ > usados sob licença legítima do detentor — ver [`corpus/RIGHTS.md`](corpus/RIGHTS.md). O
240
+ > PDF original e reproduções completas ficam fora do repositório.
241
+
242
+ Caminho CPU validado em corpus livre in-repo: pdftotext prosa WER 0.016 (pg estruturada),
243
+ Tesseract scan impresso WER 0.052, pix2tex math display 0.80 (linha única; matriz ~0.50).
244
+
245
+ ---
246
+
247
+ ## Documentação
248
+
249
+ - Arquitetura: [`docs/explanation/arquitetura.md`](docs/explanation/arquitetura.md) · Filosofia: [`philosophy.md`](docs/explanation/philosophy.md)
250
+ - Perfis medidos: [`docs/profiles/`](docs/profiles/) · Tecnologias: [`docs/reference/tecnologias.md`](docs/reference/tecnologias.md)
251
+ - Referência de CLI: [`docs/reference/cli.md`](docs/reference/cli.md) · Escolher intent: [`docs/how-to/escolher_intent.md`](docs/how-to/escolher_intent.md)
252
+ - Direitos do corpus: [`corpus/RIGHTS.md`](corpus/RIGHTS.md) · Timeline: [`docs/explanation/diario.md`](docs/explanation/diario.md)
253
+
254
+ Licença: MIT (ver [`LICENSE`](LICENSE)).
@@ -0,0 +1,214 @@
1
+ # pdf2md
2
+
3
+ Conversor PDF → Markdown que **mede cada extrator em vértices primitivos**
4
+ (velocidade, memória, VRAM, latência, qualidade por elemento, footprint de
5
+ instalação) e deixa um **roteador escolher o caminho mais barato que ainda
6
+ satisfaz o seu intent**. Em vez de "o melhor extrator universal", entrega a
7
+ decisão: `--rapido` usa um caminho CPU puro (offline, determinístico, ~0.02 s/pg);
8
+ `--qualidade` gasta GPU/marker só quando você pede e o host comporta.
9
+
10
+ O **núcleo roda em qualquer máquina** — só `pip`, sem GPU, sem modelos, sem rede.
11
+ As capacidades pesadas (marker/GPU, pix2tex, OCR, VLM) são **opcionais** e
12
+ detectadas em runtime por `pdf2md doctor`.
13
+
14
+ ```bash
15
+ pip install pdf2md-tool # núcleo CPU — nada externo
16
+ pdf2md convert paper.pdf --intent rapido
17
+ ```
18
+ > No PyPI o pacote chama-se **`pdf2md-tool`** (o slug `pdf2md` estava reservado por
19
+ > outra conta). O **comando** e o **import** continuam `pdf2md`. Do código-fonte:
20
+ > `git clone … && pip install -e .`.
21
+
22
+ ---
23
+
24
+ ## Quickstart sem GPU (o caminho portável)
25
+
26
+ ```bash
27
+ pip install pdf2md-tool
28
+ pdf2md doctor # o que você tem (core sempre OK; resto opcional)
29
+ pdf2md convert paper.pdf --intent rapido --out out/
30
+ ```
31
+
32
+ `--rapido` roteia para **pdftotext (PyMuPDF)**: prosa fiel, math como Unicode cru,
33
+ 0 VRAM, ~63 MB RAM, ~0.1 s de cold-start, determinístico. Não precisa instalar
34
+ mais nada. Para recuperar math em LaTeX, layout e tabelas, use `--qualidade` (que
35
+ quer marker/GPU) — `pdf2md doctor --intent qualidade` diz exatamente o que falta.
36
+
37
+ ---
38
+
39
+ ## Por que vale a pena — vértices medidos (RTX 3060, N pequeno)
40
+
41
+ | Vértice | Como o roteador fecha | Âncora medida |
42
+ |---|---|---|
43
+ | **Velocidade** | `--rapido`/`--low-resource`/`--indexacao` → pdftotext | **0.02 s/pg** vs marker 12.9 (~**630×**) |
44
+ | **RAM** | teto duro 160 MB em `--low-resource`; degrada, não estoura | pdftotext **63 MB** vs marker ~1500 (est.) |
45
+ | **CPU-only** | pipeline 100% CPU (pdftotext + cropper built-in + pix2tex) | math display **0.80** sem GPU |
46
+ | **Latência** | default sem warm-up; refiners caros só quando o intent paga | pdftotext **0.1 s** vs marker ~30 s (est.) |
47
+ | **Qualidade** | roteada por **sub-elemento** (prosa/math/matriz/logo) | prosa 0.95 · scan impresso WER 0.052 · matriz 0.50 ⚠️ |
48
+ | **Footprint** | core offline; pesado é opt-in (`doctor`) | core = só wheels do pyproject |
49
+
50
+ > **Escopo honesto:** perfis medidos em **1 host** (RTX 3060) e N pequeno; RAM/cold-start
51
+ > de marker/VLM são **estimados**. É "roteamento medido para este corpus", não benchmark
52
+ > universal. Ver [`docs/profiles/`](docs/profiles/) (os 7 perfis) e
53
+ > [`tickets/closed/T090…`](tickets/closed/T090_macro_intent_routing.md).
54
+
55
+ ---
56
+
57
+ ## Status
58
+
59
+ | Camada / capacidade | Estado | Notas |
60
+ |---|---|---|
61
+ | **Roteador macro-intent** `--rapido/--qualidade/--balanceado/--auto/--indexacao/--low-resource` | Estável (T090) | `route()` puro profile-driven; degradação honesta |
62
+ | **Extração CPU** pdftotext (PyMuPDF) + Tesseract (scan) | Estável | offline, determinístico |
63
+ | **Cropper de fórmula CPU + pix2tex** (math display → LaTeX) | Estável | cropper built-in; runtime pix2tex externo (torch) |
64
+ | **Extração GPU** marker-pdf (Surya+Texify) | Estável | venv próprio + GPU; math/layout nativo |
65
+ | **MD → PDF** pandoc + Chrome + KaTeX | Estável | |
66
+ | **Round-trip textual + multi-iteração** | Estável | validador sem ground-truth |
67
+ | **Pixel-roundtrip visual L0.5** (SSIM + align) | Estável | extra `[rtpixel]` |
68
+ | **Otimização adaptativa de imagens** | Estável | −38.6% em N&C, sem perda visual |
69
+ | **Telemetria por step + agregada** | Estável | `psutil` + `nvidia-smi` |
70
+ | Reconstrução vetorial de logos (VLM small-image) | Pesquisa | T180 (não promovido) |
71
+ | Vetorização SVG (potrace) · tabelas (TEDS) · cross-hardware | Pesquisa | T132 · BURACO #2 · T091 |
72
+
73
+ ---
74
+
75
+ ## Instalação
76
+
77
+ ```bash
78
+ git clone https://github.com/LeoPR/pdf2md && cd pdf2md
79
+ pip install -e . # núcleo CPU (typer, pymupdf, pillow, psutil)
80
+ pip install -e '.[rtpixel]' # + validador visual (numpy/scipy/scikit-image)
81
+ pip install -e '.[ocr]' # + wrapper pytesseract (engine é externo)
82
+ pip install -e '.[all]' # tudo que é pip-puro seguro
83
+ ```
84
+
85
+ **Capacidades externas (NÃO instaláveis por pip deste pacote — `pdf2md doctor` valida):**
86
+
87
+ | Capacidade | Como obter | Por que externo |
88
+ |---|---|---|
89
+ | **marker/GPU** (math+layout nativo) | venv próprio + `PDF2MD_MARKER` | conflito `pillow<11` + torch/CUDA |
90
+ | **pix2tex** (math→LaTeX CPU) | venv com torch + `PDF2MD_PIX2TEX_PYTHON` | torch é pesado/OS-específico |
91
+ | **tesseract** (OCR scan) | engine UB-Mannheim no PATH + extra `[ocr]` | binário de sistema |
92
+ | **pandoc + Chrome** (MD→PDF) | no PATH | binários de sistema |
93
+ | **ollama + gemma3/qwen** (logos) | daemon `:11434` + `ollama pull` | server + modelos fora do pip |
94
+
95
+ > `pip install pdf2md-tool[gpu]` **não existe de propósito**: marker fixa `Pillow<11` e é
96
+ > impossível co-instalar no mesmo ambiente. A interface honesta para a stack pesada é o
97
+ > `doctor`, não um extra pip. Dev roda a suíte com `uv sync --all-extras`.
98
+ >
99
+ > *Setup do autor (não é requisito):* venv junction em `Z:\venvs\pdf2md`; ver
100
+ > [`docs/reference/conventions.md`](docs/reference/conventions.md).
101
+
102
+ ---
103
+
104
+ ## Uso
105
+
106
+ ```
107
+ MACRO por intent (roteador T090 — escolhe a stack por host+doc)
108
+ pdf2md convert FILE.pdf --intent rapido # CPU puro, velocidade máxima
109
+ pdf2md convert FILE.pdf --intent qualidade # marker/GPU se houver; senão degrada
110
+ pdf2md convert FILE.pdf --intent auto # melhor stack que CABE no host
111
+ pdf2md convert FILE.pdf --intent indexacao # pass1 indexa tudo; pass2 enfileira math-heavy
112
+ pdf2md route FILE.pdf --intent qualidade # dry-run: mostra o pipeline (--execute roda)
113
+
114
+ LEGADO (mantido; --intent substitui)
115
+ pdf2md convert FILE.pdf --quick / --best
116
+
117
+ SUBCOMANDOS FINOS (controle granular / retomar pipeline parcial)
118
+ pdf2md extract · restruct · optimize · stats · rt · rt-multi · aggr · prov · norm · pdfs
119
+
120
+ META
121
+ pdf2md doctor [--intent N] # capabilities do host (+ o que um intent usaria aqui)
122
+ pdf2md version · help <cmd>
123
+ ```
124
+
125
+ ### Os 6 intents (resumo; detalhes em [how-to/escolher_intent](docs/how-to/escolher_intent.md))
126
+
127
+ | Intent | PRIMARY | Para quê |
128
+ |---|---|---|
129
+ | `--rapido` | pdftotext (mesmo com GPU) | indexar/pré-processar em massa; wall-time mínimo |
130
+ | `--low-resource` | pdftotext (teto RAM 160 MB) | máquinas magras; optimize desliga se estourar |
131
+ | `--indexacao` | pdftotext (pass1) + marker (pass2 enfileirável) | milhares de docs; pass2 só nos de perda recuperável |
132
+ | `--balanceado` (default) | marker se houver, senão pdftotext | uso geral |
133
+ | `--qualidade` | marker (degrada p/ pdftotext+pix2tex sem GPU) | máxima fidelidade; math LaTeX, layout |
134
+ | `--auto` | melhor que o host comporta | "faça a melhor coisa possível aqui" |
135
+
136
+ Sem GPU, `--qualidade` **degrada com aviso honesto** (`.rationale` vai pra proveniência),
137
+ nunca finge qualidade nem quebra em silêncio.
138
+
139
+ ---
140
+
141
+ ## Variáveis de ambiente
142
+
143
+ | Variável | Função |
144
+ |---|---|
145
+ | `PDF2MD_MARKER` | path do `marker_single` (sem fallback de máquina — **necessária** p/ usar marker) |
146
+ | `PDF2MD_PIX2TEX_PYTHON` | python de um venv com `pix2tex` (runtime math→LaTeX CPU) |
147
+ | `PDF2MD_TESSERACT` | path do `tesseract` (senão PATH → local padrão do SO) |
148
+ | `PDF2MD_PANDOC` / `PDF2MD_CHROME` | paths de pandoc / Chrome (senão PATH → local padrão do SO) |
149
+ | `PDF2MD_ZCACHE` / `PDF2MD_AULAQUANTUM` | raízes de corpus zcache / source privado (default = drives do autor) |
150
+
151
+ Descoberta (em `pdf2md/discovery.py`): `env → PATH (multi-nome, multi-SO) → local padrão
152
+ do SO → nome do comando`. Sem paths absolutos presos a uma máquina.
153
+
154
+ ---
155
+
156
+ ## Estrutura do repo
157
+
158
+ ```
159
+ pdf2md/
160
+ ├── src/pdf2md/ (lógica do pacote)
161
+ │ ├── cli.py CLI (macro --intent, route, doctor, subcomandos)
162
+ │ ├── routing.py ROTEADOR T090: route() puro + HostInfo/DocInfo + pass2_warranted
163
+ │ ├── _profiles.py MAPA: perfis medidos (route-relevant), dep-free
164
+ │ ├── executor.py executa o Pipeline que route() decide
165
+ │ ├── extractors.py PRIMARYs CPU: pdftotext + tesseract
166
+ │ ├── formula_cropper.py cropper de fórmula display CPU (built-in)
167
+ │ ├── discovery.py descoberta portável de ferramentas externas
168
+ │ ├── optimize.py otimização adaptativa de imagens
169
+ │ ├── telemetry.py INSTRUMENTO: wall/cpu/mem/gpu/io por step
170
+ │ ├── pixel_roundtrip.py validador visual L0.5 (extra [rtpixel])
171
+ │ ├── roundtrip.py · multi_roundtrip.py round-trip textual + estabilidade
172
+ │ ├── pdfs.py · restructure.py · normalize.py · provenance.py · stats.py · aggregate.py
173
+ │ └── _pix2tex_runner.py script no venv externo do pix2tex (subprocess)
174
+ ├── corpus/ dataset em 3 tiers (ver corpus/RIGHTS.md)
175
+ │ ├── examples/ excertos LIVRES commitados (prova pronta, sem baixar nada)
176
+ │ └── registry.py resolve(doc_id) → in-repo | zcache | private
177
+ ├── docs/ Diátaxis (tutorials/how-to/reference/explanation/profiles)
178
+ ├── lab/ bancada experimental (eNN por experimento)
179
+ └── tickets/ work items (open/closed/research) + INDEX.md
180
+ ```
181
+
182
+ PDFs pesados ficam **fora do repo** (zcache `Z:/caches/...` via `PDF2MD_ZCACHE`, ou sources
183
+ privados). O tier in-repo (`corpus/examples/`) tem só excertos livres pequenos.
184
+
185
+ ---
186
+
187
+ ## Resultados (medidos)
188
+
189
+ Round-trip e telemetria sobre **Nielsen & Chuang QCQI** (704 pág, RTX 3060):
190
+
191
+ | Métrica | Valor |
192
+ |---|---:|
193
+ | Round-trip textual (cap. 4, marker) | **95.09%** |
194
+ | Multi-iteração (paper) | drift 0.86% em 5 iter (**estável**) |
195
+ | Otimização de imagens (cap. 4) | 4.5 → 2.7 MB (**−38.6%**) |
196
+ | Extração full-doc (marker) | ~4145 s (~70 min) |
197
+
198
+ > Os números do N&C são **resultados derivados** (métricas, não reprodução da obra),
199
+ > usados sob licença legítima do detentor — ver [`corpus/RIGHTS.md`](corpus/RIGHTS.md). O
200
+ > PDF original e reproduções completas ficam fora do repositório.
201
+
202
+ Caminho CPU validado em corpus livre in-repo: pdftotext prosa WER 0.016 (pg estruturada),
203
+ Tesseract scan impresso WER 0.052, pix2tex math display 0.80 (linha única; matriz ~0.50).
204
+
205
+ ---
206
+
207
+ ## Documentação
208
+
209
+ - Arquitetura: [`docs/explanation/arquitetura.md`](docs/explanation/arquitetura.md) · Filosofia: [`philosophy.md`](docs/explanation/philosophy.md)
210
+ - Perfis medidos: [`docs/profiles/`](docs/profiles/) · Tecnologias: [`docs/reference/tecnologias.md`](docs/reference/tecnologias.md)
211
+ - Referência de CLI: [`docs/reference/cli.md`](docs/reference/cli.md) · Escolher intent: [`docs/how-to/escolher_intent.md`](docs/how-to/escolher_intent.md)
212
+ - Direitos do corpus: [`corpus/RIGHTS.md`](corpus/RIGHTS.md) · Timeline: [`docs/explanation/diario.md`](docs/explanation/diario.md)
213
+
214
+ Licença: MIT (ver [`LICENSE`](LICENSE)).
@@ -0,0 +1,76 @@
1
+ [project]
2
+ # Nome de DISTRIBUIÇÃO no PyPI. O slug "pdf2md" está reservado por outra conta
3
+ # (registrado sem releases). Além disso o PyPI rejeita nomes "muito similares" por
4
+ # ultranormalização (remove ._- e mapeia o->0, i/l->1): p.ex. "pdftomd" colidiria
5
+ # com "pdf-to-md" (ambos -> pdft0md). "pdf2md-tool" tem chave "pdf2mdt001", distinta
6
+ # de tudo (verificado, 81 grafias 404). Import e comando CLI permanecem "pdf2md".
7
+ name = "pdf2md-tool"
8
+ version = "0.8.0"
9
+ description = "Conversor PDF→MD CPU-first: roteamento por intent (rápido/qualidade), núcleo offline e round-trip mensurável"
10
+ authors = [
11
+ { name = "Leonardo Marques de Souza", email = "leonardo.marques.souza@gmail.com" }
12
+ ]
13
+ readme = "README.md"
14
+ license = { text = "MIT" }
15
+ requires-python = ">=3.11"
16
+ keywords = ["pdf", "markdown", "pdf-to-markdown", "ocr", "latex", "document-conversion", "cli", "round-trip"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Text Processing :: Markup :: Markdown",
29
+ "Topic :: Scientific/Engineering",
30
+ "Topic :: Utilities",
31
+ ]
32
+
33
+ # Deps base do CLI. Marker/pandoc/Chrome são checados em runtime via `pdf2md doctor`,
34
+ # não declarados aqui (cada um vive no seu venv ou no PATH, sem conflito pillow<11).
35
+ dependencies = [
36
+ "typer>=0.12",
37
+ "pymupdf>=1.24",
38
+ "pillow>=10",
39
+ "psutil>=5.9",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ # Extras pip REAIS (pure-pip, sem conflito pillow/torch). marker/pix2tex/ollama NÃO
44
+ # entram aqui — são capabilities EXTERNAS (venv/binário/server), checadas por
45
+ # `pdf2md doctor`. Ver tickets/closed/T090 + docs. (publish-prep marco B)
46
+ rtpixel = ["numpy>=1.26", "scipy>=1.14", "scikit-image>=0.22"] # validador visual L0.5 (SSIM/align)
47
+ ocr = ["pytesseract>=0.3"] # wrapper; engine tesseract é externo (doctor)
48
+ all = ["pdf2md-tool[rtpixel,ocr]"] # tudo que é pip-puro seguro (auto-ref: nome de distribuição)
49
+
50
+ [project.scripts]
51
+ pdf2md = "pdf2md.cli:app"
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/LeoPR/pdf2md"
55
+ Repository = "https://github.com/LeoPR/pdf2md"
56
+ Issues = "https://github.com/LeoPR/pdf2md/issues"
57
+
58
+ [build-system]
59
+ requires = ["setuptools>=64"]
60
+ build-backend = "setuptools.build_meta"
61
+
62
+ [tool.setuptools.packages.find]
63
+ where = ["src"]
64
+ include = ["pdf2md*"]
65
+
66
+ [tool.ruff]
67
+ line-length = 100
68
+ target-version = "py311"
69
+
70
+ [dependency-groups]
71
+ dev = [
72
+ "pip>=26.1.1",
73
+ "setuptools>=82.0.1",
74
+ "wheel>=0.47.0",
75
+ "pytest>=8",
76
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """pdf2md — conversor PDF→Markdown CPU-first com roteamento por intent.
2
+
3
+ Núcleo CPU (pdftotext/PyMuPDF) offline e determinístico; capacidades pesadas
4
+ (marker/GPU, pix2tex, OCR, VLM) são opcionais e detectadas em runtime via
5
+ `pdf2md doctor`. CLI em `pdf2md.cli:app`.
6
+ """
7
+
8
+ __version__ = "0.8.0"
@@ -0,0 +1,45 @@
1
+ """Runner standalone de pix2tex — executado pelo python de um venv EXTERNO (torch).
2
+
3
+ NÃO importa pdf2md (o venv externo não tem o pacote). Só pix2tex + PIL + stdlib.
4
+ O executor (pdf2md.executor) cropa as fórmulas no venv geral e chama:
5
+
6
+ <pix2tex_python> _pix2tex_runner.py <crop_dir> <out_json>
7
+
8
+ Lê todos os PNGs de <crop_dir>, roda LatexOCR e escreve {filename: latex} em
9
+ <out_json>. Mantém o torch fora do venv geral (ver feedback_venv_efemero_para_labs).
10
+ """
11
+ import json
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def main(argv) -> int:
17
+ if len(argv) != 2:
18
+ print("uso: _pix2tex_runner.py <crop_dir> <out_json>", file=sys.stderr)
19
+ return 2
20
+ crop_dir, out_json = Path(argv[0]), Path(argv[1])
21
+ crops = sorted(crop_dir.glob("*.png"))
22
+ if not crops:
23
+ out_json.write_text("{}", encoding="utf-8")
24
+ return 0
25
+ try:
26
+ from PIL import Image
27
+ from pix2tex.cli import LatexOCR
28
+ except ImportError as e:
29
+ print(f"[ERRO] runtime pix2tex ausente: {e}", file=sys.stderr)
30
+ return 3
31
+
32
+ model = LatexOCR()
33
+ out = {}
34
+ for png in crops:
35
+ try:
36
+ out[png.name] = model(Image.open(png))
37
+ except Exception as e: # uma fórmula ruim não derruba o lote
38
+ out[png.name] = ""
39
+ print(f"[warn] {png.name}: {e}", file=sys.stderr)
40
+ out_json.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
41
+ return 0
42
+
43
+
44
+ if __name__ == "__main__":
45
+ raise SystemExit(main(sys.argv[1:]))