Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- README.md +30 -7
- app.py +186 -0
- requirements.txt +2 -0
README.md
CHANGED
|
@@ -1,13 +1,36 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CliniGuard Diagnosis ICD NER
|
| 3 |
+
emoji: 🏥
|
| 4 |
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.15.1
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: PubMedBERT clinical diagnosis entity extraction NER
|
| 10 |
+
python_version: "3.12"
|
| 11 |
+
startup_duration_timeout: 30m
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# CliniGuard Diagnosis ICD NER
|
| 15 |
+
|
| 16 |
+
Interactive demo for [genzeonplatform/cliniguard-diagnosis-icd-ner](https://huggingface.co/genzeonplatform/cliniguard-diagnosis-icd-ner),
|
| 17 |
+
a transformer-based clinical Named Entity Recognition model built on **PubMedBERT** (`BertForTokenClassification`)
|
| 18 |
+
for automated extraction of diagnoses, conditions, and coding-support entities from unstructured clinical text.
|
| 19 |
+
|
| 20 |
+
The model recognizes **9 diagnosis and coding entity types** (19 BIO labels):
|
| 21 |
+
|
| 22 |
+
| Entity | Description |
|
| 23 |
+
|--------|-------------|
|
| 24 |
+
| `PRIMARY_DIAGNOSIS` | Principal reason for encounter |
|
| 25 |
+
| `SECONDARY_DIAGNOSIS` | Additional diagnoses |
|
| 26 |
+
| `DIFFERENTIAL_DIAGNOSIS` | Diagnoses under consideration |
|
| 27 |
+
| `COMORBIDITY` | Co-existing conditions |
|
| 28 |
+
| `COMPLICATION` | Hospital-acquired or treatment complications |
|
| 29 |
+
| `CHRONIC_CONDITION` | Long-term conditions |
|
| 30 |
+
| `ACUTE_CONDITION` | Acute episodes or events |
|
| 31 |
+
| `DIAGNOSIS_STATUS` | Current clinical status (active, resolved, improving) |
|
| 32 |
+
| `DIAGNOSIS_DATE` | Date of diagnosis or onset |
|
| 33 |
+
|
| 34 |
+
Enter clinical text (discharge summaries, progress notes, clinical narratives) and the model will
|
| 35 |
+
highlight and structure the recognized diagnosis entities using the standard HuggingFace
|
| 36 |
+
`token-classification` pipeline with `aggregation_strategy="simple"`.
|
app.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import spaces # MUST be first — ZeroGPU patches torch.cuda before transformers imports torch
|
| 2 |
+
import torch
|
| 3 |
+
import gradio as gr
|
| 4 |
+
import json
|
| 5 |
+
from transformers import pipeline as hf_pipeline
|
| 6 |
+
|
| 7 |
+
MODEL_ID = "genzeonplatform/cliniguard-diagnosis-icd-ner"
|
| 8 |
+
|
| 9 |
+
# Entity type -> human-readable description (9 diagnosis/coding categories)
|
| 10 |
+
ENTITY_INFO = {
|
| 11 |
+
"PRIMARY_DIAGNOSIS": "Principal reason for encounter",
|
| 12 |
+
"SECONDARY_DIAGNOSIS": "Additional diagnoses",
|
| 13 |
+
"DIFFERENTIAL_DIAGNOSIS": "Diagnoses under consideration",
|
| 14 |
+
"COMORBIDITY": "Co-existing conditions",
|
| 15 |
+
"COMPLICATION": "Hospital-acquired or treatment complications",
|
| 16 |
+
"CHRONIC_CONDITION": "Long-term conditions",
|
| 17 |
+
"ACUTE_CONDITION": "Acute episodes or events",
|
| 18 |
+
"DIAGNOSIS_STATUS": "Current clinical status (active, resolved, improving)",
|
| 19 |
+
"DIAGNOSIS_DATE": "Date of diagnosis or onset",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
# Color map for HighlightedText (9 distinct colors)
|
| 23 |
+
COLOR_MAP = {
|
| 24 |
+
"PRIMARY_DIAGNOSIS": "#ef4444",
|
| 25 |
+
"SECONDARY_DIAGNOSIS": "#f97316",
|
| 26 |
+
"DIFFERENTIAL_DIAGNOSIS": "#eab308",
|
| 27 |
+
"COMORBIDITY": "#22c55e",
|
| 28 |
+
"COMPLICATION": "#3b82f6",
|
| 29 |
+
"CHRONIC_CONDITION": "#a855f7",
|
| 30 |
+
"ACUTE_CONDITION": "#ec4899",
|
| 31 |
+
"DIAGNOSIS_STATUS": "#14b8a6",
|
| 32 |
+
"DIAGNOSIS_DATE": "#6b7280",
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# Load the token-classification pipeline at module scope.
|
| 36 |
+
# aggregation_strategy="simple" merges B-/I- subword tokens into full entity spans,
|
| 37 |
+
# exactly as documented in the model card.
|
| 38 |
+
nlp = hf_pipeline(
|
| 39 |
+
"token-classification",
|
| 40 |
+
model=MODEL_ID,
|
| 41 |
+
aggregation_strategy="simple",
|
| 42 |
+
)
|
| 43 |
+
# Move the underlying model to CUDA eagerly (ZeroGPU hijack intercepts this).
|
| 44 |
+
nlp.model.to("cuda")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@spaces.GPU(duration=60)
|
| 48 |
+
def extract_entities(text: str):
|
| 49 |
+
"""Extract diagnosis entities from unstructured clinical text.
|
| 50 |
+
|
| 51 |
+
Recognizes 9 diagnosis/coding entity types: PRIMARY_DIAGNOSIS,
|
| 52 |
+
SECONDARY_DIAGNOSIS, DIFFERENTIAL_DIAGNOSIS, COMORBIDITY, COMPLICATION,
|
| 53 |
+
CHRONIC_CONDITION, ACUTE_CONDITION, DIAGNOSIS_STATUS, and DIAGNOSIS_DATE.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
text: Clinical text to analyze (discharge summaries, progress notes, etc.)
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
A tuple of (highlighted_text, structured_json, entity_summary_table).
|
| 60 |
+
"""
|
| 61 |
+
# Run inference; pipeline handles tokenization + aggregation
|
| 62 |
+
raw_entities = nlp(text)
|
| 63 |
+
|
| 64 |
+
# Build HighlightedText format: {"text": ..., "entities": [{"start","end","entity"}]}
|
| 65 |
+
entities = [
|
| 66 |
+
{
|
| 67 |
+
"start": ent["start"],
|
| 68 |
+
"end": ent["end"],
|
| 69 |
+
"entity": ent["entity_group"],
|
| 70 |
+
}
|
| 71 |
+
for ent in raw_entities
|
| 72 |
+
]
|
| 73 |
+
highlighted = {"text": text, "entities": entities}
|
| 74 |
+
|
| 75 |
+
# Build structured JSON output
|
| 76 |
+
structured = [
|
| 77 |
+
{
|
| 78 |
+
"text": ent["word"],
|
| 79 |
+
"type": ent["entity_group"],
|
| 80 |
+
"description": ENTITY_INFO.get(ent["entity_group"], ""),
|
| 81 |
+
"score": round(float(ent["score"]), 4),
|
| 82 |
+
"start": ent["start"],
|
| 83 |
+
"end": ent["end"],
|
| 84 |
+
}
|
| 85 |
+
for ent in raw_entities
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
# Build entity summary table (type, count, examples, description)
|
| 89 |
+
summary = {}
|
| 90 |
+
for ent in raw_entities:
|
| 91 |
+
label = ent["entity_group"]
|
| 92 |
+
if label not in summary:
|
| 93 |
+
summary[label] = []
|
| 94 |
+
summary[label].append(ent["word"])
|
| 95 |
+
|
| 96 |
+
table_data = []
|
| 97 |
+
for label, mentions in sorted(summary.items()):
|
| 98 |
+
table_data.append([
|
| 99 |
+
label,
|
| 100 |
+
len(mentions),
|
| 101 |
+
", ".join(mentions[:5]) + ("..." if len(mentions) > 5 else ""),
|
| 102 |
+
ENTITY_INFO.get(label, ""),
|
| 103 |
+
])
|
| 104 |
+
|
| 105 |
+
return highlighted, json.dumps(structured, indent=2), table_data
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# --- Gradio UI ---
|
| 109 |
+
|
| 110 |
+
CSS = """
|
| 111 |
+
#col-container { max-width: 1100px; margin: 0 auto; }
|
| 112 |
+
.dark .gradio-container { color: var(--body-text-color); }
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
EXAMPLES = [
|
| 116 |
+
["Discharge Diagnosis: Primary: acute myocardial infarction. "
|
| 117 |
+
"Secondary: type 2 diabetes mellitus, essential hypertension. "
|
| 118 |
+
"Status: improving. Complication: acute kidney injury."],
|
| 119 |
+
["Patient admitted with community-acquired pneumonia. "
|
| 120 |
+
"PMH: COPD, coronary artery disease, hyperlipidemia. "
|
| 121 |
+
"Differential: pulmonary embolism vs pneumonia. Status: suspected. Date: 03/15/2024."],
|
| 122 |
+
["Assessment: sepsis is worsening with acute respiratory failure. "
|
| 123 |
+
"Chronic conditions include type 2 diabetes and chronic kidney disease stage 3. "
|
| 124 |
+
"Comorbidity: obesity. Complication: hospital-acquired pneumonia."],
|
| 125 |
+
["Principal Dx: congestive heart failure with acute respiratory failure. "
|
| 126 |
+
"Secondary: atrial fibrillation, chronic kidney disease. "
|
| 127 |
+
"Status: active. Date of onset: 01/20/2024."],
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
|
| 131 |
+
gr.Markdown(
|
| 132 |
+
"""
|
| 133 |
+
# 🏥 CliniGuard Diagnosis ICD NER
|
| 134 |
+
|
| 135 |
+
Extract diagnosis entities — primary/secondary/differential diagnoses, comorbidities,
|
| 136 |
+
complications, chronic/acute conditions, status, and dates — from unstructured clinical text
|
| 137 |
+
using a **PubMedBERT**-based token-classification model
|
| 138 |
+
([genzeonplatform/cliniguard-diagnosis-icd-ner](https://huggingface.co/genzeonplatform/cliniguard-diagnosis-icd-ner)).
|
| 139 |
+
"""
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
with gr.Column(elem_id="col-container"):
|
| 143 |
+
with gr.Row():
|
| 144 |
+
input_text = gr.Textbox(
|
| 145 |
+
label="Clinical Text",
|
| 146 |
+
placeholder="Enter clinical text (discharge summaries, progress notes, clinical narratives)...",
|
| 147 |
+
lines=8,
|
| 148 |
+
scale=4,
|
| 149 |
+
)
|
| 150 |
+
run_btn = gr.Button("Extract Entities", variant="primary", scale=1)
|
| 151 |
+
|
| 152 |
+
highlighted_output = gr.HighlightedText(
|
| 153 |
+
label="Annotated Clinical Text",
|
| 154 |
+
combine_adjacent=True,
|
| 155 |
+
show_legend=True,
|
| 156 |
+
color_map=COLOR_MAP,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
with gr.Accordion("Structured Output", open=False):
|
| 160 |
+
json_output = gr.JSON(label="Extracted Entities (JSON)")
|
| 161 |
+
|
| 162 |
+
entity_table = gr.Dataframe(
|
| 163 |
+
headers=["Entity Type", "Count", "Examples", "Description"],
|
| 164 |
+
label="Entity Summary",
|
| 165 |
+
interactive=False,
|
| 166 |
+
wrap=True,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
gr.Examples(
|
| 170 |
+
examples=EXAMPLES,
|
| 171 |
+
inputs=[input_text],
|
| 172 |
+
outputs=[highlighted_output, json_output, entity_table],
|
| 173 |
+
fn=extract_entities,
|
| 174 |
+
cache_examples=True,
|
| 175 |
+
cache_mode="lazy",
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
run_btn.click(
|
| 179 |
+
fn=extract_entities,
|
| 180 |
+
inputs=[input_text],
|
| 181 |
+
outputs=[highlighted_output, json_output, entity_table],
|
| 182 |
+
api_name="extract_entities",
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
if __name__ == "__main__":
|
| 186 |
+
demo.launch(mcp_server=True)
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers
|
| 2 |
+
torch
|