multimodalart HF Staff commited on
Commit
f0a7ae8
·
verified ·
1 Parent(s): 1cd3f1e

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +18 -6
  2. app.py +191 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,13 +1,25 @@
1
  ---
2
- title: Cliniguard Laboratory Ner
3
- emoji: 🐨
4
- colorFrom: green
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: CliniGuard Laboratory NER
3
+ emoji: 🧪
4
+ colorFrom: red
5
  colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.20.0
 
8
  app_file: app.py
9
+ short_description: Clinical lab result entity extraction with Bio_ClinicalBERT
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 30m
12
  ---
13
 
14
+ # CliniGuard Laboratory NER
15
+
16
+ Interactive demo of [genzeonplatform/cliniguard-laboratory-ner](https://huggingface.co/genzeonplatform/cliniguard-laboratory-ner),
17
+ a Bio_ClinicalBERT token-classification model that extracts **10 laboratory result entity categories**
18
+ (test names, values, units, reference ranges, abnormality flags, specimen types, dates, LOINC codes,
19
+ panels, and status) from unstructured clinical text.
20
+
21
+ Paste a lab report excerpt, progress note, or discharge summary, and the model highlights each
22
+ entity in place and lists it in a structured table alongside its confidence score.
23
+
24
+ Built by Genzeon Platforms for healthcare AI pipelines — lab result extraction, critical value
25
+ detection, lab trending, and LOINC mapping support.
app.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CliniGuard Laboratory NER — interactive demo for clinical laboratory result entity extraction.
2
+
3
+ Built on Bio_ClinicalBERT fine-tuned for token classification (BIO tagging, 21 labels,
4
+ 10 laboratory result entity categories). Runs on ZeroGPU.
5
+ """
6
+
7
+ import spaces # MUST be first — before torch / transformers
8
+ import torch
9
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
10
+
11
+ import gradio as gr
12
+
13
+ MODEL_ID = "genzeonplatform/cliniguard-laboratory-ner"
14
+
15
+ # Entity type -> (legend label, description, hex color). Colors are picked to be
16
+ # visually distinct across the 10 lab categories when rendered by HighlightedText.
17
+ ENTITY_META = {
18
+ "LAB_TEST_NAME": ("Test name", "#1f77b4"),
19
+ "LAB_VALUE": ("Value", "#ff7f0e"),
20
+ "LAB_UNIT": ("Unit", "#2ca02c"),
21
+ "REFERENCE_RANGE": ("Reference range", "#d62728"),
22
+ "ABNORMALITY_FLAG": ("Abnormality flag", "#9467bd"),
23
+ "SPECIMEN_TYPE": ("Specimen", "#8c564b"),
24
+ "LAB_DATE": ("Date", "#e377c2"),
25
+ "LOINC_CODE": ("LOINC code", "#17becf"),
26
+ "TEST_PANEL": ("Panel", "#bcbd22"),
27
+ "LAB_STATUS": ("Status", "#7f7f7f"),
28
+ }
29
+
30
+ DESCRIPTIONS = {
31
+ "LAB_TEST_NAME": "Name of the laboratory test (e.g. hemoglobin, glucose, troponin I)",
32
+ "LAB_VALUE": "Numeric or qualitative result (e.g. 7.2, 145, positive, trace)",
33
+ "LAB_UNIT": "Unit of measurement (e.g. mg/dL, g/dL, mEq/L, x10^3/uL)",
34
+ "REFERENCE_RANGE": "Normal reference range (e.g. 12.0-17.5, < 200, 3.5-5.0)",
35
+ "ABNORMALITY_FLAG": "Abnormality indicator (e.g. H, L, Critical High, Normal)",
36
+ "SPECIMEN_TYPE": "Type of specimen (e.g. blood, serum, urine, CSF)",
37
+ "LAB_DATE": "Collection date/time (e.g. 03/15/2024, this morning, hospital day 3)",
38
+ "LOINC_CODE": "LOINC identifier code (e.g. 718-7, 2345-7, 2160-0)",
39
+ "TEST_PANEL": "Panel/order set name (e.g. CBC, BMP, CMP, lipid panel)",
40
+ "LAB_STATUS": "Result status (e.g. final, preliminary, pending)",
41
+ }
42
+
43
+ # Load model at module scope, eager .to("cuda") — ZeroGPU intercepts & packs weights.
44
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
45
+ model = AutoModelForTokenClassification.from_pretrained(MODEL_ID).to("cuda")
46
+ nlp = pipeline(
47
+ "token-classification",
48
+ model=model,
49
+ tokenizer=tokenizer,
50
+ aggregation_strategy="simple",
51
+ device=0, # pipeline follows model device
52
+ )
53
+
54
+
55
+ def _entities_to_highlight(text: str, entities):
56
+ """Convert HF pipeline entities into the (text, label) list HighlightedText wants,
57
+ filling the gaps with plain text spans."""
58
+ if not entities:
59
+ return [(text, None)]
60
+
61
+ parts = []
62
+ cursor = 0
63
+ for ent in entities:
64
+ start = ent["start"]
65
+ end = ent["end"]
66
+ label = ent["entity_group"]
67
+ if start > cursor:
68
+ parts.append((text[cursor:start], None))
69
+ parts.append((text[start:end], label))
70
+ cursor = end
71
+ if cursor < len(text):
72
+ parts.append((text[cursor:], None))
73
+ return parts
74
+
75
+
76
+ @spaces.GPU(duration=30)
77
+ def extract_entities(text: str):
78
+ """Extract laboratory result entities (test names, values, units, ranges, flags, …) from clinical text.
79
+
80
+ Args:
81
+ text: clinical text containing laboratory results (lab reports, progress notes, discharge summaries).
82
+ """
83
+ text = (text or "").strip()
84
+ if not text:
85
+ return [], "Please enter some clinical text to analyze."
86
+
87
+ entities = nlp(text)
88
+
89
+ # Summary table for the side panel
90
+ rows = []
91
+ for ent in entities:
92
+ label = ent["entity_group"]
93
+ legend_label = ENTITY_META.get(label, (label, None))[0]
94
+ rows.append([legend_label, ent["word"], f"{ent['score']:.3f}",
95
+ ent["start"], ent["end"]])
96
+
97
+ highlight = _entities_to_highlight(text, entities)
98
+ n = len(rows)
99
+ summary = f"Found **{n}** entit{'y' if n == 1 else 'ies'} across {len({r[0] for r in rows}) or 0} categor{'y' if n == 1 else 'ies'}." if n else "No entities found."
100
+ return highlight, rows, summary
101
+
102
+
103
+ # Build the legend options for HighlightedText from the model's labels.
104
+ # HighlightedText accepts `combine_entities=True` to merge adjacent same-label spans.
105
+ HIGHLIGHT_LABELS = {label: meta[1] for label, meta in ENTITY_META.items()}
106
+
107
+ CSS = """
108
+ #col-container { max-width: 1100px; margin: 0 auto; }
109
+ .dark .gradio-container { color: var(--body-text-color); }
110
+ .legend-chip {
111
+ display: inline-block; padding: 2px 8px; margin: 2px;
112
+ border-radius: 6px; color: #fff; font-size: 0.85em; font-weight: 600;
113
+ }
114
+ """
115
+
116
+ EXAMPLES = [
117
+ ["Lab Results: Hemoglobin: 7.2 g/dL (Reference: 12.0-17.5) [Critical Low]. Specimen: blood. Status: final. CBC panel. LOINC: 718-7."],
118
+ ["Sodium 145 mEq/L (ref 135-145) [H]. Potassium 3.2 mEq/L (ref 3.5-5.0) [L]. BMP panel drawn this morning, blood specimen, preliminary."],
119
+ ["Glucose: 210 mg/dL (ref 70-99) [Critical High]. HbA1c 9.8% (ref < 5.7). Lipid panel: LDL 160 mg/dL [H]. Status: final. LOINC: 2345-7."],
120
+ ["Troponin I 0.05 ng/mL (ref < 0.04) [H]. CK-MB 8.2 ng/mL (ref < 6.3) [H]. Cardiac panel, serum specimen, hospital day 3, pending."],
121
+ ]
122
+
123
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="CliniGuard Laboratory NER") as demo:
124
+ gr.Markdown(
125
+ "# 🧪 CliniGuard Laboratory NER\n"
126
+ "Extract laboratory result entities — test names, values, units, reference ranges, "
127
+ "abnormality flags, specimen types, dates, LOINC codes, panels, and status — from "
128
+ "unstructured clinical text. Built on **Bio_ClinicalBERT** fine-tuned by Genzeon Platforms "
129
+ "for token classification across 10 laboratory entity categories."
130
+ )
131
+
132
+ legend_html = "<div>" + "".join(
133
+ f'<span class="legend-chip" style="background:{color}">{label}</span>'
134
+ for label, (_, color) in ENTITY_META.items()
135
+ ) + "</div>"
136
+ gr.HTML(legend_html)
137
+
138
+ with gr.Row():
139
+ text_in = gr.Textbox(
140
+ label="Clinical text",
141
+ placeholder="Paste clinical text containing laboratory results…",
142
+ lines=8,
143
+ scale=3,
144
+ )
145
+ run = gr.Button("Extract entities", variant="primary", scale=1)
146
+
147
+ highlight_out = gr.HighlightedText(
148
+ label="Highlighted entities",
149
+ combine_entities=True,
150
+ show_legend=True,
151
+ color_map={label: meta[1] for label, meta in ENTITY_META.items()},
152
+ )
153
+
154
+ summary_md = gr.Markdown()
155
+
156
+ table_out = gr.Dataframe(
157
+ headers=["Category", "Entity", "Score", "Start", "End"],
158
+ label="Extracted entities",
159
+ interactive=False,
160
+ wrap=True,
161
+ )
162
+
163
+ run.click(
164
+ fn=extract_entities,
165
+ inputs=text_in,
166
+ outputs=[highlight_out, table_out, summary_md],
167
+ api_name="extract_entities",
168
+ )
169
+ text_in.submit(
170
+ fn=extract_entities,
171
+ inputs=text_in,
172
+ outputs=[highlight_out, table_out, summary_md],
173
+ api_name="extract_entities_submit",
174
+ )
175
+
176
+ gr.Examples(
177
+ examples=EXAMPLES,
178
+ inputs=text_in,
179
+ outputs=[highlight_out, table_out, summary_md],
180
+ fn=extract_entities,
181
+ cache_examples=True,
182
+ cache_mode="lazy",
183
+ )
184
+
185
+ with gr.Accordion("Entity types", open=False):
186
+ gr.Markdown("\n".join(
187
+ f"- **{label}** — {DESCRIPTIONS[label]}" for label in ENTITY_META
188
+ ))
189
+
190
+ if __name__ == "__main__":
191
+ demo.launch(mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ transformers
2
+ torch