Skip to content

Commit 250e272

Browse files
authored
Create HeartPredictor.py
1 parent 286b236 commit 250e272

1 file changed

Lines changed: 274 additions & 0 deletions

File tree

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
"""
2+
HeartPredictor v2.0 - Health Intelligence Tool
3+
Predicts risk of heart disease from patient data using a logistic regression model
4+
Supports batch CSV input with smooth UI and real-time results
5+
"""
6+
7+
import os, sys, threading, csv, numpy as np, pandas as pd
8+
import tkinter as tk
9+
from tkinter import filedialog, messagebox, ttk
10+
11+
from sklearn.linear_model import LogisticRegression
12+
from sklearn.preprocessing import StandardScaler
13+
from sklearn.model_selection import train_test_split
14+
15+
import ttkbootstrap as tb
16+
from ttkbootstrap.constants import *
17+
18+
# ---------------------- UTIL ----------------------
19+
def resource_path(file_name):
20+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
21+
return os.path.join(base_path, file_name)
22+
23+
# ---------------------- ML MODEL ----------------------
24+
class HeartDiseaseModel:
25+
def __init__(self):
26+
self.model = LogisticRegression(max_iter=1000)
27+
self.scaler = StandardScaler()
28+
self.features = ["age","sex","cp","trestbps","chol","fbs","restecg",
29+
"thalach","exang","oldpeak","slope","ca","thal"]
30+
self._train_dummy_model()
31+
32+
def _train_dummy_model(self):
33+
"""
34+
Creates a dummy dataset to train a simple logistic regression model.
35+
In practice, replace this with a real dataset.
36+
"""
37+
np.random.seed(42)
38+
X = np.random.randint(0, 100, size=(500, len(self.features)))
39+
y = np.random.randint(0, 2, size=(500,))
40+
X_scaled = self.scaler.fit_transform(X)
41+
self.model.fit(X_scaled, y)
42+
43+
def predict_risk(self, patient_data):
44+
"""
45+
patient_data: dict of feature_name -> value
46+
Returns: "Low", "Medium", "High"
47+
"""
48+
try:
49+
x = np.array([float(patient_data.get(f,0)) for f in self.features]).reshape(1,-1)
50+
x_scaled = self.scaler.transform(x)
51+
prob = self.model.predict_proba(x_scaled)[0][1] # probability of heart disease
52+
if prob < 0.33:
53+
return "Low"
54+
elif prob < 0.66:
55+
return "Medium"
56+
else:
57+
return "High"
58+
except Exception:
59+
return "Low"
60+
61+
# ---------------------- WORKER ----------------------
62+
class PredictionWorker:
63+
def __init__(self, files, model, callbacks):
64+
self.files = files
65+
self.model = model
66+
self._running = True
67+
self.callbacks = callbacks
68+
69+
def stop(self):
70+
self._running = False
71+
72+
def run(self):
73+
total_files = len(self.files)
74+
results = []
75+
76+
for i, path in enumerate(self.files):
77+
if not self._running:
78+
break
79+
try:
80+
df = pd.read_csv(path)
81+
for _, row in df.iterrows():
82+
if not self._running:
83+
break
84+
patient_data = row.to_dict()
85+
name = patient_data.get("name","Unknown")
86+
risk = self.model.predict_risk(patient_data)
87+
results.append((path, name, risk))
88+
if "found" in self.callbacks:
89+
self.callbacks["found"](path, name, risk)
90+
except Exception as e:
91+
print(f"Failed to process {path}: {e}")
92+
93+
if total_files > 0 and "progress" in self.callbacks:
94+
self.callbacks["progress"](int((i + 1) / total_files * 100))
95+
96+
if "finished" in self.callbacks:
97+
self.callbacks["finished"]()
98+
99+
# ---------------------- MAIN APP ----------------------
100+
class HeartPredictorApp:
101+
APP_NAME = "HeartPredictor"
102+
APP_VERSION = "2.0"
103+
SUPPORTED_EXT = (".csv",)
104+
105+
def __init__(self):
106+
self.root = tb.Window(themename="darkly")
107+
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
108+
self.root.minsize(1000, 600)
109+
110+
self.worker_obj = None
111+
self.model = HeartDiseaseModel()
112+
self.smooth_value = 0
113+
self.target_progress = 0
114+
self.file_set = set()
115+
116+
self._build_ui()
117+
self._apply_styles()
118+
119+
# ---------------------- UI ----------------------
120+
def _build_ui(self):
121+
main = tb.Frame(self.root, padding=10)
122+
main.pack(fill=BOTH, expand=True)
123+
124+
tb.Label(main, text=f"❤️ {self.APP_NAME} - Health Intelligence",
125+
font=("Segoe UI", 22, "bold")).pack(pady=(0, 4))
126+
tb.Label(main, text="Predict heart disease risk from patient CSV files",
127+
font=("Segoe UI", 10, "italic"), foreground="#9ca3af").pack(pady=(0, 20))
128+
129+
# Row 1: File selection
130+
row1 = tb.Frame(main)
131+
row1.pack(fill=X, pady=(0,6))
132+
133+
self.path_input = tb.Entry(row1, width=80)
134+
self.path_input.pack(side=LEFT, fill=X, expand=True, padx=(0,6))
135+
self.path_input.insert(0, "Select patient CSV files here…")
136+
137+
browse_btn = tb.Button(row1, text="📂 Browse", bootstyle=INFO, command=self.browse)
138+
browse_btn.pack(side=LEFT, padx=3)
139+
140+
self.start_btn = tb.Button(row1, text="🚀 Start Prediction", bootstyle=SUCCESS, command=self.start)
141+
self.start_btn.pack(side=LEFT, padx=3)
142+
143+
self.cancel_btn = tb.Button(row1, text="⏹ Cancel", bootstyle=DANGER, command=self.cancel)
144+
self.cancel_btn.pack(side=LEFT, padx=3)
145+
self.cancel_btn.config(state=DISABLED)
146+
147+
export_btn = tb.Button(row1, text="💾 Export Results", bootstyle=PRIMARY, command=self.export_results)
148+
export_btn.pack(side=LEFT, padx=3)
149+
150+
# Progress
151+
self.progress = tb.Progressbar(main, bootstyle="success-striped", maximum=100)
152+
self.progress.pack(fill=X, pady=(0,6))
153+
154+
# Treeview
155+
columns = ("selected", "filename", "patient", "risk")
156+
self.tree = ttk.Treeview(main, columns=columns, show="headings", selectmode="extended", height=20)
157+
self.tree.heading("selected", text="✅")
158+
self.tree.heading("filename", text="Filename")
159+
self.tree.heading("patient", text="Patient Name")
160+
self.tree.heading("risk", text="Predicted Risk")
161+
self.tree.column("selected", width=50, anchor=CENTER)
162+
self.tree.column("filename", width=300)
163+
self.tree.column("patient", width=200)
164+
self.tree.column("risk", width=100)
165+
self.tree.pack(fill=BOTH, expand=True, pady=(0,6))
166+
167+
self.stats_label = tb.Label(main, text="TOTAL: 0 | LOW: 0 | MEDIUM: 0 | HIGH: 0")
168+
self.stats_label.pack(anchor=E)
169+
170+
self.root.after(15, self.animate_progress)
171+
172+
# ---------------------- Browse ----------------------
173+
def browse(self):
174+
paths = filedialog.askopenfilenames(filetypes=[("CSV Files", "*.csv")])
175+
if paths:
176+
for path in paths:
177+
if path not in self.file_set:
178+
self.file_set.add(path)
179+
self.tree.insert("", END, values=("☑️", path, "-", "-"))
180+
181+
# ---------------------- Actions ----------------------
182+
def start(self):
183+
selected_files = [self.tree.item(i)['values'][1] for i in self.tree.get_children()
184+
if self.tree.item(i)['values'][0]=="☑️"]
185+
if not selected_files:
186+
messagebox.showwarning("No Selection", "Select CSV files before prediction.")
187+
return
188+
self.progress["value"] = 0
189+
self.smooth_value = 0
190+
self.target_progress = 0
191+
self.start_btn.config(state=DISABLED)
192+
self.cancel_btn.config(state=NORMAL)
193+
threading.Thread(target=self._run_worker, args=(selected_files,), daemon=True).start()
194+
195+
def _run_worker(self, files):
196+
self.worker_obj = PredictionWorker(
197+
files,
198+
model=self.model,
199+
callbacks={
200+
"found": self.add_result,
201+
"progress": self.set_target,
202+
"finished": self.finish
203+
}
204+
)
205+
self.worker_obj.run()
206+
207+
def add_result(self, file, patient, risk):
208+
for i in self.tree.get_children():
209+
if self.tree.item(i)['values'][1] == file:
210+
self.tree.item(i, values=("☑️", file, patient, risk))
211+
colors = {"High":"#dc2626","Medium":"#facc15","Low":"#4ade80"}
212+
self.tree.tag_configure(risk, foreground=colors.get(risk))
213+
self.tree.item(i, tags=(risk,))
214+
self.update_stats()
215+
break
216+
217+
def update_stats(self):
218+
counts = {"Low":0,"Medium":0,"High":0,"TOTAL":0}
219+
for i in self.tree.get_children():
220+
risk = self.tree.item(i)['values'][3]
221+
if risk in counts:
222+
counts[risk] += 1
223+
counts["TOTAL"] += 1
224+
self.stats_label.config(text=f"TOTAL: {counts['TOTAL']} | LOW: {counts['Low']} | MEDIUM: {counts['Medium']} | HIGH: {counts['High']}")
225+
226+
def set_target(self, v):
227+
self.target_progress = v
228+
229+
def animate_progress(self):
230+
if self.smooth_value < self.target_progress:
231+
self.smooth_value += 1
232+
self.progress["value"] = self.smooth_value
233+
self.root.after(15, self.animate_progress)
234+
235+
def cancel(self):
236+
if self.worker_obj:
237+
self.worker_obj.stop()
238+
self.finish()
239+
240+
def finish(self):
241+
self.start_btn.config(state=NORMAL)
242+
self.cancel_btn.config(state=DISABLED)
243+
self.progress["value"] = 100
244+
245+
# ---------------------- Export ----------------------
246+
def export_results(self):
247+
selected_files = [self.tree.item(i)['values'] for i in self.tree.get_children()
248+
if self.tree.item(i)['values'][0]=="☑️"]
249+
if not selected_files:
250+
messagebox.showwarning("Export", "No selected results to export")
251+
return
252+
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV Files","*.csv")])
253+
if path:
254+
with open(path,"w",encoding="utf-8", newline='') as f:
255+
writer = csv.writer(f)
256+
writer.writerow(["Filename","Patient Name","Predicted Risk"])
257+
for s in selected_files:
258+
writer.writerow([s[1], s[2], s[3]])
259+
messagebox.showinfo("Export", "Export completed")
260+
261+
# ---------------------- Styles ----------------------
262+
def _apply_styles(self):
263+
style = tb.Style(theme="darkly") # don't assign to self.root.style
264+
style.configure("TProgressbar", troughcolor="#1b1f3a", background="#7c3aed", thickness=14)
265+
266+
# ---------------------- Run ----------------------
267+
def run(self):
268+
self.root.mainloop()
269+
270+
271+
# ---------------------- RUN ----------------------
272+
if __name__ == "__main__":
273+
app = HeartPredictorApp()
274+
app.run()

0 commit comments

Comments
 (0)