Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import joblib
|
3 |
+
import os
|
4 |
+
import pandas as pd
|
5 |
+
|
6 |
+
# Dictionary containing the model names and corresponding pickle file names
|
7 |
+
model_paths = {
|
8 |
+
'AdaBoost': 'pjas-thyroid-AdaBoost.pkl',
|
9 |
+
'Decision Tree': 'pjas-thyroid-Decision Tree.pkl',
|
10 |
+
'Gaussian Naive Bayes': 'pjas-thyroid-Gaussian Naive Bayes.pkl',
|
11 |
+
'Gradient Boosting': 'pjas-thyroid-Gradient Boosting.pkl',
|
12 |
+
'K-Nearest Neighbors': 'pjas-thyroid-K-Nearest Neighbors.pkl',
|
13 |
+
'Logistic Regression': 'pjas-thyroid-Logistic Regression.pkl',
|
14 |
+
'Random Forest': 'pjas-thyroid-Random Forest.pkl',
|
15 |
+
'Support Vector Machine': 'pjas-thyroid-Support Vector Machine.pkl',
|
16 |
+
'XGBoost': 'pjas-thyroid-XGBoost.pkl'
|
17 |
+
}
|
18 |
+
|
19 |
+
# Example: Hard-coded dictionary of accuracies per model.
|
20 |
+
model_accuracies = {
|
21 |
+
'AdaBoost': 97.13,
|
22 |
+
'Logistic Regression': 96.87,
|
23 |
+
'Gaussian Naive Bayes': 95.3,
|
24 |
+
'Gradient Boosting': 98.96,
|
25 |
+
'Support Vector Machine': 96.34,
|
26 |
+
'Decision Tree': 97.91,
|
27 |
+
'K-Nearest Neighbors': 97.13,
|
28 |
+
'Random Forest': 98.96,
|
29 |
+
'XGBoost': 98.43
|
30 |
+
}
|
31 |
+
|
32 |
+
# We assume:
|
33 |
+
# 0 -> Cancer cannot recur
|
34 |
+
# 1 -> Cancer can recur
|
35 |
+
|
36 |
+
response_details = {
|
37 |
+
"0": """**Excellent Response**
|
38 |
+
Negative imaging studies and suppressed thyroglobulin levels
|
39 |
+
(below 0.2 ng/mL or stimulated Tg below 1 ng/mL).""",
|
40 |
+
"1": """**Indeterminate Response**
|
41 |
+
Nonspecific findings on imaging studies, making it difficult
|
42 |
+
to confidently classify as benign or malignant,
|
43 |
+
with potentially low thyroglobulin levels.""",
|
44 |
+
"2": """**Biochemical Incomplete**
|
45 |
+
Negative imaging but elevated thyroglobulin levels
|
46 |
+
(suppressed Tg above 1 ng/mL or stimulated Tg above 10 ng/mL)
|
47 |
+
or rising anti-Tg antibody levels.""",
|
48 |
+
"3": """**Structural Incomplete**
|
49 |
+
Presence of identifiable structural disease on imaging,
|
50 |
+
regardless of thyroglobulin level."""
|
51 |
+
}
|
52 |
+
|
53 |
+
def predict_cancer(age, gender, response, tumor_size, lymph_node_spread, focality):
|
54 |
+
"""
|
55 |
+
Generates a Markdown table with emoji icons for each model's prediction,
|
56 |
+
including 'TumorSize', 'LymphNodeSpread', and 'Focality' as additional features.
|
57 |
+
"""
|
58 |
+
# 1. Load your pre-fitted scaler from disk
|
59 |
+
scaler_file = "model/pjas-thyroid-scaler.pkl"
|
60 |
+
if not os.path.exists(scaler_file):
|
61 |
+
return "Error: Scaler file not found. Please check your path or name."
|
62 |
+
|
63 |
+
scaler = joblib.load(scaler_file)
|
64 |
+
|
65 |
+
# 2. Simple encodings for demonstration
|
66 |
+
gender_val = 0 if gender == "Female" else 1
|
67 |
+
response_val = int(response)
|
68 |
+
tumor_val = int(tumor_size) # Convert Tumor Size dropdown value to int
|
69 |
+
lymph_val = int(lymph_node_spread) # Convert Lymph Node Spread dropdown value to int
|
70 |
+
focality_val = int(focality) # Convert Focality dropdown value to int
|
71 |
+
|
72 |
+
# 3. Create a DataFrame for the features
|
73 |
+
features = pd.DataFrame({
|
74 |
+
'Age': [age],
|
75 |
+
'Gender': [gender_val],
|
76 |
+
'T': [tumor_val],
|
77 |
+
'N': [lymph_val],
|
78 |
+
'Focality': [focality_val],
|
79 |
+
'Response': [response_val]
|
80 |
+
})
|
81 |
+
|
82 |
+
# 4. Remove any NaN
|
83 |
+
features = features.dropna()
|
84 |
+
|
85 |
+
# 5. Scale the 'Age' column
|
86 |
+
# If you have more features to scale, include them here accordingly
|
87 |
+
features[['Age']] = scaler.transform(features[['Age']])
|
88 |
+
|
89 |
+
# 6. Sort models by accuracy (descending)
|
90 |
+
sorted_model_names = sorted(
|
91 |
+
model_paths.keys(),
|
92 |
+
key=lambda m: model_accuracies[m],
|
93 |
+
reverse=True
|
94 |
+
)
|
95 |
+
|
96 |
+
# 7. Build a Markdown table
|
97 |
+
table_header = (
|
98 |
+
"| **Model** | **Accuracy** | **Prediction** |\n"
|
99 |
+
"|-----------------------------|--------------|--------------------------------|\n"
|
100 |
+
)
|
101 |
+
table_rows = []
|
102 |
+
|
103 |
+
# 8. Emojis for predictions
|
104 |
+
can_recur_emoji = "🔴" # "Cancer can recur"
|
105 |
+
cannot_recur_emoji = "🟢" # "Cancer cannot-recur"
|
106 |
+
|
107 |
+
# 9. Iterate through each model and make predictions
|
108 |
+
for model_name in sorted_model_names:
|
109 |
+
pickle_file = model_paths[model_name]
|
110 |
+
model_file_path = os.path.join("model", pickle_file)
|
111 |
+
|
112 |
+
if not os.path.exists(model_file_path):
|
113 |
+
row = f"| {model_name} | N/A | **Error**: file not found |"
|
114 |
+
table_rows.append(row)
|
115 |
+
continue
|
116 |
+
|
117 |
+
model = joblib.load(model_file_path)
|
118 |
+
prediction = model.predict(features) # e.g., [0] or [1]
|
119 |
+
pred_value = prediction[0]
|
120 |
+
|
121 |
+
# 10. Convert numeric prediction to icon + text
|
122 |
+
if pred_value == 1:
|
123 |
+
pred_text = f"{can_recur_emoji} Sorry, Your Cancer can recur"
|
124 |
+
else:
|
125 |
+
pred_text = f"{cannot_recur_emoji} Great News! Your Cancer cannot-recur"
|
126 |
+
|
127 |
+
accuracy = model_accuracies.get(model_name, "N/A")
|
128 |
+
row = f"| {model_name} | {accuracy}% | {pred_text} |"
|
129 |
+
table_rows.append(row)
|
130 |
+
|
131 |
+
# 11. Combine into a single Markdown table
|
132 |
+
md_table = table_header + "\n".join(table_rows)
|
133 |
+
return md_table
|
134 |
+
|
135 |
+
def clear_md():
|
136 |
+
"""Clears the Markdown output."""
|
137 |
+
return ""
|
138 |
+
|
139 |
+
with gr.Blocks() as demo:
|
140 |
+
gr.Markdown("# Thyroid Cancer Recurrence Predictor")
|
141 |
+
|
142 |
+
# Existing inputs
|
143 |
+
age_slider = gr.Slider(
|
144 |
+
minimum=1,
|
145 |
+
maximum=100,
|
146 |
+
step=1,
|
147 |
+
label="Age",
|
148 |
+
value=44,
|
149 |
+
interactive=True
|
150 |
+
)
|
151 |
+
|
152 |
+
gender_radio = gr.Radio(
|
153 |
+
choices=["Female", "Male"],
|
154 |
+
value="Female",
|
155 |
+
label="Gender",
|
156 |
+
interactive=True
|
157 |
+
)
|
158 |
+
|
159 |
+
# New Tumor Size dropdown with descriptive text
|
160 |
+
tumor_size_dropdown = gr.Dropdown(
|
161 |
+
choices=[
|
162 |
+
("T1a (≤1 cm, confined to the thyroid)", "0"),
|
163 |
+
("T1b (>1 cm and ≤2 cm, confined to the thyroid)", "1"),
|
164 |
+
("T2 (>2 cm and ≤4 cm, confined to the thyroid)", "2"),
|
165 |
+
("T3a (>4 cm, confined to the thyroid)", "3"),
|
166 |
+
("T3b (Minimal extrathyroidal extension)", "4"),
|
167 |
+
("T4a (Moderate extrathyroidal extension, operable)", "5"),
|
168 |
+
("T4b (Extensive extrathyroidal extension, inoperable)", "6")
|
169 |
+
],
|
170 |
+
value="0", # Default T1a
|
171 |
+
label="Tumor Size",
|
172 |
+
interactive=True
|
173 |
+
)
|
174 |
+
|
175 |
+
# New Lymph Node Spread dropdown with descriptive text
|
176 |
+
lymph_node_dropdown = gr.Dropdown(
|
177 |
+
choices=[
|
178 |
+
("N0 (No spread to nearby lymph nodes)", "0"),
|
179 |
+
("N1a (Spread to lymph nodes in the neck close to the thyroid)", "1"),
|
180 |
+
("N1b (Spread to lymph nodes in the neck farther from the thyroid or upper chest)", "2")
|
181 |
+
],
|
182 |
+
value="0", # Default N0
|
183 |
+
label="Lymph Node Spread",
|
184 |
+
interactive=True
|
185 |
+
)
|
186 |
+
|
187 |
+
# New Focality of Differential Thyroid Cancer dropdown with descriptive text
|
188 |
+
focality_dropdown = gr.Dropdown(
|
189 |
+
choices=[
|
190 |
+
("Uni-focal (Single focus of thyroid cancer)", "1"),
|
191 |
+
("Multi-focal (Multiple foci of thyroid cancer)", "0")
|
192 |
+
],
|
193 |
+
value="1", # Default Uni-focal
|
194 |
+
label="Focality of Differential Thyroid Cancer",
|
195 |
+
interactive=True
|
196 |
+
)
|
197 |
+
|
198 |
+
response_dropdown = gr.Dropdown(
|
199 |
+
choices=[
|
200 |
+
("✅ Excellent Response - Negative imaging studies and suppressed thyroglobulin levels (below 0.2 ng/mL or stimulated Tg below 1 ng/mL)", "0"),
|
201 |
+
("❓ Indeterminate Response - Nonspecific findings on imaging studies with potentially low thyroglobulin levels", "1"),
|
202 |
+
("⚠️ Biochemical Incomplete - Negative imaging but elevated thyroglobulin levels or rising anti-Tg antibody levels", "2"),
|
203 |
+
("❌ Structural Incomplete - Presence of identifiable structural disease on imaging, regardless of thyroglobulin level", "3")
|
204 |
+
],
|
205 |
+
value="0", # Default
|
206 |
+
label="Response",
|
207 |
+
interactive=True
|
208 |
+
)
|
209 |
+
|
210 |
+
# Predict button
|
211 |
+
predict_button = gr.Button(
|
212 |
+
value="Predict",
|
213 |
+
variant="primary"
|
214 |
+
)
|
215 |
+
prediction_output = gr.Markdown(label="Prediction Results")
|
216 |
+
|
217 |
+
# Include all dropdowns as inputs for the predict function
|
218 |
+
predict_button.click(
|
219 |
+
fn=predict_cancer,
|
220 |
+
inputs=[age_slider, gender_radio, response_dropdown, tumor_size_dropdown, lymph_node_dropdown, focality_dropdown],
|
221 |
+
outputs=prediction_output
|
222 |
+
)
|
223 |
+
|
224 |
+
demo.launch()
|