JobenTan commited on
Commit
c6b80e1
·
verified ·
1 Parent(s): c8855a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +22 -128
app.py CHANGED
@@ -5,27 +5,17 @@ import torch.optim as optim
5
  from torchvision import transforms
6
  from torch.utils.data import Dataset, DataLoader
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
- import albumentations as A
17
- from albumentations.pytorch import ToTensorV2
18
- from torchvision import transforms
19
- import cv2
20
-
21
- # Device
22
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
23
 
24
- # FastAPI instance
25
  app = FastAPI()
26
 
27
- # ================== MODEL DEFINITIONS ==================
28
 
 
29
  m1 = smp.Unet(
30
  encoder_name="resnet34",
31
  encoder_weights="imagenet",
@@ -35,91 +25,7 @@ m1 = smp.Unet(
35
  m1.load_state_dict(torch.load("Segmentation_Model.pth", map_location=device))
36
  m1.eval()
37
 
38
- # Classification Model
39
- m2 = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
40
- m2.classifier = nn.Linear(1024, 4)
41
- m2.load_state_dict(torch.load("Classification_Model.pth", map_location=device))
42
- m2.eval().to(device)
43
-
44
-
45
- unet_transform = A.Compose([
46
- A.Resize(256, 256),
47
- A.Normalize(mean=0.5, std=0.5),
48
- ToTensorV2()
49
- ])
50
-
51
- classifier_transform = transforms.Compose([
52
- transforms.Resize((224, 224)),
53
- transforms.ToTensor(),
54
- transforms.Normalize(mean=0.41, std=0.16)
55
- ])
56
-
57
- # Inference Function
58
- def analyze(image):
59
- try:
60
- if image is None:
61
- return "No image uploaded", "0.00%"
62
-
63
- # Grayscale image for UNet
64
- image_gray = image.convert("L")
65
- img_np = np.array(image_gray)
66
-
67
- # UNet input
68
- augmented = unet_transform(image=img_np)
69
- unet_input = augmented["image"].unsqueeze(0).to(device)
70
-
71
- # Segmentation
72
- with torch.no_grad():
73
- mask_pred = m1(unet_input)
74
- mask_pred = torch.sigmoid(mask_pred)
75
- mask = (mask_pred > 0.5).float().squeeze().cpu().numpy()
76
-
77
- # Resize to match classifier input
78
- resized_gray = image_gray.resize((224, 224), Image.BILINEAR)
79
- mask_img = Image.fromarray((mask * 255).astype(np.uint8))
80
- mask_resized = transforms.functional.resize(mask_img, [224, 224])
81
- image_np = np.array(resized_gray).astype(np.float32)
82
- mask_np = (np.array(mask_resized) > 127).astype(np.float32)
83
-
84
- lung_image = image_np * mask_np
85
- lung_image_3ch = np.stack([lung_image] * 3, axis=-1)
86
- lung_image_3ch = np.clip(lung_image_3ch, 0, 255).astype(np.uint8)
87
- lung_image_pil = Image.fromarray(lung_image_3ch)
88
-
89
- input_tensor = classifier_transform(lung_image_pil).unsqueeze(0).to(device)
90
-
91
- # Classification
92
- with torch.no_grad():
93
- logits = m2(input_tensor)
94
- probs = torch.softmax(logits, dim=1)
95
- confidence, pred_class = torch.max(probs, dim=1)
96
-
97
- classes = ["COVID", "Lung_Opacity", "Normal", "Viral Pneumonia"]
98
- confidence_percent = f"{confidence.item() * 100:.2f}%"
99
-
100
- return classes[pred_class.item()], confidence_percent
101
-
102
- except Exception as e:
103
- return f"[Error] {str(e)}", "0.00%"
104
-
105
- # ================== GRADIO INTERFACE ==================
106
-
107
- with gr.Blocks() as interface:
108
- with gr.Row():
109
- img = gr.Image(type="pil", label="Upload")
110
- btn = gr.Button("Analyze")
111
- with gr.Row():
112
- pred = gr.Text(label="Prediction")
113
- conf = gr.Text(label="Confidence")
114
-
115
- btn.click(fn=analyze, inputs=img, outputs=[pred, conf])
116
-
117
- interface.queue()
118
-
119
- app = gr.mount_gradio_app(app, interface, path="/")
120
-
121
- # ================== TRAINING API ==================
122
- # Data prepare
123
  class LungXrayDataset(Dataset):
124
  def __init__(self, entries, transform):
125
  self.entries = entries
@@ -154,17 +60,16 @@ async def trigger_train(request: Request):
154
 
155
  if not samples or len(samples) < 100:
156
  return {"error": "Not enough validated samples (minimum 100 required)."}
157
-
158
- # Classification Model
159
- m2 = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
160
- m2.classifier = nn.Linear(1024, 4)
161
- m2.load_state_dict(torch.load("Classification_Model.pth", map_location=device))
162
- m2.eval().to(device)
163
-
164
- m2_new = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
165
- m2_new.classifier = nn.Linear(1024, 4)
166
- m2_new.load_state_dict(torch.load("Classification_Model.pth", map_location=device))
167
- m2_new.to(device)
168
 
169
  transform = transforms.Compose([
170
  transforms.Resize((224, 224)),
@@ -176,15 +81,12 @@ async def trigger_train(request: Request):
176
  samples, test_size=0.2, stratify=[s["true_label"] for s in samples], random_state=42
177
  )
178
 
179
- train_ds = LungXrayDataset(train_entries, transform)
180
- val_ds = LungXrayDataset(val_entries, transform)
181
- train_loader = DataLoader(train_ds, batch_size=16, shuffle=True)
182
- val_loader = DataLoader(val_ds, batch_size=16)
183
 
184
  criterion = nn.CrossEntropyLoss()
185
  optimizer = optim.Adam(m2_new.parameters(), lr=1e-4)
186
 
187
- print(f"Training triggered with {len(samples)} samples by API")
188
  for epoch in range(5):
189
  m2_new.train()
190
  for imgs, labels in train_loader:
@@ -208,25 +110,17 @@ async def trigger_train(request: Request):
208
  "accuracy": round(accuracy_score(y_true, y_pred), 4),
209
  "f1_macro": round(f1_score(y_true, y_pred, average="macro"), 4),
210
  }
211
-
212
- eval_old = evaluate(m2, val_loader)
213
  eval_new = evaluate(m2_new, val_loader)
214
-
215
- if eval_new["f1_macro"] > eval_old["f1_macro"]:
 
216
  torch.save(m2_new.state_dict(), "Classification_Model.pth")
217
- model_used = "new"
218
- print("[NOTE] New model used and saved based on higher F1 score.")
219
- else:
220
- model_used = "old"
221
- print("[NOTE] Old model retained (new model did not improve F1 score).")
222
-
223
  return {
224
  "old_model": eval_old,
225
  "new_model": eval_new,
226
  "model_used": model_used,
227
  "updated_model_path": "Classification_Model.pth" if model_used == "new" else "unchanged"
228
  }
229
-
230
- if __name__ == "__main__":
231
- import uvicorn
232
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
5
  from torchvision import transforms
6
  from torch.utils.data import Dataset, DataLoader
7
  from PIL import Image
 
 
8
  from sklearn.model_selection import train_test_split
9
  from sklearn.metrics import accuracy_score, f1_score
10
  import segmentation_models_pytorch as smp
11
  from torchvision.models import densenet121, DenseNet121_Weights
 
 
 
 
 
 
 
 
 
12
 
13
+ # FastAPI app instance
14
  app = FastAPI()
15
 
16
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
 
18
+ # Load segmentation model
19
  m1 = smp.Unet(
20
  encoder_name="resnet34",
21
  encoder_weights="imagenet",
 
25
  m1.load_state_dict(torch.load("Segmentation_Model.pth", map_location=device))
26
  m1.eval()
27
 
28
+ # Dataset for incremental training
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  class LungXrayDataset(Dataset):
30
  def __init__(self, entries, transform):
31
  self.entries = entries
 
60
 
61
  if not samples or len(samples) < 100:
62
  return {"error": "Not enough validated samples (minimum 100 required)."}
63
+
64
+ # Load models
65
+ def load_model():
66
+ model = densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1)
67
+ model.classifier = nn.Linear(1024, 4)
68
+ model.load_state_dict(torch.load("Classification_Model.pth", map_location=device))
69
+ return model.to(device)
70
+
71
+ m2_old = load_model().eval()
72
+ m2_new = load_model()
 
73
 
74
  transform = transforms.Compose([
75
  transforms.Resize((224, 224)),
 
81
  samples, test_size=0.2, stratify=[s["true_label"] for s in samples], random_state=42
82
  )
83
 
84
+ train_loader = DataLoader(LungXrayDataset(train_entries, transform), batch_size=16, shuffle=True)
85
+ val_loader = DataLoader(LungXrayDataset(val_entries, transform), batch_size=16)
 
 
86
 
87
  criterion = nn.CrossEntropyLoss()
88
  optimizer = optim.Adam(m2_new.parameters(), lr=1e-4)
89
 
 
90
  for epoch in range(5):
91
  m2_new.train()
92
  for imgs, labels in train_loader:
 
110
  "accuracy": round(accuracy_score(y_true, y_pred), 4),
111
  "f1_macro": round(f1_score(y_true, y_pred, average="macro"), 4),
112
  }
113
+
114
+ eval_old = evaluate(m2_old, val_loader)
115
  eval_new = evaluate(m2_new, val_loader)
116
+
117
+ model_used = "new" if eval_new["f1_macro"] > eval_old["f1_macro"] else "old"
118
+ if model_used == "new":
119
  torch.save(m2_new.state_dict(), "Classification_Model.pth")
120
+
 
 
 
 
 
121
  return {
122
  "old_model": eval_old,
123
  "new_model": eval_new,
124
  "model_used": model_used,
125
  "updated_model_path": "Classification_Model.pth" if model_used == "new" else "unchanged"
126
  }