PyKMExtract is a research-oriented Kaplan-Meier digitization toolkit. It extracts structured time / survival curves from published KM figures, validates the result, and bridges the output into PyHEOR for Guyot reconstruction and downstream survival modeling.
PyKMExtract is built around one conservative idea:
simple default extraction pipeline + optional AI enhancement modules
The default path stays deterministic and inspectable. AI is allowed, but only as an explicit refinement layer, not as the core measurement engine.
Current default pipeline:
semantic -> axis detection -> color extraction -> KM step sampling -> coordinate mapping -> validation
This repository is currently a practical MVP, not a fully general KM digitizer.
What already works well:
- structured semantic input from JSON or a vision model
- automatic left/bottom axis detection on common white-background KM figures
- color-based curve extraction for 1-2 high-contrast curves
- KM-style step-preserving sampling instead of naive linear interpolation
- validation, scoring, and review bundle export
- PyHEOR bridge for Guyot reconstruction and KM redraw
What is still intentionally conservative:
- grayscale or near-color curves
- dense multi-curve figures
- heavy confidence ribbons and dense censoring marks
- low-resolution scans and compressed screenshots
- full PDF page splitting and GUI-style manual correction
- Structured semantic extraction from
semantic.jsonor an OpenAI-compatible vision endpoint - Axis bounds and optional four-point axis-anchor refinement
- KM-specific step sampling
- Validation signals for monotonicity, range, coverage,
at-risk, and overlap ambiguity - Human review bundle with
overlay.png,review.md, digitized CSV, and optional IPD redraw - Batch workflow for grouped studies such as
study01_full.png,study01_pfs.png,study01_os.png
Editable install:
pip install -e .Or run directly from source:
PYTHONPATH=src python3 -m pykmextract.cli ...PYTHONPATH=src python3 -m pykmextract.cli figure.png \
--semantic-json semantic.json \
--output-json extraction.json \
--overlay overlay.pngexport OPENROUTER_API_KEY="..."
PYTHONPATH=src python3 -m pykmextract.cli figure.png \
--provider openai-compatible \
--base-url https://openrouter.ai/api/v1 \
--model openai/gpt-5.4 \
--api-key-env OPENROUTER_API_KEY \
--output-json extraction.json \
--overlay overlay.pngexport OPENROUTER_API_KEY="..."
PYTHONPATH=src python3 -m pykmextract.cli figure.png \
--provider openai-compatible \
--base-url https://openrouter.ai/api/v1 \
--model openai/gpt-5.4 \
--api-key-env OPENROUTER_API_KEY \
--axis-refine \
--axis-review-image axis_review.png \
--output-json extraction.json \
--overlay overlay.pngimport pykmextract as pkm
result = pkm.extract("figure.png", semantic=semantic_payload)
curve_df = result.curve_frame()
validation_df = result.validation_frame()
result.save_review_bundle("runs/example")
ipd = result.to_pyheor_ipd()Grouped figures should use a predictable naming pattern:
study01_full.pngstudy01_pfs.pngstudy01_os.png
Build a manifest:
PYTHONPATH=src python3 -m pykmextract.batch \
--image-dir images \
--literature-md images/literatures.md \
--output-json images/manifest.jsonRun grouped extraction:
export OPENROUTER_API_KEY="..."
PYTHONPATH=src python3 -m pykmextract.batch_run \
--image-dir images \
--literature-md images/literatures.md \
--base-url https://openrouter.ai/api/v1 \
--model openai/gpt-5.4 \
--api-key-env OPENROUTER_API_KEY \
--axis-refine \
--output-dir runs/openrouter-batchBatch output is organized per study and per endpoint:
runs/study01/os/runs/study01/pfs/runs/study02/os/runs/study02/pfs/
Main structured fields:
semanticaxis_boundsaxis_anchorscurves[].timecurves[].survivalvalidation.scorevalidation.levelvalidation.issues
Review bundle outputs:
original.pngoverlay.pngdigitized_curves.csvreview.mdreconstructed_km.pngwhen IPD reconstruction succeedsipd_<curve>.csvwhen IPD reconstruction succeeds
Latest batch summary:
All 10 panel overlays:
Manual visual assessment of the current batch:
| Panel | Score / Level | Assessment | Main concern |
|---|---|---|---|
study01 / os |
80 / high |
good | tail diverges from at-risk |
study01 / pfs |
85 / high |
usable but weaker in the middle and tail | Sorafenib coverage is thin in the back half |
study02 / os |
80 / high |
usable | both curves diverge from at-risk |
study02 / pfs |
80 / high |
weak | Placebo plus chemotherapy remains too low in the middle and tail |
study03 / os |
80 / high |
weak | coarse steps and weak at-risk agreement |
study03 / pfs |
80 / high |
weak | late segments still look approximate |
study04 / os |
100 / high |
best panel in the batch | strongest visual match |
study04 / pfs |
100 / medium |
good but still needs review | long overlapping segment between curves |
study05 / os |
80 / high |
good | visually strong; score limited by at-risk checks |
study05 / pfs |
80 / high |
good | similar to study05 / os |
Most realistic takeaway:
study04 / osis the best showcase panelstudy05 / osandstudy05 / pfsare visually better than their raw80 / highstudy02 / pfs,study03 / os, andstudy03 / pfsremain weak casesstudy04 / pfsis intentionally downgraded because ofoverlap_ambiguity
At the moment, reconstructed_km.png may visually lose the final flat tail segment even when the digitized curve and reconstructed IPD still contain late follow-up.
This is currently a downstream PyHEOR display limitation:
- reconstructed IPD still keeps late censoring times
- KM tables are currently reported only at event times
- the last horizontal plateau after the final event may therefore not be drawn
So if the redrawn KM tail looks shorter than the original figure, do not immediately assume digitization failed.
Key modules:
src/pykmextract/pipeline.py: main extraction orchestratorsrc/pykmextract/runtime.py: shared runtime helpers for CLI and batchsrc/pykmextract/extractor/semantic.py: semantic parsing and normalizationsrc/pykmextract/extractor/coord.py: axis detection and pixel-to-data mappingsrc/pykmextract/extractor/pixel.py: color extraction and KM step samplingsrc/pykmextract/extractor/validator.py: validation and confidence scoringsrc/pykmextract/extractor/axis_refiner.py: AI axis-refinement orchestrationsrc/pykmextract/extractor/_axis_refiner_prompts.py: axis-refinement promptssrc/pykmextract/extractor/_axis_refiner_payloads.py: candidate generation and payload normalizationsrc/pykmextract/extractor/_axis_refiner_board.py: axis review boardssrc/pykmextract/enhancements.py: optional AI enhancement orchestrationsrc/pykmextract/microtune.py: bounded post-extraction micro-tuning toolssrc/pykmextract/review.py: overlay export and review bundle generationsrc/pykmextract/bridge/pyheor.py: PyHEOR bridge
Run the full test suite:
python3 -m unittest discover -s tests -vCurrent tests cover:
- synthetic end-to-end extraction
- CLI smoke paths
- axis refinement prompts and candidate logic
- pixel sampling behavior
- review bundle export
- provider response parsing
- PyHEOR bridge smoke path
Project files:
- keep the default extraction path simple
- keep AI enhancement optional and explicit
- prefer understandable heuristics over stacked special cases
- keep difficult figures visible as failure cases instead of hiding them
