AdarshRajDS commited on
Commit
e2f2323
·
1 Parent(s): 65e9ff5

Add mold detection FastAPI backend v2

Browse files
Files changed (6) hide show
  1. Dockerfile +1 -0
  2. advanced_decision.py +55 -0
  3. app.py +1 -1
  4. dino.py +28 -0
  5. gradcam.py +30 -0
  6. requirements.txt +1 -0
Dockerfile CHANGED
@@ -28,3 +28,4 @@ EXPOSE 7860
28
  CMD uvicorn app:app --host 0.0.0.0 --port 7860
29
 
30
 
 
 
28
  CMD uvicorn app:app --host 0.0.0.0 --port 7860
29
 
30
 
31
+
advanced_decision.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+ import numpy as np
4
+
5
+ MOLD_HIGH = 0.85
6
+ MOLD_LOW = 0.50
7
+ BIO_TH = 0.70
8
+ UNCERT_TH = 0.15
9
+ PATCH_TH = 0.50
10
+ DINO_TH = 0.75
11
+
12
+ def enable_dropout(model):
13
+ for m in model.modules():
14
+ if m.__class__.__name__.startswith("Dropout"):
15
+ m.train()
16
+
17
+ def mc_uncertainty(model, img_tensor, mold_idx, T=15):
18
+ enable_dropout(model)
19
+ probs = []
20
+ for _ in range(T):
21
+ out = model(img_tensor.unsqueeze(0))
22
+ cp = F.softmax(out["class"],1)[0]
23
+ probs.append(cp[mold_idx].item())
24
+ model.eval()
25
+ return float(np.mean(probs)), float(np.std(probs))
26
+
27
+ def patch_consistency(model, image, transform, mold_idx, device, stride=112):
28
+ w,h = image.size
29
+ votes = []
30
+ for y in range(0, h-224+1, stride):
31
+ for x in range(0, w-224+1, stride):
32
+ patch = image.crop((x,y,x+224,y+224))
33
+ pt = transform(patch).unsqueeze(0).to(device)
34
+ with torch.no_grad():
35
+ out = model(pt)
36
+ cp = F.softmax(out["class"],1)[0]
37
+ votes.append(cp[mold_idx].item())
38
+ return float(np.mean(np.array(votes) > 0.7)) if votes else 0.0
39
+
40
+ def final_decision_v2(
41
+ mold_p, bio_p, uncert, patch_ratio, dino_sim
42
+ ):
43
+ if (
44
+ mold_p > MOLD_HIGH and
45
+ bio_p > BIO_TH and
46
+ uncert < UNCERT_TH and
47
+ patch_ratio > PATCH_TH and
48
+ dino_sim > DINO_TH
49
+ ):
50
+ return "Mold"
51
+
52
+ if mold_p > MOLD_LOW and bio_p > BIO_TH:
53
+ return "Possible Mold"
54
+
55
+ return "Not Mold"
app.py CHANGED
@@ -27,7 +27,7 @@ app.add_middleware(
27
  device = "cuda" if torch.cuda.is_available() else "cpu"
28
 
29
  # Model path for HuggingFace Spaces (flat structure)
30
- model_path = Path("resnet50_multitask_bio.pth")
31
 
32
  print(f"Loading model from: {model_path.absolute()}")
33
  print(f"Model exists: {model_path.exists()}")
 
27
  device = "cuda" if torch.cuda.is_available() else "cpu"
28
 
29
  # Model path for HuggingFace Spaces (flat structure)
30
+ model_path = Path("resnet50_multitask_mold.pth")
31
 
32
  print(f"Loading model from: {model_path.absolute()}")
33
  print(f"Model exists: {model_path.exists()}")
dino.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import torch
4
+ import torch.hub
5
+ from PIL import Image
6
+ from sklearn.metrics.pairwise import cosine_similarity
7
+
8
+ def load_dino(device):
9
+ model = torch.hub.load("facebookresearch/dinov2", "dinov2_vits14")
10
+ model.eval().to(device)
11
+ return model
12
+
13
+ def build_embeddings(dino, transform, image_dir, device):
14
+ embs = []
15
+ for f in os.listdir(image_dir):
16
+ if f.lower().endswith((".jpg",".png",".jpeg")):
17
+ img = Image.open(os.path.join(image_dir,f)).convert("RGB")
18
+ t = transform(img).unsqueeze(0).to(device)
19
+ with torch.no_grad():
20
+ e = dino(t)
21
+ embs.append(e.squeeze().cpu().numpy())
22
+ return np.vstack(embs)
23
+
24
+ def similarity(dino, mold_embs, image, transform, device):
25
+ t = transform(image).unsqueeze(0).to(device)
26
+ with torch.no_grad():
27
+ e = dino(t).cpu().numpy()
28
+ return float(cosine_similarity(e, mold_embs).max())
gradcam.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+
4
+ class GradCAM:
5
+ def __init__(self, model, target_layer):
6
+ self.model = model
7
+ self.gradients = None
8
+ self.activations = None
9
+
10
+ target_layer.register_forward_hook(self._fwd)
11
+ target_layer.register_backward_hook(self._bwd)
12
+
13
+ def _fwd(self, m, i, o):
14
+ self.activations = o
15
+
16
+ def _bwd(self, m, gi, go):
17
+ self.gradients = go[0]
18
+
19
+ def generate(self, img_tensor, class_idx):
20
+ out = self.model(img_tensor.unsqueeze(0))
21
+ score = out["class"][:, class_idx].sum()
22
+ self.model.zero_grad()
23
+ score.backward()
24
+
25
+ w = self.gradients.mean(dim=(2,3), keepdim=True)
26
+ cam = (w * self.activations).sum(dim=1)
27
+ cam = torch.relu(cam)
28
+ cam = cam / (cam.max() + 1e-8)
29
+
30
+ return cam.detach().cpu().numpy()[0]
requirements.txt CHANGED
@@ -7,3 +7,4 @@ numpy<2
7
  python-multipart
8
 
9
 
 
 
7
  python-multipart
8
 
9
 
10
+