File size: 11,145 Bytes
f02dec9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | """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=yy<fringe
else:mask=(yy<rng.integers(3,7))|(((xx<2) if fi==2 else (xx>5))&(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<fringe_depth
elif fi in (2,3):shell &= (yy<fringe_depth)&((xx>=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)<width
if rng.random()<.7:tooth[[0,7]]=True
if rng.random()<.15:tooth[int(rng.integers(8))]=False
mask=((yy>=band_y)&(yy<band_y+thick))|((yy<band_y)&tooth[None])
tex=np.clip(metal[None,None]*rng.uniform(.7,1.15,(8,8,1)),0,1)
tex[:,::period]=highlight
gems=(rng.random((8,8))<.07)&mask;tex[gems]=rng.uniform(.05,1,(gems.sum(),3))
uv[y:y+8,x+32:x+40,:3][mask]=tex[mask];uv[y:y+8,x+32:x+40,3][mask]=1;labels[y:y+8,x+32:x+40][mask]=13
ey,ex,(dy,dx)=cap_edges()[fi]
for col in np.flatnonzero(tooth):
for distance in range(extension):
top[ey[col]+distance*dy,ex[col]+distance*dx]=True
uv[:8,40:48,:3][top]=np.broadcast_to(metal,(8,8,3))[top]
uv[:8,40:48,3][top]=1;labels[:8,40:48][top]=13;caps[:8,40:48]=top
elif kind=='hat':
for offset in range(100):
donor,_,components=make_accessory_skin(original,seed+offset*7919,return_components=True)
if (components>0).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))
|