"""Ideal calibrated rectified stereo geometry and conditional sensitivity."""
import math,csv,json
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
P=Path(__file__).resolve().parent
F=800.
def estimate(b,d,sigma=.5,delta=.5):
 if not all(math.isfinite(v) for v in [b,d,sigma,delta]) or b<0 or d<0 or sigma<0 or delta<0:raise ValueError('Finite nonnegative inputs required')
 if b==0 or d==0:return dict(depth_m=None,local_standard_uncertainty_m=None,bounded_lower_m=None,bounded_upper_m=None,upper_unbounded=False,estimate_available=False)
 return dict(depth_m=F*b/d,local_standard_uncertainty_m=F*b*sigma/d**2,bounded_lower_m=F*b/(d+delta),bounded_upper_m=F*b/(d-delta) if d>delta else None,upper_unbounded=d<=delta,estimate_available=True)
rows=[]
for b in [.05,.1,.2]:
 for i in range(191):
  z=1+i*.1;d=F*b/z
  rows.append(dict(baseline_m=b,axial_depth_input_m=z,disparity_px=d,**estimate(b,d)))
edge=[]
for b,d in [(0,0),(0,1),(.1,0),(.1,.1),(.1,.5),(.1,.5001),(.1,1),(.1,40)]:edge.append(dict(baseline_m=b,disparity_px=d,**estimate(b,d)))
for name,data in [('stereo.csv',rows),('boundary-cases.csv',edge)]:
 with (P/name).open('w',newline='') as f:w=csv.DictWriter(f,fieldnames=data[0]);w.writeheader();w.writerows(data)
err=0
for r in rows:
 b=r['baseline_m'];d=r['disparity_px'];z=r['depth_m']
 assert math.isclose(z,r['axial_depth_input_m'],rel_tol=1e-14)
 assert math.isclose(F*b/r['bounded_lower_m'],d+.5,rel_tol=1e-14)
 assert math.isclose(F*b/r['bounded_upper_m'],d-.5,rel_tol=1e-14)
 h=d*1e-5;fd=((F*b/(d+h))-(F*b/(d-h)))/(2*h);err=max(err,abs(abs(fd)*.5/r['local_standard_uncertainty_m']-1))
assert err<1e-8
assert math.isclose(estimate(.1,40)['depth_m'],2)
assert math.isclose(estimate(.1,40)['local_standard_uncertainty_m'],.025)
assert estimate(.1,40,sigma=0)['local_standard_uncertainty_m']==0
assert estimate(.1,.5)['upper_unbounded'] and estimate(.1,.1)['upper_unbounded']
assert not estimate(0,1)['estimate_available'] and not estimate(.1,0)['estimate_available']
assert estimate(.1,40,delta=0)['bounded_lower_m']==estimate(.1,40,delta=0)['bounded_upper_m']==2
bad=0
for args in [(-1,1),(1,-1),(math.nan,1),(1,math.inf),(1,1,-.1),(1,1,.5,-.1)]:
 try:estimate(*args)
 except ValueError:bad+=1
assert bad==6
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':10,'axes.spines.top':False,'axes.spines.right':False,'svg.fonttype':'none','pdf.fonttype':42})
fig,ax=plt.subplots(1,2,figsize=(10.5,4.6))
for b,c in [(.05,'#a24a85'),(.1,'#087f92'),(.2,'#7451a0')]:
 rr=[r for r in rows if r['baseline_m']==b];z=[r['axial_depth_input_m'] for r in rr]
 ax[0].plot(z,[r['disparity_px'] for r in rr],color=c,label=f'Baseline {b:g} m')
 ax[1].plot(z,[r['local_standard_uncertainty_m'] for r in rr],color=c,label=f'Baseline {b:g} m')
ax[0].set(xlabel='Axial depth (m)',ylabel='Disparity (pixels)',title='A  |  Farther points have smaller disparity');ax[1].set(xlabel='Axial depth (m)',ylabel='First-order depth standard uncertainty (m)',title='B  |  Fixed disparity uncertainty grows with depth')
for a in ax:a.grid(alpha=.2);a.legend(fontsize=8)
fig.suptitle('Ideal stereo geometry: separation is only one part of accuracy',fontsize=14);fig.text(.5,.04,'Assigned focal length 800 pixels; disparity standard uncertainty 0.5 pixels. Calibration and correspondence are treated as exact.',ha='center',fontsize=8);fig.text(.5,.01,'Local first-order propagation, not measured camera accuracy. The largest plotted sigma_d/disparity ratio is 0.25; linearization is weakest there.',ha='center',fontsize=8);fig.tight_layout(rect=[0,.08,1,.93])
for ext in ['png','svg','pdf']:fig.savefig(P/f'stereo-sensitivity.{ext}',dpi=190)
fig,ax=plt.subplots(figsize=(9,4.4));rr=[r for r in rows if r['baseline_m']==.1];z=[r['depth_m'] for r in rr]
ax.plot(z,[r['bounded_lower_m']-r['depth_m'] for r in rr],color='#087f92',label='Lower endpoint minus nominal depth');ax.plot(z,[r['bounded_upper_m']-r['depth_m'] for r in rr],color='#a24a85',label='Upper endpoint minus nominal depth');ax.axhline(0,color='#888',lw=.7);ax.set(xlabel='Nominal axial depth (m)',ylabel='Bounded depth deviation (m)',title='A bounded disparity interval produces asymmetric depth limits');ax.legend(fontsize=8);ax.grid(alpha=.2);fig.text(.5,.045,'800-pixel focal length, 0.1 m baseline, deterministic disparity bound ±0.5 pixels. This is not a confidence interval.',ha='center',fontsize=9);fig.text(.5,.012,'For positive measured disparity at or below 0.5 pixels, positive depth has no finite upper bound. Zero disparity gives no finite nominal estimate.',ha='center',fontsize=8);fig.tight_layout(rect=[0,.09,1,1])
for ext in ['png','svg','pdf']:fig.savefig(P/f'bounded-depth-interval.{ext}',dpi=190)
meta={'title':'Rectified stereo depth and two distinct uncertainty descriptions','status':'Ideal analytical model, no measured cameras or human/clinical performance','rows':{'stereo':len(rows),'boundary_cases':len(edge)},'parameters':{'focal_length_px':800,'baselines_m':[.05,.1,.2],'depth_m':[1,20],'depth_step_m':.1,'disparity_standard_uncertainty_px':.5,'separate_deterministic_disparity_bound_px':.5},'equations':{'disparity':'d=f*B/Z','depth':'Z=f*B/d for positive B,d','local_standard_uncertainty':'sigmaZ=f*B*sigma_d/d²','bounded_interval':'[fB/(d+delta),fB/(d-delta)] when d>delta; upper unbounded for 0<d<=delta'},'checks':{'all_pass':True,'finite_difference_relative_error_max':err,'inverse_geometry_all_rows':True,'interval_endpoint_inversions':True,'zero_baseline_and_disparity_noestimate':True,'unbounded_upper_cases':True,'zero_sigma_and_delta':True,'invalid_inputs_rejected':bad},'reference_example':estimate(.1,40),'limitations':['Depth is axial optical-axis depth, not Euclidean camera-to-point distance.','Parallel rectified ideal pinhole cameras with equal known focal length, matched principal points and correct correspondence; no calibration errors or distortion.','Standard uncertainty propagation is first order and conditional on exact f,B. It is not a confidence interval and becomes unreliable for large sigma_d/d.','The separately chosen bounded error is deterministic and not an interpretation of the standard deviation.','No matching outliers, textureless/repeated patterns, occlusion, specular response, motion or drift is modeled.','Wider baseline may impair overlap and correspondence; the ideal formula does not imply universally better reconstruction.','No display cross-talk, tracking latency, visual comfort or tissue-imaging capability is established.']}
(P/'study.json').write_text(json.dumps(meta,indent=2)+'\n');(P/'validation.json').write_text(json.dumps(meta['checks'],indent=2)+'\n');print(meta['checks'])

