63 lines
5.7 KiB
Python
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')
|