Skip to main content

Extract readings from medical device images using a local LLM (Ollama + MedGemma)

Project description

medextract

Extract blood pressure readings from medical device photos using a local AI model — no API key, no internet, no cost.

Built on Ollama + MedGemma, the library processes photos of BP monitors and returns structured data (systolic, diastolic, pulse, brand, AHA classification, and 10 more fields) ready for CSV export or direct use in Python.

Tests Python License Version


Features

  • Fully local — runs on your machine via Ollama, no data sent to any server
  • No API key required — free to use with no rate limits
  • 14 fields extracted per image — systolic, diastolic, pulse, brand, date, time, IHB, AFib, battery, glare, confidence, and more
  • Parallel processing — multiple images processed simultaneously
  • Progress bar — live tqdm progress with current reading and ETA
  • Resume support — safely restart a crashed 1000-image batch without reprocessing
  • AHA BP classification — Normal → Elevated → Stage 1/2 → Hypertensive Crisis
  • Validation — flags out-of-range and physiologically impossible readings
  • Input validation — clear errors for invalid parameters before processing starts
  • 37 tests, CI on every push — GitHub Actions runs tests across Python 3.10, 3.11, 3.12
  • Works as a Python library or CLI tool

Prerequisites

Install Ollama, then pull the model:

ollama serve
ollama pull medgemma1.5:4b

Install

From GitHub:

pip install git+https://github.com/shaunakmirajgaonkar/Healthnexaa.git

For local development:

git clone https://github.com/shaunakmirajgaonkar/Healthnexaa.git
cd Healthnexaa
pip install -e .

Dependencies installed automatically: ollama>=0.6.1, pillow>=10.2.0, pandas>=2.1.1, tqdm>=4.66.1


Quickstart

Process a folder

from medextract import extract_folder

rows = extract_folder("/path/to/bp-monitor-photos")

for row in rows:
    print(row["systolic"], "/", row["diastolic"], "—", row["bp_classification"])

Single image

from medextract import analyze_image, classify_bp

result = analyze_image("/path/to/photo.jpg")

if result:
    print("Systolic :", result["systolic"])
    print("Diastolic:", result["diastolic"])
    print("Pulse    :", result["pulse"])
    print("Brand    :", result["brand"])
    print("Category :", classify_bp(result["systolic"], result["diastolic"]))
    print("Confidence:", result["confidence"], "/ 10")

Save to CSV

import pandas as pd
from medextract import extract_folder

rows = extract_folder("/path/to/photos")
pd.DataFrame(rows).to_csv("results.csv", index=False)

Validate readings

from medextract import extract_folder, validate_bp, classify_bp

rows = extract_folder("/path/to/photos")

for row in rows:
    warnings = validate_bp(row)
    category = classify_bp(row["systolic"], row["diastolic"])
    print(f"{row['file_name']:30s}  {row['systolic']}/{row['diastolic']}  {category}")
    if warnings:
        print("  WARNINGS:", warnings)

1000+ images — progress bar and resume

from medextract import extract_folder

# tqdm progress bar shown automatically with live BP readout and ETA
rows = extract_folder("/path/to/1000-photos", workers=5)

If the run crashes midway, resume without reprocessing done images:

rows = extract_folder(
    "/path/to/photos",
    resume_csv="results.csv",   # skips files already in this CSV
    workers=5,
)

All parameters

from medextract import extract_folder

rows = extract_folder(
    folder="/path/to/photos",
    model="medgemma1.5:4b",     # any Ollama vision model
    workers=5,                   # parallel workers (more = faster on M2/M3)
    image_size=768,              # max px before encoding (larger = more accurate)
    max_retries=5,               # retries per image on failure
    resume_csv="results.csv",    # resume a crashed batch
)

CLI

# Basic
python3 -m medextract.cli /path/to/photos --output results.csv

# Full options
python3 -m medextract.cli /path/to/photos \
    --output results.csv \
    --workers 5 \
    --model medgemma1.5:4b \
    --image-size 768 \
    --max-retries 5 \
    --resume

# Help
python3 -m medextract.cli --help

Output Fields

Field Type Description
file_name string Source image filename
systolic int Top BP number (0 if unreadable)
diastolic int Bottom BP number (0 if unreadable)
pulse int Pulse / BPM (0 if unreadable)
brand string Device brand ("Unknown" if not visible)
date string Date shown on device (YYYY-MM-DD or null)
time string Time shown on device (HH:MM or null)
memory_slot string M1, M2, or null
ihb bool Irregular heartbeat indicator shown
afib bool AFib indicator shown
battery_low bool Battery warning shown
has_glare bool Glare affecting readability
confidence int 1–10 Model confidence (always clamped to 1–10)
bp_classification string AHA category
extracted_at string Timestamp (YYYY-MM-DD HH:MM:SS)

BP Classification (AHA Standard)

Classification Systolic Diastolic
Normal < 120 < 80
Elevated 120–129 < 80
Stage 1 Hypertension 130–139 80–89
Stage 2 Hypertension ≥ 140 ≥ 90
HYPERTENSIVE CRISIS > 180 > 120

Performance (Apple Silicon)

Workers 100 images 500 images 1000 images
3 (default) ~19 min ~97 min ~3.2 hrs
5 ~12 min ~58 min ~1.9 hrs
8 ~7 min ~36 min ~1.2 hrs

Supported Image Formats

.png .jpg .jpeg .webp


Running Tests

pip install -e ".[dev]"
pytest tests/ -v

37 tests covering classify_bp, validate_bp, image loading, Ollama checks, input validation, resume, and analyze_image — all with mocked Ollama responses (no model required to run tests).

Tests run automatically on every push and pull request via GitHub Actions across Python 3.10, 3.11, and 3.12.


Project Structure

medextract/                  ← installable Python library
├── __init__.py              ← public API (5 exports)
├── extractor.py             ← core logic — no global state
└── cli.py                   ← command-line interface

.github/workflows/
└── tests.yml                ← CI — auto-runs tests on every push

tests/                       ← pytest test suite (37 tests)
└── test_extractor.py

examples/                    ← original standalone scripts (reference only)
├── README.md
└── *.py

plans/                       ← project documentation
├── usage-guide.md
├── library-creation-guide.md
├── project-plan.md
└── medical-device-extraction-guide.md

pyproject.toml               ← package config, pinned deps

License

MIT — see LICENSE

Project details


Download files

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

Source Distribution

medextract-0.3.0.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

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

medextract-0.3.0-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file medextract-0.3.0.tar.gz.

File metadata

  • Download URL: medextract-0.3.0.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.1

File hashes

Hashes for medextract-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b95d13db8c3d5f7cc22ff5c6e12bd6cab5e74efb5089a6991155d9f6259a35a8
MD5 990061569741cc8c91b1c23edc691229
BLAKE2b-256 4913ea7dc70b0b096bf855f1a4c29121102da8dde7c72ff91316dd059750e7eb

See more details on using hashes here.

File details

Details for the file medextract-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: medextract-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.1

File hashes

Hashes for medextract-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3b2d32528205e3b1ad60c17e16a7df3114f340ff3c12bebbd2f4de1a4198d11b
MD5 57133c0b2ff56f99787ac1bda89d166d
BLAKE2b-256 90187abbce4d4aad7e27b8080fdaa24ad42bb0015f4ad8f1e192606ef9826b63

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