File size: 8,651 Bytes
a756962
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import os
os.environ['TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD'] = '1'

import numpy as np
import torch
import gradio as gr
from PIL import Image
import fitz  # PyMuPDF
import torchxrayvision as xrv
from torchvision import transforms
import re

# --- Device & Model ---
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
MODEL = xrv.models.get_model("densenet121-res224-all").to(DEVICE).eval()
LABELS = MODEL.pathologies

# --- Extended Medical Information ---
DISEASE_INFO = {
    "Atelectasis": {
        "description":"Collapse of part or all of a lung, reducing oxygen exchange.",
        "cause":"Blocked airway, lung compression, post-surgery.",
        "recommendation":"Deep breathing exercises, possibly bronchoscopy or physiotherapy.",
    },
    "Cardiomegaly": {
        "description":"Enlargement of the heart, seen as a broad silhouette.",
        "cause":"High blood pressure, valve disease, cardiomyopathy.",
        "recommendation":"Echocardiogram, consult a cardiologist.",
    },
    "Consolidation": {
        "description":"Lung region filled with liquid instead of air.",
        "cause":"Often from pneumonia (bacterial or viral).",
        "recommendation":"Consult a physician; likely antibiotics and follow-up chest X-ray.",
    },
    "Edema": {
        "description":"Fluid accumulation in the lungs.",
        "cause":"Heart failure, kidney issues.",
        "recommendation":"Treat underlying cause, may need diuretics, consult cardiology.",
    },
    "Effusion": {
        "description":"Fluid buildup between lung and chest wall.",
        "cause":"Infection, heart failure, cancer.",
        "recommendation":"May need drainage (thoracentesis), see a pulmonologist.",
    },
    "Emphysema": {
        "description":"Damage and enlargement of lung air sacs (alveoli).",
        "cause":"Mainly smoking.",
        "recommendation":"Quit smoking, pulmonary rehab, inhalers.",
    },
    "Fibrosis": {
        "description":"Scarring of lung tissue, making breathing difficult.",
        "cause":"Longstanding inflammation, auto-immune disease, occupational exposure.",
        "recommendation":"Pulmonologist consult, immunosuppression/antifibrotic therapy.",
    },
    "Fracture": {
        "description":"Break/crack in a bone (commonly ribs).",
        "cause":"Trauma, accident, fall.",
        "recommendation":"Pain management, monitor for organ injury, orthopedic consult if severe.",
    },
    "Infiltration": {
        "description":"Something abnormal (cells/fluid) in the lungs.",
        "cause":"Most often infection, sometimes inflammation or cancer.",
        "recommendation":"See physician, further tests to clarify cause.",
    },
    "Mass": {
        "description":"Lump or growth seen on X-ray.",
        "cause":"Could be benign or malignant tumor.",
        "recommendation":"Consult pulmonologist or oncologist, consider CT/biopsy.",
    },
    "Nodule": {
        "description":"Small round or oval spot in the lung.",
        "cause":"Old infection, benign, or early cancer.",
        "recommendation":"May need CT and follow-up scans, discuss with doctor.",
    },
    "Pleural_Thickening": {
        "description":"Thickening of chest lining.",
        "cause":"Old infection, asbestos exposure.",
        "recommendation":"Pulmonology follow-up; rarely needs intervention.",
    },
    "Pneumonia": {
        "description":"Infection causing inflammation in the lungs.",
        "cause":"Bacteria, viruses, or fungus.",
        "recommendation":"Antibiotics/antivirals if needed. Seek prompt medical attention.",
    },
    "Pneumothorax": {
        "description":"Collapsed lung (air leaks into chest cavity).",
        "cause":"Trauma, rupture, sometimes spontaneous.",
        "recommendation":"May need emergency care to remove air; consult ER.",
    },
    # Add more if you want to extend—see LABELS for all possible findings
}

# --- Recommendations for top-line advice ---
ADVICE = {
    "Pneumonia": "Possible infection. Recommend antibiotics and pulmonology consult.",
    "Cardiomegaly": "Enlarged heart. Recommend echocardiography and cardiologist review.",
    "Effusion": "Fluid in lung space. May need thoracentesis.",
    "Fracture": "Possible bone break. Requires orthopedic consultation.",
    "Edema": "Pulmonary fluid overload. Evaluate for heart failure.",
}

def get_advice(label):
    return ADVICE.get(label, "Please consult a radiologist for further evaluation.")

def get_disease_info(label):
    d = DISEASE_INFO.get(label)
    if d:
        return (
            f"<b>{label}</b>: {d['description']}<br>"
            f"<b>Possible Causes:</b> {d['cause']}<br>"
            f"<b>Recommendation:</b> {d['recommendation']}"
        )
    return f"<b>{label}</b>: No extra info available. Please consult a radiologist."

# --- Image Preprocessing ---
def preprocess_image(pil_img: Image.Image) -> torch.Tensor:
    """Convert to grayscale, normalize, and resize for model."""
    if pil_img.mode != "L":
        pil_img = pil_img.convert("L")
    img_array = np.array(pil_img).astype(np.float32)
    img_array = xrv.datasets.normalize(img_array, 255)
    img_array = img_array[None, ...]  # [1, H, W]
    img_array = xrv.datasets.XRayCenterCrop()(img_array)
    img_array = xrv.datasets.XRayResizer(224)(img_array)
    tensor = torch.from_numpy(img_array).unsqueeze(0).to(DEVICE)
    return tensor

# --- X-ray Analysis (No CAM) ---
def analyse_xray(img: Image.Image):
    try:
        if img is None:
            return "Please upload an X-ray image.", None
        x = preprocess_image(img)
        with torch.no_grad():
            output = MODEL(x)
            probs = torch.sigmoid(output)[0] * 100
        topk = torch.topk(probs, 5)

        html = "<h3>🩺 Top 5 Predictions</h3><table border='1'><tr><th>Condition</th><th>Confidence</th><th>Details</th></tr>"
        for idx in topk.indices:
            label = LABELS[idx]
            html += (
                f"<tr><td>{label}</td>"
                f"<td>{probs[idx]:.1f}%</td>"
                f"<td>{get_disease_info(label)}</td></tr>"
            )
        html += "</table>"

        top_label = LABELS[topk.indices[0].item()]
        html += f"<br><b>Recommended Action for '{top_label}':</b> {get_advice(top_label)}"

        return html, img.resize((224, 224))
    except Exception as e:
        return f"Error processing image: {str(e)}", None

# --- Report PDF Analysis ---
def analyse_report(file):
    try:
        if file is None:
            return "Please upload a PDF report."
        doc = fitz.open(file.name)
        text = "\n".join(page.get_text() for page in doc)
        doc.close()
        found = []
        for label in LABELS:
            if re.search(rf"\b{label.lower()}\b", text.lower()):
                found.append(label)
        if found:
            html = "<h3>📃 Findings Detected in Report:</h3><ul>"
            for label in found:
                html += f"<li>{get_disease_info(label)}</li>"
            html += "</ul>"
        else:
            html = "<p>No known conditions detected from report text.</p>"
        return html
    except Exception as e:
        return f"Error processing PDF: {str(e)}"

# --- Gradio UI ---
with gr.Blocks(title="🩻 RadiologyScan AI") as demo:
    gr.Markdown(
        "## 🩻 RadiologyScan AI\n"
        "Perform fast AI-based analysis of Chest X-rays and medical reports\n"
        "<em>This tool provides informative summaries for common radiological findings. Not a substitute for a professional medical opinion.</em>"
    )
    with gr.Tabs():
        with gr.Tab("🔍 X-ray Analysis"):
            x_input = gr.Image(label="Upload Chest X-ray", type="pil")
            x_out_html = gr.HTML()
            x_out_image = gr.Image(label="Resized X-ray (224x224)")
            analyze_btn = gr.Button("Analyze X-ray")
            clear_btn = gr.Button("Clear")
            analyze_btn.click(analyse_xray, inputs=x_input, outputs=[x_out_html, x_out_image])
            clear_btn.click(lambda: (None, "", None), None, [x_input, x_out_html, x_out_image])

        with gr.Tab("📄 PDF Report Analysis"):
            pdf_input = gr.File(file_types=[".pdf"], label="Upload PDF Medical Report")
            pdf_output = gr.HTML()
            analyze_pdf_btn = gr.Button("Analyze Report")
            clear_pdf_btn = gr.Button("Clear")
            analyze_pdf_btn.click(analyse_report, inputs=pdf_input, outputs=pdf_output)
            clear_pdf_btn.click(lambda: (None, ""), None, [pdf_input, pdf_output])

if __name__ == "__main__":
    demo.launch(server_port=int(os.getenv("PORT", 7860)), show_error=True)