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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clinparser-0.1.0.tar.gz (21.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clinparser-0.1.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

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

Hashes for clinparser-0.1.0.tar.gz
Algorithm Hash digest
SHA256 284c290d14eaa48bdbb5cc9a5de970979794e8e95ccaa723f2d48a25a2f38a48
MD5 b92138b1a2364daf423902337b6c0bd2
BLAKE2b-256 be9eb52b6523b1dc6d9b062080a2beb6e8a8da2c4ef8b2d2bd6bfaa6b3c032b4

See more details on using hashes here.

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

Hashes for clinparser-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 13a018bfe9c3b6ac21d7c3a836a114d6e6caa68a4ef8bdf13aaa4f9cf855fa49
MD5 9a2c966d23a2633f15b9a98c259f4327
BLAKE2b-256 06f7b42f47168591ec0553c23c53b6714a6937cbb8bc6b6d76af1bad47dd5e7c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page