| from dataclasses import dataclass |
| from abc import ABC, abstractmethod |
| from numpy import ndarray |
| from scipy.spatial import cKDTree |
| from typing import Dict, Optional |
|
|
| import numpy as np |
| import random |
|
|
| from ..rig_package.info.asset import Asset |
| from ..rig_package.utils import sample_vertex_groups |
| from .spec import ConfigSpec |
|
|
| @dataclass |
| class SamplerResult(): |
| sampled_vertices: Optional[ndarray]=None |
| sampled_normals: Optional[ndarray]=None |
| sampled_vertex_groups: Optional[Dict[str, ndarray]]=None |
|
|
| |
| skin_samples: Optional[int]=None |
|
|
| class Sampler(ABC): |
| @abstractmethod |
| def sample( |
| self, |
| asset: Asset, |
| ) -> SamplerResult: |
| ''' |
| Return sampled vertices, sampled normals and vertex groups. |
| ''' |
| pass |
| |
| @classmethod |
| @abstractmethod |
| def parse(cls, **kwargs) -> 'Sampler': |
| pass |
|
|
| @dataclass |
| class SamplerMix(Sampler, ConfigSpec): |
| num_samples: int |
| num_vertex_samples: int |
| num_skin_samples: Optional[int]=None |
| replace: bool=True |
| all_skeleton: Optional[bool]=None |
| max_distance: float=0.1 |
| rate_distance: float=0.1 |
| |
| @classmethod |
| def parse(cls, **kwargs) -> 'SamplerMix': |
| cls.check_keys(kwargs) |
| return SamplerMix( |
| num_samples=kwargs.get('num_samples', 0), |
| num_vertex_samples=kwargs.get('num_vertex_samples', 0), |
| num_skin_samples=kwargs.get('num_skin_samples', None), |
| replace=kwargs.get('replace', True), |
| all_skeleton=kwargs.get('all_skeleton', None), |
| max_distance=kwargs.get('max_distance', 0.1), |
| rate_distance=kwargs.get('rate_distance', 0.1), |
| ) |
| |
| def sample_on_skin( |
| self, |
| skin: ndarray, |
| vertices: ndarray, |
| faces: ndarray, |
| ): |
| face_has_skin = np.any(skin[faces] > 0, axis=-1) |
| if face_has_skin.sum() == 0: |
| face_has_skin = np.ones_like(face_has_skin) |
| elif self.max_distance < 1e-5: |
| return face_has_skin |
| else: |
| |
| p = np.unique(faces[face_has_skin].reshape(-1)) |
| tree = cKDTree(vertices[p]) |
| dis, _ = tree.query(vertices, k=1) |
| dis_skin = np.sqrt(((np.max(vertices[p], axis=0) - np.min(vertices[p], axis=0))**2).sum()) |
| mask_face_near = np.any(dis[faces] < min(self.max_distance, dis_skin * self.rate_distance), axis=-1) |
| face_has_skin |= mask_face_near |
| return face_has_skin |
| |
| def sample( |
| self, |
| asset: Asset, |
| ) -> SamplerResult: |
| if asset.vertices is None: |
| raise ValueError("do not have vertices") |
| if asset.faces is None: |
| raise ValueError("do not have faces") |
| vertex_groups = [] |
| mapping = {} |
| tot = 0 |
| for k, v in asset.vertex_groups.items(): |
| if v.ndim == 1: |
| v = v[:, None] |
| elif v.ndim != 2: |
| raise ValueError(f"ndim of key {k} is {v.ndim}") |
| s = tot |
| e = tot + v.shape[1] |
| mapping[k] = slice(s,e) |
| vertex_groups.append(v) |
| if len(vertex_groups) > 0: |
| vertex_groups = np.concatenate(vertex_groups, axis=1) |
| else: |
| vertex_groups = None |
| final_sampled_vertices, final_sampled_normals, sampled_vertex_groups = sample_vertex_groups( |
| vertices=asset.vertices, |
| faces=asset.faces, |
| num_samples=self.num_samples, |
| vertex_normals=asset.vertex_normals, |
| face_normals=asset.face_normals, |
| vertex_groups=vertex_groups, |
| face_mask=None, |
| shuffle=True, |
| same=True, |
| ) |
| if vertex_groups is not None: |
| final_sampled_vertices = final_sampled_vertices[:, 0] |
| if final_sampled_normals is not None: |
| final_sampled_normals = final_sampled_normals[:, 0] |
| final_sampled_vertex_groups = {} |
| if sampled_vertex_groups is not None: |
| for k, s in mapping.items(): |
| final_sampled_vertex_groups[k] = sampled_vertex_groups[:, s] |
| if vertex_groups is not None and self.num_skin_samples is not None: |
| dense_vertices = [] |
| dense_normals = [] |
| dense_skin = [] |
| if 'skin' not in mapping: |
| raise ValueError("do not have skin") |
| if self.all_skeleton: |
| dense_indices = [i for i in range(asset.J)] |
| else: |
| dense_indices = [random.randint(0, asset.J-1)] |
| for indice in dense_indices: |
| _s = asset.vertex_groups['skin'][:, indice] |
| face_has_skin = self.sample_on_skin( |
| skin=_s, |
| vertices=asset.vertices, |
| faces=asset.faces, |
| ) |
| sampled_vertices, sampled_normals, sampled_skin = sample_vertex_groups( |
| vertices=asset.vertices, |
| faces=asset.faces, |
| vertex_normals=asset.vertex_normals, |
| face_normals=asset.face_normals, |
| vertex_groups=_s, |
| num_samples=self.num_skin_samples, |
| num_vertex_samples=self.num_vertex_samples, |
| face_mask=face_has_skin, |
| shuffle=True, |
| same=True, |
| ) |
| assert sampled_skin is not None |
| assert sampled_skin.ndim == 2 |
| dense_vertices.append(sampled_vertices[:, 0]) |
| if sampled_normals is not None: |
| dense_normals.append(sampled_normals[:, 0]) |
| dense_skin.append(sampled_skin[:, 0]) |
| dense_vertices = np.stack(dense_vertices, axis=0) |
| if len(dense_normals) > 0: |
| dense_normals = np.stack(dense_normals, axis=0) |
| else: |
| dense_normals = None |
| dense_skin = np.stack(dense_skin, axis=0) |
| final_sampled_vertex_groups['skin'] = final_sampled_vertex_groups['skin'][:, dense_indices] |
| if asset.meta is None: |
| asset.meta = {} |
| asset.meta['dense_vertices'] = dense_vertices |
| asset.meta['dense_normals'] = dense_normals |
| asset.meta['dense_skin'] = dense_skin |
| asset.meta['dense_indices'] = dense_indices |
| return SamplerResult( |
| sampled_vertices=final_sampled_vertices, |
| sampled_normals=final_sampled_normals if final_sampled_normals is not None else None, |
| sampled_vertex_groups=final_sampled_vertex_groups, |
| skin_samples=self.num_skin_samples, |
| ) |
|
|
| def get_sampler(**kwargs) -> Sampler: |
| __target__ = kwargs.get('__target__') |
| assert __target__ is not None |
| del kwargs['__target__'] |
| if __target__ == 'mix': |
| sampler = SamplerMix.parse(**kwargs) |
| else: |
| raise ValueError(f"sampler method {__target__} not supported") |
| return sampler |