Spaces:
Runtime error
Runtime error
File size: 8,110 Bytes
81f630c d79e350 81f630c d79e350 81f630c d79e350 81f630c d79e350 81f630c d79e350 81f630c d79e350 81f630c d79e350 82f9c82 81f630c d79e350 81f630c d79e350 81f630c d79e350 0dca7cf d79e350 82f9c82 d79e350 0dca7cf 82f9c82 d79e350 82f9c82 d79e350 82f9c82 d79e350 81f630c d79e350 a4b1ce6 d79e350 81f630c d79e350 82f9c82 d79e350 797566f d79e350 82f9c82 81f630c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | """
Bee Colony Survival β multi-modal (audio + virus) batch predictor (Hugging Face Space).
For every uploaded hive recording:
1. AST (Audio Spectrogram Transformer) encodes the AUDIO -> 768-d embedding,
2. the file name is matched to a row of the bundled AI_Data_Training.xlsx to read
the viral loads (CBPV/DWV/KBV) + metadata + ground-truth labels,
3. a class-balanced gradient-boosting model on [audio embedding + viral/metadata]
predicts Terminal (T) / Survivor (S) for 0-3 and 4-6 months at a STRICT
high-precision threshold (constraint: 0-3 T => 4-6 T),
4. prediction and ground truth are shown side by side in a results table.
Batch upload is supported. Files not in the workbook still get a prediction from
audio + parsed metadata (viral loads default to 0) and show "not available" truth.
"""
import os
import json
import numpy as np
import joblib
import gradio as gr
import bee_features as bf
import ast_encoder as ae
import xls_lookup as xl
HERE = os.path.dirname(os.path.abspath(__file__))
MODELDIR = os.path.join(HERE, "model")
XLSX = os.path.join(HERE, "AI_Data_Training.xlsx")
CFG = json.load(open(os.path.join(MODELDIR, "config.json")))
M03 = joblib.load(os.path.join(MODELDIR, "model_03.joblib"))
M46 = joblib.load(os.path.join(MODELDIR, "model_46.joblib"))
TAB_ORDER = CFG["tabular_order"]
THR03, THR46 = CFG["threshold_03"], CFG["threshold_46"]
LUT = xl.build_lookup(XLSX) # ground truth + viral loads from Excel
FE, AST, AST_DEV = ae.load_ast() # AST weights download on first run
LBL = {"S": "Survivor (S)", "T": "Terminal (T)"}
HEADERS = ["File", "Colony", "Country", "Month", "Viral CBPV / DWV / KBV",
"Predicted 0β3", "Actual 0β3", "Predicted 4β6", "Actual 4β6", "Match"]
def _results_html(rows):
"""Render results as a self-styled HTML table (full control, no truncation)."""
th = "".join(
f'<th style="border:1px solid #e3e3e3;padding:9px 14px;background:#f4f4f4;'
f'color:#000;font-weight:600;text-align:left;white-space:nowrap;">{h}</th>'
for h in HEADERS)
trs = []
for r in rows:
tds = []
for i, v in enumerate(r):
extra = "white-space:nowrap;"
if HEADERS[i] == "Match":
c = {"β": "#157347", "β": "#b42318"}.get(v, "#888")
extra += f"text-align:center;font-weight:700;font-size:16px;color:{c};"
tds.append(f'<td style="border:1px solid #e3e3e3;padding:9px 14px;color:#000;'
f'{extra}">{v}</td>')
trs.append("<tr>" + "".join(tds) + "</tr>")
return ('<div style="overflow-x:auto;width:100%;">'
'<table style="border-collapse:collapse;background:#fff;color:#000;'
'font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;">'
f'<thead><tr>{th}</tr></thead><tbody>{"".join(trs)}</tbody></table></div>')
def _predict_one(path):
fname = os.path.basename(path)
parsed = bf.parse_filename(fname)
entry = xl.lookup(LUT, fname)
colony = (entry or {}).get("colony") or parsed["colony"] or "?"
country = (entry or {}).get("country") or parsed["country"] or "?"
month = (entry or {}).get("month") or parsed["month"] or "?"
cbpv = (entry or {}).get("cbpv", 0.0)
dwv = (entry or {}).get("dwv", 0.0)
kbv = (entry or {}).get("kbv", 0.0)
emb = ae.encode_file(path, FE, AST, AST_DEV) # AUDIO
tabd = bf.build_tabular_dict(country, month, cbpv, dwv, kbv) # VIRUS + metadata
x = np.hstack([emb, [tabd[k] for k in TAB_ORDER]]).reshape(1, -1)
p03 = float(M03.predict_proba(x)[0, 1]); p46 = float(M46.predict_proba(x)[0, 1])
pred03 = "T" if p03 >= THR03 else "S"
pred46 = "T" if (p46 >= THR46 or pred03 == "T") else "S"
if entry is not None:
gt03, gt46 = entry["y03"], entry["y46"]
correct = "β" if (gt03 == pred03 and gt46 == pred46) else "β"
t03, t46 = LBL[gt03], LBL[gt46]
else:
t03 = t46 = "not available"; correct = "β"
viral = f"{cbpv:.2f} / {dwv:.2f} / {kbv:.2f}"
return [fname, colony, country, month, viral,
LBL[pred03], t03, LBL[pred46], t46, correct]
def predict_batch(files, progress=gr.Progress()):
if not files:
return "", "Upload one or more `.wav` files (or click an example), then press **Predict**."
paths = [f if isinstance(f, str) else f.name for f in files]
rows = []
for p in progress.tqdm(paths, desc="Scoring recordings"):
try:
rows.append(_predict_one(p))
except Exception as e: # noqa
rows.append([os.path.basename(p), "?", "?", "?", "",
"error", str(e)[:30], "error", "", "β"])
n_known = sum(1 for r in rows if r[9] != "β")
n_ok = sum(1 for r in rows if r[9] == "β")
summary = (f"Scored **{len(rows)}** file(s) Β· **{n_known}** found in the Excel workbook "
f"Β· **{n_ok}/{n_known}** predicted correctly (both horizons).")
return _results_html(rows), summary
CSS = """
.gradio-container {background:#ffffff !important; color:#000000 !important;
font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; max-width:1150px;}
.gradio-container * {color:#000000;}
h1,h2,h3,p,label {color:#000000 !important;}
button.primary, .primary {background:#000000 !important; color:#ffffff !important;
border:1px solid #000000 !important; border-radius:6px !important;}
button.primary:hover, .primary:hover {background:#222222 !important;}
button.primary *, .primary * {color:#ffffff !important;}
.secondary {background:#ffffff !important; color:#000000 !important; border:1px solid #000000 !important;}
footer {display:none !important;}
"""
THEME = gr.themes.Base(primary_hue=gr.themes.colors.gray,
neutral_hue=gr.themes.colors.gray).set(
body_background_fill="#ffffff", body_text_color="#000000",
block_background_fill="#ffffff", block_border_color="#e5e5e5",
button_primary_background_fill="#000000", button_primary_text_color="#ffffff",
button_primary_background_fill_hover="#222222")
EXDIR = os.path.join(HERE, "examples")
example_files = (sorted(os.path.join(EXDIR, f) for f in os.listdir(EXDIR) if f.endswith(".wav"))
if os.path.isdir(EXDIR) else [])
with gr.Blocks(title="Bee Colony Survival β Multimodal AI Predictor", theme=THEME, css=CSS) as demo:
gr.Markdown("# Bee Colony Survival β Multimodal AI Predictor")
gr.Markdown("Upload one or more hive **audio recordings** (batch supported). The model "
"combines the **audio** (AST embedding) with the recording's **viral loads and "
"metadata** (read from the bundled Excel workbook) to predict **Survivor (S)** vs "
"**Terminal (T)** for the next 0β3 and 4β6 months, shown against the ground truth.")
with gr.Row():
audio_in = gr.File(label="Hive recordings (.wav) β batch upload",
file_count="multiple", file_types=[".wav"], type="filepath")
with gr.Row():
btn = gr.Button("Predict", variant="primary")
clear = gr.Button("Clear", variant="secondary")
summary = gr.Markdown()
gr.Markdown("### Results")
table = gr.HTML()
if example_files:
gr.Markdown("**Example recordings** β click any one to load it and run the prediction "
"(all are Terminal colonies the model correctly detects). "
"You can also drag several into the upload box above for a batch.")
gr.Examples(
examples=[[[f]] for f in example_files],
inputs=[audio_in],
outputs=[table, summary],
fn=predict_batch,
run_on_click=True,
cache_examples=False,
examples_per_page=20,
label="Bundled examples",
)
btn.click(predict_batch, inputs=audio_in, outputs=[table, summary])
clear.click(lambda: (None, "", ""), outputs=[audio_in, table, summary])
if __name__ == "__main__":
demo.launch()
|