Skip to main content

ClinParser

Structure-aware PDF parsing for clinical documents. No LLM, fully offline.

License: MIT Python 3.9+

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, and images attached — 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

  1. PDF → Markdown via docling.
  2. TOC extraction — parses each heading's numeric prefix and builds the nesting tree from it directly.
  3. Content merge — slices each section's body text out of the Markdown.
  4. Table extraction — converts Markdown pipe-tables to {columns, data} JSON (and optionally CSV).
  5. 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

MIT

Metadata

Release files for clinparser 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clinparser 0.1.0
File Size Uploaded
clinparser-0.1.0.tar.gz 21.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clinparser 0.1.0
File Interpreter ABI Platform
clinparser-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 42.8 kB

Release files / clinparser-0.1.0.tar.gz

Download URL clinparser-0.1.0.tar.gz
Size 21.3 kB
Tags Source
SHA-256 checksum
How to use checksums
284c290d14eaa48bdbb5cc9a5de970979794e8e95ccaa723f2d48a25a2f38a48
BLAKE2b-256 checksum
How to use checksums
be9eb52b6523b1dc6d9b062080a2beb6e8a8da2c4ef8b2d2bd6bfaa6b3c032b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / clinparser-0.1.0-py3-none-any.whl

Download URL clinparser-0.1.0-py3-none-any.whl
Size 21.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
13a018bfe9c3b6ac21d7c3a836a114d6e6caa68a4ef8bdf13aaa4f9cf855fa49
BLAKE2b-256 checksum
How to use checksums
06f7b42f47168591ec0553c23c53b6714a6937cbb8bc6b6d76af1bad47dd5e7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page