Source code for docarray.document.mixins.mesh

from enum import Enum
from typing import TYPE_CHECKING, Union

import numpy as np

if TYPE_CHECKING:  # pragma: no cover
    from docarray.typing import T
    import trimesh


[docs]class MeshEnum(Enum): FILE_EXTENSIONS = [ 'glb', 'obj', 'ply', ] VERTICES = 'vertices' FACES = 'faces'
[docs]class PointCloudEnum(Enum): COLORS = 'point_cloud_colors'
[docs]class MeshDataMixin: """Provide helper functions for :class:`Document` to support 3D mesh data and point cloud.""" def _load_mesh( self, force: str = None ) -> Union['trimesh.Trimesh', 'trimesh.Scene']: """Load a trimesh.Mesh or trimesh.Scene object from :attr:`.uri`. :param force: str or None. For 'mesh' try to coerce scenes into a single mesh. For 'scene' try to coerce everything into a scene. :return: trimesh.Mesh or trimesh.Scene object """ import urllib.parse import trimesh scheme = urllib.parse.urlparse(self.uri).scheme loader = trimesh.load_remote if scheme in ['http', 'https'] else trimesh.load mesh = loader(self.uri, force=force) return mesh
[docs] def load_uri_to_point_cloud_tensor( self: 'T', samples: int, as_chunks: bool = False ) -> 'T': """Convert a 3d mesh-like :attr:`.uri` into :attr:`.tensor` :param samples: number of points to sample from the mesh :param as_chunks: when multiple geometry stored in one mesh file, then store each geometry into different :attr:`.chunks` :return: itself after processed """ if as_chunks: import trimesh from docarray.document import Document # try to coerce everything into a scene scene = self._load_mesh(force='scene') for geo in scene.geometry.values(): geo: trimesh.Trimesh self.chunks.append(Document(tensor=np.array(geo.sample(samples)))) else: # combine a scene into a single mesh mesh = self._load_mesh(force='mesh') self.tensor = np.array(mesh.sample(samples)) return self
[docs] def load_uri_to_vertices_and_faces(self: 'T') -> 'T': """Convert a 3d mesh-like :attr:`.uri` into :attr:`.chunks` as vertices and faces :return: itself after processed """ from docarray.document import Document mesh = self._load_mesh(force='mesh') vertices = mesh.vertices.view(np.ndarray) faces = mesh.faces.view(np.ndarray) self.chunks = [ Document(name=MeshEnum.VERTICES.value, tensor=vertices), Document(name=MeshEnum.FACES.value, tensor=faces), ] return self
[docs] def load_vertices_and_faces_to_point_cloud(self: 'T', samples: int) -> 'T': """Convert a 3d mesh of vertices and faces from :attr:`.chunks` into point cloud :attr:`.tensor` :param samples: number of points to sample from the mesh :return: itself after processed """ vertices = None faces = None for chunk in self.chunks: if chunk.tags['name'] == MeshEnum.VERTICES.value: vertices = chunk.tensor if chunk.tags['name'] == MeshEnum.FACES.value: faces = chunk.tensor if vertices is not None and faces is not None: import trimesh mesh = trimesh.Trimesh(vertices=vertices, faces=faces) self.tensor = np.array(mesh.sample(samples)) else: raise AttributeError( 'Point cloud tensor can not be set, since vertices and faces chunk tensor have not been set.' ) return self
[docs] def load_uris_to_rgbd_tensor(self: 'T') -> 'T': """Load RGB image from :attr:`.uri` of :attr:`.chunks[0]` and depth image from :attr:`.uri` of :attr:`.chunks[1]` and merge them into :attr:`.tensor`. :return: itself after processed """ from PIL import Image if len(self.chunks) != 2: raise ValueError( f'The provided Document does not have two chunks but instead {len(self.chunks)}. To load uris to RGBD tensor, the Document needs to have two chunks, with the first one providing the RGB image uri, and the second one providing the depth image uri.' ) for chunk in self.chunks: if chunk.uri == '': raise ValueError( 'A chunk of the given Document does not provide a uri.' ) rgb_img = np.array(Image.open(self.chunks[0].uri).convert('RGB')) depth_img = np.array(Image.open(self.chunks[1].uri)) if rgb_img.shape[0:2] != depth_img.shape: raise ValueError( f'The provided RGB image and depth image are not of the same shapes: {rgb_img.shape[0:2]} != {depth_img.shape}' ) self.tensor = np.concatenate( (rgb_img, np.expand_dims(depth_img, axis=2)), axis=-1 ) return self