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
 model=json.load(open('/workspace/scratch/finalcolour.json'))[s]
 def correct(z):
  lin=np.where(z<=.04045,z/12.92,((z+.055)/1.055)**2.4)
  return np.maximum(lin-np.array(model['input_offset']),1e-10)**np.array(model['gamma'])@np.array(model['matrix'])*model['anchor']
 a=correct(a);b=correct(b)
 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)
 B=b
 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*perp).sum(2)
 den=(perp*perp).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 cv2,numpy as np,json,tifffile
from scipy import ndimage as ndi
from PIL import Image
cv2.setNumThreads(2)
s='HH-9374';a=tifffile.imread('/workspace/input/photos/'+s+'.tif').astype('float32')/65535;b=tifffile.imread('/workspace/input/charts/'+s+'_chart.tif').astype('float32')/65535
x0,y0,x1,y1=json.load(open('/workspace/scratch/chartboxes.json'))[s];x0-=12;x1+=12;y0-=12;y1+=12
left=np.median(b[y0:y1,x0-20:x0],axis=1);right=np.median(b[y0:y1,x1:x1+20],axis=1)
f=np.linspace(0,1,x1-x0)[None,:,None];b[y0:y1,x0:x1]=left[:,None,:]*(1-f)+right[:,None,:]*f
poly=[[92,284],[120,257],[180,242],[250,219],[310,202],[366,180],[458,174],[541,154],[625,150],[715,148],[787,150],[842,151],[912,159],[982,171],[1049,185],[1117,206],[1175,234],[1220,261],[1236,289],[1233,339],[1207,383],[1160,416],[1086,448],[1000,477],[910,499],[824,517],[720,530],[615,538],[510,535],[410,524],[322,503],[228,479],[165,447],[130,410],[104,366],[101,322]]
m=np.zeros(a.shape[:2],'uint8');cv2.fillPoly(m,[np.array(poly,'int32')+[520,460]],1)
di=ndi.distance_transform_edt(m);do=ndi.distance_transform_edt(1-m);fg=di>22;bg=do>27
_,fi=ndi.distance_transform_edt(~fg,return_indices=True);_,bi=ndi.distance_transform_edt(~bg,return_indices=True)
a=a**2.2;b=b**2.2
ratio=cv2.GaussianBlur(a/(b+.002),(5,5),1)
R=ratio[bi[0],bi[1]];B=b*R
F=cv2.GaussianBlur(a,(5,5),1)[fi[0],fi[1]];D=F-B;al=np.clip(((a-B)*D).sum(2)/((D*D).sum(2)+1e-7),0,1);al[fg]=1;al[bg]=0;al[al<.03]=0
# At the upper edge, thin upright fibres cross a chart-obscured floorboard.
# A horizontal morphological background estimate removes that board without retouching RGB.
opened=cv2.morphologyEx(a,cv2.MORPH_OPEN,np.ones((1,31),np.uint8))
D2=F-opened
alpha2=np.clip(((a-opened)*D2).sum(2)/((D2*D2).sum(2)+1e-6),0,1)
for y in range(580,645):
 weight=np.clip((645-y)/22,0,1)
 al[y,980:1430]=al[y,980:1430]*(1-weight)+alpha2[y,980:1430]*weight
edge=(~fg)&(~bg)
# Sparse downward tips are also separated from board grain by the horizontal opening.
for y in range(940,1035):
 weight=np.clip((y-970)/25,0,1)
 al[y]=al[y]*(1-weight)+alpha2[y]*weight
al[fg]=1;al[edge & ((a-B).mean(2)<.002)]=0
al[bg]=0;al[al<.045]=0
cv2.imwrite('/workspace/scratch/finalmasks/'+s+'.png',np.rint(al*255).astype('uint8'))
im=Image.open('/workspace/input/photos/'+s+'.tif');o=Image.new('RGB',im.size,(171,112,13));o.paste(im,(0,0),Image.fromarray(np.rint(al*255).astype('uint8')));o.crop((520,460,1840,1080)).save('/workspace/scratch/rugnew.png')
import cv2,numpy as np,tifffile
from PIL import Image
from scipy import ndimage as ndi
s='HH-2342';p='/workspace/scratch/finalmasks/'+s+'.png';old=cv2.imread(p,0);m=cv2.imread('/workspace/scratch/refined/'+s+'.png',0)>127
# Refine only the floor-facing arc; the dark sofa edge is already isolated.
a=(tifffile.imread('/workspace/input/photos/'+s+'.tif').astype('float32')/65535)**2.2
b=(tifffile.imread('/workspace/input/charts/'+s+'_chart.tif').astype('float32')/65535)**2.2
fg=ndi.distance_transform_edt(m)>12;bg=ndi.distance_transform_edt(~m)>12
_,fi=ndi.distance_transform_edt(~fg,return_indices=True);_,bi=ndi.distance_transform_edt(~bg,return_indices=True)
ratio=cv2.GaussianBlur(a/(b+.001),(5,5),1);B=b*ratio[bi[0],bi[1]];F=cv2.GaussianBlur(a,(5,5),1)[fi[0],fi[1]]
B=cv2.GaussianBlur(a,(5,5),1)[bi[0],bi[1]]
D=F-B*((F*B).sum(2)/((B*B).sum(2)+1e-8))[:,:,None]
al=np.clip((a*D).sum(2)/((D*D).sum(2)+1e-8),0,1);B2=cv2.morphologyEx(a,cv2.MORPH_CLOSE,np.ones((1,41),np.uint8));D2=F-B2
al2=np.clip(((a-B2)*D2).sum(2)/((D2*D2).sum(2)+1e-6),0,1)
al=np.minimum(al,al2);al[fg]=1;al[bg]=0;al[al<.03]=0
old[1050:]=np.rint(al[1050:]*255).astype('uint8');cv2.imwrite(p,old)
im=Image.open('/workspace/input/photos/'+s+'.tif');o=Image.new('RGB',im.size,(214,128,99));o.paste(im,(0,0),Image.fromarray(old));o.resize((1200,800)).save('/workspace/scratch/puffinnew.jpg')
