import json,collections,numpy as np,pandas as pd from pathlib import Path rows=json.load(open('reports/test_subject_results.json'));primary=json.load(open('reports/demo_subject_models_test.json'));rng=np.random.default_rng(2026) p=np.array([r['metrics']['accuracy'] for r in sorted(primary,key=lambda r:r['subject'])]);out={} for model in ['precue','late_matched','permuted','loso_bandpower','loso_svm']: r=sorted([r for r in rows if r['model']==model],key=lambda r:r['subject']) if len(r)!=15:continue a=np.array([q['metrics']['accuracy'] for q in r]);d=p-a;boot=rng.choice(d,(10000,15),replace=True).mean(1);out['primary_minus_'+model]={'mean_accuracy_difference':float(d.mean()),'paired_subject_bootstrap_ci95':np.quantile(boot,[.025,.975]).tolist(),'control_accuracy':float(a.mean())} # Matched duration comparison explicitly separates late imagination from pre-onset fixation. pre=sorted([r for r in rows if r['model']=='precue'],key=lambda r:r['subject']);late=sorted([r for r in rows if r['model']=='late_matched'],key=lambda r:r['subject']) if len(pre)==len(late)==15: d=np.array([b['metrics']['accuracy']-a['metrics']['accuracy'] for a,b in zip(pre,late)]);out['matched_late_minus_precue']={'mean_accuracy_difference':float(d.mean()),'paired_subject_bootstrap_ci95':np.quantile(rng.choice(d,(10000,15),replace=True).mean(1),[.025,.975]).tolist()} json.dump(out,open('reports/control_comparisons.json','w'),indent=2) # Per-class recall and calibration for primary, preserving the classifier's actual predicted label. ys=[];ps=[];cs=[] for r in primary: z=np.load('reports/predictions/'+r['name']+'.npz');ys.extend(z['y']);ps.extend(z['pred']);cs.extend(z['prob'][np.arange(len(z['pred'])),z['pred']]) y=np.array(ys);p=np.array(ps);c=np.array(cs);bins=[];ece=0 for low in np.arange(0,1,.1): mask=(c>=low)&(cthreshold;pred=np.load('reports/predictions/'+r['name']+'.npz')['pred'];correct=pred==y quality.append({'subject':subject,'train_95th_percentile_peak_to_peak_microvolt':threshold,'high_amplitude_trials':int(high.sum()),'high_amplitude_correct':int(correct[high].sum()),'other_trials':int((~high).sum()),'other_correct':int(correct[~high].sum())}) high_n=sum(r['high_amplitude_trials'] for r in quality);other_n=sum(r['other_trials'] for r in quality) json.dump({'definition':'Maximum peak-to-peak amplitude over selected channels after CAR, relative to within-subject training 95th percentile. Descriptive amplitude flag, not a validated artifact detector. No trials excluded from primary results.','subjects':quality,'high_amplitude_accuracy':sum(r['high_amplitude_correct'] for r in quality)/high_n if high_n else None,'high_amplitude_n':high_n,'other_accuracy':sum(r['other_correct'] for r in quality)/other_n,'other_n':other_n},open('reports/amplitude_quality_analysis.json','w'),indent=2)