"""Static descriptive benchmark figure; no invented confidence intervals.""" from pathlib import Path import json import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt root=Path(__file__).parent j=json.loads((root.parents[1]/'cascade_benchmark/receipts/metrics-680.json').read_text(encoding='utf8')) fig,axes=plt.subplots(2,1,figsize=(10,7.8),layout='constrained') panels=[('Image alerts / Alertes image',[ ('Positive-image recall / Rappel images positives',j['image']['recall'],'#377eb8'), ('Negative false-alarm rate / Fausses alertes négatives',j['image']['negative_false_alarm_rate'],'#d95f02')]), ('Flame localization / Localisation flamme',[ ('Proposal containment / Couverture propositions',j['flame']['proposal_gt_containment_recall'],'#7a7a7a'), ('Box recall at IoU ≥ .5 / Rappel boîtes',j['flame']['recall'],'#377eb8'), ('Tiny flame recall / Rappel petites flammes',j['flame']['tiny_recall'],'#377eb8')])] for ax,(title,rows) in zip(axes,panels): values=[100*r[1]['value'] for r in rows] ax.barh(range(len(rows)),values,color=[r[2] for r in rows],height=.58) ax.set_yticks(range(len(rows)),[r[0] for r in rows],fontsize=9) ax.invert_yaxis();ax.set_xlim(0,125);ax.set_xticks([0,25,50,75,100]);ax.set_xticklabels(['0%','25%','50%','75%','100%']) ax.set_title(title,loc='left',fontsize=12,fontweight='bold') ax.spines[['top','right','left']].set_visible(False);ax.grid(axis='x',alpha=.18);ax.set_axisbelow(True) for i,(_,r,_) in enumerate(rows):ax.text(values[i]+1,i,f"{values[i]:.2f}% {r['success']}/{r['n']}",va='center',fontsize=9) fig.suptitle('FireViewer v2.5 · 680 images · frozen PyTorch FP32 cascade on L4',fontsize=14,fontweight='bold') fig.supxlabel('229 visual families; correlated views. Descriptive ratios, no independent-event confidence intervals.\nTiny: box area/image area ≤ (32/1088)². Point-base accuracy unmeasured. LiteRT/Android performance not shown.',fontsize=9) out=root/'cascade-v25-benchmark.png';fig.savefig(out,dpi=155);print(out)