JoanaS's picture
Update app.py
e8a4c36 verified
## 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()