Files

63 lines
5.7 KiB
Python

#!/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')