import numpy as np import pandas as pd import gradio as gr import pickle import plotly.graph_objects as go from plotly.subplots import make_subplots # Load model weights and metadata with open('model_weights.pkl', 'rb') as f: model_weights = pickle.load(f) with open('scaler.pkl', 'rb') as f: scaler = pickle.load(f) with open('feature_names.pkl', 'rb') as f: feature_names = pickle.load(f) with open('continuous_cols.pkl', 'rb') as f: continuous_cols = pickle.load(f) with open('n_models.pkl', 'rb') as f: n_models = pickle.load(f) # Load individual models models = {} model_names = [ "logistic_regression", "decision_tree", "gradient_boosting", "knn", "svm", "random_forest", "xgboost" ] model_display_names = { "logistic_regression": "Logistic Regression", "decision_tree": "Decision Tree", "gradient_boosting": "Gradient Boosting", "knn": "KNN", "svm": "SVM", "random_forest": "Random Forest", "xgboost": "XGBoost" } for model_name in model_names: with open(f'model_{model_name}.pkl', 'rb') as f: models[model_display_names[model_name]] = pickle.load(f) print(f"Loaded {len(models)} models successfully!") def create_gauge_chart(probability): """Create a gauge chart for risk probability""" # Determine color based on risk level if probability < 30: color = "#10b981" # Green risk_level = "Low Risk" elif probability < 50: color = "#f59e0b" # Orange risk_level = "Moderate Risk" elif probability < 70: color = "#ef4444" # Red risk_level = "High Risk" else: color = "#dc2626" # Dark Red risk_level = "Very High Risk" fig = go.Figure(go.Indicator( mode = "gauge+number+delta", value = probability, domain = {'x': [0, 1], 'y': [0, 1]}, title = {'text': f"{risk_level}", 'font': {'size': 24, 'color': color}}, number = {'suffix': "%", 'font': {'size': 48, 'color': color}}, gauge = { 'axis': {'range': [None, 100], 'tickwidth': 2, 'tickcolor': "gray"}, 'bar': {'color': color, 'thickness': 0.75}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 30], 'color': '#d1fae5'}, {'range': [30, 50], 'color': '#fef3c7'}, {'range': [50, 70], 'color': '#fee2e2'}, {'range': [70, 100], 'color': '#fecaca'} ], 'threshold': { 'line': {'color': "black", 'width': 4}, 'thickness': 0.75, 'value': 50 } } )) fig.update_layout( height=300, margin=dict(l=20, r=20, t=80, b=20), paper_bgcolor="white", font={'family': "Arial"} ) return fig def create_summary_text(probability, weighted_sum, threshold, prediction_result, model_predictions, model_weights): """Create formatted HTML summary""" if weighted_sum > threshold: status_color = "#dc2626" status_icon = "⚠️" recommendation = """

⚠️ Action Recommended

""" else: status_color = "#10b981" status_icon = "✓" recommendation = """

✓ Lower Risk Detected

""" # Create model predictions breakdown predictions_breakdown = "" disease_count = sum(1 for p in model_predictions.values() if p == 1) safe_count = len(model_predictions) - disease_count html = f"""
{recommendation}

How This Works

This prediction uses an ensemble of 7 different machine learning models. Each model's prediction is weighted based on its performance metrics. The final decision is made when the weighted sum exceeds the threshold.

Medical Disclaimer: This tool is for educational purposes only and should not replace professional medical advice, diagnosis, or treatment. Always consult with qualified healthcare providers for medical decisions.

""" return html def predict_heart_disease(age, resting_bp, cholesterol, max_hr, oldpeak, fasting_bs, sex, chest_pain_type, resting_ecg, exercise_angina, st_slope): # Encode categorical inputs using LabelEncoder compatible mappings # These must match what LabelEncoder produced during training sex_mapping = {"Male": 1, "Female": 0} sex = sex_mapping.get(sex, 0) # Convert fasting_bs from string to integer fasting_bs = int(fasting_bs) # For string categorical values, LabelEncoder sorts them alphabetically: # ChestPainType: ["ASY", "ATA", "NAP", "TA"] -> 0, 1, 2, 3 chest_pain_type_mapping = {"ASY": 0, "ATA": 1, "NAP": 2, "TA": 3} chest_pain_type = chest_pain_type_mapping.get(chest_pain_type, 0) # RestingECG: ["LVH", "Normal", "ST"] -> 0, 1, 2 resting_ecg_mapping = {"LVH": 0, "Normal": 1, "ST": 2} resting_ecg = resting_ecg_mapping.get(resting_ecg, 1) # ExerciseAngina: ["N", "Y"] -> 0, 1 exercise_angina_mapping = {"No": 0, "Yes": 1} exercise_angina = exercise_angina_mapping.get(exercise_angina, 0) # ST_Slope: ["Down", "Flat", "Up"] -> 0, 1, 2 st_slope_mapping = {"Down": 0, "Flat": 1, "Up": 2} st_slope = st_slope_mapping.get(st_slope, 2) # Create input dataframe matching exact training column order input_df = pd.DataFrame({ "Age": [age], "Sex": [sex], "ChestPainType": [chest_pain_type], "RestingBP": [resting_bp], "Cholesterol": [cholesterol], "FastingBS": [fasting_bs], "RestingECG": [resting_ecg], "MaxHR": [max_hr], "ExerciseAngina": [exercise_angina], "Oldpeak": [oldpeak], "ST_Slope": [st_slope] }) # Ensure columns match feature_names order exactly input_df = input_df[feature_names] # Scale continuous features input_df[continuous_cols] = scaler.transform(input_df[continuous_cols]) # Get predictions from all models model_predictions = {} weighted_sum = 0 total_weights = sum(model_weights.values()) for name, model in models.items(): prediction = model.predict(input_df)[0] weight = model_weights[name] model_predictions[name] = prediction weighted_sum += prediction * weight # Calculate probability and threshold threshold = n_models / 2 # Probability is the weighted sum normalized by total possible weighted sum max_possible_sum = sum(model_weights.values()) probability = (weighted_sum / max_possible_sum) * 100 # Determine final prediction if weighted_sum > threshold: result = "High Risk Detected" else: result = "Low Risk - You Are Safe" # Create visualizations gauge_chart = create_gauge_chart(probability) summary_html = create_summary_text(probability, weighted_sum, threshold, result, model_predictions, model_weights) return summary_html, gauge_chart # Create Gradio interface with custom CSS css = """ .gradio-container { font-family: 'Arial', sans-serif; } .output-html { border: none !important; } """ with gr.Blocks(css=css, theme=gr.themes.Soft()) as iface: gr.Markdown( """ # HeartSense - Heart Disease Prediction System ### Advanced ML Ensemble for Cardiovascular Risk Assessment Enter patient details below to get a comprehensive risk analysis using 7 different machine learning models. """ ) with gr.Column(): with gr.Row(scale=1): with gr.Column(scale=1): gr.Markdown("### Patient Information") age = gr.Number(label="Age", value=50) sex = gr.Radio(["Female", "Male"], label="Sex", value="Male") fasting_bs = gr.Radio(["0", "1"], label="Fasting Blood Sugar", value="0") with gr.Column(scale=1): gr.Markdown("### Cardiac Measurements") resting_bp = gr.Number(label="Resting BP (mm Hg)", value=120) cholesterol = gr.Number(label="Cholesterol (mg/dL)", value=200) max_hr = gr.Number(label="Max Heart Rate", value=150) oldpeak = gr.Number(label="Oldpeak", value=0) with gr.Column(scale=1): gr.Markdown("### Clinical Indicators") chest_pain_type = gr.Radio(["ASY", "ATA", "NAP", "TA"], label="Chest Pain Type", value="ASY") resting_ecg = gr.Radio(["Normal", "ST", "LVH"], label="Resting ECG", value="Normal") exercise_angina = gr.Radio(["No", "Yes"], label="Exercise Angina", value="No") st_slope = gr.Radio(["Up", "Flat", "Down"], label="ST Slope", value="Flat") predict_btn = gr.Button("Analyze Risk", variant="primary", size="lg") with gr.Column(scale=2): gauge_output = gr.Plot(label="Risk Gauge") summary_output = gr.HTML(label="Summary") gr.Markdown( """ ### Example Cases Try these example inputs to see how the system works: """ ) gr.Examples( examples=[ [55, 130, 250, 140, 1.0, "0", "Male", "ASY", "Normal", "No", "Flat"], [45, 110, 180, 160, 0, "0", "Female", "NAP", "Normal", "No", "Up"], [60, 140, 280, 130, 2.0, "1", "Male", "ASY", "ST", "Yes", "Down"], ], inputs=[age, resting_bp, cholesterol, max_hr, oldpeak, fasting_bs, sex, chest_pain_type, resting_ecg, exercise_angina, st_slope], ) predict_btn.click( fn=predict_heart_disease, inputs=[age, resting_bp, cholesterol, max_hr, oldpeak, fasting_bs, sex, chest_pain_type, resting_ecg, exercise_angina, st_slope], outputs=[summary_output, gauge_output] ) if __name__ == "__main__": iface.launch()