"""Authored v102 joint semantics; real reported cases never enter this dataset.""" from functools import lru_cache import numpy as np import torch from torch.utils.data import Dataset from SkingToolkit.dense_uv_parser.skin_dataset import load_skin from SkingToolkit.dense_uv_parser.ownership_data import make_ownership_skin from SkingToolkit.dense_uv_parser.headwear_data import make_headwear_skin, make_textured_hair_negative from SkingToolkit.dense_uv_parser.accessory_data import make_accessory_skin FACES=((8,8),(24,8),(0,8),(16,8),(8,0),(16,0)) @lru_cache(maxsize=1) def cap_edges(): """Map each side's top-row columns to the adjacent top UV edge geometrically.""" from SkingToolkit.dense_uv_parser.uv_topology import build_simple_uv_topology pos=build_simple_uv_topology().world_position top=pos[:8,40:48].reshape(64,3) edges=[] for x,y in FACES[:4]: indices=torch.cdist(pos[y,x+32:x+40],top).argmin(1).numpy() yy,xx=indices//8,indices%8 if len(np.unique(yy))==1:direction=(1 if yy[0]==0 else -1,0) else:direction=(0,1 if xx[0]==0 else -1) edges.append((yy,xx,direction)) return edges def make_joint_skin(original,seed,kind=None,beard_layer=None,partial_hair=None): rng=np.random.default_rng(seed+1020906) if kind is None:kind=str(rng.choice(['bare','crown','hat','glasses','phones'],p=[.22,.32,.16,.16,.14])) uv=original.permute(1,2,0).numpy().copy();uv[:16]=0 labels=np.zeros((64,64),np.int64);nose=np.zeros_like(labels,bool);beard=np.zeros_like(nose);caps=np.zeros_like(nose) yy,xx=np.mgrid[:8,:8] skin=rng.uniform(.2,.95,3) if rng.random()<.8:skin=np.array(rng.choice([[.96,.78,.66],[.8,.55,.4],[.48,.32,.24],[.27,.19,.16],[.7,.6,.52]])) hair=rng.uniform(.01,.15,3) if rng.random()<.65 else rng.uniform(.05,.9,3) shade=rng.uniform(.65,1.2,(4,4,1)).repeat(2,0).repeat(2,1) hair_material=np.clip(hair[None,None]*shade+rng.normal(0,.018,(8,8,1)),0,1) if rng.random()<.35: tiles=original[:3,8:16,8:16].permute(1,2,0).numpy() hair_material=np.clip(.8*hair_material+.2*tiles[rng.permutation(8)],0,1) hair_layer=int(rng.random()<(.15 if kind in ('crown','hat') else .35)) # Partial shells teach local layer decisions: front fringe can protrude # while the top/back hair remains on the base head. partial_hair=(rng.random()<.45 and kind not in ('crown','hat')) if partial_hair is None else partial_hair if partial_hair:hair_layer=1 fringe_depth=int(rng.integers(1,4)) fringe_wrap=rng.random()<.3 depth=int(rng.integers(1,5));fringe=np.maximum(1,depth+rng.integers(-1,2,(1,8))) for fi,(x,y) in enumerate(FACES): base=np.clip(skin[None,None]+rng.normal(0,.02,(8,8,1)),0,1) uv[y:y+8,x:x+8,:3]=base;uv[y:y+8,x:x+8,3]=1;labels[y:y+8,x:x+8]=1 if fi in (1,4):mask=np.ones((8,8),bool) elif fi==5:mask=np.zeros((8,8),bool) elif fi==0:mask=yy5))&(yy<7)) # Inner substrate is authored too; an outer hair shell has real support. uv[y:y+8,x:x+8,:3][mask]=hair_material[mask];labels[y:y+8,x:x+8][mask]=2 if hair_layer: shell=mask.copy() if partial_hair: if fi==0:shell &= yy=6) if fi==2 else (xx<2))&fringe_wrap elif fi==4: shell[:]=False ey,ex,_=cap_edges()[0];shell[ey,ex]=True else:shell[:]=False uv[y:y+8,x+32:x+40,:3][shell]=hair_material[shell] uv[y:y+8,x+32:x+40,3][shell]=1;labels[y:y+8,x+32:x+40][shell]=4 # Distinct eyebrows/eyes/nose shading remain facial texture, in many palettes. eye_y=int(rng.integers(3,5)) for x in (1,5): uv[8+eye_y,8+x:10+x,:3]=rng.uniform(.05,.9,3);labels[8+eye_y,8+x:10+x]=1 if eye_y>2 and rng.random()<.5: uv[7+eye_y,8+x:10+x,:3]=hair;labels[7+eye_y,8+x:10+x]=1 if rng.random()<.88: width=int(rng.integers(1,3));height=int(rng.integers(1,3));nx=int(rng.integers(3,5-width+1));ny=min(6,eye_y+1) nose[8+ny:min(16,8+ny+height),8+nx:8+nx+width]=True tint=skin*rng.uniform(.6,1.05)+rng.uniform(-.06,.06,3) if rng.random()<.5:tint+=np.array([.08,-.04,-.04]) uv[:,:,:3][nose]=np.clip(tint,0,1);labels[nose]=1 layer=int(rng.choice([0,1,2],p=[.45,.2,.35])) if beard_layer is None else beard_layer if layer not in (0,1,2):raise ValueError('beard_layer must be 0 (inner), 1 (outer), or 2 (aligned mixed)') if rng.random()<.72 or beard_layer is not None: color=np.clip(hair*rng.uniform(.65,1.5)+rng.uniform(0,.04,3),0,1) symmetric=rng.random()<.8 mask=(yy>=int(rng.integers(5,7)))|((yy>=4)&((xx==0)|(xx==7))) if rng.random()<.6:mask |= (yy==5)&(xx>=2)&(xx<=5) # An open mouth preserves facial texture within the beard object. mask[6,3:5]=False if not symmetric:mask[:,int(rng.integers(0,3))]=False for fi in (0,2,3,5): x,y=FACES[fi] if fi==0:m=mask elif fi==5:m=yy>=5 else: # Cube UV sides have opposite horizontal orientation. Match # the actual front seam, not the rear edge of the side face. m=(yy>=6)&((xx>=5) if fi==2 else (xx<3)) m[:,7 if fi==2 else 0]=mask[:,0 if fi==2 else 7] material=np.clip(color[None,None]*rng.uniform(.8,1.15,(8,8,1)),0,1) if symmetric and fi==0:material=(material+material[:,::-1])/2 # Mixed beards share face-local coordinates and material. Visible # inner beard remains around an outer subset; holes exist in both. masks=[(layer,m)] if layer!=2 else [(0,m),(1,m & ((yy>=7) if fi!=5 else (yy>=5)))] if layer==2 and fi==0: outer=m & ((yy>=7)|((yy==5)&(xx>=2)&(xx<=5))) masks=[(0,m),(1,outer)] for target_layer,selected in masks: tx=x+32*target_layer uv[y:y+8,tx:tx+8,:3][selected]=material[selected];uv[y:y+8,tx:tx+8,3][selected]=1 labels[y:y+8,tx:tx+8][selected]=5 if target_layer else 3;beard[y:y+8,tx:tx+8][selected]=True # Features covered by authored foreground objects are not visible facial labels. if kind=='crown': metal=rng.uniform(.05,.98,3) if rng.random()<.4:metal=np.array([rng.uniform(.6,1),rng.uniform(.45,.9),rng.uniform(.05,.3)]) highlight=np.clip(metal*rng.uniform(1,1.7)+rng.uniform(0,.2,3),0,1) band_y=int(rng.integers(1,4));thick=int(rng.integers(1,3)) period=int(rng.integers(2,5));phase=int(rng.integers(period));width=int(rng.integers(1,3)) top=np.zeros((8,8),bool);extension=int(rng.choice([0,1,2,3],p=[.2,.2,.4,.2])) for fi,(x,y) in enumerate(FACES[:4]): tooth=((np.arange(8)+phase)%period)=band_y)&(yy0).any():break else:raise RuntimeError('No authored hat') selected=components.numpy()>0 # Hat geometry takes precedence over the separately authored hair shell. hair_outer=(labels==4);uv[hair_outer]=0;labels[hair_outer]=0 uv[selected]=donor.permute(1,2,0).numpy()[selected] labels[selected]=components.numpy()[selected]+7 elif kind in ('glasses','phones'): requested=3 if kind=='glasses' else 4 for offset in range(200): donor,own=make_ownership_skin(original,seed+offset*7919) if (own==requested).any():break else:raise RuntimeError('No authored object') selected=own.numpy()==requested uv[selected]=donor.permute(1,2,0).numpy()[selected];labels[selected]=6 if kind=='glasses' else 7 nose &= labels==1;beard &= (labels==3)|(labels==5);caps &= labels==13 return {'uv':torch.from_numpy(uv).permute(2,0,1).float(),'labels':torch.from_numpy(labels), 'nose':torch.from_numpy(nose),'beard':torch.from_numpy(beard),'caps':torch.from_numpy(caps)} class JointHeadDataset(Dataset): """60% new joint labels, 20% legacy ownership, 20% legacy headwear/replay.""" def __init__(self,paths,count,seed,joint_only=False): self.paths,self.count,self.seed,self.epoch,self.joint_only=list(paths),count,seed,0,joint_only def __len__(self):return self.count def __getitem__(self,index): seed=self.seed+index*104729+self.epoch*self.count*104729 original=load_skin(self.paths[(index+self.epoch*self.count)%len(self.paths)]) mode=0 if self.joint_only else (1 if index%5==3 else 2 if index%5==4 else 0) if mode==0:item=make_joint_skin(original,seed) elif mode==1: uv,labels=make_ownership_skin(original,seed);item={'uv':uv,'labels':labels} else: uv,labels=(make_textured_hair_negative(original,seed) if index%10==4 else make_headwear_skin(original,seed));item={'uv':uv,'labels':labels} for key in ('nose','beard','caps'):item.setdefault(key,torch.zeros(64,64,dtype=torch.bool)) item['mode']=mode return item def render_joint_batch(batch,renderer,views): from SkingToolkit.dense_uv_parser.utils import build_dense_parser_batch images=[];labels=[];features=[] uv=batch['uv'];truth=batch['labels'];masks=torch.stack([batch[k] for k in ('nose','beard','caps')],1) for view in views: image,t=build_dense_parser_batch(uv,renderer,view) xy=(t['uv']*63).round().long().clamp(0,63);index=xy[:,1]*64+xy[:,0] valid=(t['foreground'][:,0]>.5)&(t['part']==0) target=truth.flatten(1).gather(1,index.flatten(1)).reshape_as(index).masked_fill(~valid,0) feature=masks.flatten(2).gather(2,index.flatten(1)[:,None].expand(-1,3,-1)).reshape(-1,3,*index.shape[-2:])&valid[:,None] images.append(image);labels.append(target);features.append(feature) return (torch.stack(images,1).flatten(0,1),torch.stack(labels,1).flatten(0,1), batch['mode'].repeat_interleave(len(views)),torch.stack(features,1).flatten(0,1))