Skip to content

Commit 85ddb9f

Browse files
committed
bench: add pdfium comparison, show pages/sec metric
Add pypdfium2 (Chrome's PDF engine) to benchmark comparisons. Changed output to show pages/sec which is more useful for comparison. Removed reading order from default benchmark since it's experimental.
1 parent 552e80c commit 85ddb9f

1 file changed

Lines changed: 67 additions & 28 deletions

File tree

benchmark/accuracy.py

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
"""
33
ZPDF Correctness Benchmark
44
5-
Compares text extraction accuracy against MuPDF (reference), Tika (PDFBox), and pdftotext (Poppler).
5+
Compares text extraction accuracy and speed against:
6+
- MuPDF (mutool) - accuracy reference
7+
- pdfium (pypdfium2) - speed reference (Chrome's PDF engine)
8+
- Tika (PDFBox)
9+
- pdftotext (Poppler)
610
711
Usage:
812
python benchmark/accuracy.py [pdf_files...]
@@ -17,6 +21,13 @@
1721
sys.path.insert(0, str(Path(__file__).parent.parent / "python"))
1822
import zpdf
1923

24+
# Try to import pypdfium2 for pdfium comparison
25+
try:
26+
import pypdfium2 as pdfium
27+
HAS_PDFIUM = True
28+
except ImportError:
29+
HAS_PDFIUM = False
30+
2031

2132
def normalize(text: str) -> str:
2233
import re
@@ -86,6 +97,22 @@ def extract_pdftotext(pdf_path: str) -> tuple:
8697
return result.stdout, elapsed
8798

8899

100+
def extract_pdfium(pdf_path: str) -> tuple:
101+
"""Extract text using pdfium (Chrome's PDF engine)."""
102+
if not HAS_PDFIUM:
103+
return "", 0
104+
105+
start = time.perf_counter()
106+
pdf = pdfium.PdfDocument(pdf_path)
107+
texts = []
108+
for page in pdf:
109+
textpage = page.get_textpage()
110+
texts.append(textpage.get_text_bounded())
111+
text = "\f".join(texts)
112+
elapsed = (time.perf_counter() - start) * 1000
113+
return text, elapsed, len(pdf)
114+
115+
89116
def check_tool(name: str, cmd: list) -> bool:
90117
try:
91118
subprocess.run(cmd, capture_output=True)
@@ -114,23 +141,31 @@ def main():
114141
print("mutool (MuPDF) not found - required as reference")
115142
sys.exit(1)
116143

117-
print("ZPDF Accuracy Benchmark")
144+
print("ZPDF Accuracy & Speed Benchmark")
118145
print()
119-
print("Reference: MuPDF (mutool)")
120-
print(f"Tools: zpdf (reading order), zpdf (stream order), mutool{', tika' if has_tika else ''}{', pdftotext' if has_pdftotext else ''}")
146+
print("Reference: MuPDF (mutool) for accuracy")
147+
tools = ["zpdf", "mutool"]
148+
if HAS_PDFIUM:
149+
tools.append("pdfium")
150+
if has_tika:
151+
tools.append("tika")
152+
if has_pdftotext:
153+
tools.append("pdftotext")
154+
print(f"Tools: {', '.join(tools)}")
121155
print()
122156

123157
for pdf in pdf_files:
124158
print(f"--- {pdf.name} ---")
125159

126160
try:
127161
# Extract with all tools
128-
# zpdf with reading order (default)
129-
zpdf_ro_text, zpdf_ro_ms, pages = extract_zpdf(str(pdf), reading_order=True)
130-
# zpdf with stream order (for comparison)
131-
zpdf_so_text, zpdf_so_ms, _ = extract_zpdf(str(pdf), reading_order=False)
162+
zpdf_text, zpdf_ms, pages = extract_zpdf(str(pdf), reading_order=False)
132163
mutool_text, mutool_ms = extract_mutool(str(pdf))
133164

165+
pdfium_text, pdfium_ms = ("", 0)
166+
if HAS_PDFIUM:
167+
pdfium_text, pdfium_ms, _ = extract_pdfium(str(pdf))
168+
134169
tika_text, tika_ms = ("", 0)
135170
if has_tika:
136171
tika_text, tika_ms = extract_tika(str(pdf))
@@ -140,51 +175,55 @@ def main():
140175
pdftotext_text, pdftotext_ms = extract_pdftotext(str(pdf))
141176

142177
# Normalize
143-
zpdf_ro_norm = normalize(zpdf_ro_text)
144-
zpdf_so_norm = normalize(zpdf_so_text)
178+
zpdf_norm = normalize(zpdf_text)
145179
mutool_norm = normalize(mutool_text)
180+
pdfium_norm = normalize(pdfium_text) if HAS_PDFIUM else ""
146181
tika_norm = normalize(tika_text) if has_tika else ""
147182
pdftotext_norm = normalize(pdftotext_text) if has_pdftotext else ""
148183

149184
# Compute metrics vs MuPDF reference
150185
print(f"Pages: {pages}")
151186
print()
152-
print(f"{'Tool':<20} {'Char Acc':>10} {'WER':>8} {'Time':>10} {'Speedup':>10}")
153-
print("-" * 60)
187+
print(f"{'Tool':<12} {'Char Acc':>10} {'WER':>8} {'Time':>10} {'pages/sec':>12}")
188+
print("-" * 54)
154189

155-
# zpdf (reading order) - default
156-
acc = char_accuracy(zpdf_ro_norm, mutool_norm)
157-
wer = word_error_rate(zpdf_ro_norm, mutool_norm)
158-
speedup = mutool_ms / zpdf_ro_ms if zpdf_ro_ms > 0 else 0
159-
print(f"{'zpdf (reading)':<20} {acc:>9.1%} {wer:>7.1%} {zpdf_ro_ms:>8.0f}ms {speedup:>9.1f}x")
160-
161-
# zpdf (stream order) - for comparison
162-
acc = char_accuracy(zpdf_so_norm, mutool_norm)
163-
wer = word_error_rate(zpdf_so_norm, mutool_norm)
164-
speedup = mutool_ms / zpdf_so_ms if zpdf_so_ms > 0 else 0
165-
print(f"{'zpdf (stream)':<20} {acc:>9.1%} {wer:>7.1%} {zpdf_so_ms:>8.0f}ms {speedup:>9.1f}x")
190+
# zpdf
191+
acc = char_accuracy(zpdf_norm, mutool_norm)
192+
wer = word_error_rate(zpdf_norm, mutool_norm)
193+
pps = pages / (zpdf_ms / 1000) if zpdf_ms > 0 else 0
194+
print(f"{'zpdf':<12} {acc:>9.1%} {wer:>7.1%} {zpdf_ms:>8.0f}ms {pps:>10,.0f}")
166195

167196
# MuPDF (reference = 100%)
168-
print(f"{'mutool':<20} {'100.0%':>10} {'0.0%':>8} {mutool_ms:>8.0f}ms {'1.0x':>10}")
197+
pps = pages / (mutool_ms / 1000) if mutool_ms > 0 else 0
198+
print(f"{'mutool':<12} {'100.0%':>10} {'0.0%':>8} {mutool_ms:>8.0f}ms {pps:>10,.0f}")
199+
200+
# pdfium
201+
if HAS_PDFIUM:
202+
acc = char_accuracy(pdfium_norm, mutool_norm)
203+
wer = word_error_rate(pdfium_norm, mutool_norm)
204+
pps = pages / (pdfium_ms / 1000) if pdfium_ms > 0 else 0
205+
print(f"{'pdfium':<12} {acc:>9.1%} {wer:>7.1%} {pdfium_ms:>8.0f}ms {pps:>10,.0f}")
169206

170207
# Tika
171208
if has_tika:
172209
acc = char_accuracy(tika_norm, mutool_norm)
173210
wer = word_error_rate(tika_norm, mutool_norm)
174-
speedup = mutool_ms / tika_ms if tika_ms > 0 else 0
175-
print(f"{'tika':<20} {acc:>9.1%} {wer:>7.1%} {tika_ms:>8.0f}ms {speedup:>9.1f}x")
211+
pps = pages / (tika_ms / 1000) if tika_ms > 0 else 0
212+
print(f"{'tika':<12} {acc:>9.1%} {wer:>7.1%} {tika_ms:>8.0f}ms {pps:>10,.0f}")
176213

177214
# pdftotext
178215
if has_pdftotext:
179216
acc = char_accuracy(pdftotext_norm, mutool_norm)
180217
wer = word_error_rate(pdftotext_norm, mutool_norm)
181-
speedup = mutool_ms / pdftotext_ms if pdftotext_ms > 0 else 0
182-
print(f"{'pdftotext':<20} {acc:>9.1%} {wer:>7.1%} {pdftotext_ms:>8.0f}ms {speedup:>9.1f}x")
218+
pps = pages / (pdftotext_ms / 1000) if pdftotext_ms > 0 else 0
219+
print(f"{'pdftotext':<12} {acc:>9.1%} {wer:>7.1%} {pdftotext_ms:>8.0f}ms {pps:>10,.0f}")
183220

184221
print()
185222

186223
except Exception as e:
187224
print(f"Error: {e}")
225+
import traceback
226+
traceback.print_exc()
188227
print()
189228

190229

0 commit comments

Comments
 (0)