VaneshDev commited on
Commit
ebb3e81
·
verified ·
1 Parent(s): 2df20e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -212
app.py CHANGED
@@ -1,220 +1,144 @@
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from PIL import Image
3
  import torch
4
- from torchvision import models, transforms
5
- import fitz # PyMuPDF
6
- import logging
7
- import os
8
- import re # For better pattern matching
 
9
 
10
- # Setup logging
11
  logging.basicConfig(level=logging.INFO)
12
- logger = logging.getLogger(__name__)
13
-
14
- # Condition list (add your specific diseases here)
15
- conditions = [
16
- "Normal", "Pneumonia", "Cancer", "TB", "Other",
17
- "Coronary Artery Disease", "Aortic Aneurysm", "Stroke", "Peripheral Artery Disease",
18
- "Brain Tumor", "Alzheimer's Disease", "Multiple Sclerosis", "Epilepsy",
19
- "COPD", "Lung Cancer", "Pulmonary Embolism",
20
- "Fractures", "Arthritis", "Osteoporosis",
21
- "Appendicitis", "Gallstones", "Kidney Stones", "Infections", "Abdominal Aortic Aneurysm", "Diverticulitis"
22
- ]
23
-
24
- # Condition details for diagnosis (can be expanded with real data)
25
- condition_details = {
26
- "Normal": {"description": "No abnormal signs detected.", "recommendation": "Routine check-ups recommended."},
27
- "Pneumonia": {"description": "Lung inflammation detected, possibly infectious.", "recommendation": "Seek medical attention for treatment."},
28
- "Cancer": {"description": "Suspicious masses suggest cancer; further imaging needed.", "recommendation": "Consult an oncologist."},
29
- "TB": {"description": "Cavitary lesions or nodular opacities indicate tuberculosis.", "recommendation": "Immediate medical evaluation and anti-TB therapy required."},
30
- "Other": {"description": "Unclear abnormality; further investigation needed.", "recommendation": "Consult a radiologist."},
31
- "Coronary Artery Disease": {"description": "Narrowing of coronary arteries detected.", "recommendation": "Cardiology consultation required."},
32
- "Aortic Aneurysm": {"description": "Abnormal enlargement of the aorta.", "recommendation": "Vascular surgery evaluation."},
33
- "Stroke": {"description": "Signs of brain ischemia or hemorrhage.", "recommendation": "Urgent neurological evaluation."},
34
- "Peripheral Artery Disease": {"description": "Reduced blood flow in peripheral arteries.", "recommendation": "Vascular specialist consultation."},
35
- "Brain Tumor": {"description": "Abnormal mass in the brain detected.", "recommendation": "Consult a neurosurgeon."},
36
- "Alzheimer's Disease": {"description": "Signs of neurodegenerative changes.", "recommendation": "Neurology consultation."},
37
- "Multiple Sclerosis": {"description": "Demyelinating lesions in the CNS.", "recommendation": "Neurology consultation."},
38
- "Epilepsy": {"description": "Signs of seizure activity.", "recommendation": "Neurology consultation."},
39
- "COPD": {"description": "Lung damage from COPD observed.", "recommendation": "Pulmonary consultation."},
40
- "Lung Cancer": {"description": "Malignant lung masses detected.", "recommendation": "Oncology consultation."},
41
- "Pulmonary Embolism": {"description": "Blockage in pulmonary arteries.", "recommendation": "Urgent medical attention."},
42
- "Fractures": {"description": "Bone break detected.", "recommendation": "Orthopedic evaluation."},
43
- "Arthritis": {"description": "Joint inflammation detected.", "recommendation": "Rheumatology consultation."},
44
- "Osteoporosis": {"description": "Reduced bone density observed.", "recommendation": "Bone health specialist consultation."},
45
- "Appendicitis": {"description": "Inflammation of the appendix.", "recommendation": "Surgical evaluation."},
46
- "Gallstones": {"description": "Stones in the gallbladder detected.", "recommendation": "Gastroenterology consultation."},
47
- "Kidney Stones": {"description": "Stones in the kidneys detected.", "recommendation": "Urology consultation."},
48
- "Infections": {"description": "Signs of infection observed.", "recommendation": "Infectious disease consultation."},
49
- "Abdominal Aortic Aneurysm": {"description": "Enlargement of the abdominal aorta.", "recommendation": "Vascular surgery evaluation."},
50
- "Diverticulitis": {"description": "Inflammation of diverticula in the colon.", "recommendation": "Gastroenterology consultation."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
-
53
- # Load a pre-trained DenseNet121 model
54
- model = models.densenet121(pretrained=True)
55
- model.classifier = torch.nn.Linear(model.classifier.in_features, len(conditions)) # Adjust the classifier for our condition count
56
- model.eval()
57
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
58
- model.to(device)
59
-
60
- # Image preprocessing function
61
- def preprocess_image(image):
62
- transform = transforms.Compose([
63
- transforms.Resize((224, 224)), # Resize to match the model input size
64
- transforms.ToTensor(),
65
- transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # Standard ImageNet normalization
66
- ])
67
- return transform(image).unsqueeze(0).to(device)
68
-
69
- # X-ray prediction function with summary output
70
- def predict_xray(image):
71
- try:
72
- if image is None:
73
- return "Please upload an image."
74
-
75
- img_tensor = preprocess_image(image)
76
- with torch.no_grad():
77
- output = model(img_tensor)
78
-
79
- # Get probabilities for each condition
80
- probs = torch.nn.functional.softmax(output, dim=1)[0]
81
- results = {conditions[i]: probs[i].item() * 100 for i in range(len(conditions))}
82
-
83
- # Identify the condition with the highest probability
84
- top_condition = max(results, key=results.get)
85
- confidence = results[top_condition]
86
-
87
- # Construct a summary based on prediction
88
- if confidence < 50:
89
- return f"""
90
- <div style="font-family:Arial">
91
- <h3>Summary</h3>
92
- <p><b>Disease Identified:</b> Uncertain</p>
93
- <p><b>Cause/Status:</b> The model is not confident enough to provide a clear diagnosis.</p>
94
- <p><b>Treatment/Recommendation:</b> Please consult a radiologist or upload a better-quality image for better accuracy.</p>
95
- </div>
96
- """
97
-
98
- # Fetch details for the identified condition
99
- info = condition_details.get(top_condition, condition_details["Other"])
100
- return f"""
101
- <div style="font-family:Arial">
102
- <h3>Summary</h3>
103
- <p><b>Disease Identified:</b> {top_condition}</p>
104
- <p><b>Cause/Status:</b> {info['description']}</p>
105
- <p><b>Treatment/Recommendation:</b> {info['recommendation']}</p>
106
- </div>
107
- """
108
-
109
- except Exception as e:
110
- logger.error(f"Error in prediction: {e}")
111
- return f"Error: {str(e)}"
112
-
113
- # Enhanced PDF analysis function to extract patient details and disease
114
- def analyze_report(file):
115
- if not file or not file.name.endswith(".pdf"):
116
- return "Please upload a valid PDF file."
117
-
118
- try:
119
- doc = fitz.open(file.name)
120
- text = "".join(page.get_text() for page in doc) # Extract all text from the PDF
121
- doc.close()
122
-
123
- # Initialize variables for patient details and condition
124
- patient_info = {
125
- "Name": "Not found",
126
- "Age": "Not found",
127
- "Gender": "Not found",
128
- "Medical Record Number": "Not found",
129
- "Date of Exam": "Not found"
130
- }
131
- condition = "Unclear"
132
- confidence = "Not specified"
133
-
134
- # Extract patient details using regex patterns
135
- name_match = re.search(r'Name:\s*([^\n]+)', text, re.IGNORECASE)
136
- if name_match:
137
- patient_info["Name"] = name_match.group(1).strip()
138
-
139
- age_match = re.search(r'Age:\s*(\d+)', text, re.IGNORECASE)
140
- if age_match:
141
- patient_info["Age"] = age_match.group(1).strip()
142
-
143
- gender_match = re.search(r'Gender:\s*(Male|Female|Other)', text, re.IGNORECASE)
144
- if gender_match:
145
- patient_info["Gender"] = gender_match.group(1).strip()
146
-
147
- mrn_match = re.search(r'Medical Record Number:\s*([^\n]+)', text, re.IGNORECASE)
148
- if mrn_match:
149
- patient_info["Medical Record Number"] = mrn_match.group(1).strip()
150
-
151
- date_match = re.search(r'Date of Exam:\s*([^\n]+)', text, re.IGNORECASE)
152
- if date_match:
153
- patient_info["Date of Exam"] = date_match.group(1).strip()
154
-
155
- # Improved condition matching with regex
156
- if re.search(r'\btuberculosis\b|\bTB\b', text, re.IGNORECASE):
157
- condition = "TB"
158
- # Look for percentage of lung involvement
159
- percentage_match = re.search(r'lung involvement:\s*approximately\s*(\d+)%', text, re.IGNORECASE)
160
- if percentage_match:
161
- confidence = f"{percentage_match.group(1)}% lung involvement"
162
- elif re.search(r'\bstroke\b', text, re.IGNORECASE):
163
- condition = "Stroke"
164
- elif re.search(r'\bcancer\b', text, re.IGNORECASE):
165
- condition = "Cancer"
166
- elif re.search(r'\bfracture\b', text, re.IGNORECASE):
167
- condition = "Fractures"
168
- elif re.search(r'\bpneumonia\b', text, re.IGNORECASE):
169
- condition = "Pneumonia"
170
- # Add more conditions as needed from the conditions list
171
-
172
- # Fetch condition details
173
- info = condition_details.get(condition, condition_details["Other"])
174
-
175
- # Construct summary output
176
- summary = f"""
177
- <div style="font-family:Arial">
178
- <h3>Summary</h3>
179
- <p><b>Disease Identified:</b> {condition}</p>
180
- <p><b>Cause/Status:</b> {info['description']} {f'({confidence})' if confidence != 'Not specified' else ''}</p>
181
- <p><b>Treatment/Recommendation:</b> {info['recommendation']}</p>
182
- <h4>Patient Details</h4>
183
- <p><b>Name:</b> {patient_info['Name']}</p>
184
- <p><b>Age:</b> {patient_info['Age']}</p>
185
- <p><b>Gender:</b> {patient_info['Gender']}</p>
186
- <p><b>Medical Record Number:</b> {patient_info['Medical Record Number']}</p>
187
- <p><b>Date of Exam:</b> {patient_info['Date of Exam']}</p>
188
- </div>
189
- """
190
- return summary
191
-
192
- except Exception as e:
193
- logger.error(f"Failed to process PDF: {e}")
194
- return f"Failed to process PDF: {str(e)}"
195
-
196
- # Gradio interface
197
- def create_interface():
198
- with gr.Blocks() as demo:
199
- gr.Markdown("<h1 style='text-align:center;'>🩻 RadiologyScan AI</h1><p style='text-align:center;'>AI-powered X-ray and Report Analysis</p>")
200
-
201
- with gr.Tabs():
202
- with gr.TabItem("X-ray Analysis"):
203
- img_input = gr.Image(label="Upload Chest X-ray", type="pil")
204
- summary_output = gr.HTML(label="Summary Result")
205
- with gr.Row():
206
- gr.Button("Analyze X-ray", elem_id="analyze_button", scale=0.3).click(predict_xray, inputs=img_input, outputs=summary_output)
207
- gr.Button("Clear", elem_id="clear_button", scale=0.3).click(lambda: [None, ""], inputs=None, outputs=[img_input, summary_output])
208
-
209
- with gr.TabItem("Report Analysis"):
210
- pdf_input = gr.File(label="Upload PDF Report", file_types=[". PDFs"])
211
- summary_output_report = gr.HTML(label="Summary Result")
212
- with gr.Row():
213
- gr.Button("Analyze Report", elem_id="analyze_button", scale=0.3).click(analyze_report, inputs=pdf_input, outputs=summary_output_report)
214
- gr.Button("Clear", elem_id="clear_button", scale=0.3).click(lambda: [None, ""], inputs=None, outputs=[pdf_input, summary_output_report])
215
-
216
- return demo
217
 
218
  if __name__ == "__main__":
219
- demo = create_interface()
220
- demo.launch(server_port=7860, ssr_mode=False)
 
1
+ """
2
+ RadiologyScan AI – X-ray & Report analyser
3
+ Author : <you>
4
+ ▶ requirements.txt needs:
5
+ torch torchvision torchxrayvision==1.2.0
6
+ pillow gradio pymupdf torchcam==0.4.0
7
+ transformers>=4.40.0 accelerate
8
+ """
9
+
10
+ import os, re, logging, tempfile
11
  import gradio as gr
12
  from PIL import Image
13
  import torch
14
+ import torch.nn.functional as F
15
+ from torchvision import transforms
16
+ import torchxrayvision as xrv # CheXNet-style models
17
+ import fitz # PyMuPDF
18
+ from torchcam.methods import SmoothGradCAMpp # visual explainability
19
+ from transformers import pipeline
20
 
 
21
  logging.basicConfig(level=logging.INFO)
22
+ log = logging.getLogger(__name__)
23
+
24
+ # ------------------------------------------------------------------
25
+ # 1. Load model – 18-label denseNet trained on multiple X-ray sets
26
+ # ------------------------------------------------------------------
27
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
28
+ MODEL = xrv.models.get_model("densenet121-res224-all").to(DEVICE).eval()
29
+ LABELS = MODEL.pathologies # 18 canonical labels
30
+
31
+ TRANSFORM = transforms.Compose([
32
+ transforms.Resize(224),
33
+ transforms.CenterCrop(224),
34
+ transforms.ToTensor(),
35
+ transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
36
+ ])
37
+
38
+ # ------------------------ helper ----------------------------------
39
+ def preprocess(pil_img: Image.Image) -> torch.Tensor:
40
+ if pil_img.mode != "RGB":
41
+ pil_img = pil_img.convert("RGB")
42
+ return TRANSFORM(pil_img).unsqueeze(0).to(DEVICE)
43
+
44
+ # ------------------------------------------------------------------
45
+ # 2. X-ray prediction with Grad-CAM + textual advice
46
+ # ------------------------------------------------------------------
47
+ cam_extractor = SmoothGradCAMpp(MODEL)
48
+
49
+ def analyse_xray(img: Image.Image):
50
+ if img is None: return "Please upload an image."
51
+
52
+ x = preprocess(img)
53
+ with torch.no_grad():
54
+ logits = MODEL(x)
55
+ probs = torch.sigmoid(logits)[0] * 100 # multi-label %
56
+ topk = torch.topk(probs, 3) # show best 3
57
+
58
+ # Grad-CAM heat-map for the highest score
59
+ target = topk.indices[0].item()
60
+ activation_map = cam_extractor(target, logits)[0] # H×W
61
+ heatmap = cam_extractor.overlay(torch.squeeze(x).cpu(), activation_map)
62
+
63
+ # Build HTML summary
64
+ rows = "".join(
65
+ f"<tr><td>{LABELS[i]}</td><td>{probs[i]:.1f}%</td></tr>"
66
+ for i in topk.indices
67
+ )
68
+ advice = medical_advice(LABELS[target])
69
+ html = f"""
70
+ <h3>AI findings</h3>
71
+ <table>{rows}</table>
72
+ <p><b>Advice:</b> {advice}</p>
73
+ """
74
+
75
+ return html, Image.fromarray(heatmap)
76
+
77
+ # simple rule-based advice (extend or swap for knowledge-graph)
78
+ ADVICE = {
79
+ "Pneumonia": "Consult a pulmonologist; antibiotics or antivirals as indicated.",
80
+ "Cardiomegaly": "Recommend echocardiography; refer to cardiology.",
81
+ "Fracture": "Orthopaedic consultation; consider CT if uncertain.",
82
  }
83
+ def medical_advice(label): return ADVICE.get(label, "Discuss with a radiologist for next steps.")
84
+
85
+ # ------------------------------------------------------------------
86
+ # 3. PDF report summariser (LLM pipeline fallback)
87
+ # ------------------------------------------------------------------
88
+ # Regex first else call an LLM summariser (small DistilBART)
89
+ summariser = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6")
90
+
91
+ def analyse_report(file):
92
+ if file is None: return "Please upload a PDF."
93
+
94
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
95
+ tmp.write(file.read())
96
+ path = tmp.name
97
+ doc = fitz.open(path)
98
+ text = "\n".join(page.get_text() for page in doc)
99
+ doc.close()
100
+
101
+ disease = regex_find_disease(text)
102
+ if not disease:
103
+ # fallback LLM summary
104
+ short = summariser(text[:4000], max_length=120, min_length=30, do_sample=False)[0]["summary_text"]
105
+ return f"<h3>Report summary</h3><p>{short}</p>"
106
+
107
+ advice = medical_advice(disease)
108
+ return f"""
109
+ <h3>Disease detected:</h3><p>{disease}</p>
110
+ <p><b>Recommendation:</b> {advice}</p>
111
+ """
112
+
113
+ def regex_find_disease(t:str):
114
+ patterns = {
115
+ "Pneumonia" : r"\bpneumonia\b",
116
+ "Cardiomegaly" : r"\bcardiomegaly\b",
117
+ "Fracture" : r"\bfracture\b",
118
+ }
119
+ for k,v in patterns.items():
120
+ if re.search(v, t, flags=re.I): return k
121
+ return None
122
+
123
+ # ------------------------------------------------------------------
124
+ # 4. Gradio UI
125
+ # ------------------------------------------------------------------
126
+ with gr.Blocks(title="🩻 RadiologyScan AI") as demo:
127
+ gr.Markdown("## 🩻 RadiologyScan AI – Chest X-ray & Report Analyser")
128
+
129
+ with gr.Tabs():
130
+ with gr.Tab("X-ray image"):
131
+ in_img = gr.Image(label="Upload chest X-ray")
132
+ out_html= gr.HTML()
133
+ out_cam = gr.Image(label="Explainability map")
134
+ gr.Button("Analyse").click(analyse_xray, in_img, outputs=[out_html,out_cam])
135
+ gr.Button("Clear").click(lambda: (None,"",None), None, [in_img,out_html,out_cam])
136
+
137
+ with gr.Tab("Radiology report"):
138
+ in_pdf = gr.File(label="Upload PDF report", file_types=[".pdf"])
139
+ out_rep = gr.HTML()
140
+ gr.Button("Analyse").click(analyse_report, in_pdf, out_rep)
141
+ gr.Button("Clear").click(lambda: (None,""), None, [in_pdf,out_rep])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  if __name__ == "__main__":
144
+ demo.launch(show_error=True, server_port=int(os.getenv("PORT",7860)))