hkai20000 commited on
Commit
7c63ce1
·
verified ·
1 Parent(s): 9d01bfd

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +139 -15
main.py CHANGED
@@ -8,10 +8,20 @@ import cv2
8
  import numpy as np
9
  from PIL import Image
10
  import io
11
- from typing import Dict, Any, Optional
 
 
12
 
13
  app = FastAPI(title="ScanAssured OCR & NER API")
14
 
 
 
 
 
 
 
 
 
15
  # Enable CORS for Flutter app
16
  app.add_middleware(
17
  CORSMiddleware,
@@ -192,9 +202,6 @@ def extract_text_structured(result) -> str:
192
  all_words = []
193
 
194
  for page in result.pages:
195
- page_height = page.dimensions[0]
196
- page_width = page.dimensions[1]
197
-
198
  for block in page.blocks:
199
  for line in block.lines:
200
  line_text = ""
@@ -202,7 +209,6 @@ def extract_text_structured(result) -> str:
202
 
203
  for word in line.words:
204
  line_text += word.value + " "
205
- # Get vertical position (y coordinate)
206
  min_y = min(min_y, word.geometry[0][1])
207
 
208
  if line_text.strip():
@@ -212,10 +218,8 @@ def extract_text_structured(result) -> str:
212
  'x': line.geometry[0][0] if hasattr(line, 'geometry') else 0
213
  })
214
 
215
- # Sort by vertical position (top to bottom), then horizontal (left to right)
216
- all_words.sort(key=lambda w: (round(w['y'] * 20) / 20, w['x'])) # Group similar y positions
217
 
218
- # Join with newlines for lines at different vertical positions
219
  result_text = ""
220
  prev_y = -1
221
  for word_info in all_words:
@@ -227,6 +231,106 @@ def extract_text_structured(result) -> str:
227
 
228
  return result_text.strip()
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  # --- FastAPI Routes ---
231
 
232
  @app.get("/")
@@ -316,32 +420,52 @@ async def process_image(
316
  doc = DocumentFile.from_images([img_bytes])
317
  result = ocr_predictor_instance(doc)
318
 
319
- # Use structured extraction for better layout preservation
 
 
 
320
  structured_text = extract_text_structured(result)
321
  cleaned_text = basic_cleanup(structured_text)
 
322
 
323
  print(f"OCR Structured Text:\n{structured_text[:500]}...")
 
324
 
325
  # Perform NER on cleaned text
326
  print("Running NER...")
327
  entities = ner_pipeline(cleaned_text)
328
 
329
- # Filter and structure entities
330
  structured_entities = []
331
  for entity in entities:
332
- if entity.get('score', 0.0) > 0.6:
333
  structured_entities.append({
334
  'entity_group': entity['entity_group'],
335
  'score': float(entity['score']),
336
  'word': entity['word'].strip(),
337
  })
338
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  return {
340
- "structured_text": structured_text, # Preserves layout (newlines, spacing)
341
- "cleaned_text": cleaned_text, # Single line for NER
342
- "medical_entities": structured_entities,
 
343
  "model_id": NER_MODELS[ner_model_id]["name"],
344
- "ocr_model": f"{det_arch} + {reco_arch}"
 
 
345
  }
346
 
347
  except Exception as e:
 
8
  import numpy as np
9
  from PIL import Image
10
  import io
11
+ import json
12
+ import os
13
+ from typing import Dict, Any, Optional, List
14
 
15
  app = FastAPI(title="ScanAssured OCR & NER API")
16
 
17
+ # --- DRUG INTERACTIONS DATABASE ---
18
+ DRUG_INTERACTIONS = {}
19
+ interactions_path = os.path.join(os.path.dirname(__file__), 'interactions_data.json')
20
+ if os.path.exists(interactions_path):
21
+ with open(interactions_path, 'r') as f:
22
+ DRUG_INTERACTIONS = json.load(f)
23
+ print(f"Loaded {len(DRUG_INTERACTIONS)} drug interaction entries")
24
+
25
  # Enable CORS for Flutter app
26
  app.add_middleware(
27
  CORSMiddleware,
 
202
  all_words = []
203
 
204
  for page in result.pages:
 
 
 
205
  for block in page.blocks:
206
  for line in block.lines:
207
  line_text = ""
 
209
 
210
  for word in line.words:
211
  line_text += word.value + " "
 
212
  min_y = min(min_y, word.geometry[0][1])
213
 
214
  if line_text.strip():
 
218
  'x': line.geometry[0][0] if hasattr(line, 'geometry') else 0
219
  })
220
 
221
+ all_words.sort(key=lambda w: (round(w['y'] * 20) / 20, w['x']))
 
222
 
 
223
  result_text = ""
224
  prev_y = -1
225
  for word_info in all_words:
 
231
 
232
  return result_text.strip()
233
 
234
+ def extract_words_with_boxes(result) -> list:
235
+ """
236
+ Extract all words with their bounding boxes from docTR result.
237
+ Returns list of {word, bbox} where bbox is [[x0,y0], [x1,y1]] normalized 0-1.
238
+ """
239
+ words_with_boxes = []
240
+
241
+ for page in result.pages:
242
+ for block in page.blocks:
243
+ for line in block.lines:
244
+ for word in line.words:
245
+ # geometry is ((x0, y0), (x1, y1)) normalized
246
+ bbox = [
247
+ [word.geometry[0][0], word.geometry[0][1]],
248
+ [word.geometry[1][0], word.geometry[1][1]]
249
+ ]
250
+ words_with_boxes.append({
251
+ 'word': word.value,
252
+ 'bbox': bbox
253
+ })
254
+
255
+ return words_with_boxes
256
+
257
+ def check_drug_interactions(detected_drugs: List[str]) -> List[Dict]:
258
+ """
259
+ Check for known interactions between detected drugs.
260
+ Returns list of interaction warnings.
261
+ """
262
+ interactions = []
263
+ drugs_lower = [d.lower().strip() for d in detected_drugs]
264
+
265
+ # Check each pair of drugs
266
+ for i, drug1 in enumerate(drugs_lower):
267
+ for drug2 in drugs_lower[i+1:]:
268
+ # Check if drug1 interacts with drug2
269
+ if drug1 in DRUG_INTERACTIONS:
270
+ if drug2 in DRUG_INTERACTIONS[drug1]:
271
+ interaction = DRUG_INTERACTIONS[drug1][drug2]
272
+ interactions.append({
273
+ 'drug1': detected_drugs[i],
274
+ 'drug2': detected_drugs[drugs_lower.index(drug2)],
275
+ 'severity': interaction.get('severity', 'info'),
276
+ 'description': interaction.get('description', ''),
277
+ 'recommendation': interaction.get('recommendation'),
278
+ })
279
+ # Check reverse (drug2 interacts with drug1)
280
+ elif drug2 in DRUG_INTERACTIONS:
281
+ if drug1 in DRUG_INTERACTIONS[drug2]:
282
+ interaction = DRUG_INTERACTIONS[drug2][drug1]
283
+ interactions.append({
284
+ 'drug1': detected_drugs[drugs_lower.index(drug2)],
285
+ 'drug2': detected_drugs[i],
286
+ 'severity': interaction.get('severity', 'info'),
287
+ 'description': interaction.get('description', ''),
288
+ 'recommendation': interaction.get('recommendation'),
289
+ })
290
+
291
+ return interactions
292
+
293
+ def map_entities_to_boxes(entities: list, words_with_boxes: list, cleaned_text: str) -> list:
294
+ """
295
+ Map NER entities back to word bounding boxes.
296
+ Uses fuzzy matching to find entity words in OCR words.
297
+ """
298
+ entities_with_boxes = []
299
+
300
+ for entity in entities:
301
+ entity_word = entity['word'].lower().strip()
302
+ entity_parts = entity_word.split()
303
+
304
+ # Find matching word(s) in OCR output
305
+ matched_boxes = []
306
+ for word_info in words_with_boxes:
307
+ ocr_word = word_info['word'].lower().strip()
308
+ # Check if OCR word matches any part of entity
309
+ for part in entity_parts:
310
+ if part in ocr_word or ocr_word in part:
311
+ matched_boxes.append(word_info['bbox'])
312
+ break
313
+
314
+ # Combine bounding boxes if multiple matches
315
+ if matched_boxes:
316
+ # Get bounding box that encompasses all matched words
317
+ min_x = min(box[0][0] for box in matched_boxes)
318
+ min_y = min(box[0][1] for box in matched_boxes)
319
+ max_x = max(box[1][0] for box in matched_boxes)
320
+ max_y = max(box[1][1] for box in matched_boxes)
321
+ combined_bbox = [[min_x, min_y], [max_x, max_y]]
322
+ else:
323
+ combined_bbox = None
324
+
325
+ entities_with_boxes.append({
326
+ 'entity_group': entity['entity_group'],
327
+ 'score': entity['score'],
328
+ 'word': entity['word'],
329
+ 'bbox': combined_bbox
330
+ })
331
+
332
+ return entities_with_boxes
333
+
334
  # --- FastAPI Routes ---
335
 
336
  @app.get("/")
 
420
  doc = DocumentFile.from_images([img_bytes])
421
  result = ocr_predictor_instance(doc)
422
 
423
+ # Get image dimensions for frontend highlighting
424
+ img_height, img_width = preprocessed_img.shape[:2]
425
+
426
+ # Extract text and word bounding boxes
427
  structured_text = extract_text_structured(result)
428
  cleaned_text = basic_cleanup(structured_text)
429
+ words_with_boxes = extract_words_with_boxes(result)
430
 
431
  print(f"OCR Structured Text:\n{structured_text[:500]}...")
432
+ print(f"Extracted {len(words_with_boxes)} words with bounding boxes")
433
 
434
  # Perform NER on cleaned text
435
  print("Running NER...")
436
  entities = ner_pipeline(cleaned_text)
437
 
438
+ # Structure entities (return all with score > 0.1, let frontend filter)
439
  structured_entities = []
440
  for entity in entities:
441
+ if entity.get('score', 0.0) > 0.1:
442
  structured_entities.append({
443
  'entity_group': entity['entity_group'],
444
  'score': float(entity['score']),
445
  'word': entity['word'].strip(),
446
  })
447
 
448
+ # Map entities to bounding boxes
449
+ entities_with_boxes = map_entities_to_boxes(structured_entities, words_with_boxes, cleaned_text)
450
+
451
+ # Check for drug interactions
452
+ detected_drugs = []
453
+ for entity in structured_entities:
454
+ if entity['entity_group'] in ['CHEM', 'CHEMICAL', 'TREATMENT']:
455
+ detected_drugs.append(entity['word'])
456
+
457
+ interactions = check_drug_interactions(detected_drugs) if detected_drugs else []
458
+ print(f"Found {len(interactions)} drug interactions")
459
+
460
  return {
461
+ "structured_text": structured_text,
462
+ "cleaned_text": cleaned_text,
463
+ "medical_entities": entities_with_boxes,
464
+ "interactions": interactions, # NEW: Drug interaction warnings
465
  "model_id": NER_MODELS[ner_model_id]["name"],
466
+ "ocr_model": f"{det_arch} + {reco_arch}",
467
+ "image_width": img_width,
468
+ "image_height": img_height
469
  }
470
 
471
  except Exception as e: