import os import sys from pathlib import Path import joblib import numpy as np import pandas as pd import streamlit as st from huggingface_hub import hf_hub_download ARTIFACT_REPO = "wasicse/mvppred-artifacts" ARTIFACT_FILES = [ "angle_bundle.joblib", "bite_bundle.joblib", "distance_capacity_bundle.joblib", "endurance_bundle.joblib", "jump_accel_bundle.joblib", "jump_distance_bundle.joblib", "jump_power_bundle.joblib", "jump_vel_bundle.joblib", "sprint_bundle.joblib", ] @st.cache_resource def ensure_artifacts(): outdir = Path("artifacts_inference") outdir.mkdir(exist_ok=True) for name in ARTIFACT_FILES: target = outdir / name if not target.exists(): downloaded = hf_hub_download( repo_id=ARTIFACT_REPO, filename=name, repo_type="model", ) target.write_bytes(Path(downloaded).read_bytes()) ensure_artifacts() ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from infer import predict_with_confidence BUNDLE_DIR = "artifacts_inference" INTERVAL = "q90" CONFIDENCE_EMOJI = {"High": "🟢 High", "Medium": "🟡 Medium", "Low": "🔴 Low"} @st.cache_resource def load_bundle(path: str): return joblib.load(path) st.set_page_config(page_title="Lizard Performance Predictor", layout="wide") st.title("MVPpred: Lizard Performance Predictor") st.caption("Enter morphological measurements (use -1 for missing) to predict lizard performance traits.") if not os.path.isdir(BUNDLE_DIR): st.error(f"Bundle directory not found: {BUNDLE_DIR}") st.stop() bundle_files = sorted([f for f in os.listdir(BUNDLE_DIR) if f.endswith("_bundle.joblib")]) if not bundle_files: st.error("No *_bundle.joblib files found in bundle directory.") st.stop() targets = [f.replace("_bundle.joblib", "") for f in bundle_files] selected_targets = st.multiselect("Targets to predict", targets, default=targets) st.divider() default_sample = { "sex_num": 0, "mass": 4.7, "svl": 62.09, "hl": 18.55, "hw": 10.22, "hh": 6.24, "femur": 12.44, "tibia": 10.93, "metat": 4.18, "hindtoe": 9.54, "humerus": 8.43, "radius": 7.19, "metac": 2.48, "foretoe": 6.28, "tail": 72.69, } st.subheader("Enter sample") with st.form("manual_input_form"): default_sex = "m" if int(default_sample["sex_num"]) == 0 else "f" sex = st.selectbox("Sex", ["m", "f"], index=0 if default_sex == "m" else 1, help="Biological sex of the lizard") col1, col2, col3 = st.columns(3) with col1: mass = st.number_input("mass", value=float(default_sample["mass"]), help="Body mass (g)") svl = st.number_input("svl", value=float(default_sample["svl"]), help="Snout-vent length (mm)") hl = st.number_input("hl", value=float(default_sample["hl"]), help="Head length (mm)") hw = st.number_input("hw", value=float(default_sample["hw"]), help="Head width (mm)") hh = st.number_input("hh", value=float(default_sample["hh"]), help="Head height (mm)") with col2: femur = st.number_input("femur", value=float(default_sample["femur"]), help="Femur length (mm)") tibia = st.number_input("tibia", value=float(default_sample["tibia"]), help="Tibia length (mm)") metat = st.number_input("metat", value=float(default_sample["metat"]), help="Metatarsal length (mm)") hindtoe = st.number_input("hindtoe", value=float(default_sample["hindtoe"]), help="Longest hind toe length (mm)") with col3: humerus = st.number_input("humerus", value=float(default_sample["humerus"]), help="Humerus length (mm)") radius = st.number_input("radius", value=float(default_sample["radius"]), help="Radius length (mm)") metac = st.number_input("metac", value=float(default_sample["metac"]), help="Metacarpal length (mm)") foretoe = st.number_input("foretoe", value=float(default_sample["foretoe"]), help="Longest fore toe length (mm)") tail = st.number_input("tail", value=float(default_sample["tail"]), help="Tail length (mm)") run_btn = st.form_submit_button("Run predictions", use_container_width=True) if run_btn: if not selected_targets: st.warning("Please select at least one target.") st.stop() input_row = { "sex": 0 if sex == "m" else 1, "mass": mass, "svl": svl, "hl": hl, "hw": hw, "hh": hh, "femur": femur, "tibia": tibia, "metat": metat, "hindtoe": hindtoe, "humerus": humerus, "radius": radius, "metac": metac, "foretoe": foretoe, "tail": tail, } df = pd.DataFrame([input_row]) progress = st.progress(0) status = st.empty() all_outputs = [] n = len(selected_targets) for i, t in enumerate(selected_targets, start=1): status.write(f"Running {t} ({i}/{n}) …") path = os.path.join(BUNDLE_DIR, f"{t}_bundle.joblib") bundle = load_bundle(path) out = predict_with_confidence(bundle, df, interval=INTERVAL) out.insert(0, "target", t) all_outputs.append(out.reset_index(drop=True)) progress.progress(i / n) progress.empty() status.success("Predictions complete.") result = pd.concat(all_outputs, axis=0, ignore_index=True) # Map confidence label to emoji version result["confidence_label"] = result["confidence_label"].map( lambda x: CONFIDENCE_EMOJI.get(x, x) ) # Drop confidence_score — label is sufficient display_result = result.drop(columns=["confidence_score"]).copy() for col in ["prediction", "lower", "upper"]: display_result[col] = display_result[col].round(3) # Hide note column if all empty if display_result["note"].str.strip().eq("").all(): display_result = display_result.drop(columns=["note"]) st.subheader("Predictions") st.caption("Interval shown is the 90% conformal prediction interval.") st.markdown( """ """, unsafe_allow_html=True, ) col_cfg = { "target": st.column_config.TextColumn("Target", width="small"), "prediction": st.column_config.NumberColumn("Prediction", format="%.3f", width="small"), "lower": st.column_config.NumberColumn("Lower (90%)", format="%.3f", width="small"), "upper": st.column_config.NumberColumn("Upper (90%)", format="%.3f", width="small"), "confidence_label": st.column_config.TextColumn("Confidence", width="small"), } if "note" in display_result.columns: col_cfg["note"] = st.column_config.TextColumn("Note", width="medium") st.dataframe(display_result, use_container_width=True, hide_index=True, column_config=col_cfg) csv_out = result.to_csv(index=False).encode("utf-8") st.download_button( "Download results CSV", csv_out, file_name="predictions_with_confidence.csv", mime="text/csv", )