MaziyarPanahi commited on
Commit
c72cb4c
·
1 Parent(s): 32dd65f
Files changed (3) hide show
  1. README.md +2 -2
  2. app.py +548 -4
  3. requirements.txt +7 -0
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Openmed Clinical Ner Demo
3
- emoji: 📉
4
  colorFrom: indigo
5
  colorTo: green
6
  sdk: gradio
 
1
  ---
2
+ title: ⚕️ Openmed Clinical NER
3
+ emoji: ⚕️
4
  colorFrom: indigo
5
  colorTo: green
6
  sdk: gradio
app.py CHANGED
@@ -1,7 +1,551 @@
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
 
 
 
 
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
1
+ """
2
+ Beautiful Medical NER Demo using OpenMed Models
3
+ A comprehensive Named Entity Recognition demo for medical professionals
4
+ featuring multiple specialized medical models with beautiful entity visualization.
5
+ """
6
+
7
  import gradio as gr
8
+ import spacy
9
+ from spacy import displacy
10
+ from transformers import pipeline
11
+ import warnings
12
+ import logging
13
+ from typing import Dict, List, Tuple
14
+ import random # Added for random color generation
15
+
16
+ # Suppress warnings for cleaner output
17
+ warnings.filterwarnings("ignore")
18
+ logging.getLogger("transformers").setLevel(logging.ERROR)
19
+
20
+ # Model configurations
21
+ MODELS = {
22
+ "Oncology Detection": {
23
+ "model_id": "OpenMed/OpenMed-NER-OncologyDetect-SuperMedical-355M",
24
+ "description": "Specialized in cancer, genetics, and oncology entities",
25
+ },
26
+ "Pharmaceutical Detection": {
27
+ "model_id": "OpenMed/OpenMed-NER-PharmaDetect-SuperClinical-434M",
28
+ "description": "Detects drugs, chemicals, and pharmaceutical entities",
29
+ },
30
+ "Disease Detection": {
31
+ "model_id": "OpenMed/OpenMed-NER-DiseaseDetect-SuperClinical-434M",
32
+ "description": "Identifies diseases, conditions, and pathologies",
33
+ },
34
+ "Genome Detection": {
35
+ "model_id": "OpenMed/OpenMed-NER-GenomeDetect-ModernClinical-395M",
36
+ "description": "Recognizes genes, proteins, and genomic entities",
37
+ },
38
+ }
39
+
40
+ # Medical text examples for each model
41
+ EXAMPLES = {
42
+ "Oncology Detection": [
43
+ "The patient presented with metastatic adenocarcinoma of the lung with mutations in EGFR and KRAS genes. Treatment with erlotinib was initiated, targeting the epidermal growth factor receptor pathway.",
44
+ "Histological examination revealed invasive ductal carcinoma with high-grade nuclear features. The tumor showed positive estrogen receptor and HER2 amplification, indicating potential for targeted therapy.",
45
+ "The oncologist recommended adjuvant chemotherapy with doxorubicin and cyclophosphamide, followed by paclitaxel, to target rapidly dividing cancer cells in the breast tissue.",
46
+ ],
47
+ "Pharmaceutical Detection": [
48
+ "The patient was prescribed metformin 500mg twice daily for diabetes management, along with lisinopril 10mg for hypertension control and atorvastatin 20mg for cholesterol reduction.",
49
+ "Administration of morphine sulfate provided effective pain relief, while ondansetron prevented chemotherapy-induced nausea. The patient also received dexamethasone as an anti-inflammatory agent.",
50
+ "The pharmacokinetic study evaluated the absorption of ibuprofen and its interaction with warfarin, monitoring plasma concentrations and potential bleeding risks.",
51
+ ],
52
+ "Disease Detection": [
53
+ "The patient was diagnosed with type 2 diabetes mellitus, hypertension, and coronary artery disease. Additional findings included diabetic nephropathy and peripheral neuropathy.",
54
+ "Clinical presentation was consistent with acute myocardial infarction complicated by cardiogenic shock. The patient also had a history of chronic obstructive pulmonary disease and atrial fibrillation.",
55
+ "Laboratory results confirmed the diagnosis of rheumatoid arthritis with elevated inflammatory markers. The patient also exhibited symptoms of Sjögren's syndrome and osteoporosis.",
56
+ ],
57
+ "Genome Detection": [
58
+ "Genetic analysis revealed mutations in the BRCA1 and BRCA2 genes, significantly increasing the risk of hereditary breast and ovarian cancer. The p53 tumor suppressor gene also showed alterations.",
59
+ "Expression profiling identified upregulation of MYC oncogene and downregulation of PTEN tumor suppressor. The mTOR signaling pathway showed significant activation in the tumor samples.",
60
+ "Whole genome sequencing detected variants in CFTR gene associated with cystic fibrosis, along with polymorphisms in CYP2D6 affecting drug metabolism and APOE influencing Alzheimer's risk.",
61
+ ],
62
+ }
63
+
64
+
65
+ class MedicalNERApp:
66
+ def __init__(self):
67
+ self.pipelines = {}
68
+ self.nlp = spacy.blank("en") # SpaCy model for visualization
69
+ self.load_models()
70
+
71
+ def load_models(self):
72
+ """Load and cache all models for better performance"""
73
+ print("🏥 Loading Medical NER Models...")
74
+
75
+ for model_name, config in MODELS.items():
76
+ print(f"Loading {model_name}...")
77
+ try:
78
+ # Set aggregation_strategy to None to get raw BIO tokens for manual grouping
79
+ ner_pipeline = pipeline(
80
+ "ner", model=config["model_id"], aggregation_strategy=None
81
+ )
82
+ self.pipelines[model_name] = ner_pipeline
83
+ print(f"✅ {model_name} loaded successfully")
84
+
85
+ except Exception as e:
86
+ print(f"❌ Error loading {model_name}: {str(e)}")
87
+ self.pipelines[model_name] = None
88
+
89
+ print("🎉 All models loaded and cached!")
90
+
91
+ def group_entities(self, ner_results: List[Dict], text: str) -> List[Dict]:
92
+ """
93
+ Groups raw BIO-tagged tokens into final entities.
94
+ """
95
+ print(f"\nDEBUG: Raw model output:")
96
+ for token in ner_results:
97
+ print(f"Token: {token['word']:20} | Label: {token['entity']:20} | Score: {token['score']:.3f}")
98
+
99
+ final_entities = []
100
+ current_entity = None
101
+
102
+ for i, token in enumerate(ner_results):
103
+ # Skip special tokens and whitespace-only tokens
104
+ if not token['word'].strip():
105
+ continue
106
+
107
+ label = token['entity']
108
+ score = token['score']
109
+
110
+ # Skip O tags
111
+ if label == 'O':
112
+ if current_entity:
113
+ print(f"DEBUG: Finalizing entity on O tag: {current_entity}")
114
+ final_entities.append(current_entity)
115
+ current_entity = None
116
+ continue
117
+
118
+ # Clean the label
119
+ clean_label = label.replace('B-', '').replace('I-', '')
120
+
121
+ # Start of new entity
122
+ if label.startswith('B-'):
123
+ # Check if this should be merged with the previous entity
124
+ # This handles cases where the model outputs consecutive B- tags for the same entity
125
+ if (current_entity and
126
+ clean_label == current_entity['label'] and
127
+ token['start'] <= current_entity['end'] + 2): # Allow small gaps
128
+
129
+ # Merge with current entity
130
+ current_entity['end'] = token['end']
131
+ current_entity['text'] = text[current_entity['start']:token['end']]
132
+ current_entity['tokens'].append(token['word'])
133
+ current_entity['score'] = (current_entity['score'] + score) / 2
134
+ print(f"DEBUG: Merged consecutive B- tag: {current_entity}")
135
+ else:
136
+ # Finalize previous and start new
137
+ if current_entity:
138
+ print(f"DEBUG: Finalizing entity on B- tag: {current_entity}")
139
+ final_entities.append(current_entity)
140
+
141
+ current_entity = {
142
+ 'label': clean_label,
143
+ 'start': token['start'],
144
+ 'end': token['end'],
145
+ 'text': text[token['start']:token['end']],
146
+ 'tokens': [token['word']],
147
+ 'score': score
148
+ }
149
+ print(f"DEBUG: Started new entity: {current_entity}")
150
+
151
+ # Inside of entity
152
+ elif label.startswith('I-'):
153
+ # If we have a current entity and labels match
154
+ if current_entity and clean_label == current_entity['label']:
155
+ current_entity['end'] = token['end']
156
+ current_entity['text'] = text[current_entity['start']:token['end']]
157
+ current_entity['tokens'].append(token['word'])
158
+ current_entity['score'] = (current_entity['score'] + score) / 2
159
+ print(f"DEBUG: Extended entity: {current_entity}")
160
+ else:
161
+ # Orphan I- tag, treat as B-
162
+ if current_entity:
163
+ print(f"DEBUG: Finalizing entity on orphan I- tag: {current_entity}")
164
+ final_entities.append(current_entity)
165
+
166
+ current_entity = {
167
+ 'label': clean_label,
168
+ 'start': token['start'],
169
+ 'end': token['end'],
170
+ 'text': text[token['start']:token['end']],
171
+ 'tokens': [token['word']],
172
+ 'score': score
173
+ }
174
+ print(f"DEBUG: Started new entity from orphan I- tag: {current_entity}")
175
+
176
+ # Add final entity if exists
177
+ if current_entity:
178
+ print(f"DEBUG: Finalizing last entity: {current_entity}")
179
+ final_entities.append(current_entity)
180
+
181
+ # Post-process: merge adjacent entities of the same type that are very close
182
+ merged_entities = []
183
+ for entity in final_entities:
184
+ if (merged_entities and
185
+ merged_entities[-1]['label'] == entity['label'] and
186
+ entity['start'] <= merged_entities[-1]['end'] + 3): # Allow small gaps
187
+
188
+ # Merge with last entity
189
+ last_entity = merged_entities[-1]
190
+ merged_entity = {
191
+ 'label': entity['label'],
192
+ 'start': last_entity['start'],
193
+ 'end': entity['end'],
194
+ 'text': text[last_entity['start']:entity['end']],
195
+ 'tokens': last_entity['tokens'] + entity['tokens'],
196
+ 'score': (last_entity['score'] + entity['score']) / 2
197
+ }
198
+ merged_entities[-1] = merged_entity
199
+ print(f"DEBUG: Post-merged entities: {merged_entity}")
200
+ else:
201
+ merged_entities.append(entity)
202
+
203
+ print(f"\nDEBUG: Final grouped entities:")
204
+ for entity in merged_entities:
205
+ print(f"Entity: {entity['text']:30} | Label: {entity['label']:20} | Score: {entity['score']:.3f}")
206
+
207
+ return merged_entities
208
+
209
+ def _finalize_entity(self, tokens: List[Dict], text: str) -> Dict:
210
+ """Helper to construct a final entity from its constituent tokens."""
211
+ label = tokens[0]['entity'].replace('B-', '').replace('I-', '')
212
+ start_char = tokens[0]['start']
213
+ end_char = tokens[-1]['end']
214
+
215
+ return {
216
+ "label": label,
217
+ "start": start_char,
218
+ "end": end_char,
219
+ "text": text[start_char:end_char],
220
+ "confidence": sum(t['score'] for t in tokens) / len(tokens),
221
+ }
222
+
223
+ def create_spacy_visualization(self, text: str, entities: List[Dict], model_name: str) -> str:
224
+ """Create spaCy displaCy visualization with dynamic colors."""
225
+ print("\nDEBUG: Creating spaCy visualization")
226
+ print(f"Input text: {text}")
227
+ print("Entities to visualize:")
228
+ for ent in entities:
229
+ print(f" {ent['text']} ({ent['label']}) [{ent['start']}:{ent['end']}]")
230
+
231
+ doc = self.nlp(text)
232
+ spacy_ents = []
233
+
234
+ for entity in entities:
235
+ try:
236
+ # Clean up the entity text (remove leading/trailing spaces)
237
+ start = entity['start']
238
+ end = entity['end']
239
+
240
+ # Strip leading spaces
241
+ while start < end and text[start].isspace():
242
+ start += 1
243
+ # Strip trailing spaces
244
+ while end > start and text[end-1].isspace():
245
+ end -= 1
246
+
247
+ # Try to create span with cleaned boundaries
248
+ span = doc.char_span(start, end, label=entity['label'])
249
+ if span is not None:
250
+ spacy_ents.append(span)
251
+ print(f"✓ Created span: '{span.text}' -> {entity['label']}")
252
+ else:
253
+ print(f"✗ Failed to create span for: '{text[start:end]}' -> {entity['label']}")
254
+ # Try original boundaries as fallback
255
+ span = doc.char_span(entity['start'], entity['end'], label=entity['label'])
256
+ if span is not None:
257
+ spacy_ents.append(span)
258
+ print(f"✓ Created span with original boundaries: '{span.text}' -> {entity['label']}")
259
+ else:
260
+ print(f"✗ Failed with original boundaries too: '{entity['text']}' -> {entity['label']}")
261
+ except Exception as e:
262
+ print(f"Error creating span for entity {entity}: {str(e)}")
263
+
264
+ # Filter out overlapping entities
265
+ spacy_ents = spacy.util.filter_spans(spacy_ents)
266
+ doc.ents = spacy_ents
267
+
268
+ print(f"\nDEBUG: Final spaCy entities:")
269
+ for ent in doc.ents:
270
+ print(f" {ent.text} ({ent.label_}) [{ent.start_char}:{ent.end_char}]")
271
+
272
+ # Define a bright, engaging color palette
273
+ color_palette = {
274
+ "DISEASE": "#FF5733", # Bright red-orange
275
+ "CHEM": "#33FF57", # Bright green
276
+ "GENE/PROTEIN": "#3357FF", # Bright blue
277
+ "Cancer": "#FF33F6", # Bright pink
278
+ "Cell": "#33FFF6", # Bright cyan
279
+ "Organ": "#F6FF33", # Bright yellow
280
+ "Tissue": "#FF8333", # Bright orange
281
+ "Simple_chemical": "#8333FF", # Bright purple
282
+ "Gene_or_gene_product": "#33FF83", # Bright mint
283
+ }
284
+
285
+ # Get unique entity types and assign colors
286
+ unique_labels = sorted(list(set(ent.label_ for ent in doc.ents)))
287
+ colors = {}
288
+ for label in unique_labels:
289
+ colors[label] = color_palette.get(label, "#" + ''.join([hex(x)[2:].zfill(2) for x in (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))]))
290
+
291
+ options = {
292
+ "ents": unique_labels,
293
+ "colors": colors,
294
+ "style": "max-width: 100%; line-height: 2.5; direction: ltr;"
295
+ }
296
+
297
+ print(f"\nDEBUG: Visualization options:")
298
+ print(f"Entity types: {unique_labels}")
299
+ print(f"Color mapping: {colors}")
300
+
301
+ return displacy.render(doc, style="ent", options=options, page=False)
302
+
303
+ def predict_entities(self, text: str, model_name: str) -> Tuple[str, str]:
304
+ """
305
+ Predict entities using a robust aggregation strategy.
306
+ """
307
+ if not text.strip():
308
+ return "<p>Please enter medical text to analyze.</p>", "No text provided"
309
+
310
+ if model_name not in self.pipelines or self.pipelines[model_name] is None:
311
+ return f"<p>❌ Model {model_name} is not available.</p>", "Model not available"
312
+
313
+ try:
314
+ print(f"\nDEBUG: Processing text with {model_name}")
315
+ print(f"Text: {text}")
316
+
317
+ # Get raw token predictions
318
+ raw_tokens = self.pipelines[model_name](text)
319
+ print(f"Got {len(raw_tokens)} raw tokens from model")
320
+
321
+ if not raw_tokens:
322
+ print("No tokens returned from model")
323
+ return "<p>No entities detected.</p>", "No entities found"
324
+
325
+ # Group raw tokens into complete entities
326
+ final_entities = self.group_entities(raw_tokens, text)
327
+ print(f"Grouped into {len(final_entities)} final entities")
328
+
329
+ if not final_entities:
330
+ print("No entities after grouping")
331
+ return "<p>No entities detected.</p>", "No entities found"
332
+
333
+ # Create visualization and summary
334
+ html_output = self.create_spacy_visualization(text, final_entities, model_name)
335
+ print(f"Generated visualization HTML ({len(html_output)} chars)")
336
+
337
+ wrapped_html = self.wrap_displacy_output(html_output, model_name, len(final_entities))
338
+ print(f"Wrapped visualization HTML ({len(wrapped_html)} chars)")
339
+
340
+ summary = self.create_summary(final_entities, model_name)
341
+ print(f"Generated summary ({len(summary)} chars)")
342
+
343
+ return wrapped_html, summary
344
+
345
+ except Exception as e:
346
+ import traceback
347
+ print(f"ERROR in predict_entities: {str(e)}")
348
+ traceback.print_exc()
349
+ error_msg = f"Error during prediction: {str(e)}"
350
+ return f"<p>❌ {error_msg}</p>", error_msg
351
+
352
+ def wrap_displacy_output(self, displacy_html: str, model_name: str, entity_count: int) -> str:
353
+ """Wrap displaCy output in a beautiful container."""
354
+ return f"""
355
+ <div style="font-family: 'Segoe UI', Arial, sans-serif;
356
+ border-radius: 10px;
357
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
358
+ overflow: hidden;">
359
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
360
+ color: white; padding: 15px; text-align: center;">
361
+ <h3 style="margin: 0; font-size: 18px;">�� {model_name}</h3>
362
+ <p style="margin: 5px 0 0 0; opacity: 0.9; font-size: 14px;">
363
+ Found {entity_count} medical entities
364
+ </p>
365
+ </div>
366
+ <div style="padding: 20px; margin: 0; line-height: 2.5;">
367
+ {displacy_html}
368
+ </div>
369
+ </div>
370
+ """
371
+
372
+ def create_summary(self, entities: List[Dict], model_name: str) -> str:
373
+ """Create a summary of detected entities."""
374
+ if not entities:
375
+ return "No entities detected."
376
+
377
+ entity_counts = {}
378
+ for entity in entities:
379
+ label = entity["label"]
380
+ if label not in entity_counts:
381
+ entity_counts[label] = []
382
+ entity_counts[label].append(entity)
383
+
384
+ summary_parts = [f"📊 **{model_name} Summary**\n"]
385
+ summary_parts.append(f"Total entities detected: **{len(entities)}**\n")
386
+
387
+ for label, ents in sorted(entity_counts.items()):
388
+ avg_confidence = sum(e["score"] for e in ents) / len(ents)
389
+ unique_texts = sorted(list(set(e["text"] for e in ents)))
390
+
391
+ summary_parts.append(
392
+ f"• **{label}**: {len(ents)} instances "
393
+ f"(avg confidence: {avg_confidence:.2f})\n"
394
+ f" Examples: {', '.join(unique_texts[:3])}"
395
+ f"{'...' if len(unique_texts) > 3 else ''}\n"
396
+ )
397
+
398
+ return "\n".join(summary_parts)
399
+
400
+
401
+ # Initialize the app
402
+ print("🚀 Initializing Medical NER Application...")
403
+ ner_app = MedicalNERApp()
404
+
405
+
406
+ def predict_wrapper(text: str, model_name: str):
407
+ """Wrapper function for Gradio interface"""
408
+ html_output, summary = ner_app.predict_entities(text, model_name)
409
+ return html_output, summary
410
+
411
+
412
+ def load_example(model_name: str, example_idx: int):
413
+ """Load example text for the selected model"""
414
+ if model_name in EXAMPLES and 0 <= example_idx < len(EXAMPLES[model_name]):
415
+ return EXAMPLES[model_name][example_idx]
416
+ return ""
417
+
418
+
419
+ # Create Gradio interface
420
+ with gr.Blocks(
421
+ title="🏥 Medical NER Expert",
422
+ theme=gr.themes.Soft(),
423
+ css="""
424
+ .gradio-container {
425
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
426
+ }
427
+ .main-header {
428
+ text-align: center;
429
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
430
+ color: white;
431
+ padding: 2rem;
432
+ border-radius: 15px;
433
+ margin-bottom: 2rem;
434
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
435
+ }
436
+ .model-info {
437
+ padding: 1rem;
438
+ border-radius: 10px;
439
+ border-left: 4px solid #667eea;
440
+ margin: 1rem 0;
441
+ }
442
+ """,
443
+ ) as demo:
444
+
445
+ # Header
446
+ gr.HTML(
447
+ """
448
+ <div class="main-header">
449
+ <h1>🏥 Medical NER Expert</h1>
450
+ <p>Advanced Named Entity Recognition for Medical Professionals</p>
451
+ <p>Powered by OpenMed's specialized medical AI models with spaCy displaCy visualization</p>
452
+ </div>
453
+ """
454
+ )
455
+
456
+ with gr.Row():
457
+ with gr.Column(scale=2):
458
+ # Model selection
459
+ model_dropdown = gr.Dropdown(
460
+ choices=list(MODELS.keys()),
461
+ value="Oncology Detection",
462
+ label="🔬 Select Medical NER Model",
463
+ info="Choose the specialized model for your analysis",
464
+ )
465
+
466
+ # Model info display
467
+ model_info = gr.HTML(
468
+ value=f"""
469
+ <div class="model-info">
470
+ <strong>Oncology Detection</strong><br>
471
+ {MODELS["Oncology Detection"]["description"]}
472
+ </div>
473
+ """
474
+ )
475
+
476
+ # Text input
477
+ text_input = gr.Textbox(
478
+ lines=8,
479
+ placeholder="Enter medical text here for entity recognition...",
480
+ label="📝 Medical Text Input",
481
+ value=EXAMPLES["Oncology Detection"][0],
482
+ )
483
+
484
+ # Example buttons
485
+ with gr.Row():
486
+ example_buttons = []
487
+ for i in range(3):
488
+ btn = gr.Button(f"Example {i+1}", size="sm", variant="secondary")
489
+ example_buttons.append(btn)
490
+
491
+ # Analyze button
492
+ analyze_btn = gr.Button("🔍 Analyze Text", variant="primary", size="lg")
493
+
494
+ with gr.Column(scale=3):
495
+ # Results
496
+ results_html = gr.HTML(
497
+ label="🎯 Entity Recognition Results",
498
+ value="<p>Select a model and enter text to see entity recognition results.</p>",
499
+ )
500
+
501
+ # Summary
502
+ summary_output = gr.Markdown(
503
+ value="Analysis summary will appear here...",
504
+ label="📊 Analysis Summary",
505
+ )
506
+
507
+ # Update model info when model changes
508
+ def update_model_info(model_name):
509
+ if model_name in MODELS:
510
+ return f"""
511
+ <div class="model-info">
512
+ <strong>{model_name}</strong><br>
513
+ {MODELS[model_name]["description"]}<br>
514
+ <small>Model: {MODELS[model_name]["model_id"]}</small>
515
+ </div>
516
+ """
517
+ return ""
518
+
519
+ model_dropdown.change(
520
+ update_model_info, inputs=[model_dropdown], outputs=[model_info]
521
+ )
522
+
523
+ # Example button handlers
524
+ for i, btn in enumerate(example_buttons):
525
+ btn.click(
526
+ lambda model_name, idx=i: load_example(model_name, idx),
527
+ inputs=[model_dropdown],
528
+ outputs=[text_input],
529
+ )
530
+
531
+ # Main analysis function
532
+ analyze_btn.click(
533
+ predict_wrapper,
534
+ inputs=[text_input, model_dropdown],
535
+ outputs=[results_html, summary_output],
536
+ )
537
 
538
+ # Auto-update when model changes (load first example)
539
+ model_dropdown.change(
540
+ lambda model_name: load_example(model_name, 0),
541
+ inputs=[model_dropdown],
542
+ outputs=[text_input],
543
+ )
544
 
545
+ if __name__ == "__main__":
546
+ demo.launch(
547
+ share=False, # Not needed on Spaces
548
+ show_error=True,
549
+ server_name="0.0.0.0",
550
+ server_port=7860,
551
+ )
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio
2
+ transformers
3
+ torch
4
+ tokenizers
5
+ numpy
6
+ accelerate
7
+ safetensors