r0m4k commited on
Commit
f6c987a
Β·
unverified Β·
1 Parent(s): db77748

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +59 -126
README.md CHANGED
@@ -1,166 +1,99 @@
1
- ---
2
- title: Blood Test Explainer
3
- emoji: πŸ“Š
4
- colorFrom: green
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.17.3
8
- python_version: "3.10.13"
9
- app_file: app.py
10
- pinned: false
11
- startup_duration_timeout: 1h
12
- ---
13
-
14
- # Blood Test Explainer
15
-
16
- Blood test results often arrive as dense PDFs, scans, photos, or lab documents filled with abbreviations, reference ranges, units, and flags. For many people, the result is anxiety rather than understanding: they can see that something is high or low, but they do not know what it means, what questions to ask, or what practical next steps might support better health.
17
-
18
- Blood Test Explainer turns an uploaded blood test into a clear, interactive health dashboard. The goal is to extract the important markers, organize them into a readable visual experience, explain each result in plain language, and help the user prepare for a better conversation with a clinician.
19
-
20
- The project focuses on education and personal clarity, not diagnosis. It should help people understand their lab report, notice which markers may deserve attention, and explore general lifestyle ideas such as food, movement, sleep, and supplement topics that may be worth discussing with a qualified professional.
21
-
22
- The final experience should feel calm, trustworthy, and useful:
23
-
24
- - Upload a blood test document, image, scan, or PDF.
25
- - See extracted markers, values, units, and reference ranges.
26
- - Review results in a polished interactive interface.
27
- - Understand what each marker generally reflects.
28
- - Get practical lifestyle-oriented suggestions for supporting specific markers.
29
- - Generate thoughtful questions to bring to a doctor or healthcare provider.
30
-
31
- The long-term vision is to make medical paperwork less intimidating and help people move from confusion to informed action.
32
-
33
- ## First App Version
34
-
35
- The first version focuses only on extraction: upload a lab report and convert it into structured raw values such as marker name, value, unit, reference range, status, source snippet, and confidence.
36
-
37
- ## Current Pipeline
38
-
39
- The app now runs extraction and deterministic knowledge-graph enrichment:
40
-
41
- 1. The extractor reads an uploaded image, PDF, or text document and returns patient context plus raw lab values.
42
- 2. `src.report_pipeline.build_health_report` resolves marker aliases against `kb/cbc_knowledge_graph.json`, selects age/sex-aware reference context, and merges marker explanations, importance, and food/exercise/supplement guidance.
43
- 3. `app.py` renders the enriched report as the final health-report UI.
44
-
45
- The knowledge graph is educational context, not diagnosis. The lab-provided reference range remains the primary comparison when it is available.
46
 
47
- ## Extraction Backends
48
 
49
- The default path is **Transformers vision** with our fine-tuned [blood-test-minicpmv-4_6-medreason](https://huggingface.co/build-small-hackathon/blood-test-minicpmv-4_6-medreason) checkpoint (MiniCPM-V 4.6 + MedReason SFT). It handles PDFs, scans, and photos through the same document pipeline in `src/document_processing.py`.
50
 
51
- | `EXTRACTOR_BACKEND` | Used for | PDF / image uploads |
52
- |---|---|---|
53
- | `transformers` (default), `auto`, `zerogpu` | Normal app + HF Space | Yes |
54
- | `llamacpp-gpu` + `LLAMACPP_VISION=1` | Opt-in llama.cpp vision lane | Yes |
55
- | `llamacpp-gpu` (vision off) | Text-only GGUF lane | No β€” `.txt` / `.csv` only |
56
- | `local` / `server` | Local `llama-server` experiments | Yes |
57
- | `llamacpp` | Local in-process GGUF + mmproj | Yes |
58
 
59
- Backend selection lives in `src/extraction/factory.py`.
 
 
 
60
 
61
- ## Running with llama.cpp
62
 
63
- The app does **not** use llama.cpp by default. Enable it only when you need the optional GGUF lane.
64
 
65
- ### Why keep llama.cpp?
66
 
67
- 1. **Hackathon badge** β€” the project can target the **Llama Champion** badge by running inference through `llama-cpp-python` over GGUF inside `@spaces.GPU`.
68
- 2. **Fine-tuned GGUF swap** β€” after fine-tuning, you can point `LLAMACPP_*` at a quantized GGUF repo without changing the Gradio app.
69
- 3. **Lighter text-only lane** β€” without `LLAMACPP_VISION=1`, the llama.cpp path skips mmproj and works for plain-text lab exports (`.txt` / `.csv`).
70
- 4. **Local offline experiments** β€” `EXTRACTOR_BACKEND=local` (external `llama-server`) or `llamacpp` (in-process GGUF + mmproj) for off-grid development.
71
 
72
- For normal PDF/image blood-test uploads, keep the default Transformers backend.
73
 
74
- ### Vision llama.cpp (PDFs and images)
75
 
76
- Use the same vision document pipeline as Transformers, but route inference through llama.cpp:
77
 
78
- ```bash
79
- pip install -r requirements.txt
 
 
 
 
 
 
80
 
81
- export EXTRACTOR_BACKEND=llamacpp-gpu
82
- export LLAMACPP_VISION=1
83
- export LLAMACPP_GGUF_REPO=openbmb/MiniCPM-V-4.6-gguf
84
- export LLAMACPP_MODEL_FILE=MiniCPM-V-4_6-Q4_K_M.gguf
85
- export LLAMACPP_MMPROJ_FILE=mmproj-model-f16.gguf
86
- export LLAMACPP_CHAT_HANDLER=MiniCPMv26ChatHandler # override if your wheel needs a different handler
87
 
88
- python app.py
89
- ```
90
 
91
- On Hugging Face Spaces, set the same variables in **Settings β†’ Repository secrets / Variables**, then restart the Space. Generation runs inside `@spaces.GPU` in `src/extraction/llamacpp_gpu.py`.
92
 
93
- ### Text-only llama.cpp (no vision)
94
 
95
- For `.txt` / `.csv` uploads only:
96
 
97
- ```bash
98
- export EXTRACTOR_BACKEND=llamacpp-gpu
99
- export LLAMACPP_GGUF_REPO=openbmb/MiniCPM-V-4.6-gguf
100
- export LLAMACPP_MODEL_FILE=MiniCPM-V-4_6-Q4_K_M.gguf
101
 
102
- python app.py
103
- ```
104
 
105
- PDF or image uploads fail with a clear error unless `LLAMACPP_VISION=1` is set.
106
 
107
- ### Local llama-server (advanced)
108
 
109
- When pip `llama-cpp-python` is too old for MiniCPM-V 4.6 vision, run a separate server:
110
 
111
- ```bash
112
- llama-server -m model.gguf --mmproj mmproj.gguf --port 8080
113
- EXTRACTOR_BACKEND=local python app.py
114
- ```
115
 
116
- See `src/extraction/local_server.py`.
117
 
118
- ## Hugging Face Space Deployment
119
 
120
- The Hugging Face Space is deployed as a **Gradio Space** with Transformers extraction on ZeroGPU.
121
 
122
- Default Space variables:
123
 
124
- ```bash
125
- EXTRACTOR_BACKEND=transformers
126
- ZEROGPU_MODEL_ID=build-small-hackathon/blood-test-minicpmv-4_6-medreason
 
 
 
 
 
 
 
127
  ```
128
 
129
- `ZEROGPU_MODEL_ID` is optional β€” the app defaults to the fine-tuned repo above. Override with `openbmb/MiniCPM-V-4.6` only for base-model experiments.
130
 
131
- Optional llama.cpp badge lane (not enabled in the default deployment):
132
 
133
- ```bash
134
- EXTRACTOR_BACKEND=llamacpp-gpu
135
- LLAMACPP_VISION=1
136
- LLAMACPP_GGUF_REPO=openbmb/MiniCPM-V-4.6-gguf
137
- LLAMACPP_MODEL_FILE=MiniCPM-V-4_6-Q4_K_M.gguf
138
- LLAMACPP_MMPROJ_FILE=mmproj-model-f16.gguf
139
- ```
140
 
141
- When a new fine-tuned checkpoint is ready, replace `ZEROGPU_MODEL_ID` (or update `DEFAULT_HF_REPO` in `src/model_paths.py`) for the primary lane and the `LLAMACPP_*` variables for the optional GGUF lane. Do not commit model files to the Space git repo.
142
 
143
- This workflow should not be changed back to Docker unless the project intentionally gives up ZeroGPU.
144
-
145
- ## Local Setup
146
 
147
- Default (Transformers vision):
148
 
149
- ```bash
150
- pip install -r requirements.txt
151
- python app.py
152
- ```
153
 
154
- Explicit backend:
155
 
156
- ```bash
157
- EXTRACTOR_BACKEND=transformers python app.py
158
- ```
159
 
160
- The Space runtime also installs `llama-cpp-python` so the optional llama.cpp lane can be enabled without code changes. On Linux x86_64 Spaces it uses the prebuilt CPU manylinux wheel:
161
-
162
- ```text
163
- https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.28/llama_cpp_python-0.3.28-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
164
- ```
165
 
166
- That avoids both the CUDA runtime mismatch that was causing the Space to abort on `libcudart.so.12` and the slow source build that was timing out on Hugging Face.
 
1
+ # Blood Test Explainer, teaching a 1.3B model to read your lab report, offline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ We are **Roman and Dimitris**, graduates of the **American College of Greece (Deree) AI Lab**, where we currently do research. We built *Blood Test Explainer* for the Build Small hackathon. You upload a photo or PDF of a blood test, and a small model running entirely on the Space reads it, pulls out the markers, values and reference ranges, and explains what each one means in plain language, grounded in a medical knowledge base.
4
 
5
+ **Our inspiration was a real problem for real people.** Almost everyone has stared at a lab report, seen a column of numbers and "H"/"L" flags, and had no idea what any of it meant. The information is right there, but it is locked in medical shorthand, and it is exactly the kind of private data you do not want to paste into a chatbot you do not control. We wanted a tool a parent or a neighbor could use on their own laptop, that reads the report, explains it honestly, and never sends their health data anywhere.
6
 
7
+ Here is the whole pipeline:
 
 
 
 
 
 
8
 
9
+ ```
10
+ PDF / image -> MiniCPM-V 4.6 (vision) -> structured JSON -> KB-grounded explanation
11
+ reads the document markers + values per-marker + patterns
12
+ ```
13
 
14
+ To make it easy to evaluate, we organized this write-up around the **six merit badges** (each one maps to a concrete engineering decision) and then the **three sponsor technologies** that made it possible.
15
 
16
+ ---
17
 
18
+ ## πŸ”Œ Off the Grid
19
 
20
+ The whole thing runs on the model in front of you. MiniCPM-V 4.6 is loaded inside the Space and does the reading there; there is no call to OpenAI, Anthropic, or any hosted inference API. For a health tool this is not a nice-to-have, it is the point: your blood test never leaves the machine it is processed on. The same design runs on a laptop with the model on local hardware, which is what "small models, local-first" is supposed to feel like.
 
 
 
21
 
22
+ ## 🎯 Well-Tuned
23
 
24
+ This is the part we are proudest of, because it started as a failure. The base MiniCPM-V 4.6 is already a strong document reader, so our first instinct was to fine-tune it on our exact extraction schema. That collapsed the model: by memorizing our narrow synthetic format it forgot how to read a real report, and field-level F1 on real reports dropped from 0.66 to 0.08.
25
 
26
+ We built a field-level evaluation (precision / recall / F1 on hand-labeled real reports) so we could measure every change honestly. Here is the whole journey:
27
 
28
+ | Iteration | What we did | Marker F1 | Recall | vs base |
29
+ |---|---|---|---|---|
30
+ | **Base MiniCPM-V 4.6** | nothing | **0.655** | 0.529 | |
31
+ | v1, schema LoRA | fit our JSON schema (4k synthetic, 2 epochs, lr 1e-4) | 0.078 | 0.059 | catastrophic |
32
+ | v2, gentler + diverse | lr 2e-5, 1 epoch, varied synthetic layouts | 0.333 | 0.265 | still worse |
33
+ | v3, + real reports | mixed in real labeled reports, oversampled | 0.417 | 0.294 | still worse |
34
+ | **medreason (100 ex)** | **fine-tune on general medical reasoning** | **0.746** | **0.647** | **+0.09** |
35
+ | medreason (4000 ex) | more reasoning data | 0.667 | 0.559 | +0.01 |
36
 
37
+ ![Fine-tuning journey: F1 across our iterations vs the base model](docs/finetune_journey.png)
 
 
 
 
 
38
 
39
+ The breakthrough was to stop teaching the model our schema and teach it general medical knowledge instead. We took a LoRA, froze the vision encoder, and fine-tuned only the language layers on a general medical-reasoning dataset (FreedomIntelligence/medical-o1-reasoning-SFT), text only, nothing about extraction. The model got *better* at extraction (F1 0.66 to 0.75, recall 0.53 to 0.65) because it became a better medical reader in general. A second surprise: 100 reasoning examples beat 4,000, so less was more. We then merged the LoRA into the base and published a single standalone model on the Hub, which is what the Space loads.
 
40
 
41
+ ![Before / after: base vs the medical-reasoning fine-tune](docs/before_after.png)
42
 
43
+ ## πŸ¦™ Llama Champion
44
 
45
+ The app ships two interchangeable backends behind one interface. The default runs the model through Transformers, and a second backend runs the same MiniCPM-V through the **llama.cpp** runtime, selectable with a single environment variable. We install the prebuilt llama.cpp wheel so the Space builds without a slow source compile, which means the model can run through the llama.cpp runtime when we point it there, while Transformers stays the default for the hosted demo.
46
 
47
+ ## 🎨 Off-Brand
 
 
 
48
 
49
+ The interface is a custom frontend, not the default Gradio look. We built a guided three-step "agent trace" (read the document, extract the values, explain the results) with custom HTML/CSS report cards, marker status styling, a workflow timeline, and embedded explanation videos next to flagged markers. The goal was for a non-technical person to follow what the app is doing at every step, instead of facing a bare form.
 
50
 
51
+ ## πŸ“‘ Sharing is Caring
52
 
53
+ We published our extraction and interpretation **traces on the Hub** as a dataset, so anyone can see exactly what the model read from each report, what it produced, and how the knowledge base turned that into an explanation. It is a small contribution back to the community building on small models, and a transparent record of how the agent behaves.
54
 
55
+ ## πŸ““ Field Notes
56
 
57
+ This article is the Field Notes entry. We wanted it to be honest about the parts that did not work (the fine-tune that made things worse) as much as the parts that did, because the most useful thing we can hand the next team is the lesson: do not fine-tune a capable base model on your own narrow output schema, and measure everything before you trust it.
 
 
 
58
 
59
+ ---
60
 
61
+ ## OpenBMB, the model at the center
62
 
63
+ MiniCPM-V 4.6 is the heart of the project. At roughly 1.3B parameters it is small enough to run offline on the Space and on a laptop, which is the whole premise, yet capable enough to read messy, real-world lab report layouts directly from an image with no separate OCR step. It is also what we fine-tuned for medical reasoning, and it is the model the app ships. Everything the user sees, extraction and explanation, comes from one OpenBMB model.
64
 
65
+ We also lean on a strict JSON contract from the model so the rest of the app is deterministic:
66
 
67
+ ```json
68
+ {
69
+ "patient": { "age": "45", "age_years": 45.0, "sex": "male" },
70
+ "tests": [
71
+ { "marker": "Hemoglobin", "value": "12.5", "unit": "g/dL",
72
+ "reference_range": "13.0 - 17.0", "status": "low",
73
+ "source_text": "Hemoglobin 12.5 g/dL 13.0-17.0 L", "confidence": 0.0 }
74
+ ],
75
+ "notes": []
76
+ }
77
  ```
78
 
79
+ ## Modal, where the fine-tuning ran
80
 
81
+ Every training run, the LoRA merge, and the before/after evaluations ran on **Modal** with an A100. The generator builds its own synthetic data on the box, the LoRA trains, the adapter merges into the base, and the merged model is pushed to the Hub, all as Modal functions. Modal also let us iterate fast through the failed runs and the recovery without managing any infrastructure, which is the only reason we found the medical-reasoning approach in time.
82
 
83
+ ## Codex (OpenAI), how we built fast
 
 
 
 
 
 
84
 
85
+ We used **Codex** as the commit and pull-request engine throughout. Two people moving quickly under a deadline meant a lot of small, reviewable PRs, and Codex handled the commit/PR mechanics so we could keep our attention on the model and the product. It is also how we kept the repository history clean while the architecture changed underneath us more than once.
86
 
87
+ ---
 
 
88
 
89
+ ## The knowledge base and grounding
90
 
91
+ Across all of this, one rule held: the model never invents medical facts. The facts live in a curated knowledge base, what each marker measures, what a high or low value is commonly associated with, and questions worth asking a doctor, and the model only phrases them. The KB grew from 31 markers to **107 markers with curated explanation videos**, plus **cross-marker patterns** (anemia picture, iron-deficiency, B12, a liver-enzyme cluster, lipid/cardiovascular risk, kidney, thyroid, glycemic) that mean more together than alone.
 
 
 
92
 
93
+ ## Limitations and safety
94
 
95
+ This is an educational tool, not a diagnosis, and every report says so. The model can misread a value, especially on noisy scans, and our labeled evaluation set is still small, so we treat the numbers as directional. It should help someone understand their results and ask better questions of a clinician, not replace one.
 
 
96
 
97
+ ---
 
 
 
 
98
 
99
+ *Built by Roman and Dimitris, American College of Greece (Deree) AI Lab. Thanks to the Build Small hackathon, Gradio, Hugging Face, OpenBMB, OpenAI, and Modal.*