JobenTan commited on
Commit
0c4f9b9
·
verified ·
1 Parent(s): fa395ed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -76
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import torch
2
  import torch.nn as nn
3
  import torch.optim as optim
@@ -6,19 +7,20 @@ from torch.utils.data import Dataset, DataLoader
6
  from PIL import Image
7
  import requests
8
  from io import BytesIO
9
- from pymongo import MongoClient
10
  from sklearn.model_selection import train_test_split
11
  from sklearn.metrics import accuracy_score, f1_score
12
  import segmentation_models_pytorch as smp
13
- from torchvision.models import densenet121
14
  import gradio as gr
15
- import json
16
 
 
17
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
 
19
- client = MongoClient("mongodb+srv://xcovidinsight:FYP25S108@cluster0.vqesy.mongodb.net/")
20
- db = client["covid_system"]
21
- collection = db["validated_xrays"]
 
22
 
23
  class UNet(nn.Module):
24
  def __init__(self):
@@ -36,7 +38,6 @@ class UNet(nn.Module):
36
  class MyClassifier(nn.Module):
37
  def __init__(self, num_classes=4):
38
  super().__init__()
39
- from torchvision.models import DenseNet121_Weights
40
  base_model = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
41
  in_features = base_model.classifier.in_features
42
  base_model.classifier = nn.Linear(in_features, num_classes)
@@ -45,63 +46,101 @@ class MyClassifier(nn.Module):
45
  def forward(self, x):
46
  return self.model(x)
47
 
48
- def fetch_image(url):
49
- try:
50
- response = requests.get(url)
51
- image = Image.open(BytesIO(response.content)).convert("L").resize((256, 256))
52
- return image
53
- except Exception as e:
54
- print(f"Failed to fetch image from {url}: {e}")
55
- return None
56
 
57
- def trigger_incremental_train():
58
- samples = list(collection.find())
59
- samples = [s for s in samples if "image_path" in s and "true_label" in s]
60
 
61
- if not samples or len(samples) < 100:
62
- return {"error": "Not enough validated samples (minimum 100 required)."}
63
 
64
- m1 = UNet()
65
- m1.model.load_state_dict(torch.load("Segmentation Model.pth"))
66
- m1.to(device).eval()
67
 
68
- m2_old = MyClassifier()
69
- m2_old.load_state_dict(torch.load("Classification Model.pth", map_location=device))
70
- m2_old.to(device).eval()
71
 
72
- m2 = MyClassifier()
73
- m2.load_state_dict(torch.load("Classification Model.pth", map_location=device))
74
- m2.to(device)
 
 
 
 
 
 
 
 
75
 
76
- class LungXrayDataset(Dataset):
77
- def __init__(self, entries, transform):
78
- self.entries = entries
79
- self.transform = transform
80
- self.label_map = {label: i for i, label in enumerate(sorted(set(s["true_label"] for s in entries)))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- def __len__(self):
83
- return len(self.entries)
 
 
 
84
 
85
- def __getitem__(self, idx):
86
- entry = self.entries[idx]
87
- image = fetch_image(entry["image_path"])
88
- if image is None:
89
- raise RuntimeError(f"Image at {entry['image_path']} could not be loaded.")
90
 
91
- image_tensor = transforms.ToTensor()(image).unsqueeze(0).to(device)
 
92
 
93
- with torch.no_grad():
94
- mask = m1(image_tensor).sigmoid()
95
- mask = (mask > 0.5).float()
96
- masked = image_tensor * mask
97
 
98
- masked_rgb = masked.squeeze(0).repeat(3, 1, 1).cpu()
 
 
 
99
 
100
- if self.transform:
101
- masked_rgb = self.transform(masked_rgb)
102
 
103
- label = self.label_map[entry["true_label"]]
104
- return masked_rgb, label
 
105
 
106
  transform = transforms.Compose([
107
  transforms.Resize((224, 224)),
@@ -112,30 +151,25 @@ def trigger_incremental_train():
112
  samples, test_size=0.2, stratify=[s["true_label"] for s in samples], random_state=42
113
  )
114
 
115
- train_ds = LungXrayDataset(train_entries, transform)
116
- val_ds = LungXrayDataset(val_entries, transform)
117
  train_loader = DataLoader(train_ds, batch_size=16, shuffle=True)
118
  val_loader = DataLoader(val_ds, batch_size=16)
119
 
120
  criterion = nn.CrossEntropyLoss()
121
- optimizer = optim.Adam(m2.parameters(), lr=1e-4)
122
 
123
  for epoch in range(5):
124
- m2.train()
125
- total_loss, correct = 0, 0
126
  for imgs, labels in train_loader:
127
  imgs, labels = imgs.to(device), labels.to(device)
128
- out = m2(imgs)
129
  loss = criterion(out, labels)
130
  optimizer.zero_grad()
131
  loss.backward()
132
  optimizer.step()
133
- total_loss += loss.item()
134
- correct += (out.argmax(1) == labels).sum().item()
135
- acc = correct / len(train_ds)
136
- print(f"Epoch {epoch+1} ➤ Loss: {total_loss:.4f} | Accuracy: {acc:.4f}")
137
 
138
- def evaluate(model, loader, version):
139
  model.eval()
140
  y_true, y_pred = [], []
141
  with torch.no_grad():
@@ -145,26 +179,17 @@ def trigger_incremental_train():
145
  y_pred.extend(outputs.argmax(1).cpu().numpy())
146
  y_true.extend(labels.numpy())
147
  return {
148
- "version": version,
149
  "accuracy": round(accuracy_score(y_true, y_pred), 4),
150
  "f1_macro": round(f1_score(y_true, y_pred, average="macro"), 4),
151
  }
152
 
153
- eval_old = evaluate(m2_old, val_loader, "Old")
154
- eval_new = evaluate(m2, val_loader, "New")
155
 
156
- torch.save(m2.state_dict(), "New Classification Model.pth")
157
 
158
  return {
159
  "old_model": eval_old,
160
- "new_model": eval_new
 
161
  }
162
-
163
- with gr.Blocks() as demo:
164
- with gr.Row():
165
- train_button = gr.Button("Start Incremental Training")
166
- output_json = gr.JSON(label="Training Result")
167
-
168
- train_button.click(fn=trigger_incremental_train, inputs=[], outputs=output_json)
169
-
170
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ from fastapi import FastAPI, Request
2
  import torch
3
  import torch.nn as nn
4
  import torch.optim as optim
 
7
  from PIL import Image
8
  import requests
9
  from io import BytesIO
 
10
  from sklearn.model_selection import train_test_split
11
  from sklearn.metrics import accuracy_score, f1_score
12
  import segmentation_models_pytorch as smp
13
+ from torchvision.models import densenet121, DenseNet121_Weights
14
  import gradio as gr
15
+ import os
16
 
17
+ # Device
18
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
19
 
20
+ # FastAPI instance
21
+ app = FastAPI()
22
+
23
+ # ================== MODEL DEFINITIONS ==================
24
 
25
  class UNet(nn.Module):
26
  def __init__(self):
 
38
  class MyClassifier(nn.Module):
39
  def __init__(self, num_classes=4):
40
  super().__init__()
 
41
  base_model = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
42
  in_features = base_model.classifier.in_features
43
  base_model.classifier = nn.Linear(in_features, num_classes)
 
46
  def forward(self, x):
47
  return self.model(x)
48
 
49
+ # ================== LOAD MODELS ==================
50
+ m1 = UNet()
51
+ m1.model.load_state_dict(torch.load("weights/Segmentation_Model.pth", map_location=device))
52
+ m1.to(device).eval()
 
 
 
 
53
 
54
+ m2 = MyClassifier()
55
+ m2.load_state_dict(torch.load("weights/Classification_Model.pth", map_location=device))
56
+ m2.eval().to(device)
57
 
58
+ classes = ["COVID", "Lung_Opacity", "Normal", "Viral Pneumonia"]
 
59
 
60
+ # ================== INFERENCE FUNCTION ==================
 
 
61
 
62
+ def analyze(image):
63
+ image_gray = image.convert("L")
64
+ image_tensor = transforms.ToTensor()(image_gray).unsqueeze(0).to(device)
65
 
66
+ with torch.no_grad():
67
+ mask = m1(image_tensor).sigmoid()
68
+ mask = (mask > 0.5).float()
69
+ masked = image_tensor * mask
70
+
71
+ masked_rgb = masked.squeeze(0).repeat(3, 1, 1)
72
+
73
+ transform = transforms.Compose([
74
+ transforms.Resize((224, 224)),
75
+ transforms.Normalize(mean=[0.41]*3, std=[0.16]*3)
76
+ ])
77
 
78
+ processed = transform(masked_rgb).unsqueeze(0).to(device)
79
+
80
+ with torch.no_grad():
81
+ logits = m2(processed)
82
+ probs = torch.softmax(logits, dim=1)
83
+ confidence, pred_class = torch.max(probs, dim=1)
84
+
85
+ return classes[pred_class.item()], f"{confidence.item() * 100:.2f}%"
86
+
87
+ # ================== GRADIO INTERFACE ==================
88
+
89
+ gradio_ui = gr.Interface(
90
+ fn=analyze,
91
+ inputs=gr.Image(type="pil"),
92
+ outputs=[
93
+ gr.Text(label="Prediction"),
94
+ gr.Text(label="Confidence")
95
+ ],
96
+ title="Chest X-Ray Analyzer",
97
+ description="Upload a chest X-ray image to predict the disease."
98
+ )
99
+
100
+ # Mount Gradio app
101
+ app = gr.mount_gradio_app(app, gradio_ui, path="/")
102
+
103
+ # ================== TRAINING API ==================
104
+ class LungXrayDataset(Dataset):
105
+ def __init__(self, entries, transform, unet):
106
+ self.entries = entries
107
+ self.transform = transform
108
+ self.label_map = {label: i for i, label in enumerate(sorted(set(s["true_label"] for s in entries)))}
109
+ self.unet = unet
110
+
111
+ def __len__(self):
112
+ return len(self.entries)
113
+
114
+ def __getitem__(self, idx):
115
+ entry = self.entries[idx]
116
+ response = requests.get(entry["image_path"])
117
+ image = Image.open(BytesIO(response.content)).convert("L").resize((256, 256))
118
 
119
+ image_tensor = transforms.ToTensor()(image).unsqueeze(0).to(device)
120
+ with torch.no_grad():
121
+ mask = self.unet(image_tensor).sigmoid()
122
+ mask = (mask > 0.5).float()
123
+ masked = image_tensor * mask
124
 
125
+ masked_rgb = masked.squeeze(0).repeat(3, 1, 1).cpu()
 
 
 
 
126
 
127
+ if self.transform:
128
+ masked_rgb = self.transform(masked_rgb)
129
 
130
+ label = self.label_map[entry["true_label"]]
131
+ return masked_rgb, label
 
 
132
 
133
+ @app.post("/trigger_incremental_train")
134
+ async def trigger_train(request: Request):
135
+ data = await request.json()
136
+ samples = data.get("samples", [])
137
 
138
+ if not samples or len(samples) < 100:
139
+ return {"error": "Not enough validated samples (minimum 100 required)."}
140
 
141
+ m2_new = MyClassifier()
142
+ m2_new.load_state_dict(torch.load("weights/Classification_Model.pth", map_location=device))
143
+ m2_new.to(device)
144
 
145
  transform = transforms.Compose([
146
  transforms.Resize((224, 224)),
 
151
  samples, test_size=0.2, stratify=[s["true_label"] for s in samples], random_state=42
152
  )
153
 
154
+ train_ds = LungXrayDataset(train_entries, transform, m1)
155
+ val_ds = LungXrayDataset(val_entries, transform, m1)
156
  train_loader = DataLoader(train_ds, batch_size=16, shuffle=True)
157
  val_loader = DataLoader(val_ds, batch_size=16)
158
 
159
  criterion = nn.CrossEntropyLoss()
160
+ optimizer = optim.Adam(m2_new.parameters(), lr=1e-4)
161
 
162
  for epoch in range(5):
163
+ m2_new.train()
 
164
  for imgs, labels in train_loader:
165
  imgs, labels = imgs.to(device), labels.to(device)
166
+ out = m2_new(imgs)
167
  loss = criterion(out, labels)
168
  optimizer.zero_grad()
169
  loss.backward()
170
  optimizer.step()
 
 
 
 
171
 
172
+ def evaluate(model, loader):
173
  model.eval()
174
  y_true, y_pred = [], []
175
  with torch.no_grad():
 
179
  y_pred.extend(outputs.argmax(1).cpu().numpy())
180
  y_true.extend(labels.numpy())
181
  return {
 
182
  "accuracy": round(accuracy_score(y_true, y_pred), 4),
183
  "f1_macro": round(f1_score(y_true, y_pred, average="macro"), 4),
184
  }
185
 
186
+ eval_old = evaluate(m2, val_loader)
187
+ eval_new = evaluate(m2_new, val_loader)
188
 
189
+ torch.save(m2_new.state_dict(), "weights/Classification_Model.pth")
190
 
191
  return {
192
  "old_model": eval_old,
193
+ "new_model": eval_new,
194
+ "updated_model_path": "weights/Classification_Model.pth"
195
  }