cat > /workspace/scratch/hardseg.py <<'PY'
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'));boxes=json.load(open('/workspace/scratch/chartboxes.json'));limits=json.load(open('/workspace/scratch/limits.json'))
Path('/workspace/scratch/hard').mkdir(exist_ok=True)
for s in sys.argv[1:] or ['HH-1041','HH-3315','HH-3360','HH-4402','HH-4461','HH-5547','HH-6618','HH-6672','HH-7150','HH-7186','HH-8205','HH-8248','HH-8256','HH-8285','HH-9395']:
 a=tifffile.imread('/workspace/input/'+sources[s]).astype('float32')/65535;b=tifffile.imread('/workspace/input/charts/'+s+'_chart.tif').astype('float32')/65535
 aa=cv2.GaussianBlur(a,(0,0),.5);bb=cv2.GaussianBlur(b,(0,0),.5)
 diff=np.linalg.norm(aa-bb,axis=2)
 ratio=(aa*bb).sum(2)/(bb*bb).sum(2)
 chroma=np.linalg.norm(aa-bb*np.minimum(ratio,1)[:,:,None],axis=2)
 gc=np.where(diff>.04,3,2).astype('uint8')
 gc[(diff>.12)&(chroma>.035)]=1;gc[diff<.014]=0
 cx0,cy0,cx1,cy1=boxes[s];gc[cy0-20:cy1+20,cx0-20:cx1+20]=3
 x0,y0,x1,y1=limits[s];x0-=70;y0-=70;x1+=70;y1+=70
 gc[:y0]=0;gc[y1:]=0;gc[:,:x0]=0;gc[:,x1:]=0
 # fine RGB grabcut incorporates colour across the chart's blind area
 cv2.grabCut((a*255).astype('uint8'),gc,None,np.zeros((1,65)),np.zeros((1,65)),3,cv2.GC_INIT_WITH_MASK)
 mask=np.isin(gc,[1,3])
 n,lab,stats,ctr=cv2.connectedComponentsWithStats(mask.astype('uint8'),8);keep=np.where(stats[:,4]>10)[0];keep=keep[keep!=0];mask=np.isin(lab,keep)
 cv2.imwrite('/workspace/scratch/hard/'+s+'.png',mask.astype('uint8')*255)
 comp=a*mask[:,:,None]+np.array([.1,.24,.37])*(1-mask[:,:,None])
 Image.fromarray(np.rint(np.clip(comp,0,1)*255).astype('uint8')).resize((1200,800)).save('/workspace/scratch/hard/'+s+'.jpg')
 print(s,flush=True)
PY
OPENBLAS_NUM_THREADS=1 python /workspace/scratch/hardseg.py