{
 "version": "ultimate-v13",
 "trim_mm": [
  210,
  297
 ],
 "bleed_mm": 3,
 "pages": [
  {
   "page": 1,
   "background": {
    "name": "Harbour Blue",
    "cmyk": [
     85,
     45,
     10,
     5
    ]
   },
   "skus": [
    "HH-1041",
    "HH-1206"
   ]
  },
  {
   "page": 2,
   "background": {
    "name": "Clay",
    "cmyk": [
     8,
     55,
     62,
     4
    ]
   },
   "skus": [
    "HH-2203",
    "HH-2342"
   ]
  },
  {
   "page": 3,
   "background": {
    "name": "Charcoal",
    "cmyk": [
     60,
     50,
     45,
     70
    ]
   },
   "skus": [
    "HH-3315",
    "HH-3360"
   ]
  },
  {
   "page": 4,
   "background": {
    "name": "Sage",
    "cmyk": [
     45,
     15,
     50,
     10
    ]
   },
   "skus": [
    "HH-4402",
    "HH-4461"
   ]
  },
  {
   "page": 5,
   "background": {
    "name": "Ink",
    "cmyk": [
     100,
     80,
     30,
     35
    ]
   },
   "skus": [
    "HH-5547",
    "HH-5593"
   ]
  },
  {
   "page": 6,
   "background": {
    "name": "Sand",
    "cmyk": [
     5,
     12,
     30,
     0
    ]
   },
   "skus": [
    "HH-6618",
    "HH-6672"
   ]
  },
  {
   "page": 7,
   "background": {
    "name": "Moss",
    "cmyk": [
     55,
     30,
     85,
     25
    ]
   },
   "skus": [
    "HH-7101",
    "HH-7158"
   ]
  },
  {
   "page": 8,
   "background": {
    "name": "Rust",
    "cmyk": [
     15,
     75,
     85,
     20
    ]
   },
   "skus": [
    "HH-7150",
    "HH-7186"
   ]
  },
  {
   "page": 9,
   "background": {
    "name": "Slate",
    "cmyk": [
     70,
     45,
     35,
     25
    ]
   },
   "skus": [
    "HH-8205",
    "HH-8248"
   ]
  },
  {
   "page": 10,
   "background": {
    "name": "Fog",
    "cmyk": [
     15,
     8,
     8,
     0
    ]
   },
   "skus": [
    "HH-8256",
    "HH-8285"
   ]
  },
  {
   "page": 11,
   "background": {
    "name": "Plum",
    "cmyk": [
     55,
     85,
     35,
     30
    ]
   },
   "skus": [
    "HH-9328",
    "HH-9340"
   ]
  },
  {
   "page": 12,
   "background": {
    "name": "Ochre",
    "cmyk": [
     10,
     35,
     90,
     5
    ]
   },
   "skus": [
    "HH-9374",
    "HH-9395"
   ]
  }
 ],
 "soft_edge_skus": [
  "HH-1206",
  "HH-2203",
  "HH-2342",
  "HH-5593",
  "HH-7101",
  "HH-7158",
  "HH-8248",
  "HH-8285",
  "HH-9328",
  "HH-9340",
  "HH-9374"
 ]
}
{
  "HH-1041": "photos/HH-1041.tif",
  "HH-1206": "photos/HH-1206.tif",
  "HH-2203": "photos/HH-2203.tif",
  "HH-2342": "photos/HH-2342.tif",
  "HH-3315": "photos/HH-3315.tif",
  "HH-3360": "photos/HH-3360.tif",
  "HH-4402": "masters/DSC_4402_master.tif",
  "HH-4461": "photos/HH-4461.tif",
  "HH-5547": "photos/HH-5547.tif",
  "HH-5593": "photos/HH-5593.tif",
  "HH-6618": "alternates/HH-6618_white.tif",
  "HH-6672": "photos/HH-6672.tif",
  "HH-7101": "photos/HH-7101.tif",
  "HH-7150": "photos/HH-7150.tif",
  "HH-7158": "photos/HH-7158.tif",
  "HH-7186": "photos/HH-7186.tif",
  "HH-8205": "photos/HH-8205.tif",
  "HH-8248": "photos/HH-8248.tif",
  "HH-8256": "photos/HH-8256.tif",
  "HH-8285": "photos/HH-8285.tif",
  "HH-9328": "photos/HH-9328.tif",
  "HH-9340": "photos/HH-9340.tif",
  "HH-9374": "photos/HH-9374.tif",
  "HH-9395": "photos/HH-9395.tif"
}import cv2,numpy as np,tifffile,json,sys
from scipy import ndimage as ndi
from PIL import Image
from pathlib import Path
cv2.setNumThreads(2)
sources=json.load(open('/workspace/scratch/sources.json'))
polys=json.load(open('/workspace/scratch/softpolys.json'))
Path('/workspace/scratch/matted').mkdir(exist_ok=True)
def matte(a,b,mask,inner=20,outer=35):
 # linear RGB for alpha calculations
 a=a**2.2;b=b**2.2
 di,ii=ndi.distance_transform_edt(mask,return_indices=True);do,io=ndi.distance_transform_edt(~mask,return_indices=True)
 fg=di>inner;bg=do>outer
 _,fi=ndi.distance_transform_edt(~fg,return_indices=True)
 _,bi=ndi.distance_transform_edt(~bg,return_indices=True)
 F=a[fi[0],fi[1]]
 # Background reflecting illumination correction in local surrounding ring
 delta=cv2.GaussianBlur(a-b,(0,0),2)
 ratio=(a+.002)/(b+.002)
 small=cv2.resize(ratio,(600,400),interpolation=cv2.INTER_AREA)
 sh=np.stack([ndi.percentile_filter(small[:,:,c],15,size=9) for c in range(3)],2)
 sh=cv2.resize(np.minimum(sh,1.0),(2400,1600),interpolation=cv2.INTER_LINEAR)
 B=b*np.clip(sh,.25,1.0)
 B=np.clip(B,0,1)
 v=F-B
 # solve matte while permitting background illumination to vary
 bn=B/(np.sqrt((B*B).sum(2))[:,:,None]+1e-8)
 perp=v-(v*bn).sum(2)[:,:,None]*bn
 diff=a-B
 num=(diff*v).sum(2)
 den=(v*v).sum(2)
 alpha=np.clip(num/(den+1e-8),0,1)
 alpha[fg]=1;alpha[bg]=0
 # floor / room difference under 8-bit noise is not coverage
 unchanged=np.linalg.norm(a-b,axis=2)<.008
 alpha[unchanged & ~fg]=0
 return alpha
for s in sys.argv[1:] or list(polys):
 a=tifffile.imread('/workspace/input/'+sources[s]).astype('float32')/65535;b=tifffile.imread('/workspace/input/charts/'+s+'_chart.tif').astype('float32')/65535
 mask=np.zeros(a.shape[:2],'uint8');cv2.fillPoly(mask,[np.rint(np.array(polys[s])*2).astype('int32')],1)
 al=matte(a,b,mask.astype(bool),140,30)
 # mostly opaque interiors; suppress remote speckles, keep natural edge
 al[al<.02]=0
 cv2.imwrite('/workspace/scratch/matted/'+s+'.png',np.rint(al*255).astype('uint8'))
 comp=a*al[:,:,None]+np.array([.1,.24,.37])*(1-al[:,:,None])
 Image.fromarray(np.rint(np.clip(comp,0,1)*255).astype('uint8')).resize((1200,800)).save('/workspace/scratch/matted/'+s+'.jpg')
 print(s,flush=True)
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(np.maximum(x-p[12:15],1e-10)**p[:3]@p[3:12].reshape(3,3))-ref).ravel()
 fit=least_squares(fun,p0,max_nfev=300)
 corrected=np.maximum(x-fit.x[12:15],1e-10)**fit.x[:3]@fit.x[3:12].reshape(3,3)
 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(),'input_offset':fit.x[12:].tolist(),'anchor':anchor}
json.dump(out,open('/workspace/scratch/finalcolour.json','w'),indent=2)
import cv2,numpy as np,json,tifffile,sys
from scipy import ndimage as ndi
from PIL import Image
from pathlib import Path
cv2.setNumThreads(2)
edits=json.load(open('/workspace/scratch/edits.json'));boxes=json.load(open('/workspace/scratch/detailboxes.json'));sources=json.load(open('/workspace/scratch/sources.json'))
Path('/workspace/scratch/refined').mkdir(exist_ok=True)
for s in sys.argv[1:] or list(edits):
 a=tifffile.imread('/workspace/input/'+sources[s]).astype('float32')/65535
 m=cv2.imread('/workspace/scratch/hard/'+s+'.png',0)
 if m is None:m=cv2.imread('/workspace/scratch/auto/'+s+'.png',0)
 e=edits[s];x,y,_,_=boxes[s]
 if 'replace' in e:
  m[:]=0;cv2.fillPoly(m,[np.array(e['replace'],dtype='int32')+[x,y]],255)
 for line,width in e.get('lines',[]):cv2.polylines(m,[np.array(line,dtype='int32')+[x,y]],False,255,width)
 if 'bottom' in e:m[y+e['bottom']:]=0
 for key,col in [('bg',0),('fg',255)]:
  for poly in e.get(key,[]):
   p=np.array(poly,dtype='int32')+[x,y];cv2.fillPoly(m,[p],col)
 if e.get('fillholes'):m=ndi.binary_fill_holes(m>127).astype('uint8')*255
 if e.get('solid_small'):
  n,lab,stats,ctr=cv2.connectedComponentsWithStats((m<128).astype('uint8'),8)
  for k in range(1,n):
   if stats[k,4]<e['solid_small']:m[lab==k]=255
 # Snap boundaries to original RGB edges in a limited 3 px band
 di=ndi.distance_transform_edt(m>127);do=ndi.distance_transform_edt(m<128)
 gc=np.where(m>127,3,2).astype('uint8');gc[di>e.get('band',3)]=1;gc[do>e.get('band',3)]=0
 cv2.grabCut((a*255).astype('uint8'),gc,None,np.zeros((1,65)),np.zeros((1,65)),2,cv2.GC_INIT_WITH_MASK)
 m=np.isin(gc,[1,3]).astype('uint8')*255
 cv2.imwrite('/workspace/scratch/refined/'+s+'.png',m)
 comp=a*(m[:,:,None]/255)+np.array([.15,.4,.45])*(1-m[:,:,None]/255)
 Image.fromarray(np.rint(comp*255).astype('uint8')).crop(tuple(boxes[s])).save('/workspace/scratch/refined/'+s+'_detail.jpg')
 print(s,flush=True)
import scribus
f=open('/workspace/scratch/scribus_api.txt','w')
f.write('\n'.join(dir(scribus)))
for fn in ['newDocument','PDFfile','setImageScale','setImageOffset','loadImage','createText','setText','insertText','setParagraphStyle','setFont','setTextDistances','setLineSpacing','setTextFlowMode','createParagraphStyle','createCharStyle','setMargins','setBleeds','setDocBleeds','setCMS','setInfo','defineColorCMYK','createImage','setTextColor','setTextAlignment','setFillColor','saveDocAs','setRedraw','textOverflows','setTextVerticalAlignment','setFirstLineOffset']:
 try:f.write('\n\n'+fn+'\n'+str(getattr(scribus,fn).__doc__))
 except:pass
f.close()
scribus.newDocument(scribus.PAPER_A4,(10,10,10,10),0,1,0,0,0,1)
p=scribus.PDFfile();open('/workspace/scratch/pdf_api.txt','w').write('\n'.join(x+': '+str(getattr(p,x))+'\n'+str(getattr(type(p),x).__doc__) for x in dir(p) if not x.startswith('__')))
scribus.quit()
