ClinParser
Structure-aware PDF parsing for clinical documents. No LLM, fully offline.
Clinical protocol PDFs are dense with information that's essential to almost every downstream process built around a trial — study design, eligibility criteria, visit schedules, statistical plans, safety definitions. As AI reshapes the clinical trials domain, more of that downstream work is becoming automatable, and nearly all of it starts by reading the protocol:
- Generating other required documents that must stay consistent with it — the SAP (Statistical Analysis Plan), IB (Investigator's Brochure), informed consent forms, and CRF (Case Report Form) design.
- Operational and compliance checks during the trial — protocol deviation detection, data quality checks against the protocol's own specifications, eligibility-criteria extraction for patient screening, and risk-based monitoring.
- Standardizing the protocol into standard formats for reuse — CDISC's USDM is the industry's emerging standard here, enabling cross-trial analytics and portfolio-level review.
Every one of those tasks starts from the same bottleneck: turning an information-rich protocol PDF into a machine-readable format without losing anything that matters — section numbering and nesting, which tables and images belong to which section, cross-references between sections, and the overall organization the authors intended.
ClinParser is that starting point. It parses a clinical protocol PDF into a structure-aware JSON tree — sections nested exactly as the protocol numbers them, each with its own text, tables, and images attached — ready to hand to an LLM for any of the tasks above, or to query, chunk, and feed into a RAG pipeline directly.
{
"number": "1.1",
"title": "Background",
"subsections": [],
"text": "Alisporivir (also known as DEB025...) is a cyclophilin (Cyp) inhibitor...",
"tables": [],
"images": []
}
Why not just use docling / Unstructured / LlamaParse directly?
Those tools infer heading level from layout — font size, boldness, indentation. That's brittle across the wildly inconsistent templates different sponsors and CROs use for protocols, and it gives you a flat stream of typed elements (paragraph, table, image + bounding box), leaving "which section does this belong to" as a join you have to do yourself.
ClinParser does two things differently:
- Numbering-anchored hierarchy, not layout heuristics. Protocol numbering is maintained rigorously by authors for regulatory reasons — it's a more reliable, fully deterministic signal than re-inferring structure from visual styling.
- Per-section bundling. Every node in the output already has its own
text,tables, andimagesattached — no post-hoc join required.
And because there's no LLM anywhere in the pipeline, it's fully reproducible and runs entirely offline — relevant when the documents in question contain PHI (Protected Health Information) or sponsor IP (intellectual property) that can't leave a secure environment.
Install
pip install clinparser
# or, for local development:
git clone https://github.com/Nandha-kumar-S/clinparser && cd clinparser
pip install -e ".[dev]"
Usage
clinparser path/to/protocol.pdf
from clinparser import PDFParser
parser = PDFParser()
result = parser.parse("path/to/protocol.pdf")
# {'toc': [...], 'non_toc': [...]}
By default, parse() writes everything to output/<timestamp>/: markdown.md and toc.json/document.json (debug artifacts), plus each image and table as its own file (images/<section>/, tables/<section>/). All three are independent and default to on:
result = parser.parse(
"path/to/protocol.pdf",
save_intermediate_files=True, # markdown.md, toc.json, document.json
save_images=True, # images/<section>/image_NNN.png
save_table_csv=True, # tables/<section>/table_NNN.csv
)
examples/input/protocol_001.pdf plus its output in examples/output/ is a full worked example.
Output shape
Every node — in toc (numbered sections) or non_toc (front/back matter that doesn't belong to a numbered section) — has the same shape:
{
"number": "3.1",
"title": "Study design",
"subsections": [ ... ],
"text": "...",
"tables": [{"columns": [...], "data": [[...]], "path": "tables/Study_design/table_001.csv"}],
"images": [{"page": 12, "bbox": [...], "path": "images/Study_design/image_001.png"}]
}
How it works
- PDF → Markdown via
docling. - TOC extraction — parses each heading's numeric prefix and builds the nesting tree from it directly.
- Content merge — slices each section's body text out of the Markdown.
- Table extraction — converts Markdown pipe-tables to
{columns, data}JSON (and optionally CSV). - Image extraction — pulls embedded images via PyMuPDF and assigns each to its containing section by page/position.
Contributing
Issues and PRs welcome. No LLM/API keys required to run or test anything in this repo.
Dependencies & licensing
ClinParser is MIT licensed, and its dependencies are all permissively licensed (MIT/BSD/HPND) — except PyMuPDF, used for image extraction and coordinate lookups. PyMuPDF is dual-licensed under AGPL-3.0 (free to use, including commercially, but distributing software built on it — or running it as a network service — requires releasing the complete source of that combined application under AGPL-compatible terms) or a commercial license from Artifex. If you're building a proprietary or SaaS product on top of ClinParser, this applies to you. See NOTICE for the full dependency list.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clinparser-0.1.0.tar.gz.
File metadata
- Download URL: clinparser-0.1.0.tar.gz
- Upload date:
- Size: 21.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
284c290d14eaa48bdbb5cc9a5de970979794e8e95ccaa723f2d48a25a2f38a48
|
|
| MD5 |
b92138b1a2364daf423902337b6c0bd2
|
|
| BLAKE2b-256 |
be9eb52b6523b1dc6d9b062080a2beb6e8a8da2c4ef8b2d2bd6bfaa6b3c032b4
|
File details
Details for the file clinparser-0.1.0-py3-none-any.whl.
File metadata
- Download URL: clinparser-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13a018bfe9c3b6ac21d7c3a836a114d6e6caa68a4ef8bdf13aaa4f9cf855fa49
|
|
| MD5 |
9a2c966d23a2633f15b9a98c259f4327
|
|
| BLAKE2b-256 |
06f7b42f47168591ec0553c23c53b6714a6937cbb8bc6b6d76af1bad47dd5e7c
|