-
Notifications
You must be signed in to change notification settings - Fork 234
feat(v2): display mesh and pointcloud #1113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 19 commits
943d636
e9acd12
c91ab6c
e979c32
21fb8ab
75b33d8
7890194
0129c3a
322a718
05d8461
0244816
255795c
db42712
dca04ce
788f834
57fb1e1
3a8dc5e
38f771d
8c31318
bbef411
b376ddd
c623f9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from docarray.documents.mesh.mesh_3d import Mesh3D | ||
|
|
||
| __all__ = ['Mesh3D'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| from typing import Any, Type, TypeVar, Union | ||
|
|
||
| from docarray.base_document import BaseDocument | ||
| from docarray.typing.tensor.tensor import AnyTensor | ||
|
|
||
| T = TypeVar('T', bound='VerticesAndFaces') | ||
|
|
||
|
|
||
| class VerticesAndFaces(BaseDocument): | ||
| """ | ||
| Document for handling 3D mesh tensor data. | ||
|
|
||
| A VerticesAndFaces Document can contain an AnyTensor containing the vertices | ||
| information (`VerticesAndFaces.vertices`), and an AnyTensor containing the faces | ||
| information (`VerticesAndFaces.faces`). | ||
| """ | ||
|
|
||
| vertices: AnyTensor | ||
| faces: AnyTensor | ||
|
|
||
| @classmethod | ||
| def validate( | ||
| cls: Type[T], | ||
| value: Union[str, Any], | ||
| ) -> T: | ||
| return super().validate(value) | ||
|
|
||
| def display(self) -> None: | ||
| """ | ||
| Plot mesh consisting of vertices and faces in notebook. | ||
| """ | ||
| import trimesh | ||
| from IPython.display import display | ||
|
|
||
| if self.vertices is None or self.faces is None: | ||
| raise ValueError( | ||
| 'Can\'t display mesh from tensors when the vertices and/or faces ' | ||
| 'are None.' | ||
| ) | ||
|
|
||
| mesh = trimesh.Trimesh(vertices=self.vertices, faces=self.faces) | ||
| display(mesh.show()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from docarray.documents.point_cloud.point_cloud_3d import PointCloud3D | ||
|
|
||
| __all__ = ['PointCloud3D'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| from typing import Any, Optional, Type, TypeVar, Union | ||
|
|
||
| import numpy as np | ||
|
|
||
| from docarray.base_document import BaseDocument | ||
| from docarray.typing import AnyTensor | ||
| from docarray.typing.tensor.abstract_tensor import AbstractTensor | ||
| from docarray.utils.misc import is_tf_available, is_torch_available | ||
|
|
||
| torch_available = is_torch_available() | ||
| if torch_available: | ||
| import torch | ||
|
|
||
| tf_available = is_tf_available() | ||
| if tf_available: | ||
| import tensorflow as tf # type: ignore | ||
|
|
||
| T = TypeVar('T', bound='PointsAndColors') | ||
|
|
||
|
|
||
| class PointsAndColors(BaseDocument): | ||
| """ | ||
| Document for handling point clouds tensor data. | ||
|
|
||
| A PointsAndColors Document can contain an AnyTensor containing the points in | ||
| 3D space information (`PointsAndColors.points`), and an AnyTensor containing | ||
| the points' color information (`PointsAndColors.colors`). | ||
| """ | ||
|
|
||
| points: AnyTensor | ||
| colors: Optional[AnyTensor] | ||
|
|
||
| @classmethod | ||
| def validate( | ||
| cls: Type[T], | ||
| value: Union[str, AbstractTensor, Any], | ||
| ) -> T: | ||
| if isinstance(value, (AbstractTensor, np.ndarray)) or ( | ||
| torch_available | ||
| and isinstance(value, torch.Tensor) | ||
| or (tf_available and isinstance(value, tf.Tensor)) | ||
| ): | ||
| value = cls(points=value) | ||
|
|
||
| return super().validate(value) | ||
|
|
||
| def display(self) -> None: | ||
| """ | ||
| Plot point cloud consisting of points in 3D space and optionally colors in | ||
| notebook. | ||
| """ | ||
| import trimesh | ||
| from IPython.display import display | ||
|
|
||
| colors = ( | ||
| self.colors | ||
| if self.colors is not None | ||
| else np.tile( | ||
| np.array([0, 0, 0]), | ||
| (self.points.get_comp_backend().shape(self.points)[0], 1), | ||
| ) | ||
| ) | ||
| pc = trimesh.points.PointCloud(vertices=self.points, colors=colors) | ||
|
|
||
| s = trimesh.Scene(geometry=pc) | ||
| display(s.show()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| from typing import NamedTuple, TypeVar | ||
| from typing import TYPE_CHECKING, TypeVar | ||
|
|
||
| import numpy as np | ||
| from pydantic import parse_obj_as | ||
|
|
@@ -7,12 +7,10 @@ | |
| from docarray.typing.tensor.ndarray import NdArray | ||
| from docarray.typing.url.url_3d.url_3d import Url3D | ||
|
|
||
| T = TypeVar('T', bound='Mesh3DUrl') | ||
|
|
||
| if TYPE_CHECKING: | ||
| from docarray.documents.mesh.vertices_and_faces import VerticesAndFaces | ||
|
|
||
| class Mesh3DLoadResult(NamedTuple): | ||
| vertices: NdArray | ||
| faces: NdArray | ||
| T = TypeVar('T', bound='Mesh3DUrl') | ||
|
|
||
|
|
||
| @_register_proto(proto_type_name='mesh_url') | ||
|
|
@@ -22,9 +20,9 @@ class Mesh3DUrl(Url3D): | |
| Can be remote (web) URL, or a local file path. | ||
| """ | ||
|
|
||
| def load(self: T) -> Mesh3DLoadResult: | ||
| def load(self: T) -> 'VerticesAndFaces': | ||
| """ | ||
| Load the data from the url into a named tuple of two NdArrays containing | ||
| Load the data from the url into a VerticesAndFaces object containing | ||
| vertices and faces information. | ||
|
|
||
| EXAMPLE USAGE | ||
|
|
@@ -34,7 +32,7 @@ def load(self: T) -> Mesh3DLoadResult: | |
| from docarray import BaseDocument | ||
| import numpy as np | ||
|
|
||
| from docarray.typing import Mesh3DUrl | ||
| from docarray.typing import Mesh3DUrl, NdArray | ||
|
|
||
|
|
||
| class MyDoc(BaseDocument): | ||
|
|
@@ -43,16 +41,28 @@ class MyDoc(BaseDocument): | |
|
|
||
| doc = MyDoc(mesh_url="toydata/tetrahedron.obj") | ||
|
|
||
| vertices, faces = doc.mesh_url.load() | ||
| assert isinstance(vertices, np.ndarray) | ||
| assert isinstance(faces, np.ndarray) | ||
| tensors = doc.mesh_url.load() | ||
| assert isinstance(tensors.vertices, NdArray) | ||
| assert isinstance(tensors.faces, NdArray) | ||
|
|
||
|
|
||
| :return: named tuple of two NdArrays representing the mesh's vertices and faces | ||
| :return: VerticesAndFaces object containing vertices and faces information. | ||
| """ | ||
| from docarray.documents.mesh.vertices_and_faces import VerticesAndFaces | ||
|
|
||
| mesh = self._load_trimesh_instance(force='mesh') | ||
|
|
||
| vertices = parse_obj_as(NdArray, mesh.vertices.view(np.ndarray)) | ||
| faces = parse_obj_as(NdArray, mesh.faces.view(np.ndarray)) | ||
|
|
||
| return Mesh3DLoadResult(vertices=vertices, faces=faces) | ||
| return VerticesAndFaces(vertices=vertices, faces=faces) | ||
|
|
||
| def display(self) -> None: | ||
| """ | ||
| Plot mesh in notebook from url. | ||
| This loads the Trimesh instance of the 3D mesh, and then displays it. | ||
| """ | ||
| from IPython.display import display | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what ahppened if we are not inside IPython ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for mesh and point cloud it would still open a pyglet window and display it, but for the other IPython displays it would just print something like '< IPython.display.Audio obj >'. |
||
|
|
||
| mesh = self._load_trimesh_instance() | ||
| display(mesh.show()) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url.displayfeels a bit weird to me, because in this case it doesn't really display the url, it displays the thing the url points to.And to do that, it has to load from that url under the hood.
So is it necessary to expose this? Why not
url.load().display()?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i see your point, but its not quite the same, when displaying it from url as it is now, we can display it with color because we just call
.show()on the trimesh instance. Forurl.load().display()we extract the vertices and faces information but as of right now there is no way to extract the color information. Therefore this displays without colors. I think it would be nice to keep the color display if url content includes this information.What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this is true that it is weird to display some data (the color) that we cannot load in our tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes true, can be a bit misleading or confusing, too. do u suggest to remove it then for the url, and do the
url.load().display()if someone doesn't want to load it into the tensors? I think this won't change on trimesh side any time soon, to easily extract color information.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let me think about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so what's the conclusion here? keep it as it is? I see the point of being able to show colors, so I don't have a strong opinion anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anna-charlotte what did you decided ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep it like this, for the colors and also to have the display method for all urls