#!/usr/bin/env python3 """Compile frozen raw confidence directly; preserve the continuity proof.""" from pathlib import Path import hashlib,json import numpy as np from PIL import Image,ImageFilter from line_detector import smooth ROOT=Path(__file__).resolve().parent RAW=ROOT/'raw-ridges' r=json.loads((RAW/'recipe.json').read_text()) cards=json.loads((ROOT/'cards.json').read_text()) inputs=json.loads((RAW/'inputs.json').read_text()) W,H=r['canvas'] def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest() def pair(left,right,path,box=None): if box: left=left.crop(box);right=right.crop(box) panel=Image.new('RGB',(left.width*2,left.height));panel.paste(left.convert('RGB'),(0,0));panel.paste(right.convert('RGB'),(left.width,0));panel.save(path) reports={} for key,card in cards.items(): source=ROOT/key/'source';out=RAW/key/'output' for folder in ['masks','runtime','review']: (out/folder).mkdir(parents=True,exist_ok=True) for name,record in card['inputs'].items(): assert sha(source/name)==record['sha256'],name for record in inputs[key].values(): assert sha(ROOT/record['path'])==record['sha256'] art=Image.open(source/'art-master.png').convert('RGB');overlay=Image.open(source/'normal-overlay.png').convert('RGBA');text=Image.open(source/'text.png').convert('RGBA') ridge=Image.open(ROOT/inputs[key]['rawRidges']['path']).convert('L') assert art.size==overlay.size==text.size==ridge.size==(W,H) leads=np.asarray(ridge,dtype=np.float32)/255. rgb=np.asarray(art,dtype=np.float32)/255.;v=rgb.max(axis=2);sat=(v-rgb.min(axis=2))/np.maximum(v,1e-8) soft=np.asarray(Image.fromarray(np.rint(sat*255).astype(np.uint8)).filter(ImageFilter.GaussianBlur(r['saturationSmoothingRadius'])),dtype=np.float32)/255. panes=r['paneCoverageFloor']+r['paneCoverageRange']*smooth(*r['saturationWeightSmoothstep'],soft) # This float field is the same pane weighting used in the continuity branch. assert np.array_equal(np.rint(panes*255).astype(np.uint8),np.asarray(Image.open(ROOT/key/'output/authoring/pane-coverage.png').convert('L'))) oa=np.asarray(overlay)[:,:,3]/255.;ta=np.asarray(text)[:,:,3]/255.;protection=1-(1-oa)*(1-ta) coverage=np.rint(panes*(1-leads)*(1-protection)*255).astype(np.uint8) assert np.all(coverage[protection==1]==0) and np.all(coverage[leads==1]==0) # A wholly unprotected neutral pane retains the existing minimum coating. visible=protection==0;assert np.all(coverage[(leads==0)&visible]>=round(r['paneCoverageFloor']*255)) master=Image.fromarray(coverage);exports=[] for size in [2000,1000,500]: im=master if size==W else master.resize((size,size*7//5),Image.Resampling.BILINEAR) p=out/'masks'/f'raw-ridge-finish-{size}.png';im.convert('RGBA').save(p);rgba=np.asarray(Image.open(p)) assert rgba.shape==(size*7//5,size,4) and np.all(rgba[:,:,3]==255) assert np.array_equal(rgba[:,:,0],rgba[:,:,1]) and np.array_equal(rgba[:,:,0],rgba[:,:,2]) assert all(rgba[y,x,0]==0 for y,x in [(0,0),(0,size-1),(size*7//5-1,0),(size*7//5-1,size-1)]) exports.append({'path':str(p.relative_to(ROOT)),'size':[size,size*7//5],'sha256':sha(p)}) fixture=card['fixture'].replace('-line-continuity-','-raw-ridges-') for src,filename in [(source/'runtime-art.png',fixture+'.png'),(out/'masks/raw-ridge-finish-1000.png',fixture+'-mask.png'),(source/'runtime-text-mask.png',fixture+'-text-mask.png')]: (out/'runtime'/filename).write_bytes(src.read_bytes()) assert sha(out/'runtime'/(fixture+'.png'))==card['inputs']['runtime-art.png']['sha256'] assert sha(out/'runtime'/(fixture+'-text-mask.png'))==card['inputs']['runtime-text-mask.png']['sha256'] old=Image.open(ROOT/inputs[key]['continuityFinish']['path']).convert('RGB');new=Image.open(out/'masks/raw-ridge-finish-1000.png').convert('RGB') pair(old,new,out/'review/continuity-raw-ridges-1000.png') pair(Image.open(ROOT/key/'output/masks/control-finish-1000.png'),new,out/'review/control-raw-ridges-1000.png') if key=='the-fall': pair(old,new,out/'review/tree-before-after.png',(300,225,720,1010)) pair(old,new,out/'review/head-before-after.png',(255,225,565,465)) else: pair(old,new,out/'review/bush-before-after.png',(460,340,920,960)) delta=np.asarray(new)[:,:,0].astype(int)-np.asarray(old)[:,:,0].astype(int) report={'status':'passed','fixture':fixture,'recipeSha256':sha(RAW/'recipe.json'),'rawConfidence':inputs[key]['rawRidges'],'checks':{'registeredCanvas':True,'paneWeightingIdentical':True,'rawConfidenceUsedDirectly':True,'activeOverlayAndGlyphSuppression':True,'fullConfidenceRidgesProtected':True,'unprotectedNeutralPaneCoverageRetained':True,'opaqueGrayscaleAllSizes':True,'allFourCornersProtected':True,'runtimeArtworkByteIdentical':True,'runtimeTextMaskByteIdentical':True},'exports':exports,'statistics':{'visibleMeanFinish':float((coverage[visible]/255).mean()),'visibleBlackFraction':float((coverage[visible]==0).mean()),'runtimeChangedPixelsFromContinuity':int(np.count_nonzero(delta)),'runtimeMeanAbsoluteChange':float(np.abs(delta).mean())},'visualReview':{'static':'Pending inspection','movingLight':'User comparison pending','approval':'Experimental candidate'},'limitations':['Raw confidence includes some etched texture and painted marks; assess mottling in motion.','Frozen raw PNG confidence is used at its existing 8-bit precision without new filtering.','No continuity or bridging checks apply to this branch; this trial deliberately omits those stages.']} (out/'validation.json').write_text(json.dumps(report,indent=2)+'\n');reports[key]=report print(json.dumps({'fixture':fixture,'statistics':report['statistics']})) (RAW/'validation.json').write_text(json.dumps({'status':'passed','recipe':r,'cards':reports,'userMovingLightReview':'Pending'},indent=2)+'\n')