User-2468 commited on
Commit
28a3d7d
·
verified ·
1 Parent(s): a78fd9d

Colorizer round6: smoke and initial model verified

Browse files
experiments/region-coherence-20260928/SHA256SUMS.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "data_and_eval.py": "019ea992052f0233954b86e06e31c85c74ca313e67a55ff7fb03248f8874aaf5",
3
+ "initial/README.md": "2508e51f14898f651c49adb184d1570b8adad3702313dee9bdac6fa0390d2bec",
4
+ "initial/config.json": "eaeb49b39f9116bfed0d954e81852dc93476298752787dba88a63c0eec97ccec",
5
+ "initial/model.safetensors": "ec1f27d74533adc83f7ab3639a091fc4d8738a434dafc7d172c7873c28a9e715",
6
+ "protocol.json": "926776a1b4c2b29dbd292dc68cd68bd12df3fdc4db541044fdadca48c3a5e4e5",
7
+ "smoke.json": "d0dc2acce2f8273d58479f00a32a6618e3eecfb2220d00afc6721ddf7f08442c",
8
+ "source/data.py": "eb43b886fe45435841439c36646985f914386a48faa712fb6806accac2ad48e0",
9
+ "source/legacy_eval.py": "dd7e8bd5f961dfb6f1c2ce98fac41824c5009188e306000b195e922e4ed03e58",
10
+ "source/metrics.py": "af0ac996f6be0a351a55e372fab637a0d06740afe4a744cb80d0eae56ec97f66",
11
+ "source/model.py": "4cc57f82ffd6378bdf23a088ce7a1ed56c09e5673de3ee5232b8ee1cacc9be0a",
12
+ "source/objectives.py": "133e85b6f2914c0e3803cc26a26fa29b779a49036219dd245c2ecfe0ba411e20",
13
+ "source/persistence.py": "8b26dd8acdc135ecbfcdd30b7f7723feb6008ac820c083403da1e820bdc15d33",
14
+ "source/previous_manifest.json": "24bc081bf48f1109d9dfa217ae0d8593c0c06af6785a5cbfc5dc5d9e25461f5c",
15
+ "source/semantic_model.py": "b019358cbcaa214743cbd244d20eb6e59eeff0ab5264093ec0927be8529b25a2",
16
+ "source/spatial.py": "d13abe399ef049e21a6459a7003461afaae0c00e0c560262c6cf375de4c9884a",
17
+ "train.py": "8d7ceb77a742afd65c9ab7ddf58e56b86ff235900371ce57b4b01c65a759a4cb"
18
+ }
experiments/region-coherence-20260928/data_and_eval.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os,sys,json,random,time,hashlib,shutil,math,traceback,io
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from torch.utils.data import Dataset,DataLoader
7
+ from huggingface_hub import hf_hub_download
8
+ import pyarrow as pa
9
+ import pyarrow.parquet as pq
10
+ from PIL import Image,ImageDraw
11
+ from skimage.color import rgb2lab,lab2rgb
12
+ from skimage.segmentation import slic
13
+ import cv2
14
+ import persistence
15
+ from semantic_model import load_semantic,save_semantic
16
+ from objectives import color_loss
17
+ from data import decode_image
18
+ import legacy_eval as ev
19
+ REPO='User-2468/mini-unet-colorizer'
20
+ REV='7b10485ee9d205ce2f7b660dfc6a90012d78b1cb'
21
+ OLD='experiments/round6-20260928'
22
+ OUT=Path('/work/results');OUT.mkdir(parents=True,exist_ok=True)
23
+ persistence.PREFIX='experiments/round7-20260928'
24
+ ev.OUT=OUT
25
+ START=time.monotonic()
26
+ SEED=709
27
+ STEPS=2000
28
+ DEVICE='cpu' if os.environ.get('SMOKE')=='1' else 'cuda'
29
+ ev.DEVICE=DEVICE
30
+ DURABLE=None
31
+ def write(name,obj):
32
+ p=OUT/name;p.parent.mkdir(parents=True,exist_ok=True);p.write_text(json.dumps(obj,indent=2));return obj
33
+ def deadline():
34
+ if time.monotonic()-START>9000:raise TimeoutError('Graceful deadline')
35
+ ev.deadline=deadline
36
+ def losses(p,t):
37
+ p=F.adaptive_avg_pool2d(p.float(),(64,64));t=t.float()
38
+ pixel=(p-t).abs().mean((1,2,3))/20
39
+ a=F.avg_pool2d(p,2).flatten(2);b=F.avg_pool2d(t,2).flatten(2)
40
+ angles=torch.arange(8,device=p.device)*math.pi/8
41
+ directions=torch.stack([angles.cos(),angles.sin()],1)
42
+ ap=torch.einsum('kc,bcn->bkn',directions,a).sort(-1).values
43
+ bp=torch.einsum('kc,bcn->bkn',directions,b).sort(-1).values
44
+ distribution=(ap-bp).abs().mean((1,2))/20
45
+ edge=((torch.diff(p,dim=-1)-torch.diff(t,dim=-1)).abs().mean((1,2,3))+(torch.diff(p,dim=-2)-torch.diff(t,dim=-2)).abs().mean((1,2,3)))/40
46
+ return pixel+.25*distribution+.1*edge
47
+ def select_loss(p,teacher,original,mode):
48
+ a=losses(p,teacher)
49
+ if mode=='teacher':return a.mean(),torch.zeros_like(a,dtype=torch.bool)
50
+ b=losses(p,original);choose=b<a
51
+ return torch.where(choose,b,a).mean(),choose
52
+ def smoke():
53
+ torch.set_num_threads(4);torch.manual_seed(SEED)
54
+ p=torch.randn(2,2,64,64,requires_grad=True);t=torch.randn_like(p)
55
+ assert torch.allclose(losses(p,t).mean(),color_loss(p,t)[0],atol=1e-6)
56
+ # Whole-image choice: exact red or blue is better than an average or a patchwork.
57
+ red=torch.zeros(1,2,64,64);red[:,0]=30
58
+ blue=-red;mix=red.clone();mix[:,:,:,32:]=blue[:,:,:,32:]
59
+ assert select_loss(red,red,blue,'set')[0]==0
60
+ assert select_loss(torch.zeros_like(red),red,blue,'set')[0]>0
61
+ assert select_loss(mix,red,blue,'set')[0]>0
62
+ m=load_semantic('/work/initial',DEVICE)
63
+ assert sum(p.numel() for p in m.parameters())==3994676
64
+ x=torch.rand(2,1,96,128,device=DEVICE)*2-1
65
+ pred=m(x);loss,_=select_loss(pred,t.to(DEVICE),-t.to(DEVICE),'set')
66
+ loss.backward()
67
+ assert torch.isfinite(loss) and all(torch.isfinite(p.grad).all() for p in m.parameters() if p.grad is not None)
68
+ save_semantic(m,OUT/'initial')
69
+ re=load_semantic(OUT/'initial',DEVICE)
70
+ with torch.no_grad():assert torch.allclose(m.eval()(x),re(x),atol=1e-5)
71
+ write('smoke.json',{'passed':True,'parameters':3994676,'loss_equivalence':True,'whole_image_choice':True,'finite_backward':True,'reload':True})
72
+ del m,re
73
+ class Photos(Dataset):
74
+ def __init__(self,table,ids,cache,archive):
75
+ self.table,self.ids,self.cache,self.archive=table,ids,cache,archive;self.epoch=0
76
+ def __len__(self):return len(self.ids)
77
+ def __getitem__(self,k):
78
+ im=decode_image(self.table,self.ids[k]);L,ab,gray=ev.gray_arrays(im)
79
+ # Independent per-example RNG; augmentation differences never alter image ordering/flips.
80
+ rng=np.random.default_rng(np.random.SeedSequence([SEED,self.epoch,k]))
81
+ target=np.array(self.cache[k],np.float32)
82
+ if self.archive and rng.random()<.5:
83
+ rgb=np.asarray(im.resize((256,256),Image.Resampling.BILINEAR),np.float32)/255
84
+ weights=rng.dirichlet(np.array([3.,5.,2.]))
85
+ g=(rgb*weights).sum(-1)
86
+ g=np.clip((g**rng.uniform(.7,1.5)-.5)*rng.uniform(.6,1.1)+.5+rng.uniform(-.08,.08),0,1)
87
+ if rng.random()<.5:g=cv2.GaussianBlur(g,(5,5),float(rng.uniform(.3,1.2)))
88
+ if rng.random()<.6:g=np.clip(g+rng.normal(0,rng.uniform(.003,.025),g.shape),0,1)
89
+ L=(rgb2lab(np.repeat(g[...,None],3,-1).astype(np.float32))[...,0][None]/50-1).astype(np.float32)
90
+ else:
91
+ if rng.random()<.5:L=np.clip(L*rng.uniform(.7,1.15)+rng.uniform(-.12,.12),-1,1)
92
+ flip=np.random.default_rng(np.random.SeedSequence([SEED+1,self.epoch,k])).random()<.5
93
+ if flip:L=L[...,::-1].copy();ab=ab[...,::-1].copy();target=target[...,::-1].copy()
94
+ original=cv2.resize(ab.transpose(1,2,0),(64,64),interpolation=cv2.INTER_AREA).transpose(2,0,1).copy()
95
+ return torch.from_numpy(L),torch.from_numpy(target),torch.from_numpy(original)
96
+ @torch.inference_mode()
97
+ def predict(m,L):
98
+ x=torch.from_numpy(L)[None].to(DEVICE);raw=m(x)
99
+ guided=ev.guided_chroma(x,raw,8)
100
+ return raw,guided
101
+ def toimage(L,ab):
102
+ rgb=lab2rgb(np.concatenate([(L[0]*50+50)[...,None],ab],-1))
103
+ return Image.fromarray(np.uint8(np.clip(rgb,0,1)*255))
104
+ @torch.inference_mode()
105
+ def review(m,table,ids,name):
106
+ m.eval();rows=[]
107
+ # Reference-derived surface regions for diagnostics only, never inference.
108
+ for i in ids:
109
+ L,target,_=ev.gray_arrays(decode_image(table,i))
110
+ raw,pred=predict(m,L);x=torch.from_numpy(L)[None].to(DEVICE)
111
+ resize=F.interpolate(x,(224,224),mode='bilinear',align_corners=False)
112
+ resized=F.interpolate(m(resize),(256,256),mode='bilinear',align_corners=False)
113
+ contrast=m((x*.95+.02).clamp(-1,1))
114
+ pab=pred[0].cpu().numpy().transpose(1,2,0)
115
+ rgb=np.asarray(decode_image(table,i).resize((256,256)),np.float32)/255
116
+ seg=slic(rgb,n_segments=100,compactness=10,start_label=0)
117
+ tab=target.transpose(1,2,0);variations=[]
118
+ for label in np.unique(seg):
119
+ mask=seg==label
120
+ if mask.sum()<64:continue
121
+ ground=tab[mask];center=np.median(ground,axis=0)
122
+ if np.percentile(np.linalg.norm(ground-center,axis=1),90)>5:continue
123
+ col=pab[mask];variations.append(float(np.mean(np.linalg.norm(col-np.median(col,axis=0),axis=1))))
124
+ rows.append({'id':int(i),'resize_ab_change':float((raw-resized).abs().mean()),'contrast_ab_change':float((raw-contrast).abs().mean()),'surface_chroma_variation':float(np.mean(variations)) if variations else None,'chroma_coverage':float((pred.norm(dim=1)>10).float().mean()),'mean_chroma':float(pred.norm(dim=1).mean())})
125
+ write(name+'_diagnostics.json',{'rows':rows,'summary':{k:float(np.mean([r[k] for r in rows if r[k] is not None])) for k in rows[0] if k!='id'},'warning':'Surface uniformity alone rewards grey collapse; never use alone to select model. Color differences from original are diagnostic only.'})
126
+ return rows
127
+ @torch.inference_mode()
128
+ def grid(models,table,ids,label):
129
+ order=np.random.default_rng(SEED+2).permutation(len(models))
130
+ names=list(models);key={chr(65+j):names[int(i)] for j,i in enumerate(order)}
131
+ write(label+'_key.json',key)
132
+ for page in range(0,min(len(ids),32),8):
133
+ chosen=ids[page:page+8];canvas=Image.new('RGB',(256*(len(models)+1),274*len(chosen)),'white');draw=ImageDraw.Draw(canvas)
134
+ for row,index in enumerate(chosen):
135
+ L,_,gray=ev.gray_arrays(decode_image(table,index));draw.text((2,row*274),f'{index}: gray | '+' | '.join(key),fill='black')
136
+ canvas.paste(Image.fromarray(np.uint8(gray*255)),(0,row*274+18))
137
+ for j,i in enumerate(order):
138
+ _,p=predict(models[names[int(i)]],L)
139
+ canvas.paste(toimage(L,p[0].cpu().numpy().transpose(1,2,0)),((j+1)*256,row*274+18))
140
+ canvas.save(OUT/f'{label}_{page//8}.jpg',quality=92)
141
+ def main():
142
+ global DURABLE
143
+ torch.set_num_threads(6);torch.manual_seed(SEED);random.seed(SEED);np.random.seed(SEED)
144
+ DURABLE=persistence.DurableRun(OUT)
145
+ shutil.copy2('/work/runner.py',OUT/'runner.py');shutil.copy2('/work/PROTOCOL.md',OUT/'PROTOCOL.md')
146
+ shutil.copytree('/work/source',OUT/'source',ignore=shutil.ignore_patterns('__pycache__'),dirs_exist_ok=True)
147
+ smoke();DURABLE.sync('round7 initial checkpoint and smoke verified')
148
+ if DEVICE=='cpu':return
149
+ torch.backends.cudnn.benchmark=True
150
+ tables=[]
151
+ for repo,rev,files in [('johnowhitaker/imagenette2-320','771c1310a2487e8076ede6b7d6307244aa8400af',['default/train/0000.parquet']),('detection-datasets/coco','26ddc382fe75dfc2a0655b5977e296ea10efebce',['default/train/0000.parquet','default/train/0001.parquet'])]:
152
+ for f in files:tables.append(pq.read_table(hf_hub_download(repo,f,repo_type='dataset',revision=rev),columns=['image']))
153
+ table=pa.concat_tables(tables);manifest=json.loads(Path('/work/source/previous_manifest.json').read_text());ids=manifest['train']
154
+ cache=np.empty((len(ids),2,64,64),np.float16)
155
+ sums=json.loads(Path(hf_hub_download(REPO,OLD+'/SHA256SUMS.json',revision=REV)).read_text())
156
+ for start in range(0,len(ids),2048):
157
+ f=f'teacher_cache/{start:05d}.npz';path=Path(hf_hub_download(REPO,OLD+'/'+f,revision=REV))
158
+ assert hashlib.sha256(path.read_bytes()).hexdigest()==sums[f]
159
+ with np.load(path) as z:
160
+ assert z['indices'].tolist()==ids[start:start+len(z['indices'])]
161
+ cache[start:start+len(z['target'])]=z['target']
162
+ fresh=pq.read_table(hf_hub_download('detection-datasets/coco','default/val/0001.parquet',repo_type='dataset',revision='26ddc382fe75dfc2a0655b5977e296ea10efebce'),columns=['image'])
163
+ hashes={hashlib.sha256(table['image'][int(i)].as_py()['bytes']).digest() for i in ids}
164
+ freshids=[]
165
+ for i in np.random.default_rng(SEED).permutation(len(fresh)):
166
+ if hashlib.sha256(fresh['image'][int(i)].as_py()['bytes']).digest() not in hashes:freshids.append(int(i))
167
+ if len(freshids)==160:break
168
+ write('manifest.json',{'train':ids,'development':freshids[:80],'heldout':freshids[80:],'fresh_file':'default/val/0001.parquet','revision':'26ddc382fe75dfc2a0655b5977e296ea10efebce','prior_model_revision':REV,'upstream_pretraining_overlap_unknown':True,'near_duplicates_not_excluded':True})
169
+ DURABLE.sync('data cache verified and split fixed')
170
+ m=load_semantic('/work/initial',DEVICE)
171
+ write('initial_validation.json',ev.evaluate(m,table,manifest['validation']))
172
+ review(m,fresh,freshids[:80],'initial_development')
173
+ review(m,fresh,freshids[80:],'initial_heldout')
174
+ write('initial_heldout_reference_metrics.json',ev.evaluate(m,fresh,freshids[80:]))
175
+ del m
176
+ arms=[('teacher_clean','teacher',False),('set_clean','set',False),('teacher_archive','teacher',True),('set_archive','set',True)]
177
+ for name,mode,archive in arms:
178
+ torch.manual_seed(SEED);np.random.seed(SEED);random.seed(SEED)
179
+ m=load_semantic('/work/initial',DEVICE)
180
+ opt=torch.optim.AdamW([{'params':m.encoder.parameters(),'lr':5e-6},{'params':[p for n,p in m.named_parameters() if not n.startswith('encoder.')],'lr':5e-5}],weight_decay=.01)
181
+ sched=torch.optim.lr_scheduler.LambdaLR(opt,lambda step:min((step+1)/100,1)*(.2+.8*(1+math.cos(math.pi*min(step,STEPS)/STEPS))/2))
182
+ ds=Photos(table,ids,cache,archive)
183
+ loader=DataLoader(ds,batch_size=32,shuffle=True,num_workers=4,pin_memory=True,generator=torch.Generator().manual_seed(SEED))
184
+ it=iter(loader);history=[];choices=0;seen=0
185
+ for step in range(1,STEPS+1):
186
+ deadline();m.train()
187
+ for layer in m.encoder.modules():
188
+ if isinstance(layer,torch.nn.BatchNorm2d):layer.eval()
189
+ try:L,t,o=next(it)
190
+ except StopIteration:ds.epoch+=1;it=iter(loader);L,t,o=next(it)
191
+ L,t,o=L.to(DEVICE),t.to(DEVICE),o.to(DEVICE);opt.zero_grad(set_to_none=True)
192
+ with torch.autocast('cuda',dtype=torch.bfloat16):p=m(L)
193
+ loss,choice=select_loss(p,t,o,mode)
194
+ assert torch.isfinite(loss)
195
+ loss.backward();torch.nn.utils.clip_grad_norm_(m.parameters(),1,error_if_nonfinite=True);opt.step();sched.step()
196
+ choices+=int(choice.sum());seen+=len(L)
197
+ if step%100==0:
198
+ history.append({'step':step,'loss':float(loss),'original_choice_fraction':choices/seen})
199
+ print('STEP',name,step,round(float(loss),4),flush=True)
200
+ if step%500==0:
201
+ path=OUT/name/f'step_{step:05d}';save_semantic(m,path)
202
+ torch.save({'optimizer':opt.state_dict(),'scheduler':sched.state_dict(),'step':step,'epoch':ds.epoch,'torch_rng':torch.get_rng_state(),'cuda_rng':torch.cuda.get_rng_state_all(),'numpy_rng':np.random.get_state(),'python_rng':random.getstate()},path/'training_state.pt')
203
+ write(name+'/history.json',history);write('status.json',{'status':'training','arm':name,'step':step,'elapsed_seconds':time.monotonic()-START})
204
+ DURABLE.sync(name+' checkpoint '+str(step))
205
+ write(name+'_validation.json',ev.evaluate(m,table,manifest['validation']))
206
+ review(m,fresh,freshids[:80],name+'_development')
207
+ review(m,fresh,freshids[80:],name+'_heldout')
208
+ write(name+'_heldout_reference_metrics.json',ev.evaluate(m,fresh,freshids[80:]))
209
+ ev.probes(m,name)
210
+ DURABLE.sync(name+' complete diagnostics')
211
+ del m,opt,sched,it,loader;torch.cuda.empty_cache()
212
+ models={'round6':load_semantic('/work/initial',DEVICE)}
213
+ for name,_,_ in arms:models[name]=load_semantic(OUT/name/f'step_{STEPS:05d}',DEVICE)
214
+ grid(models,fresh,freshids[:80],'blinded_development')
215
+ grid(models,fresh,freshids[80:],'blinded_heldout')
216
+ # Familiar historical development probes; never claim these are a held-out archival benchmark.
217
+ import requests
218
+ sources={'migrant_mother':'https://cdn.loc.gov/service/pnp/ppmsca/50200/50236v.jpg','power_house_mechanic':'https://www.archives.gov/exhibits/picturing_the_century/images/port_hine_022_v36.jpg'}
219
+ for name,url in sources.items():
220
+ try:
221
+ response=requests.get(url,timeout=45);response.raise_for_status()
222
+ im=Image.open(io.BytesIO(response.content)).convert('RGB')
223
+ tb=pa.table({'image':[{'bytes':response.content,'path':None}]})
224
+ grid(models,tb,[0],name)
225
+ write(name+'_source.json',{'url':url,'sha256':hashlib.sha256(response.content).hexdigest(),'use':'previously seen archival development probe; original colors unknown'})
226
+ except Exception as e:write(name+'_error.json',{'type':type(e).__name__,'message':str(e)})
227
+ write('status.json',{'status':'completed','elapsed_seconds':time.monotonic()-START,'production_approved':False,'next':'Review blinded grids for coherent plausible color and gray/sepia collapse. This pilot does not test a stochastic conditioned decoder.'})
228
+ DURABLE.sync('round7 four-arm experiment completed')
229
+ if __name__=='__main__':
230
+ try:main()
231
+ except BaseException as e:
232
+ write('failure.json',{'type':type(e).__name__,'traceback':traceback.format_exc().replace(os.environ.get('HF_TOKEN','__EMPTY__'),'[REDACTED]')})
233
+ if DURABLE:DURABLE.sync('round7 failure report and latest saved checkpoints')
234
+ raise
experiments/region-coherence-20260928/initial/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Experimental semantic colorizer
2
+
3
+ Head: palette. Parameters: 3994676.
4
+
5
+ Not approved for production. Requires semantic_model.py; incompatible with the old U-Net loader. Input is Lab lightness normalized to [-1,1]; output is Lab ab. See the run protocol, provenance, selection and visual comparisons. Predictions are plausible colors, not recovered historical truth.
experiments/region-coherence-20260928/initial/config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "architecture": "SemanticColorizer",
3
+ "head": "palette",
4
+ "width": 128,
5
+ "queries": 16,
6
+ "format_version": 1
7
+ }
experiments/region-coherence-20260928/initial/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec1f27d74533adc83f7ab3639a091fc4d8738a434dafc7d172c7873c28a9e715
3
+ size 16112432
experiments/region-coherence-20260928/protocol.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "base_revision": "1a9eb8af2754ad2329a24cfe50d388cb559441d0",
3
+ "artifact_revision": "a78fd9de52e4e3884e4530f634754d2a910a2c55",
4
+ "teacher_cache_revision": "7b10485ee9d205ce2f7b660dfc6a90012d78b1cb",
5
+ "seed": 92851,
6
+ "generator_parameters": 3994676,
7
+ "steps_per_arm": 2000,
8
+ "batch_size": 32,
9
+ "arms": [
10
+ "control",
11
+ "region"
12
+ ],
13
+ "matched": "Same fixed selected targets, input augmentation batches, initial weights, optimizer and updates. Treatment adds .4 object residual consistency + .15 local missing chroma.",
14
+ "supervised": "L1/20 + .25 sliced chroma distribution + .1 gradient difference (existing r.losses)",
15
+ "target_choice": "One complete original or cached DDColor target per training image, selected once by v3 complete-image loss. No per-pixel target mixing. Reject teacher proposal if it removes over40% of original chroma or has >1.5x+1.5 mean chroma edge strength.",
16
+ "region_loss": "Mean absolute spatial residual after subtracting regional mean prediction-target error. Allows constant alternative hue shifts while retaining target patterns.",
17
+ "segmentation": "Frozen torchvision DeepLabV3 ResNet50 COCO_WITH_VOC_LABELS_V1 on original RGB, default520px preprocessing. Confident connected foreground interiors + homogeneous original-color SLIC background surfaces. Max64 regions/image.",
18
+ "inference": "Unchanged architecture and one-pass inference; segmentation/teacher are training-only.",
19
+ "data": "Seeded8192 subset of original training fold; pinned Imagenette/COCO shards and verified cached DDColor. 50% mild archival-style input degradations.",
20
+ "evaluation": "Reused dense-run development and test are explicitly regression sets. New128 COCO val/0001 examples exclude round7 and dense-run evaluation IDs and training byte duplicates. New holdout used only after training; familiar archived-photo probes are not independent historical validation.",
21
+ "limitations": [
22
+ "20 foreground semantic classes only",
23
+ "touching same-class objects can merge",
24
+ "RGB-derived masks are imperfect",
25
+ "DDColor targets are fallible",
26
+ "teacher/encoder upstream pretraining overlap unknown",
27
+ "one seed and short pilot",
28
+ "synthetic film variation is not true archival training",
29
+ "frozen target selection can retain v3 biases"
30
+ ],
31
+ "budget": {
32
+ "hard_timeout_minutes": 30,
33
+ "training_stop_minutes": 23,
34
+ "maximum_compute_usd": 1.25
35
+ },
36
+ "release_policy": "All checkpoints retained for review; no automatic production replacement"
37
+ }
experiments/region-coherence-20260928/smoke.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "passed": true,
3
+ "generator_parameters": 3994676,
4
+ "uniform_shift_allowed": true,
5
+ "patch_penalized": true,
6
+ "gray_collapse_penalized": true,
7
+ "pattern_preserved": true,
8
+ "finite_backward": true,
9
+ "checkpoint_roundtrip": true
10
+ }
experiments/region-coherence-20260928/source/data.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fixed color-bin semantics and reproducible dataset membership."""
2
+ import hashlib
3
+ import io
4
+ import json
5
+ from pathlib import Path
6
+ import numpy as np
7
+ from PIL import Image
8
+ import pyarrow.parquet as pq
9
+ from scipy.spatial import cKDTree
10
+ from skimage.color import rgb2lab
11
+ import torch
12
+ from torch.utils.data import Dataset
13
+
14
+ class ColorBins:
15
+ def __init__(self, centers, weights=None):
16
+ self.centers = np.asarray(centers, np.float32)
17
+ if self.centers.ndim != 2 or self.centers.shape[1] != 2:
18
+ raise ValueError("Expected Q by 2 color centers")
19
+ self.tree = cKDTree(self.centers)
20
+ self.weights = np.ones(len(self.centers), np.float32) if weights is None else np.asarray(weights,np.float32)
21
+
22
+ def encode(self, ab, k=5, sigma=5):
23
+ k = min(k, len(self.centers))
24
+ if sigma <= 0 or k < 1:
25
+ raise ValueError("Invalid encoding parameters")
26
+ dist, idx = self.tree.query(ab.reshape(-1,2), k=k)
27
+ dist, idx = dist.reshape(-1,k), idx.reshape(-1,k)
28
+ logw = -dist**2/(2*sigma**2)
29
+ logw -= logw.max(1, keepdims=True)
30
+ weight = np.exp(logw); weight /= weight.sum(1,keepdims=True)
31
+ shape = (*ab.shape[:-1], k)
32
+ return idx.reshape(shape).astype(np.int64), weight.reshape(shape).astype(np.float32)
33
+
34
+ def load_table(path):
35
+ table = pq.read_table(path)
36
+ return table
37
+
38
+ def validate_manifest(path, table, manifest):
39
+ with open(path,'rb') as f:
40
+ digest=hashlib.file_digest(f,'sha256').hexdigest()
41
+ if digest!=manifest['dataset_sha256'] or len(table)!=manifest['rows']:
42
+ raise ValueError('Dataset differs from pinned split manifest')
43
+ groups=[list(map(int,manifest[k])) for k in ['train','validation','test']]
44
+ if any(not g or len(g)!=len(set(g)) or min(g)<0 or max(g)>=len(table) for g in groups):
45
+ raise ValueError('Invalid or repeated indices in manifest')
46
+ if any(set(groups[i])&set(groups[j]) for i,j in [(0,1),(0,2),(1,2)]):
47
+ raise ValueError('Train/validation/test overlap')
48
+
49
+ def decode_image(table, index):
50
+ obj = table['image'][int(index)].as_py()
51
+ return Image.open(io.BytesIO(obj['bytes'])).convert('RGB')
52
+
53
+ def legacy_split(n, seed=0):
54
+ # Exactly reproduce datasets.Dataset.train_test_split(test_size=.05, seed=0).
55
+ order = np.random.default_rng(seed).permutation(n)
56
+ nval = int(np.ceil(.05*n))
57
+ return order[nval:], order[:nval]
58
+
59
+ def stratified_subset(indices, labels, per_class, seed):
60
+ rng = np.random.default_rng(seed)
61
+ selected = []
62
+ for label in sorted(set(labels)):
63
+ group = np.asarray([i for i in indices if labels[i] == label])
64
+ selected.extend(rng.choice(group, min(per_class,len(group)), replace=False).tolist())
65
+ return np.asarray(selected, dtype=np.int64)
66
+
67
+ class PhotoDataset(Dataset):
68
+ def __init__(self, table, indices, bins, size=256, augment=False):
69
+ self.table, self.indices, self.bins = table, list(map(int,indices)), bins
70
+ self.size, self.augment = size, augment
71
+
72
+ def __len__(self):
73
+ return len(self.indices)
74
+
75
+ def __getitem__(self, item):
76
+ # Keep square preprocessing for controlled comparison with historical runs.
77
+ # Production inference preserves aspect ratio separately.
78
+ image = decode_image(self.table,self.indices[item]).resize((self.size,self.size),Image.Resampling.BILINEAR)
79
+ arr = np.asarray(image,dtype=np.float32)/255
80
+ if self.augment and torch.rand(()) < .5:
81
+ arr = arr[:,::-1].copy()
82
+ lab = rgb2lab(arr).astype(np.float32)
83
+ idx, weight = self.bins.encode(lab[...,1:])
84
+ return (torch.from_numpy((lab[...,:1]/50-1).transpose(2,0,1).copy()),
85
+ torch.from_numpy(lab[...,1:].transpose(2,0,1).copy()),
86
+ torch.from_numpy(idx),torch.from_numpy(weight))
87
+
88
+ def estimate_weights(table, indices, bins, mix=.7, limit=1000, seed=0):
89
+ """Re-estimate the prior ON checkpoint bins; never replace the bin vocabulary."""
90
+ if not 0 <= mix <= 1:
91
+ raise ValueError("Rebalance mixture must be in [0,1]")
92
+ rng=np.random.default_rng(seed)
93
+ chosen=rng.choice(indices,min(limit,len(indices)),replace=False)
94
+ counts=np.ones(len(bins.centers),np.float64)*1e-3
95
+ for index in chosen:
96
+ image=decode_image(table,index).resize((32,32))
97
+ ab=rgb2lab(np.asarray(image,dtype=np.float32)/255)[...,1:]
98
+ # Low chroma is a heuristic, not proof that an image was originally B&W.
99
+ if np.linalg.norm(ab,axis=-1).mean()<3:
100
+ continue
101
+ idx,weight=bins.encode(ab)
102
+ np.add.at(counts,idx.ravel(),weight.ravel())
103
+ prior=counts/counts.sum()
104
+ weights=1/((1-mix)*prior+mix/len(prior))
105
+ weights/=np.sum(prior*weights)
106
+ return weights.astype(np.float32),prior.astype(np.float32)
107
+
108
+ def write_manifest(path, table, train, validation, test, dataset_sha256):
109
+ groups=[set(map(int,g)) for g in [train,validation,test]]
110
+ assert not groups[0]&groups[1] and not groups[0]&groups[2] and not groups[1]&groups[2]
111
+ content={'dataset':'johnowhitaker/imagenette2-320','dataset_sha256':dataset_sha256,
112
+ 'rows':len(table),'split_seed':0,'legacy_holdout_fraction':.05,
113
+ 'train':list(map(int,train)),'validation':list(map(int,validation)), 'test':list(map(int,test)),
114
+ 'caveat':'Matches the recent seed-0 holdout; older upstream training exposure is not established.'}
115
+ Path(path).write_text(json.dumps(content,indent=2))
116
+ return content
experiments/region-coherence-20260928/source/legacy_eval.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Round 4: matched multiscale structure and DDColor distillation experiments."""
2
+ import os,sys,json,time,random,hashlib,traceback,shutil
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import cv2
6
+ from PIL import Image,ImageDraw
7
+ from skimage import data as sample_data
8
+ from skimage.color import rgb2lab,lab2rgb
9
+ import pyarrow as pa
10
+ import pyarrow.parquet as pq
11
+ import torch
12
+ import torch.nn.functional as F
13
+ from torch.utils.data import Dataset,DataLoader
14
+ from huggingface_hub import HfApi,hf_hub_download,PyTorchModelHubMixin
15
+ from model import load_model,save_model
16
+ from data import decode_image,ColorBins
17
+ from metrics import soft_ce,per_image_metrics
18
+ from spatial import guided_chroma
19
+
20
+ OUT=Path('round6');OUT.mkdir(exist_ok=True)
21
+ DURABLE=None
22
+ START=time.monotonic();DEADLINE=START+3.5*3600
23
+ DEVICE='cuda';SEED=409;STEPS=3000
24
+ BASE_REV='704fa80d792c3d759db91daa00b2dcfe6f0f6412'
25
+ BASE_HASH='0e4c417375684a044860f8af3ac3a2fb44e1a5729254ca33abda758ea71aea6e'
26
+ TEACHER_ID='piddnad/ddcolor_artistic'
27
+ torch.set_num_threads(6)
28
+
29
+ def deadline():
30
+ if time.monotonic()>DEADLINE:raise TimeoutError('Graceful export before remote hard timeout')
31
+
32
+ def gray_arrays(im,style='gray'):
33
+ im=im.resize((256,256),Image.Resampling.BILINEAR)
34
+ rgb=np.asarray(im,np.float32)/255
35
+ lab=rgb2lab(rgb).astype(np.float32)
36
+ if style=='film':
37
+ g=(rgb*np.array([.42,.45,.13],np.float32)).sum(-1)
38
+ g=np.clip((g**1.15-.5)*.8+.5,0,1)
39
+ else:g=np.asarray(im.convert('L'),np.float32)/255
40
+ # Feed the same neutral sRGB input to teacher and student.
41
+ gray=np.repeat(g[...,None],3,-1).astype(np.float32)
42
+ L=rgb2lab(gray)[...,0].astype(np.float32)
43
+ return (L[None]/50-1).copy(),lab[...,1:].transpose(2,0,1).copy(),gray
44
+
45
+ class Photos(Dataset):
46
+ def __init__(self,table,ids,bins=None,cache=None,augment=False,style='gray'):
47
+ self.table,self.ids,self.bins,self.cache,self.augment,self.style=table,ids,bins,cache,augment,style
48
+ def __len__(self):return len(self.ids)
49
+ def __getitem__(self,k):
50
+ L,ab,gray=gray_arrays(decode_image(self.table,self.ids[k]),self.style)
51
+ teach=np.array(self.cache[k],np.float32) if self.cache is not None else np.zeros((2,64,64),np.float32)
52
+ if self.augment:
53
+ if random.random()<.5:L=L[...,::-1].copy();ab=ab[...,::-1].copy();teach=teach[...,::-1].copy()
54
+ if random.random()<.3:L=np.clip(L*random.uniform(.85,1.15)+random.uniform(-.08,.08),-1,1)
55
+ if self.bins is not None:
56
+ idx,w=self.bins.encode(ab.transpose(1,2,0));return torch.from_numpy(L),torch.from_numpy(ab),torch.from_numpy(idx),torch.from_numpy(w),torch.from_numpy(teach)
57
+ return torch.from_numpy(L),torch.from_numpy(ab),torch.from_numpy(gray.transpose(2,0,1)),self.ids[k]
58
+
59
+ def patch_excess(p,t):
60
+ """Excess color differences across 4–64px regions, in target-flat areas."""
61
+ values=[]
62
+ for scale in [4,16]:
63
+ a=F.avg_pool2d(p,scale);b=F.avg_pool2d(t,scale)
64
+ for offset in [1,4]:
65
+ for dim in [-1,-2]:
66
+ x=a.narrow(dim,offset,a.shape[dim]-offset)-a.narrow(dim,0,a.shape[dim]-offset)
67
+ y=b.narrow(dim,offset,b.shape[dim]-offset)-b.narrow(dim,0,b.shape[dim]-offset)
68
+ dx=x.norm(dim=1);dy=y.norm(dim=1);mask=(dy<3).float()
69
+ values.append(((dx-dy-1).relu()*mask).sum((1,2))/mask.sum((1,2)).clamp_min(1))
70
+ return sum(values)/len(values)
71
+
72
+ def structural_loss(p,t):
73
+ terms=[]
74
+ for scale in [4,16]:
75
+ a=F.avg_pool2d(p,scale);b=F.avg_pool2d(t,scale)
76
+ for offset in [1,4]:
77
+ for dim in [-1,-2]:
78
+ x=a.narrow(dim,offset,a.shape[dim]-offset)-a.narrow(dim,0,a.shape[dim]-offset)
79
+ y=b.narrow(dim,offset,b.shape[dim]-offset)-b.narrow(dim,0,b.shape[dim]-offset)
80
+ terms.append(F.smooth_l1_loss(x/10,y/10,beta=.3))
81
+ return sum(terms)/len(terms)
82
+
83
+ def additional_losses(p,t,teacher):
84
+ small=F.avg_pool2d(p,4);target=F.avg_pool2d(t,4)
85
+ # Teacher is fallible; reduce guidance when it conflicts strongly with ground truth.
86
+ confidence=torch.exp(-(teacher-target).norm(dim=1,keepdim=True)/30).detach()
87
+ kd=(F.smooth_l1_loss(small/20,teacher/20,beta=.5,reduction='none')*confidence).mean()
88
+ neutral=(t.norm(dim=1)<3).float()
89
+ neutral_loss=(p.norm(dim=1)*neutral).sum()/neutral.sum().clamp_min(1)/20
90
+ return structural_loss(p,t),kd,neutral_loss
91
+
92
+ def measure(L,p,t,raw=None):
93
+ r=per_image_metrics(L,p,t);C=p.norm(dim=1);T=t.norm(dim=1)
94
+ for name,mask,event in [('missed_color',T>12,C<5),('neutral_spill',T<3,C>10)]:
95
+ r[name]=(mask&event).sum((1,2))/mask.sum((1,2)).clamp_min(1)
96
+ r['color_coverage']=(C>10).float().mean((1,2));r['patch_excess']=patch_excess(p,t)
97
+ r['raw_patch_excess']=patch_excess(p if raw is None else raw,t)
98
+ return r
99
+
100
+ @torch.inference_mode()
101
+ def teacher_predict(teacher,gray):
102
+ gray=F.interpolate(gray.to(DEVICE),size=(512,512),mode='bilinear',align_corners=False)
103
+ # Full precision is deliberate: spectral normalization / attention are not assumed BF16-safe.
104
+ out=teacher(gray).float()
105
+ assert out.shape[1]==2 and torch.isfinite(out).all()
106
+ return F.interpolate(out,size=(256,256),mode='bilinear',align_corners=False)
107
+
108
+ @torch.inference_mode()
109
+ def evaluate(model,table,ids,is_teacher=False):
110
+ result={};model.eval()
111
+ for style in ['gray','film']:
112
+ rows=[]
113
+ for L,t,gray,indices in DataLoader(Photos(table,ids,style=style),batch_size=2 if is_teacher else 8,num_workers=4):
114
+ deadline();L,t=L.to(DEVICE),t.to(DEVICE)
115
+ raw=teacher_predict(model,gray) if is_teacher else model.decode(model(L),.38)
116
+ pred=raw if is_teacher else guided_chroma(L,raw,8)
117
+ metrics=measure(L,pred,t,raw)
118
+ for j,index in enumerate(indices):rows.append({'index':int(index)}|{k:float(v[j]) for k,v in metrics.items()})
119
+ keep=[x for x in rows if x['target_chroma']>=5]
120
+ result[style]={'n_total':len(rows),'n_color':len(keep),'summary':{k:float(np.mean([x[k] for x in keep])) for k in keep[0] if k!='index'},'per_image':rows}
121
+ return result
122
+
123
+ def selection(result,baseline):
124
+ scores=[];eligible=True
125
+ for style in ['gray','film']:
126
+ s=result[style]['summary'];b=baseline[style]['summary']
127
+ eligible &= (s['ab_error']<=b['ab_error']*1.05 and s['neutral_spill']<=b['neutral_spill']+.01
128
+ and s['missed_color']<=b['missed_color']+.01 and s['color_coverage']>=.95*b['color_coverage']
129
+ and s['patch_excess']<.85*b['patch_excess'] and s['raw_patch_excess']<.9*b['raw_patch_excess'])
130
+ scores.append(s['patch_excess']/max(b['patch_excess'],1e-6)+.3*s['neutral_spill']+.2*s['ab_error']/b['ab_error'])
131
+ return float(np.mean(scores)),bool(eligible)
132
+
133
+ @torch.inference_mode()
134
+ def probes(model,tag,teacher=False):
135
+ names=['astronaut','coffee','chelsea','rocket','camera','coins','moon'];images=[]
136
+ for name in names:
137
+ original=Image.fromarray(getattr(sample_data,name)()).convert('RGB')
138
+ L,_,gray=gray_arrays(original);x=torch.from_numpy(L)[None].to(DEVICE)
139
+ ab=teacher_predict(model,torch.from_numpy(gray.transpose(2,0,1))[None]) if teacher else guided_chroma(x,model.decode(model(x),.38),8)
140
+ lab=np.concatenate([(L[0]*50+50)[...,None],ab[0].cpu().numpy().transpose(1,2,0)],-1)
141
+ rgb=np.uint8(np.clip(lab2rgb(lab),0,1)*255)
142
+ images.append((name,Image.fromarray(np.uint8(gray*255)),Image.fromarray(rgb)))
143
+ canvas=Image.new('RGB',(512,len(names)*280),'white');d=ImageDraw.Draw(canvas)
144
+ for i,(name,gray,col) in enumerate(images):
145
+ canvas.paste(gray,(0,i*280+24));canvas.paste(col,(256,i*280+24));d.text((4,i*280+4),name+' input | '+tag,fill='black')
146
+ canvas.save(OUT/(tag+'.jpg'),quality=90)
147
+
148
+ def write(name,obj):
149
+ (OUT/name).write_text(json.dumps(obj,indent=2));return obj
150
+
experiments/region-coherence-20260928/source/metrics.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Metrics are fidelity/artifact proxies; none certifies plausible color by itself."""
2
+ import numpy as np
3
+ import torch
4
+
5
+ def soft_ce(logits, idx, weights, class_weights=None):
6
+ # FP32 reduction under autocast; no full target distribution materialized.
7
+ logits=logits.float()
8
+ idx=idx.permute(0,3,1,2)
9
+ weights=weights.permute(0,3,1,2)
10
+ nll=-((logits.gather(1,idx)-logits.logsumexp(1,keepdim=True))*weights).sum(1)
11
+ if class_weights is not None:
12
+ nll=nll*class_weights[idx[:,0]]
13
+ return nll.mean()
14
+
15
+ def per_image_metrics(L, pred, target):
16
+ error=(pred-target).square().sum(1).sqrt().mean((1,2))
17
+ chroma=pred.square().sum(1).sqrt().mean((1,2))
18
+ true_chroma=target.square().sum(1).sqrt().mean((1,2))
19
+ seams=[]
20
+ for dim in [-1,-2]:
21
+ dp=pred.diff(dim=dim).square().sum(1).sqrt()
22
+ dt=target.diff(dim=dim).square().sum(1).sqrt()
23
+ dl=L.diff(dim=dim).abs()[:,0]*50
24
+ # Penalize excess chroma discontinuity where luminance is flat.
25
+ mask=(dl<2).float()
26
+ seams.append(((dp-dt).relu()*mask).sum((1,2))/mask.sum((1,2)).clamp_min(1))
27
+ return {'ab_error':error,'chroma':chroma,'target_chroma':true_chroma,
28
+ 'excess_chroma_edge':sum(seams)/2}
29
+
30
+ def summarize(rows):
31
+ keys=['ce','ab_error','chroma','target_chroma','excess_chroma_edge']
32
+ result={k:float(np.mean([r[k] for r in rows])) for k in keys}
33
+ result['n']=len(rows)
34
+ result['chroma_ratio']=result['chroma']/max(result['target_chroma'],1e-9)
35
+ return result
experiments/region-coherence-20260928/source/model.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared, checkpoint-compatible Mini U-Net. Parameters remain under 4M."""
2
+ import json
3
+ import math
4
+ from pathlib import Path
5
+ import numpy as np
6
+ import torch
7
+ from torch import nn
8
+ import torch.nn.functional as F
9
+ from huggingface_hub import PyTorchModelHubMixin, snapshot_download
10
+ from safetensors.torch import load_file, save_file
11
+ def double_conv(in_ch, out_ch):
12
+ return nn.Sequential(
13
+ nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False),
14
+ nn.BatchNorm2d(out_ch),
15
+ nn.ReLU(inplace=True),
16
+ nn.Conv2d(out_ch, out_ch, 3, padding=1, bias=False),
17
+ nn.BatchNorm2d(out_ch),
18
+ nn.ReLU(inplace=True),
19
+ )
20
+
21
+
22
+ class DilatedContextBlock(nn.Module):
23
+ def __init__(self, channels, mid_ch=96, dilations=(2, 4, 8)):
24
+ super().__init__()
25
+ self.proj_in = nn.Sequential(
26
+ nn.Conv2d(channels, mid_ch, 1, bias=False),
27
+ nn.BatchNorm2d(mid_ch),
28
+ nn.ReLU(inplace=True),
29
+ )
30
+ layers = []
31
+ for d in dilations:
32
+ layers += [
33
+ nn.Conv2d(mid_ch, mid_ch, 3, padding=d, dilation=d, bias=False),
34
+ nn.BatchNorm2d(mid_ch),
35
+ nn.ReLU(inplace=True),
36
+ ]
37
+ self.dilated = nn.Sequential(*layers)
38
+ self.proj_out = nn.Sequential(
39
+ nn.Conv2d(mid_ch, channels, 1, bias=False),
40
+ nn.BatchNorm2d(channels),
41
+ )
42
+ nn.init.zeros_(self.proj_out[-1].weight)
43
+ self.relu = nn.ReLU(inplace=True)
44
+
45
+ def forward(self, x):
46
+ y = self.proj_in(x)
47
+ y = self.dilated(y)
48
+ y = self.proj_out(y)
49
+ return self.relu(x + y)
50
+
51
+
52
+ class SmallUNetColorizer(
53
+ nn.Module,
54
+ PyTorchModelHubMixin,
55
+ pipeline_tag="image-to-image",
56
+ license="apache-2.0",
57
+ tags=["colorization", "unet", "image-to-image", "classification"],
58
+ ):
59
+ def __init__(self, bin_centers, in_ch: int = 1, base: int = 44,
60
+ context_mid_ch: int = 96, context_dilations=(2, 4, 8)):
61
+ super().__init__()
62
+ self.in_ch, self.base = in_ch, base
63
+ num_bins = len(bin_centers)
64
+ self.num_bins = num_bins
65
+ self.register_buffer("bin_centers", torch.tensor(bin_centers, dtype=torch.float32))
66
+
67
+ self.enc1 = double_conv(in_ch, base)
68
+ self.enc2 = double_conv(base, base * 2)
69
+ self.enc3 = double_conv(base * 2, base * 4)
70
+ self.enc4 = double_conv(base * 4, base * 8)
71
+ self.pool = nn.MaxPool2d(2)
72
+ self.context = DilatedContextBlock(base * 8, mid_ch=context_mid_ch,
73
+ dilations=tuple(context_dilations))
74
+ self.up3 = nn.ConvTranspose2d(base * 8, base * 4, 2, stride=2)
75
+ self.dec3 = double_conv(base * 8, base * 4)
76
+ self.up2 = nn.ConvTranspose2d(base * 4, base * 2, 2, stride=2)
77
+ self.dec2 = double_conv(base * 4, base * 2)
78
+ self.up1 = nn.ConvTranspose2d(base * 2, base, 2, stride=2)
79
+ self.dec1 = double_conv(base * 2, base)
80
+ self.out_conv = nn.Conv2d(base, num_bins, 1)
81
+
82
+ def forward(self, x):
83
+ h, w = x.shape[-2:]
84
+ x = F.pad(x, (0, (-w) % 8, 0, (-h) % 8), mode="replicate")
85
+ e1 = self.enc1(x)
86
+ e2 = self.enc2(self.pool(e1))
87
+ e3 = self.enc3(self.pool(e2))
88
+ e4 = self.context(self.enc4(self.pool(e3)))
89
+ d3 = self.dec3(torch.cat([self.up3(e4), e3], dim=1))
90
+ d2 = self.dec2(torch.cat([self.up2(d3), e2], dim=1))
91
+ d1 = self.dec1(torch.cat([self.up1(d2), e1], dim=1))
92
+ return self.out_conv(d1)[..., :h, :w]
93
+
94
+ def decode(self, logits, temperature: float = 0.38):
95
+ if not math.isfinite(temperature) or temperature <= 0:
96
+ raise ValueError("temperature must be finite and positive")
97
+ probs_t = F.softmax(logits.float() / temperature, dim=1)
98
+ return torch.einsum("bqhw,qc->bchw", probs_t, self.bin_centers)
99
+
100
+
101
+ def load_model(source, revision=None, device="cpu"):
102
+ path = Path(source)
103
+ if not path.is_dir():
104
+ path = Path(snapshot_download(source, revision=revision,
105
+ allow_patterns=["config.json", "model.safetensors"]))
106
+ config = json.loads((path / "config.json").read_text())
107
+ state = load_file(str(path / "model.safetensors"))
108
+ centers = torch.tensor(config["bin_centers"], dtype=torch.float32)
109
+ if not torch.equal(centers, state["bin_centers"]):
110
+ raise ValueError("Checkpoint config and state color bins differ; refusing ambiguous decode")
111
+ model = SmallUNetColorizer(**config)
112
+ model.load_state_dict(state, strict=True)
113
+ model.to(device).eval()
114
+ return model
115
+
116
+ def save_model(model, path):
117
+ path = Path(path); path.mkdir(parents=True, exist_ok=True)
118
+ model.save_pretrained(path)
119
+ # Mixin config can retain constructor bins; use the actual authoritative buffer.
120
+ cfg = json.loads((path / "config.json").read_text())
121
+ cfg["bin_centers"] = model.bin_centers.detach().cpu().tolist()
122
+ (path / "config.json").write_text(json.dumps(cfg, indent=2))
experiments/region-coherence-20260928/source/objectives.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import torch.nn.functional as F
4
+
5
+ def color_loss(pred,target):
6
+ """Fit one coherent target; distribution matching discourages gray averages."""
7
+ pred=F.adaptive_avg_pool2d(pred,(64,64)).float();target=target.float()
8
+ pixel=F.l1_loss(pred/20,target/20)
9
+ p=F.avg_pool2d(pred,2).flatten(2);t=F.avg_pool2d(target,2).flatten(2)
10
+ angle=torch.arange(8,device=p.device,dtype=p.dtype)*math.pi/8
11
+ directions=torch.stack([angle.cos(),angle.sin()],1)
12
+ a=torch.einsum('kc,bcn->bkn',directions,p).sort(-1).values
13
+ b=torch.einsum('kc,bcn->bkn',directions,t).sort(-1).values
14
+ distribution=F.l1_loss(a/20,b/20)
15
+ # Match boundaries in the teacher target, never minimize gradients toward zero.
16
+ edge=(F.l1_loss((pred[:,:,:,1:]-pred[:,:,:,:-1])/20,(target[:,:,:,1:]-target[:,:,:,:-1])/20)+F.l1_loss((pred[:,:,1:]-pred[:,:,:-1])/20,(target[:,:,1:]-target[:,:,:-1])/20))/2
17
+ return pixel+.25*distribution+.1*edge,{'pixel':pixel,'distribution':distribution,'edge':edge}
experiments/region-coherence-20260928/source/persistence.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Commit run artifacts atomically and verify every changed file by SHA256."""
2
+ import hashlib, json, os, time
3
+ from pathlib import Path
4
+ from huggingface_hub import HfApi, CommitOperationAdd, hf_hub_download
5
+
6
+ REPO = 'User-2468/mini-unet-colorizer'
7
+ PREFIX = 'experiments/round6-20260928'
8
+
9
+ class DurableRun:
10
+ def __init__(self, root):
11
+ if not os.environ.get('HF_TOKEN'):
12
+ raise RuntimeError('A write token is required before starting')
13
+ self.api = HfApi(token=os.environ['HF_TOKEN'])
14
+ if self.api.whoami()['name'] != 'User-2468':
15
+ raise RuntimeError('Unexpected account')
16
+ self.root = Path(root)
17
+ self.saved = {}
18
+
19
+ def sync(self, reason):
20
+ files = [p for p in sorted(self.root.rglob('*')) if p.is_file()]
21
+ hashes = {p.relative_to(self.root).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest() for p in files}
22
+ changed = [p for p in files if self.saved.get(p.relative_to(self.root).as_posix()) != hashes[p.relative_to(self.root).as_posix()]]
23
+ if not changed:
24
+ return
25
+ for attempt in range(3):
26
+ try:
27
+ head = self.api.model_info(REPO, revision='main').sha
28
+ operations = [CommitOperationAdd(path_in_repo=PREFIX+'/'+p.relative_to(self.root).as_posix(), path_or_fileobj=str(p)) for p in changed]
29
+ operations.append(CommitOperationAdd(path_in_repo=PREFIX+'/SHA256SUMS.json', path_or_fileobj=json.dumps(hashes,sort_keys=True,indent=2).encode()))
30
+ commit = self.api.create_commit(repo_id=REPO, revision='main', parent_commit=head, operations=operations, commit_message='Colorizer round6: '+reason)
31
+ for p in changed:
32
+ name = p.relative_to(self.root).as_posix()
33
+ downloaded = hf_hub_download(REPO, PREFIX+'/'+name, revision=commit.oid, force_download=True, token=os.environ['HF_TOKEN'])
34
+ if hashlib.sha256(Path(downloaded).read_bytes()).hexdigest() != hashes[name]:
35
+ raise RuntimeError('Remote checksum mismatch: '+name)
36
+ self.saved = hashes
37
+ print('PERSISTED',reason,commit.oid,'verified_files',len(changed),flush=True)
38
+ return
39
+ except Exception:
40
+ if attempt == 2:
41
+ raise
42
+ time.sleep(2**attempt)
experiments/region-coherence-20260928/source/previous_manifest.json ADDED
The diff for this file is too large to render. See raw diff
 
experiments/region-coherence-20260928/source/semantic_model.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compact pretrained semantic colorizer; dense and shared-palette variants."""
2
+ import json
3
+ from pathlib import Path
4
+ import torch
5
+ from torch import nn
6
+ import torch.nn.functional as F
7
+ from torchvision.models import mobilenet_v3_large, MobileNet_V3_Large_Weights
8
+ from safetensors.torch import save_file, load_file
9
+
10
+ class QueryBlock(nn.Module):
11
+ def __init__(self,d=96):
12
+ super().__init__()
13
+ self.self_attn=nn.MultiheadAttention(d,4,batch_first=True,dropout=0)
14
+ self.cross_attn=nn.MultiheadAttention(d,4,batch_first=True,dropout=0)
15
+ self.norms=nn.ModuleList([nn.LayerNorm(d) for _ in range(3)])
16
+ self.ff=nn.Sequential(nn.Linear(d,2*d),nn.GELU(),nn.Linear(2*d,d))
17
+ def forward(self,q,memory):
18
+ x=self.norms[0](q);q=q+self.self_attn(x,x,x,need_weights=False)[0]
19
+ x=self.norms[1](q);q=q+self.cross_attn(x,memory,memory,need_weights=False)[0]
20
+ return q+self.ff(self.norms[2](q))
21
+
22
+ def refine(d):
23
+ return nn.Sequential(nn.Conv2d(d,d,3,padding=1,bias=False),nn.GroupNorm(8,d),nn.SiLU())
24
+
25
+ class SemanticColorizer(nn.Module):
26
+ def __init__(self,head='palette',pretrained=False,width=128,queries=16):
27
+ super().__init__()
28
+ if head not in ['palette','dense']:raise ValueError(head)
29
+ self.config={'architecture':'SemanticColorizer','head':head,'width':width,'queries':queries,'format_version':1}
30
+ self.encoder=mobilenet_v3_large(weights=MobileNet_V3_Large_Weights.IMAGENET1K_V2 if pretrained else None,progress=False).features
31
+ self.lateral=nn.ModuleList([nn.Conv2d(c,width,1) for c in [24,40,112,960]])
32
+ self.refine=nn.ModuleList([refine(width) for _ in range(3)])
33
+ self.register_buffer('rgb_mean',torch.tensor([.485,.456,.406]).view(1,3,1,1))
34
+ self.register_buffer('rgb_std',torch.tensor([.229,.224,.225]).view(1,3,1,1))
35
+ if head=='palette':
36
+ self.queries=nn.Parameter(torch.randn(queries,width)*.2)
37
+ self.query_blocks=nn.ModuleList([QueryBlock(width) for _ in range(2)])
38
+ self.memory_norm=nn.LayerNorm(width)
39
+ self.query_norm=nn.LayerNorm(width)
40
+ self.pixel=nn.Conv2d(width,width,1)
41
+ self.palette=nn.Sequential(nn.Linear(width,width),nn.GELU(),nn.Linear(width,2))
42
+ self.residual=nn.Conv2d(width,2,1)
43
+ nn.init.normal_(self.palette[-1].weight,std=.01);nn.init.zeros_(self.palette[-1].bias)
44
+ nn.init.zeros_(self.residual.weight);nn.init.zeros_(self.residual.bias)
45
+ else:
46
+ self.dense=nn.Sequential(refine(width),nn.Conv2d(width,2,1))
47
+ nn.init.normal_(self.dense[-1].weight,std=.01);nn.init.zeros_(self.dense[-1].bias)
48
+ count=sum(p.numel() for p in self.parameters())
49
+ if count>=4_000_000:raise ValueError(f'Parameter budget exceeded: {count}')
50
+
51
+ @staticmethod
52
+ def neutral_rgb(L):
53
+ light=(L.float()*50+50).clamp(0,100)
54
+ y=torch.where(light>8,((light+16)/116)**3,light/903.296296)
55
+ g=torch.where(y<=.0031308,12.92*y,1.055*y.clamp_min(1e-8).pow(1/2.4)-.055)
56
+ return g.expand(-1,3,-1,-1)
57
+
58
+ def forward(self,L):
59
+ h,w=L.shape[-2:]
60
+ x=F.pad(self.neutral_rgb(L),(0,(-w)%32,0,(-h)%32),mode='replicate')
61
+ x=(x-self.rgb_mean)/self.rgb_std
62
+ features=[]
63
+ for i,layer in enumerate(self.encoder):
64
+ x=layer(x)
65
+ if i in [3,6,12,16]:features.append(x)
66
+ projected=[layer(f) for layer,f in zip(self.lateral,features)]
67
+ x=projected[-1]
68
+ for i in range(2,-1,-1):
69
+ x=self.refine[2-i](F.interpolate(x,size=projected[i].shape[-2:],mode='bilinear',align_corners=False)+projected[i])
70
+ if self.config['head']=='palette':
71
+ memory=torch.cat([F.adaptive_avg_pool2d(f,(8,8)).flatten(2).transpose(1,2) for f in projected[1:]],1)
72
+ memory=self.memory_norm(memory)
73
+ q=self.queries[None].expand(L.shape[0],-1,-1)
74
+ for block in self.query_blocks:q=block(q,memory)
75
+ q=self.query_norm(q)
76
+ palette=80*torch.tanh(self.palette(q))
77
+ masks=torch.einsum('bqd,bdhw->bqhw',q,self.pixel(x))/(self.config['width']**.5)
78
+ weights=F.softmax(masks.float(),dim=1)
79
+ ab=torch.einsum('bqhw,bqc->bchw',weights,palette.float())+2*torch.tanh(self.residual(x).float())
80
+ else:ab=80*torch.tanh(self.dense(x).float())
81
+ return F.interpolate(ab,size=(x.shape[-2]*4,x.shape[-1]*4),mode='bilinear',align_corners=False)[...,:h,:w]
82
+ def decode(self,z,temperature=.38):return z.float()
83
+
84
+ def save_semantic(model,path):
85
+ path=Path(path);path.mkdir(parents=True,exist_ok=True)
86
+ save_file({k:v.detach().cpu().contiguous() for k,v in model.state_dict().items()},str(path/'model.safetensors'))
87
+ (path/'config.json').write_text(json.dumps(model.config,indent=2))
88
+ (path/'README.md').write_text('# Experimental semantic colorizer\n\nHead: '+model.config['head']+'. Parameters: '+str(sum(p.numel() for p in model.parameters()))+'.\n\nNot approved for production. Requires semantic_model.py; incompatible with the old U-Net loader. Input is Lab lightness normalized to [-1,1]; output is Lab ab. See the run protocol, provenance, selection and visual comparisons. Predictions are plausible colors, not recovered historical truth.\n')
89
+
90
+ def load_semantic(path,device='cpu'):
91
+ path=Path(path);cfg=json.loads((path/'config.json').read_text())
92
+ model=SemanticColorizer(**{k:cfg[k] for k in ['head','width','queries']})
93
+ model.load_state_dict(load_file(str(path/'model.safetensors')),strict=True)
94
+ return model.to(device).eval()
experiments/region-coherence-20260928/source/spatial.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Controlled spatial decoding alternatives; no learned parameters."""
2
+ import math
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ def box_mean(x,radius):
7
+ # Border-normalized local windows, also work on images smaller than kernel.
8
+ return F.avg_pool2d(x,2*radius+1,stride=1,padding=radius,count_include_pad=False)
9
+
10
+ def guided_chroma(L,ab,radius=4,epsilon=.001):
11
+ """Scalar luminance-guided local linear filter (He et al., ECCV 2010).
12
+
13
+ L is the network's [-1,1] luminance. Epsilon is in [0,1] luminance units.
14
+ Uses luminance only; reference colors never enter inference.
15
+ """
16
+ if not isinstance(radius,int) or radius<0 or not math.isfinite(epsilon) or epsilon<=0:
17
+ raise ValueError('Invalid guided-filter radius or epsilon')
18
+ if radius==0:return ab
19
+ I=(L.float()+1)/2;p=ab.float()
20
+ mi=box_mean(I,radius);mp=box_mean(p,radius)
21
+ var=(box_mean(I*I,radius)-mi*mi).clamp_min(0)
22
+ cov=box_mean(I*p,radius)-mi*mp
23
+ a=cov/(var+epsilon);b=mp-a*mi
24
+ return box_mean(a,radius)*I+box_mean(b,radius)
25
+
26
+ def spatial_decode(model,logits,L,temperature=.38,pool=1,radius=0,epsilon=.001):
27
+ if pool<1 or not isinstance(pool,int):raise ValueError('pool must be a positive integer')
28
+ if pool>1:
29
+ # Average evidence before annealing, then upsample chroma, not RGB.
30
+ small=F.avg_pool2d(logits,pool,ceil_mode=True,count_include_pad=False)
31
+ ab=model.decode(small,temperature)
32
+ ab=F.interpolate(ab,size=L.shape[-2:],mode='bilinear',align_corners=False)
33
+ else:ab=model.decode(logits,temperature)
34
+ return guided_chroma(L,ab,radius,epsilon)
experiments/region-coherence-20260928/train.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os,sys,time,json,hashlib,shutil,random,copy,math,traceback,io
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import torch
6
+ from torch import nn
7
+ import torch.nn.functional as F
8
+ from torch.utils.data import Dataset,DataLoader
9
+ from torchvision.models.segmentation import deeplabv3_resnet50,DeepLabV3_ResNet50_Weights
10
+ from skimage.segmentation import slic
11
+ import cv2
12
+ from PIL import ImageDraw
13
+ from torchvision.transforms import RandomResizedCrop
14
+ from torchvision.transforms import functional as TF
15
+ from PIL import Image
16
+ from skimage.color import rgb2lab
17
+ import pyarrow as pa
18
+ import pyarrow.parquet as pq
19
+ from huggingface_hub import HfApi,hf_hub_download
20
+ REPO='User-2468/mini-unet-colorizer'
21
+ BASE='1a9eb8af2754ad2329a24cfe50d388cb559441d0'
22
+ PREFIX='experiments/region-coherence-20260928'
23
+ WORK=Path('/work');WORK.mkdir(exist_ok=True);os.chdir(WORK)
24
+ START=time.monotonic();MAX_SECONDS=23*60
25
+ api=HfApi(token=os.environ['HF_TOKEN']);assert api.whoami()['name']=='User-2468'
26
+ src='experiments/final-20260928/realism_low_v2'
27
+ source=WORK/'source';source.mkdir(exist_ok=True)
28
+ sums=json.loads(Path(hf_hub_download(REPO,src+'/SHA256SUMS.json',revision=BASE)).read_text())
29
+ for f in ['data.py','legacy_eval.py','metrics.py','model.py','objectives.py','persistence.py','previous_manifest.json','semantic_model.py','spatial.py']:
30
+ p=Path(hf_hub_download(REPO,src+'/source/'+f,revision=BASE))
31
+ assert hashlib.sha256(p.read_bytes()).hexdigest()==sums['source/'+f]
32
+ shutil.copy2(p,source/f)
33
+ shutil.copy2(hf_hub_download(REPO,src+'/data_and_eval.py',revision=BASE),WORK/'r7.py')
34
+ sys.path.insert(0,str(source));sys.path.insert(0,str(WORK))
35
+ import r7 as r
36
+ from semantic_model import load_semantic,save_semantic
37
+ from data import decode_image
38
+ r.persistence.PREFIX=PREFIX
39
+ OUT=r.OUT;durable=r.persistence.DurableRun(OUT)
40
+ initial=WORK/'initial';initial.mkdir(exist_ok=True)
41
+ for f in ['model.safetensors','config.json']:
42
+ shutil.copy2(hf_hub_download(REPO,f,revision=BASE),initial/f)
43
+ r.ev.deadline=lambda:None
44
+ SEED=92851;STEPS=2000;BATCH=32
45
+ HEAD=api.model_info(REPO,revision='main').sha
46
+ CACHE_REV='7b10485ee9d205ce2f7b660dfc6a90012d78b1cb'
47
+ torch.set_num_threads(8);torch.manual_seed(SEED);random.seed(SEED);np.random.seed(SEED)
48
+ torch.backends.cudnn.benchmark=True
49
+ torch.backends.cuda.matmul.allow_tf32=True
50
+ torch.backends.cudnn.allow_tf32=True
51
+
52
+
53
+ def coherence(pred,target,regions):
54
+ p=F.adaptive_avg_pool2d(pred.float(),(64,64))
55
+ t=target.float()
56
+ b=p.shape[0];idx=regions.long().reshape(b,1,-1)
57
+ err=(p-t).flatten(2);n=65
58
+ count=err.new_zeros(b,1,n).scatter_add_(2,idx,torch.ones_like(idx,dtype=err.dtype))
59
+ sums=err.new_zeros(b,2,n).scatter_add_(2,idx.expand(-1,2,-1),err)
60
+ mean=sums/count.clamp_min(1)
61
+ centered=(err-mean.gather(2,idx.expand(-1,2,-1))).abs().mean(1,keepdim=True)
62
+ rs=err.new_zeros(b,1,n).scatter_add_(2,idx,centered)/count.clamp_min(1)
63
+ valid=count>=16;valid[:,:,0]=False
64
+ region=(rs*valid).sum()/valid.sum().clamp_min(1)/20
65
+ # Penalize loss of chroma relative to the selected target, not universal saturation.
66
+ holes=(.65*t.norm(dim=1)-p.norm(dim=1)).relu()
67
+ mask=(t.norm(dim=1)>10)&(regions>0)
68
+ holes=(holes*mask).sum()/mask.sum().clamp_min(1)/20
69
+ return region,holes
70
+
71
+ def objective(pred,target,regions,arm):
72
+ supervised=r.losses(pred,target).mean()
73
+ region,holes=coherence(pred,target,regions)
74
+ total=supervised+(.4*region+.15*holes if arm=='region' else 0)
75
+ return total,{'supervised':float(supervised.detach()),'region':float(region.detach()),'holes':float(holes.detach())}
76
+
77
+ def smoke():
78
+ t=torch.zeros(2,2,64,64,device='cuda');t[:,0]=25
79
+ seg=torch.ones(2,64,64,device='cuda',dtype=torch.long)
80
+ # Whole-object hue shifts preserve internal structure; local defects do not.
81
+ assert coherence(t,t,seg)[0]==0
82
+ assert coherence(t+10,t,seg)[0]<1e-6
83
+ patch=t.clone();patch[:,:,20:40,20:40]=0
84
+ assert coherence(patch,t,seg)[0]>.01
85
+ assert coherence(torch.zeros_like(t),t,seg)[1]>.1
86
+ pattern=t.clone();pattern[:,:,:,::2]+=7
87
+ assert coherence(pattern,pattern,seg)[0]==0
88
+ p=patch.clone().requires_grad_(True);loss,_=objective(p,t,seg,'region')
89
+ loss.backward();assert torch.isfinite(p.grad).all() and p.grad.abs().sum()>0
90
+ m=load_semantic(initial,'cuda')
91
+ assert sum(v.numel() for v in m.parameters())==3994676
92
+ x=torch.zeros(2,1,256,256,device='cuda');loss,_=objective(m(x),t,seg,'region')
93
+ loss.backward()
94
+ assert all(torch.isfinite(v.grad).all() for v in m.parameters() if v.grad is not None)
95
+ save_semantic(m,OUT/'initial')
96
+ reload=load_semantic(OUT/'initial','cuda')
97
+ for k,v in m.state_dict().items():assert torch.equal(v,reload.state_dict()[k])
98
+ r.write('smoke.json',{'passed':True,'generator_parameters':3994676,'uniform_shift_allowed':True,'patch_penalized':True,'gray_collapse_penalized':True,'pattern_preserved':True,'finite_backward':True,'checkpoint_roundtrip':True})
99
+ del m,reload
100
+
101
+ def make_regions(label,confidence,rgb,target):
102
+ # Foreground connected components are semantic regions; touching instances may merge.
103
+ output=np.zeros((64,64),np.uint8);candidates=[]
104
+ for cls in np.unique(label):
105
+ if cls==0:continue
106
+ n,cc,stats,_=cv2.connectedComponentsWithStats((label==cls).astype(np.uint8),8)
107
+ for j in range(1,n):
108
+ if stats[j,cv2.CC_STAT_AREA]<32:continue
109
+ mask=(cc==j)
110
+ if confidence[mask].mean()<.75:continue
111
+ inner=cv2.erode(mask.astype(np.uint8),np.ones((3,3),np.uint8)).astype(bool)&(confidence>.65)
112
+ if inner.sum()>=16:candidates.append((int(inner.sum()),inner,True))
113
+ # Homogeneous reference surfaces cover background categories outside VOC's 20 classes.
114
+ sp=slic(rgb,n_segments=80,compactness=10,start_label=1,channel_axis=-1)
115
+ for j in np.unique(sp):
116
+ mask=(sp==j)&(label==0)
117
+ if mask.sum()<16:continue
118
+ colors=target[:,mask]
119
+ if np.linalg.norm(colors-colors.mean(1,keepdims=True),axis=0).mean()>5:continue
120
+ candidates.append((int(mask.sum()),mask,False))
121
+ candidates.sort(key=lambda x:(x[2],x[0]),reverse=True)
122
+ semantic=0
123
+ for _,mask,is_semantic in candidates:
124
+ mask=mask&(output==0)
125
+ if mask.sum()<16:continue
126
+ nextid=int(output.max())+1
127
+ if nextid>64:break
128
+ output[mask]=nextid;semantic+=int(is_semantic)
129
+ return output,semantic
130
+
131
+ class TrainPhotos(Dataset):
132
+ def __init__(self,rgb,targets,regions):
133
+ self.rgb,self.targets,self.regions=rgb,targets,regions
134
+ def __len__(self):return len(self.rgb)
135
+ def __getitem__(self,k):
136
+ rgb=self.rgb[k].astype(np.float32)/255
137
+ if random.random()<.5:
138
+ g=rgb@np.random.dirichlet([3.,5.,2.]).astype(np.float32)
139
+ g=np.clip((g**random.uniform(.8,1.25)-.5)*random.uniform(.75,1.15)+.5+random.uniform(-.05,.05),0,1)
140
+ if random.random()<.4:g=cv2.GaussianBlur(g,(5,5),random.uniform(.3,1.0))
141
+ if random.random()<.4:g=np.clip(g+np.random.normal(0,random.uniform(.002,.012),g.shape),0,1)
142
+ else:g=np.asarray(Image.fromarray(self.rgb[k]).convert('L'),np.float32)/255
143
+ L=rgb2lab(np.repeat(g[...,None],3,-1).astype(np.float32))[...,0][None]/50-1
144
+ target=self.targets[k].astype(np.float32);regions=self.regions[k].astype(np.int64)
145
+ if random.random()<.5:L=L[...,::-1];target=target[...,::-1];regions=regions[...,::-1]
146
+ return torch.from_numpy(L.copy()),torch.from_numpy(target.copy()),torch.from_numpy(regions.copy())
147
+
148
+ def persist(models,opts,step,history):
149
+ for name,m in models.items():
150
+ save_semantic(m,OUT/name/f'step_{step:05d}')
151
+ torch.save({'optimizer':opts[name].state_dict(),'step':step,'torch_rng':torch.get_rng_state(),'cuda_rng':torch.cuda.get_rng_state_all(),'numpy_rng':np.random.get_state(),'python_rng':random.getstate(),'note':'Loader cursor not stored; data order after resume is not bit-exact'},OUT/name/'latest_optimizer.pt')
152
+ r.write('history.json',history)
153
+ r.write('status.json',{'status':'training','step':step,'elapsed_seconds':time.monotonic()-START,'production_approved':False})
154
+ durable.sync('region pilot step '+str(step))
155
+
156
+ @torch.inference_mode()
157
+ def fixed_losses(models,table,ids,segmenter,preprocess,name):
158
+ rows=[]
159
+ for i in ids:
160
+ im=decode_image(table,i).convert('RGB').resize((256,256))
161
+ rgb=np.asarray(im,np.float32)/255
162
+ L,ab,_=r.ev.gray_arrays(im)
163
+ x=torch.from_numpy(L)[None].cuda()
164
+ t=F.avg_pool2d(torch.from_numpy(ab)[None].cuda(),4)
165
+ logits=segmenter(preprocess(torch.from_numpy(rgb).permute(2,0,1)[None].cuda()))['out'].float()
166
+ prob=F.interpolate(logits,(64,64),mode='bilinear',align_corners=False).softmax(1)
167
+ conf,label=prob.max(1)
168
+ seg,_=make_regions(label[0].cpu().numpy(),conf[0].cpu().numpy(),cv2.resize(rgb,(64,64)),t[0].cpu().numpy())
169
+ rr=torch.from_numpy(seg.astype(np.int64))[None].cuda()
170
+ row={'id':int(i)}
171
+ for key,m in models.items():
172
+ _,terms=objective(m.eval()(x),t,rr,'region')
173
+ row[key]=terms
174
+ rows.append(row)
175
+ r.write(name+'.json',{'rows':rows,'summary':{key:{term:float(np.mean([row[key][term] for row in rows])) for term in ['supervised','region','holes']} for key in models},'warning':'Fixed originals and frozen masks permit global object hue offsets for region metric. Not a proof of perceptual quality. No moving critic.'})
176
+
177
+ def main():
178
+ shutil.copytree(source,OUT/'source',dirs_exist_ok=True,ignore=shutil.ignore_patterns('__pycache__'))
179
+ shutil.copy2('/work/train.py',OUT/'train.py');shutil.copy2('/work/r7.py',OUT/'data_and_eval.py')
180
+ protocol={'base_revision':BASE,'artifact_revision':HEAD,'teacher_cache_revision':CACHE_REV,'seed':SEED,'generator_parameters':3994676,'steps_per_arm':STEPS,'batch_size':BATCH,
181
+ 'arms':['control','region'],'matched':'Same fixed selected targets, input augmentation batches, initial weights, optimizer and updates. Treatment adds .4 object residual consistency + .15 local missing chroma.',
182
+ 'supervised':'L1/20 + .25 sliced chroma distribution + .1 gradient difference (existing r.losses)',
183
+ 'target_choice':'One complete original or cached DDColor target per training image, selected once by v3 complete-image loss. No per-pixel target mixing. Reject teacher proposal if it removes over40% of original chroma or has >1.5x+1.5 mean chroma edge strength.',
184
+ 'region_loss':'Mean absolute spatial residual after subtracting regional mean prediction-target error. Allows constant alternative hue shifts while retaining target patterns.',
185
+ 'segmentation':'Frozen torchvision DeepLabV3 ResNet50 COCO_WITH_VOC_LABELS_V1 on original RGB, default520px preprocessing. Confident connected foreground interiors + homogeneous original-color SLIC background surfaces. Max64 regions/image.',
186
+ 'inference':'Unchanged architecture and one-pass inference; segmentation/teacher are training-only.',
187
+ 'data':'Seeded8192 subset of original training fold; pinned Imagenette/COCO shards and verified cached DDColor. 50% mild archival-style input degradations.',
188
+ 'evaluation':'Reused dense-run development and test are explicitly regression sets. New128 COCO val/0001 examples exclude round7 and dense-run evaluation IDs and training byte duplicates. New holdout used only after training; familiar archived-photo probes are not independent historical validation.',
189
+ 'limitations':['20 foreground semantic classes only','touching same-class objects can merge','RGB-derived masks are imperfect','DDColor targets are fallible','teacher/encoder upstream pretraining overlap unknown','one seed and short pilot','synthetic film variation is not true archival training','frozen target selection can retain v3 biases'],
190
+ 'budget':{'hard_timeout_minutes':30,'training_stop_minutes':23,'maximum_compute_usd':1.25},
191
+ 'release_policy':'All checkpoints retained for review; no automatic production replacement'}
192
+ r.write('protocol.json',protocol);smoke();durable.sync('smoke and initial model verified')
193
+ print('PREFLIGHT_PASSED',flush=True)
194
+ tables=[]
195
+ for repo,rev,files in [('johnowhitaker/imagenette2-320','771c1310a2487e8076ede6b7d6307244aa8400af',['default/train/0000.parquet']),('detection-datasets/coco','26ddc382fe75dfc2a0655b5977e296ea10efebce',['default/train/0000.parquet','default/train/0001.parquet'])]:
196
+ for f in files:tables.append(pq.read_table(hf_hub_download(repo,f,repo_type='dataset',revision=rev),columns=['image']))
197
+ table=pa.concat_tables(tables);oldmanifest=json.loads((source/'previous_manifest.json').read_text())
198
+ allids=oldmanifest['train'];cache=np.empty((len(allids),2,64,64),np.float16)
199
+ cachesums=json.loads(Path(hf_hub_download(REPO,'experiments/round6-20260928/SHA256SUMS.json',revision=CACHE_REV)).read_text())
200
+ for start in range(0,len(allids),2048):
201
+ f=f'teacher_cache/{start:05d}.npz'
202
+ path=Path(hf_hub_download(REPO,'experiments/round6-20260928/'+f,revision=CACHE_REV))
203
+ assert hashlib.sha256(path.read_bytes()).hexdigest()==cachesums[f]
204
+ with np.load(path) as z:
205
+ assert z['indices'].tolist()==allids[start:start+len(z['indices'])]
206
+ cache[start:start+len(z['target'])]=z['target']
207
+ fresh=pq.read_table(hf_hub_download('detection-datasets/coco','default/val/0001.parquet',repo_type='dataset',revision='26ddc382fe75dfc2a0655b5977e296ea10efebce'),columns=['image'])
208
+ regression=json.loads(Path(hf_hub_download(REPO,'experiments/dense-critic-20260928/manifest.json',revision=HEAD)).read_text())
209
+ oldeval=json.loads(Path(hf_hub_download(REPO,'experiments/round7-20260928/manifest.json',revision=BASE)).read_text())
210
+ trainhash={hashlib.sha256(table['image'][i].as_py()['bytes']).digest() for i in allids}
211
+ excluded=set(regression['development']+regression['heldout']+oldeval['development']+oldeval['heldout'])
212
+ untouched=[int(i) for i in np.random.default_rng(SEED).permutation(len(fresh)) if int(i) not in excluded and hashlib.sha256(fresh['image'][int(i)].as_py()['bytes']).digest() not in trainhash][:128]
213
+ assert len(untouched)==128
214
+ dev=regression['development'];held=regression['heldout']
215
+ evalhash={hashlib.sha256(fresh['image'][i].as_py()['bytes']).digest() for i in dev+held+untouched}
216
+ positions=[int(k) for k in np.random.default_rng(SEED).permutation(len(allids)) if hashlib.sha256(table['image'][allids[int(k)]].as_py()['bytes']).digest() not in evalhash][:8192]
217
+ ids=[allids[k] for k in positions];cache=cache[positions]
218
+ r.write('manifest.json',{'train':ids,'original_train_positions':positions,'development_regression':dev,'heldout_regression':held,'new_holdout':untouched,'coco_revision':'26ddc382fe75dfc2a0655b5977e296ea10efebce','imagenette_revision':'771c1310a2487e8076ede6b7d6307244aa8400af','exact_duplicate_filter':True,'upstream_pretraining_overlap_unknown':True,'near_duplicates_not_excluded':True})
219
+ baseline=load_semantic(initial,'cuda').eval().requires_grad_(False)
220
+ weights=DeepLabV3_ResNet50_Weights.COCO_WITH_VOC_LABELS_V1
221
+ segmenter=deeplabv3_resnet50(weights=weights,progress=False).cuda().eval().requires_grad_(False)
222
+ preprocess=weights.transforms()
223
+ r.write('segmentation_teacher.json',{'weights':str(weights),'url':weights.url,'parameters':sum(p.numel() for p in segmenter.parameters()),'categories':weights.meta['categories'],'preprocess':str(preprocess)})
224
+ rgbcache=np.empty((len(ids),256,256,3),np.uint8)
225
+ targets=np.empty_like(cache);regions=np.empty((len(ids),64,64),np.uint8)
226
+ teacher_choices=[];region_counts=[];foreground_counts=[];preview=Image.new('RGB',(256*4,280*8),'white')
227
+ for start in range(0,len(ids),16):
228
+ if time.monotonic()-START>16*60:raise TimeoutError('Preparation exceeded16min; aborting before training to save remaining budget')
229
+ chosen=ids[start:start+16];ls=[];original=[]
230
+ for j,i in enumerate(chosen):
231
+ im=decode_image(table,i).convert('RGB').resize((256,256),Image.Resampling.BILINEAR)
232
+ rgbcache[start+j]=np.asarray(im)
233
+ L,ab,_=r.ev.gray_arrays(im);ls.append(L);original.append(ab)
234
+ x=torch.from_numpy(np.stack(ls)).cuda()
235
+ o=F.avg_pool2d(torch.from_numpy(np.stack(original)).cuda(),4)
236
+ t=torch.from_numpy(cache[start:start+len(chosen)].astype(np.float32)).cuda()
237
+ rgb=torch.from_numpy(rgbcache[start:start+len(chosen)]).cuda().permute(0,3,1,2).float()/255
238
+ with torch.inference_mode(),torch.autocast('cuda',dtype=torch.bfloat16):
239
+ bp=baseline(x)
240
+ logits=segmenter(preprocess(rgb))['out']
241
+ cp=F.interpolate(logits.float(),(64,64),mode='bilinear',align_corners=False).softmax(1)
242
+ conf,label=cp.max(1)
243
+ with torch.no_grad():
244
+ colorok=(t.norm(dim=1).mean((1,2))>=.6*o.norm(dim=1).mean((1,2)))
245
+ edge=lambda z:torch.diff(z,dim=-1).abs().mean((1,2,3))+torch.diff(z,dim=-2).abs().mean((1,2,3))
246
+ goodedge=edge(t)<=1.5*edge(o)+1.5
247
+ use=(r.losses(bp,t)<r.losses(bp,o))&colorok&goodedge
248
+ target=torch.where(use[:,None,None,None],t,o)
249
+ targetnp=target.cpu().numpy()
250
+ for j in range(len(chosen)):
251
+ k=start+j
252
+ reg,nfg=make_regions(label[j].cpu().numpy(),conf[j].cpu().numpy(),rgbcache[k,::4,::4].astype(np.float32)/255,o[j].cpu().numpy())
253
+ regions[k]=reg;targets[k]=targetnp[j].astype(np.float16)
254
+ teacher_choices.append(bool(use[j]));region_counts.append(int(reg.max()));foreground_counts.append(nfg)
255
+ if k<8:
256
+ preview.paste(Image.fromarray(rgbcache[k]),(0,k*280+24))
257
+ colors=np.random.default_rng(123).integers(20,255,(65,3),dtype=np.uint8);colors[0]=0
258
+ preview.paste(Image.fromarray(colors[reg]).resize((256,256),Image.Resampling.NEAREST),(256,k*280+24))
259
+ full=F.interpolate(target[j:j+1].float(),(256,256),mode='bilinear',align_corners=False)[0].cpu().numpy().transpose(1,2,0)
260
+ preview.paste(r.toimage(ls[j],full),(512,k*280+24))
261
+ fullbp=bp[j].float().cpu().numpy().transpose(1,2,0)
262
+ preview.paste(r.toimage(ls[j],fullbp),(768,k*280+24))
263
+ ImageDraw.Draw(preview).text((2,k*280),f'id {chosen[j]} | RGB | region interiors | selected target | v3; DDColor={bool(use[j])}',fill='black')
264
+ if start%2048==0:print('PREP',start,'of',len(ids),flush=True)
265
+ if np.mean(np.array(region_counts)>0)<.5:raise RuntimeError('Insufficient usable region coverage')
266
+ preview.save(OUT/'training_targets_and_regions.jpg',quality=92)
267
+ r.write('target_selection.json',{'teacher_choice_fraction':float(np.mean(teacher_choices)),'teacher_choices':teacher_choices,'mean_regions':float(np.mean(region_counts)),'mean_semantic_regions':float(np.mean(foreground_counts)),'fraction_with_semantic_region':float(np.mean(np.array(foreground_counts)>0)),'region_counts':region_counts})
268
+ for start in range(0,len(ids),1024):
269
+ folder=OUT/'region_cache';folder.mkdir(exist_ok=True)
270
+ np.savez_compressed(folder/f'{start:05d}.npz',indices=np.array(ids[start:start+1024]),target=targets[start:start+1024],regions=regions[start:start+1024])
271
+ r.write('baseline_development.json',r.ev.evaluate(baseline,fresh,dev))
272
+ durable.sync('fixed targets and semantic regions verified')
273
+ models={name:load_semantic(initial,'cuda') for name in ['control','region']}
274
+ opts={name:torch.optim.AdamW([{'params':m.encoder.parameters(),'lr':3e-6,'base_lr':3e-6},{'params':[p for k,p in m.named_parameters() if not k.startswith('encoder.')],'lr':3e-5,'base_lr':3e-5}],weight_decay=.01) for name,m in models.items()}
275
+ ds=TrainPhotos(rgbcache,targets,regions)
276
+ loader=DataLoader(ds,batch_size=BATCH,shuffle=True,num_workers=6,pin_memory=True,drop_last=True,persistent_workers=True,generator=torch.Generator().manual_seed(SEED))
277
+ it=iter(loader);history=[];step=0
278
+ with torch.no_grad():
279
+ fixed_ids=np.arange(min(32,len(ids)))
280
+ fixedL=torch.from_numpy(np.stack([r.ev.gray_arrays(Image.fromarray(rgbcache[i]))[0] for i in fixed_ids])).cuda()
281
+ fixedT=torch.from_numpy(targets[fixed_ids].astype(np.float32)).cuda()
282
+ fixedR=torch.from_numpy(regions[fixed_ids].astype(np.int64)).cuda()
283
+ with torch.inference_mode():
284
+ startp=baseline(fixedL)
285
+ for name in models:
286
+ sl,st=objective(startp,fixedT,fixedR,name)
287
+ history.append({'arm':name,'step':0,'fixed_training_probe_total':float(sl),'fixed_training_probe_components':st})
288
+ for update in range(1,STEPS+1):
289
+ if time.monotonic()-START>MAX_SECONDS:break
290
+ try:L,t,rr=next(it)
291
+ except StopIteration:it=iter(loader);L,t,rr=next(it)
292
+ L,t,rr=L.cuda(non_blocking=True),t.cuda(non_blocking=True),rr.cuda(non_blocking=True)
293
+ for name,m in models.items():
294
+ m.train()
295
+ for mod in m.encoder.modules():
296
+ if isinstance(mod,nn.BatchNorm2d):mod.eval()
297
+ opt=opts[name];opt.zero_grad(set_to_none=True)
298
+ mult=min(update/100,1)*(.2+.8*(1+math.cos(math.pi*update/STEPS))/2)
299
+ for group in opt.param_groups:group['lr']=group['base_lr']*mult
300
+ with torch.autocast('cuda',dtype=torch.bfloat16):p=m(L)
301
+ loss,terms=objective(p,t,rr,name)
302
+ loss.backward();torch.nn.utils.clip_grad_norm_(m.parameters(),1,error_if_nonfinite=True);opt.step()
303
+ if update%100==0:
304
+ with torch.inference_mode():
305
+ fp=m.eval()(fixedL);fl,ft=objective(fp,fixedT,fixedR,name)
306
+ history.append({'arm':name,'step':update,'total':float(loss.detach()),'components':terms,'fixed_training_probe_total':float(fl),'fixed_training_probe_components':ft,'elapsed_seconds':time.monotonic()-START})
307
+ print('TRAIN',name,update,round(float(loss.detach()),4),'fixed',round(float(fl),4),flush=True)
308
+ step=update
309
+ if step%500==0:
310
+ persist(models,opts,step,history)
311
+ for name,m in models.items():
312
+ r.write(f'{name}/development_{step:05d}.json',r.ev.evaluate(m.eval(),fresh,dev))
313
+ r.grid({'v3':baseline,**models},fresh,dev[:24],f'development_{step:05d}')
314
+ durable.sync('development comparison '+str(step))
315
+ persist(models,opts,step,history)
316
+ for name,m in {'v3':baseline,**models}.items():
317
+ r.write(name+'/regression_heldout.json',r.ev.evaluate(m.eval(),fresh,held))
318
+ r.write(name+'/new_holdout.json',r.ev.evaluate(m.eval(),fresh,untouched))
319
+ r.review(m,fresh,untouched[:48],name+'_new_holdout')
320
+ r.ev.probes(m,name+'_probes')
321
+ if name!='v3':save_semantic(m,OUT/name/'candidate')
322
+ fixed_losses({'v3':baseline,**models},fresh,untouched[:32],segmenter,preprocess,'fixed_region_evaluation')
323
+ r.grid({'v3':baseline,**models},fresh,untouched[:32],'new_holdout_comparison')
324
+ r.grid({'v3':baseline,**models},fresh,held[:32],'regression_comparison')
325
+ import requests
326
+ for name,url in {'migrant_mother':'https://cdn.loc.gov/service/pnp/ppmsca/50200/50236v.jpg','power_house_mechanic':'https://www.archives.gov/exhibits/picturing_the_century/images/port_hine_022_v36.jpg'}.items():
327
+ try:
328
+ response=requests.get(url,timeout=15);response.raise_for_status()
329
+ im=Image.open(io.BytesIO(response.content));im.verify()
330
+ tb=pa.table({'image':[{'bytes':response.content,'path':None}]})
331
+ r.grid({'v3':baseline,**models},tb,[0],name)
332
+ r.write(name+'_source.json',{'url':url,'sha256':hashlib.sha256(response.content).hexdigest(),'use':'familiar historical development probe; original colors unknown'})
333
+ except Exception as e:r.write(name+'_error.json',{'error':str(e)})
334
+ r.write('status.json',{'status':'completed','steps_per_arm':step,'target_steps':STEPS,'stop_reason':'completed' if step==STEPS else 'time_budget','elapsed_seconds':time.monotonic()-START,'generator_parameters':3994676,'production_approved':False,'next':'Review control versus region versus v3; report all outcomes. This pilot does not prove plausibility or historical truth.'})
335
+ durable.sync('completed region ablation and comparisons')
336
+
337
+ try:main()
338
+ except BaseException:
339
+ r.write('failure.json',{'traceback':traceback.format_exc().replace(os.environ.get('HF_TOKEN','__EMPTY__'),'[REDACTED]')})
340
+ durable.sync('failure report and saved checkpoints')
341
+ raise