HH-1041 Wharf storm lantern £42
HH-1206 Cumulus round faux-fur pouf £85
HH-2203 Drift faux-fur cushion £36
HH-2342 Puffin faux-fur ball cushion £32
HH-3315 Marlow chandelier £185
HH-3360 Aldgate brass candelabra £89
HH-4402 Bistro round bar stool £119
HH-4461 Suzhou carved armchair £520
HH-5547 Small-leaf ficus in terracotta urn £64
HH-5593 Bramble faux-fur storage cube £69
HH-6618 Zebra haworthia in white pot £18
HH-6672 Arrowhead plant in terracotta pot £56
HH-7101 Nimbus faux-fur pouffe £79
HH-7150 Gallinera hen-coop armchair £138
HH-7158 Yeti faux-fur footstool £95
HH-7186 Rosewell shaded chandelier £410
HH-8205 Belgrave tiered candle chandelier £320
HH-8248 Arden teddy armchair £395
HH-8256 Tilbury oil lamp £39
HH-8285 Fell sheepskin-top stool £74
HH-9328 Fen curly-pile cushion £79
HH-9340 Fen long-pile cushion £32
HH-9374 Hebridean faux-sheepskin rug £59
HH-9395 Abbey gothic hall chair £110
total 1048
-r--r--r-- 23 root root 1052612 Oct  7 04:23 ISOcoated_v2_300_bas.icc
-r--r--r-- 23 root root     962 Oct  7 04:23 ISOcoated_v2_300_bas_LICENSE.txt
-r--r--r-- 23 root root    9312 Oct  7 04:23 sRGB_icc22.icm
-r--r--r-- 23 root root      70 Oct  7 04:23 sRGB_icc22_LICENSE.txt
import json,numpy as np
from scipy.optimize import least_squares
mods=json.load(open('/workspace/scratch/colourmodels.json'));ref=np.array(json.load(open('/workspace/input/chart_reference.json'))['srgb8'])/255
lin=lambda v:np.where(v<=.04045,v/12.92,((v+.055)/1.055)**2.4)
enc=lambda v:np.where(v<=.0031308,12.92*v,1.055*np.maximum(v,0)**(1/2.4)-.055)
out={}
for s,m in mods.items():
 obs=np.array(m['observed']);x=lin(obs);tar=lin(ref)
 p0=np.r_[np.ones(3),np.linalg.lstsq(x,tar,rcond=None)[0].ravel(),np.zeros(3)]
 def fun(p):return (enc(x**p[:3]@p[3:12].reshape(3,3)+p[12:15])-ref).ravel()
 fit=least_squares(fun,p0,max_nfev=300)
 corrected=x**fit.x[:3]@fit.x[3:12].reshape(3,3)+fit.x[12:15]
 anchor=float(x[21,1]/corrected[21,1])
 print(s,fit.x[:3].round(3),'offset',fit.x[12:].round(4),'mean/max',round(abs(fun(fit.x)).mean()*255,3),round(abs(fun(fit.x)).max()*255,3))
 out[s]={'gamma':fit.x[:3].tolist(),'matrix':fit.x[3:12].reshape(3,3).tolist(),'offset':fit.x[12:].tolist(),'anchor':anchor}
json.dump(out,open('/workspace/scratch/finalcolour.json','w'),indent=2)
import json,cv2,numpy as np,tifffile
from scipy.optimize import least_squares
from scipy.interpolate import PchipInterpolator
from PIL import Image
src=json.load(open('/workspace/scratch/sources.json'));boxes=json.load(open('/workspace/scratch/chartboxes.json'));ref=np.array(json.load(open('/workspace/input/chart_reference.json'))['srgb8'])/255
out={}
def lin(v):return np.where(v<=.04045,v/12.92,((v+.055)/1.055)**2.4)
for s in src:
 a=tifffile.imread('/workspace/input/charts/'+s+'_chart.tif').astype('float64')/65535
 x0,y0,x1,y1=boxes[s];step=(x1-x0)/5.87;pw=step*.87;dx=(x1-x0-pw)/5;dy=(y1-y0-pw)/3
 obs=[]
 for row in range(4):
  for col in range(6):
   x=round(x0+pw/2+dx*col);y=round(y0+pw/2+dy*row);r=max(3,round(pw*.25));obs.append(np.median(a[y-r:y+r,x-r:x+r],axis=(0,1)))
 obs=np.array(obs)
 # inverse neutral tone curve: a power law on each delivered channel
 pars=[];corr=[]
 for c in range(3):
  fit=least_squares(lambda p:p[0]*obs[18:,c]**p[1]-ref[18:,c],[1,1],bounds=([.1,.3],[4,3]))
  pars.append(fit.x.tolist())
 cc=np.stack([pars[c][0]*obs[:,c]**pars[c][1] for c in range(3)],1)
 M=np.linalg.lstsq(cc,ref,rcond=None)[0]
 # joint fine fit of exponents and colour matrix
 def fun(p):return (obs**p[:3] @ p[3:].reshape(3,3)-ref).ravel()
 p0=np.r_[np.array(pars)[:,1],(np.diag(np.array(pars)[:,0])@M).ravel()]
 fit=least_squares(fun,p0,max_nfev=300)
 gamma=fit.x[:3];matrix=fit.x[3:].reshape(3,3)
 error=np.abs(fun(fit.x)).max()*255
 anchor=float(lin(obs[21,1])/lin(ref[21,1]))
 print(s,'G22',round(obs[21,1]*255,2),'gamma',gamma.round(4),'max residual',round(error,3),'brightness',round(anchor,4))
 out[s]={'gamma':gamma.tolist(),'matrix':matrix.tolist(),'anchor_linear':anchor,'observed':obs.tolist()}
json.dump(out,open('/workspace/scratch/colourmodels.json','w'),indent=2)
