Spaces:
Sleeping
Sleeping
| ## Streaming Evaluation | |
| ##Import Libraries | |
| import pandas as pd | |
| import json | |
| from joblib import load | |
| import gradio as gr | |
| import time | |
| from sklearn.metrics import accuracy_score | |
| import plotly.express as px | |
| ## Load trained model | |
| model = load("rf_activity_model.pkl") | |
| ## Load label and integer mapping json file | |
| with open("label_map.json", "r") as f: | |
| label_to_int = json.load(f) | |
| int_to_label = {v: k for k, v in label_to_int.items()} | |
| ##Load human readable activity names | |
| with open("activity_names.json", "r") as f: | |
| activity_names = json.load(f) | |
| ## Risk map, each activity is mapped to a risk level, low, medium, high | |
| risk_map = { | |
| "A": "Medium", "B": "High", "C": "High", "D": "Low", "E": "Medium", | |
| "F": "Low", "G": "Low", "H": "Medium", "I": "Medium", "J": "Medium", | |
| "K": "Medium", "L": "Medium", "M": "High", "O": "High", "P": "High", | |
| "Q": "Low", "R": "Low", "S": "Low" | |
| } | |
| ## Risk level mapped to numbers, this is for escalation in logic being used later on | |
| risk_to_num = {"Low": 1, "Medium": 2, "High": 3} | |
| def stream_csv(file, high_min, med_min, low_min): | |
| ## Threshold in minutes, converted to number of samples, where each sample is 10 seconds | |
| HIGH_THRESHOLD = int((high_min * 60) / 10) | |
| MEDIUM_THRESHOLD = int((med_min * 60) / 10) | |
| LOW_THRESHOLD = int((low_min * 60) / 10) | |
| ## Load synthetic dataset | |
| df = pd.read_csv(file) | |
| ## Drop the ACTIVITY column, only keeping the features columns | |
| X = df.drop(columns=["ACTIVITY"], errors="ignore") | |
| y_true = df["ACTIVITY"].map(int_to_label) if "ACTIVITY" in df.columns else None | |
| ## Get predictions from the model | |
| preds_int = model.predict(X) | |
| ## Convert integer prediction back into activity code, A, B, C, D, etc | |
| preds_code = [int_to_label[i] for i in preds_int] | |
| ## Convert the activity code into the human-readable activity name ie. Walking, Running | |
| preds_activity = [activity_names[c] for c in preds_code] | |
| ## Map activity codes into their risk categories using risk mapping, high, medium, low | |
| preds_risk = [risk_map[c] for c in preds_code] | |
| ## Threshold in minutes, converted to number of samples, where each sample is 10 seconds | |
| high_count = medium_count = low_count = 0 | |
| latest_alert = "โ No alerts triggered." | |
| alert_history, active_alert, prev_risk = [], False, None | |
| timeline, alert_markers = [], [] | |
| ## Iterate over predicted risk values, one sample at a time | |
| for i, (code, activity, risk) in enumerate(zip(preds_code, preds_activity, preds_risk)): | |
| ## Convert the row number into minutes, with each row = 10 seconds, to know how much time have passed | |
| elapsed_min = round((i * 10) / 60, 1) # each step = 10s | |
| ## Check if the risk level have changed since the last step | |
| if prev_risk and prev_risk != risk: | |
| if active_alert: | |
| ## If risk increased ie. Medium to High, keep active alert, print out that risk escalated | |
| if risk_to_num[risk] > risk_to_num[prev_risk]: | |
| msg = f"โฌ๏ธ Risk escalated: {prev_risk} โ {risk}" | |
| alert_history.append(msg) | |
| else: | |
| ## If risk decreased, clear the alert | |
| msg = f"โ Alert cleared ({prev_risk} โ {risk})" | |
| alert_history.append(msg) | |
| active_alert = False | |
| else: | |
| ## If no alert is active, log the risk changed | |
| msg = f"โน๏ธ Risk changed: {prev_risk} โ {risk}" | |
| alert_history.append(msg) | |
| ## Update the previous risk to the current risk | |
| prev_risk = risk | |
| ## Checks if the current risk is high | |
| if risk == "High": | |
| high_count += 1; medium_count = 0; low_count = 0 | |
| ## If high count exceeds the high threshold, print alert | |
| if high_count == HIGH_THRESHOLD: | |
| msg = f"โ ๏ธ ALERT: High-risk activity exceeded threshold ({high_min} min) at t={elapsed_min} min" | |
| latest_alert, active_alert = msg, True | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ High exceeded"}) | |
| ## If high count exceeds the high threshold, print alert ongoing every 1 minute | |
| elif high_count > HIGH_THRESHOLD and high_count % 6 == 0: | |
| msg = f"โ ๏ธ ALERT: High-risk activity still ongoing at t={elapsed_min} min" | |
| latest_alert = msg | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ High ongoing"}) | |
| # Checks if the current risk is medium | |
| elif risk == "Medium": | |
| medium_count += 1; high_count = 0; low_count = 0 | |
| ## If medium count exceeds the medium threshold, print alert | |
| if medium_count == MEDIUM_THRESHOLD: | |
| msg = f"โ ๏ธ ALERT: Medium-risk activity exceeded threshold ({med_min} min) at t={elapsed_min} min" | |
| latest_alert, active_alert = msg, True | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ Med exceeded"}) | |
| ## If medium count exceeds the medium thrshold, print alert ongoing every 1 minute | |
| elif medium_count > MEDIUM_THRESHOLD and medium_count % 6 == 0: | |
| msg = f"โ ๏ธ ALERT: Medium-risk activity still ongoing at t={elapsed_min} min" | |
| latest_alert = msg | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ Med ongoing"}) | |
| elif risk == "Low": | |
| low_count += 1; high_count = 0; medium_count = 0 | |
| ## If low count exceeds the low threshold, print alert | |
| if low_count == LOW_THRESHOLD: | |
| msg = f"โ ๏ธ ALERT: Low activity/inactivity exceeded threshold ({low_min} min) at t={elapsed_min} min" | |
| latest_alert, active_alert = msg, True | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ Low exceeded"}) | |
| ## If low count exceeds the low thrshold, print alert ongoing every 1 minute | |
| elif low_count > LOW_THRESHOLD and low_count % 6 == 0: | |
| msg = f"โ ๏ธ ALERT: Low activity/inactivity still ongoing at t={elapsed_min} min" | |
| latest_alert = msg | |
| alert_history.append(msg) | |
| alert_markers.append({"Minute": elapsed_min, "Risk": risk_to_num[risk], "Text": "โ ๏ธ Low ongoing"}) | |
| # Running accuracy | |
| if y_true is not None: | |
| running_acc = accuracy_score(y_true[:i+1], preds_code[:i+1]) | |
| acc_text = f"๐ Running Accuracy: {running_acc:.3f} (up to step {i+1})" | |
| actual_name = activity_names[y_true.iloc[i]] | |
| else: | |
| acc_text, actual_name = "No ground truth available.", "N/A" | |
| # Update timeline | |
| timeline.append({"Minute": elapsed_min, "Risk": risk}) | |
| # Chart with moving window | |
| timeline_df = pd.DataFrame(timeline) | |
| fig = px.line( | |
| timeline_df, | |
| x="Minute", | |
| y=[risk_to_num[r] for r in timeline_df["Risk"]], | |
| labels={"Minute": "Minutes", "y": "Risk"}, | |
| ) | |
| fig.update_traces(mode="lines+markers") | |
| # Add markers for alerts (staggered labels, font size unchanged) | |
| if alert_markers: | |
| am_df = pd.DataFrame(alert_markers) | |
| positions = ["top center" if i % 2 == 0 else "bottom center" for i in range(len(am_df))] | |
| fig.add_scatter( | |
| x=am_df["Minute"], | |
| y=am_df["Risk"], | |
| mode="markers+text", | |
| marker=dict(color="red", size=10, symbol="triangle-up"), | |
| text=am_df["Text"], | |
| textposition=positions, # staggered top/bottom | |
| name="Alerts" | |
| ) | |
| # To enable scrolling without compression in the graph | |
| fig.update_layout( | |
| yaxis=dict( | |
| tickmode="array", | |
| tickvals=[1, 2, 3], | |
| ticktext=["Low", "Medium", "High"] | |
| ), | |
| xaxis=dict( | |
| range=[max(0, elapsed_min-10), elapsed_min+1], # moving 10-min window | |
| fixedrange=False | |
| ), | |
| height=500, | |
| margin=dict(l=40, r=40, t=40, b=120) | |
| ) | |
| yield ( | |
| pd.DataFrame({ | |
| "Predicted": [activity], | |
| "Risk": [risk], | |
| "Actual": [actual_name] | |
| }), | |
| latest_alert, | |
| "\n".join(alert_history) if alert_history else "No alerts so far", | |
| acc_text, | |
| fig | |
| ) | |
| time.sleep(1) | |
| ## Gradio User Interface | |
| with gr.Blocks() as demo: | |
| gr.Markdown("## ๐ฉบ Healthcare Activity Recognition (Streaming)") | |
| gr.Markdown( | |
| "Upload a CSV file with activity data and set the time limits (in minutes) for High, Medium, " | |
| "and Low-risk activities. The system will then play through the activities step by step, " | |
| "showing predictions in real time. If any activity lasts longer than the set time limit, an alert will appear. " | |
| "You can also see the full history of alerts and a timeline chart that tracks risk levels, " | |
| "with clear markers showing when a risk started, kept going, or was cleared." | |
| ) | |
| with gr.Row(): | |
| file_input = gr.File(type="filepath", file_types=[".csv"], label="๐ Upload CSV") | |
| with gr.Row(): | |
| high_min = gr.Number(label="โ ๏ธ High-risk threshold (minutes)", value=5, precision=0) | |
| med_min = gr.Number(label="โ ๏ธ Medium-risk threshold (minutes)", value=30, precision=0) | |
| low_min = gr.Number(label="โ ๏ธ Low-risk threshold (minutes)", value=15, precision=0) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| results_df = gr.Dataframe( | |
| headers=["Predicted", "Risk", "Actual"], | |
| label="๐ Streaming Predictions", | |
| ##This is for fixing only 3 columns for consistency | |
| datatype=["str", "str", "str"], | |
| col_count=(3, "fixed"), | |
| interactive=False | |
| ) | |
| risk_chart = gr.Plot(label="๐ Risk Timeline") | |
| with gr.Column(scale=1): | |
| ##Shows alert list or โNo alertsโ | |
| latest_alert_box = gr.Textbox(label="๐ Latest Alert", lines=3) | |
| alert_history_box = gr.Textbox(label="๐ Alert History", lines=12) | |
| ##Show accuracy and classification report | |
| accuracy_box = gr.Textbox(label="๐ Running Accuracy", lines=2) | |
| # When a file is uploaded, run analyze_csv() and show results in the table, metrics, and alerts | |
| file_input.change( | |
| fn=stream_csv, | |
| inputs=[file_input, high_min, med_min, low_min], | |
| outputs=[results_df, latest_alert_box, alert_history_box, accuracy_box, risk_chart] | |
| ) | |
| ##Start the Gradio app if this script is the main entry point | |
| if __name__ == "__main__": | |
| demo.launch() | |