muruga778 commited on
Commit
788c982
·
verified ·
1 Parent(s): af3980e

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +185 -0
  2. classes.json +1 -0
  3. metrics.json +25 -0
  4. skin_model.pth +3 -0
app.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io, json
2
+ import numpy as np
3
+ import streamlit as st
4
+ import torch
5
+ from PIL import Image
6
+ import torchvision.transforms as T
7
+ import timm
8
+ import requests
9
+
10
+ # -----------------------------
11
+ # CONFIG
12
+ # -----------------------------
13
+ MODEL_PATH = "skin_model.pth"
14
+ CLASSES_PATH = "classes.json"
15
+
16
+ TIMM_MODEL_NAME = "efficientnet_b0"
17
+ IMG_SIZE = 224
18
+ TOPK = 3
19
+
20
+ # Ollama (FREE local LLM). If you don't want LLM, set USE_LLM=False
21
+ USE_LLM = True
22
+ OLLAMA_URL = "http://localhost:11434/api/generate"
23
+ OLLAMA_MODEL = "phi3:mini" # or "mistral:7b", "llama3.1:8b"
24
+
25
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
26
+
27
+ # -----------------------------
28
+ # Severity rules (simple demo)
29
+ # -----------------------------
30
+ SEVERITY_RULES = {
31
+ "tumor_malignant": ("urgent", True),
32
+ "bullous": ("urgent", True),
33
+ "systemic": ("urgent", True),
34
+ "bacterial": ("doctor_soon", True),
35
+ "autoimmune": ("doctor_soon", True),
36
+ "infestation_bite": ("doctor_soon", True),
37
+ "drug_exanthem": ("doctor_soon", True),
38
+
39
+ "fungal": ("monitor", False),
40
+ "viral": ("monitor", False),
41
+ "eczema_dermatitis": ("monitor", False),
42
+ "psoriasis_lichen": ("monitor", False),
43
+ "tumor_benign": ("monitor", False),
44
+ "hives": ("monitor", False),
45
+ "pigment": ("monitor", False),
46
+ "hair_nail": ("monitor", False),
47
+
48
+ "acne_rosacea": ("self_care", False),
49
+ }
50
+
51
+ def severity_from_label(label: str, symptoms: str):
52
+ sev, consult = SEVERITY_RULES.get(label, ("monitor", False))
53
+ s = symptoms.lower()
54
+ red_flags = ["fever", "bleeding", "pus", "spreading fast", "severe pain", "difficulty breathing", "black", "rapidly growing"]
55
+ if any(k in s for k in red_flags):
56
+ sev, consult = "urgent", True
57
+ return sev, consult
58
+
59
+ # -----------------------------
60
+ # Load model + classes (cached)
61
+ # -----------------------------
62
+ @st.cache_resource
63
+ def load_model_and_classes():
64
+ with open(CLASSES_PATH, "r") as f:
65
+ classes = json.load(f)
66
+
67
+ num_classes = len(classes)
68
+
69
+ model = timm.create_model(TIMM_MODEL_NAME, pretrained=False, num_classes=num_classes)
70
+ state = torch.load(MODEL_PATH, map_location="cpu")
71
+ model.load_state_dict(state, strict=True)
72
+ model.to(DEVICE)
73
+ model.eval()
74
+ return model, classes
75
+
76
+ # EfficientNet preprocessing (same as training)
77
+ transform = T.Compose([
78
+ T.Resize((IMG_SIZE, IMG_SIZE)),
79
+ T.ToTensor(),
80
+ T.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225)),
81
+ ])
82
+
83
+ @torch.no_grad()
84
+ def predict_image(model, pil_img, classes):
85
+ x = transform(pil_img.convert("RGB")).unsqueeze(0).to(DEVICE)
86
+ logits = model(x) # raw scores
87
+ probs = torch.softmax(logits, dim=1).squeeze(0) # convert to probabilities
88
+
89
+ topk = torch.topk(probs, k=min(TOPK, len(classes)))
90
+ results = []
91
+ for idx, score in zip(topk.indices.tolist(), topk.values.tolist()):
92
+ results.append({"label": classes[idx], "confidence": float(score)})
93
+ return results
94
+
95
+ def call_ollama(prompt: str) -> str:
96
+ payload = {
97
+ "model": OLLAMA_MODEL,
98
+ "prompt": prompt,
99
+ "stream": False,
100
+ "options": {"temperature": 0.3}
101
+ }
102
+ r = requests.post(OLLAMA_URL, json=payload, timeout=60)
103
+ r.raise_for_status()
104
+ return r.json().get("response", "").strip()
105
+
106
+ def build_prompt(symptoms, top3, severity, doctor_consult):
107
+ return f"""
108
+ You are a health assistant for a university hackathon demo.
109
+ Be careful and do NOT diagnose with certainty.
110
+
111
+ User symptoms:
112
+ {symptoms}
113
+
114
+ Image model top-3 predictions:
115
+ {top3}
116
+
117
+ Severity decision:
118
+ severity={severity}, doctor_consult={doctor_consult}
119
+
120
+ Explain in simple English:
121
+ - What top prediction means
122
+ - What to do now (safe steps)
123
+ - When to see a doctor (based on severity + red flags)
124
+ - Ask 2 follow-up questions
125
+ Add: "Not medical advice"
126
+ """.strip()
127
+
128
+ # -----------------------------
129
+ # Streamlit UI
130
+ # -----------------------------
131
+ st.set_page_config(page_title="Skin Disease Demo", page_icon="🧴", layout="centered")
132
+
133
+ st.title("🧴 Skin Disease Prediction Demo")
134
+ st.write("Upload a skin image + type symptoms text. The model shows **Top-3 predictions** and a simple severity suggestion.")
135
+
136
+ model, classes = load_model_and_classes()
137
+
138
+ st.caption(f"Running on **{DEVICE.upper()}** | Model: {TIMM_MODEL_NAME} | Classes: {len(classes)}")
139
+
140
+ img_file = st.file_uploader("Upload skin image (jpg/png)", type=["jpg", "jpeg", "png"])
141
+ symptoms = st.text_area("Symptoms (example: itchy red patch, burning, spreading, fever?)", height=100)
142
+
143
+ colA, colB = st.columns(2)
144
+ with colA:
145
+ use_llm = st.checkbox("Use LLM explanation (Ollama)", value=USE_LLM)
146
+ with colB:
147
+ st.write("")
148
+
149
+ if img_file is not None:
150
+ pil_img = Image.open(io.BytesIO(img_file.read()))
151
+ st.image(pil_img, caption="Uploaded Image", use_container_width=True)
152
+
153
+ if st.button("Predict"):
154
+ top3 = predict_image(model, pil_img, classes)
155
+ top1 = top3[0]
156
+ severity, doctor_consult = severity_from_label(top1["label"], symptoms)
157
+
158
+ st.subheader("✅ Prediction")
159
+ st.write(f"**Top-1:** `{top1['label']}` — **Confidence:** `{top1['confidence']*100:.2f}%`")
160
+
161
+ # Confidence bar
162
+ st.progress(min(int(top1["confidence"] * 100), 100))
163
+
164
+ st.subheader("Top-3 (recommended in demo)")
165
+ for i, item in enumerate(top3, start=1):
166
+ st.write(f"**{i}.** `{item['label']}` — `{item['confidence']*100:.2f}%`")
167
+
168
+ st.subheader("⚠️ Severity suggestion (rule-based)")
169
+ st.write(f"**Severity:** `{severity}`")
170
+ st.write(f"**Doctor consult needed?** `{doctor_consult}`")
171
+
172
+ st.info("This is a demo/education tool. Not medical advice.")
173
+
174
+ # LLM explanation
175
+ if use_llm:
176
+ st.subheader("🧠 LLM Explanation (simple language)")
177
+ try:
178
+ prompt = build_prompt(symptoms, top3, severity, doctor_consult)
179
+ explanation = call_ollama(prompt)
180
+ st.write(explanation)
181
+ except Exception as e:
182
+ st.warning(f"LLM not available. Reason: {e}")
183
+ st.write("Tip: Start Ollama + pull a model (phi3:mini).")
184
+ else:
185
+ st.warning("Upload an image to start.")
classes.json ADDED
@@ -0,0 +1 @@
 
 
1
+ ["acne_rosacea", "autoimmune", "bacterial", "bullous", "drug_exanthem", "eczema_dermatitis", "fungal", "hair_nail", "hives", "infestation_bite", "pigment", "psoriasis_lichen", "systemic", "tumor_benign", "tumor_malignant", "viral"]
metrics.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "train_mode": "GROUPED_8",
3
+ "top1_accuracy": 0.6904047976011994,
4
+ "top3_accuracy": 0.8405797101449275,
5
+ "f1_macro": 0.6349998743100523,
6
+ "f1_weighted": 0.687291371392067,
7
+ "classes": [
8
+ "acne_rosacea",
9
+ "autoimmune",
10
+ "bacterial",
11
+ "bullous",
12
+ "drug_exanthem",
13
+ "eczema_dermatitis",
14
+ "fungal",
15
+ "hair_nail",
16
+ "hives",
17
+ "infestation_bite",
18
+ "pigment",
19
+ "psoriasis_lichen",
20
+ "systemic",
21
+ "tumor_benign",
22
+ "tumor_malignant",
23
+ "viral"
24
+ ]
25
+ }
skin_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:543f630f52f752fbe8e26fad84bbea764bc8ca8cefb689e493d7cc8d02cb4bc9
3
+ size 16407865