-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #264 from daavoo/add-open3d-integration
Add open3d integration
- Loading branch information
Showing
9 changed files
with
276 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import numpy as np | ||
import pandas as pd | ||
|
||
|
||
def from_open3d(o3d_data, **kwargs): | ||
"""Create a PyntCloud instance from Open3D's PointCloud/TriangleMesh instance""" | ||
try: | ||
import open3d as o3d | ||
except ImportError: | ||
raise ImportError("Open3D must be installed. Try `pip install open3d`") | ||
|
||
if not isinstance(o3d_data, (o3d.geometry.PointCloud, o3d.geometry.TriangleMesh)): | ||
raise TypeError(f"Type {type(o3d_data)} not supported for conversion." | ||
f"Expected {o3d.geometry.PointCloud} or {o3d.geometry.TriangleMesh}") | ||
|
||
mesh = None | ||
if isinstance(o3d_data, o3d.geometry.TriangleMesh): | ||
mesh = pd.DataFrame(data=np.asarray(o3d_data.triangles), | ||
columns=['v1', 'v2', 'v3']) | ||
|
||
points = pd.DataFrame(data=np.asarray(o3d_data.vertices), | ||
columns=["x", "y", "z"]) | ||
|
||
if o3d_data.vertex_colors: | ||
colors = (np.asarray(o3d_data.vertex_colors) * 255).astype(np.uint8) | ||
points["red"] = colors[:, 0] | ||
points["green"] = colors[:, 1] | ||
points["blue"] = colors[:, 2] | ||
|
||
if o3d_data.vertex_normals: | ||
normals = np.asarray(o3d_data.vertex_normals) | ||
points["nx"] = normals[:, 0] | ||
points["ny"] = normals[:, 1] | ||
points["nz"] = normals[:, 2] | ||
|
||
elif isinstance(o3d_data, o3d.geometry.PointCloud): | ||
points = pd.DataFrame(data=np.asarray(o3d_data.points), | ||
columns=["x", "y", "z"]) | ||
|
||
if o3d_data.colors: | ||
colors = (np.asarray(o3d_data.colors) * 255).astype(np.uint8) | ||
points["red"] = colors[:, 0] | ||
points["green"] = colors[:, 1] | ||
points["blue"] = colors[:, 2] | ||
|
||
if o3d_data.normals: | ||
normals = np.asarray(o3d_data.normals) | ||
points["nx"] = normals[:, 0] | ||
points["ny"] = normals[:, 1] | ||
points["nz"] = normals[:, 2] | ||
|
||
return { | ||
"points": points, | ||
"mesh": mesh | ||
} | ||
|
||
|
||
def to_open3d(cloud, | ||
mesh=True, | ||
colors=True, | ||
normals=True, | ||
**kwargs): | ||
"""Convert PyntCloud's instance `cloud` to Open3D's PointCloud/TriangleMesh instance""" | ||
try: | ||
import open3d as o3d | ||
except ImportError: | ||
raise ImportError("Open3D must be installed. Try `pip install open3d`") | ||
|
||
if mesh and cloud.mesh is not None: | ||
triangle_mesh = o3d.geometry.TriangleMesh() | ||
triangle_mesh.triangles = o3d.utility.Vector3iVector(cloud.mesh[["v1", "v2", "v3"]].values) | ||
triangle_mesh.vertices = o3d.utility.Vector3dVector(cloud.xyz) | ||
if colors and {'red', 'green', 'blue'}.issubset(cloud.points.columns): | ||
triangle_mesh.vertex_colors = o3d.utility.Vector3dVector(cloud.points[['red', 'green', 'blue']].values) | ||
if normals and {'nx', 'ny', 'nz'}.issubset(cloud.points.columns): | ||
triangle_mesh.vertex_normals = o3d.utility.Vector3dVector(cloud.points[['nx', 'ny', 'nz']].values) | ||
return triangle_mesh | ||
else: | ||
point_cloud = o3d.geometry.PointCloud() | ||
point_cloud.points = o3d.utility.Vector3dVector(cloud.xyz) | ||
if colors and {'red', 'green', 'blue'}.issubset(cloud.points.columns): | ||
point_cloud.colors = o3d.utility.Vector3dVector(cloud.points[['red', 'green', 'blue']].values) | ||
if normals and {'nx', 'ny', 'nz'}.issubset(cloud.points.columns): | ||
point_cloud.normals = o3d.utility.Vector3dVector(cloud.points[['nx', 'ny', 'nz']].values) | ||
return point_cloud |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ matplotlib | |
numba | ||
pytest | ||
pyvista | ||
open3d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import pytest | ||
import numpy as np | ||
from pyntcloud import PyntCloud | ||
|
||
try: | ||
import pyvista as pv | ||
SKIP_PYVISTA = False | ||
except: | ||
pv = None | ||
SKIP_PYVISTA = True | ||
|
||
try: | ||
import open3d as o3d | ||
SKIP_OPEN3D = False | ||
except: | ||
o3d = None | ||
SKIP_OPEN3D = True | ||
|
||
|
||
@pytest.mark.skipif(SKIP_PYVISTA, reason="Requires PyVista") | ||
def test_pyvista_conversion(data_path): | ||
original_point_cloud = pv.read(str(data_path / "diamond.ply")) | ||
cloud = PyntCloud.from_instance("pyvista", original_point_cloud) | ||
assert np.allclose(cloud.xyz, original_point_cloud.points) | ||
assert {'red', 'green', 'blue'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['red', 'green', 'blue']].values, original_point_cloud.point_arrays["RGB"]) | ||
assert {'nx', 'ny', 'nz'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['nx', 'ny', 'nz']].values, original_point_cloud.point_arrays["Normals"]) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_PYVISTA, reason="Requires PyVista") | ||
def test_pyvista_normals_are_handled(): | ||
poly = pv.Sphere() | ||
pc = PyntCloud.from_instance("pyvista", poly) | ||
assert all(x in pc.points.columns for x in ["nx", "ny", "nz"]) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_PYVISTA, reason="Requires PyVista") | ||
def test_pyvista_multicomponent_scalars_are_splitted(): | ||
poly = pv.Sphere() | ||
poly.point_arrays["foo"] = np.zeros_like(poly.points) | ||
pc = PyntCloud.from_instance("pyvista", poly) | ||
assert all(x in pc.points.columns for x in ["foo_0", "foo_1", "foo_2"]) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_PYVISTA, reason="Requires PyVista") | ||
def test_pyvista_rgb_is_handled(): | ||
""" Serves as regression test for old `in` behaviour that could cause a subtle bug | ||
if poin_arrays contain a field with `name in "RGB"` | ||
""" | ||
poly = pv.Sphere() | ||
poly.point_arrays["RG"] = np.zeros_like(poly.points)[:, :2] | ||
pc = PyntCloud.from_instance("pyvista", poly) | ||
assert all(x in pc.points.columns for x in ["RG_0", "RG_1"]) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_OPEN3D, reason="Requires Open3D") | ||
def test_open3d_point_cloud(data_path): | ||
point_cloud = o3d.io.read_point_cloud(str(data_path.joinpath("diamond.ply"))) | ||
cloud = PyntCloud.from_instance("open3d", point_cloud) | ||
assert np.allclose(cloud.xyz, np.asarray(point_cloud.points)) | ||
assert {'red', 'green', 'blue'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['red', 'green', 'blue']].values / 255., np.asarray(point_cloud.colors)) | ||
|
||
assert {'nx', 'ny', 'nz'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['nx', 'ny', 'nz']].values, np.asarray(point_cloud.normals)) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_OPEN3D, reason="Requires Open3D") | ||
def test_open3d_triangle_mesh(data_path): | ||
triangle_mesh = o3d.io.read_triangle_mesh(str(data_path.joinpath("diamond.ply"))) | ||
cloud = PyntCloud.from_instance("open3d", triangle_mesh) | ||
assert cloud.mesh is not None | ||
assert np.allclose(cloud.mesh.values, triangle_mesh.triangles) | ||
|
||
assert np.allclose(cloud.xyz, triangle_mesh.vertices) | ||
|
||
assert {'red', 'green', 'blue'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['red', 'green', 'blue']].values / 255., triangle_mesh.vertex_colors) | ||
|
||
assert {'nx', 'ny', 'nz'}.issubset(cloud.points.columns) | ||
assert np.allclose(cloud.points[['nx', 'ny', 'nz']].values, triangle_mesh.vertex_normals) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import pytest | ||
import numpy as np | ||
from pyntcloud import PyntCloud | ||
|
||
try: | ||
import pyvista as pv | ||
SKIP_PYVISTA = False | ||
except: | ||
pv = None | ||
SKIP_PYVISTA = True | ||
|
||
try: | ||
import open3d as o3d | ||
SKIP_OPEN3D = False | ||
except: | ||
o3d = None | ||
SKIP_OPEN3D = True | ||
|
||
|
||
@pytest.mark.skipif(SKIP_PYVISTA, reason="Requires PyVista") | ||
def test_pyvista_conversion(data_path): | ||
cloud = PyntCloud.from_file(str(data_path.joinpath("diamond.ply"))) | ||
poly = cloud.to_instance("pyvista", mesh=True) | ||
assert np.allclose(cloud.xyz, poly.points) | ||
assert np.allclose(cloud.mesh.values, poly.faces[:, 1:]) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_OPEN3D, reason="Requires Open3D") | ||
def test_open3d_point_cloud_conversion(data_path): | ||
cloud = PyntCloud.from_file(str(data_path.joinpath("diamond.ply"))) | ||
point_cloud = cloud.to_instance("open3d", mesh=False) | ||
assert isinstance(point_cloud, o3d.geometry.PointCloud) | ||
assert np.allclose(cloud.xyz, point_cloud.points) | ||
|
||
|
||
@pytest.mark.skipif(SKIP_OPEN3D, reason="Requires Open3D") | ||
def test_open3d_triangle_mesh_conversion(data_path): | ||
cloud = PyntCloud.from_file(str(data_path.joinpath("diamond.ply"))) | ||
# mesh=True by default | ||
triangle_mesh = cloud.to_instance("open3d") | ||
assert isinstance(triangle_mesh, o3d.geometry.TriangleMesh) | ||
assert np.allclose(cloud.xyz, triangle_mesh.vertices) | ||
assert np.allclose(cloud.mesh.values, triangle_mesh.triangles) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters