isl-org / isl-org/Open3D

Combining pointclouds in python is slower than expected.

Open
#7,201 4 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
C++
Stars
14k
Forks
2.6k
Avg merge
5d 18h
Merged PRs (30d)
6

Description

### Checklist

- [x] I have searched for [similar issues](https://github.com/isl-org/Open3D/issues).
- [x] For Python issues, I have tested with the [latest development wheel](https://www.open3d.org/docs/latest/getting_started.html#development-version-pip).
- [x] I have checked the [release documentation](https://www.open3d.org/docs/release/) and the [latest documentation](https://www.open3d.org/docs/latest/) (for `main` branch).

### Describe the issue

When operating on big pointclouds we've noticed that Open3d performs surprisingly bad. It is somehow faster to convert the pointclouds into numpy arrays, concatenate those and convert them back into a open3d pointcloud.

### Steps to reproduce the bug

```python
import time

import numpy as np
import open3d as o3d

# points1 = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]], dtype=np.float64)
# points2 = np.array([[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]], dtype=np.float64)
points1 = np.random.rand(1430_000, 3)
points2 = np.random.rand(1440_000, 3)
points3 = np.random.rand(2870_000, 3)

pcd1 = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points1))
pcd2 = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points2))

start = time.perf_counter()
v = o3d.utility.Vector3dVector(points1)
end = time.perf_counter()
print(f'Time vector: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
o3d.geometry.PointCloud(v)
end = time.perf_counter()
print(f'Time pcd: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
res = o3d.geometry.PointCloud()
res.points = v
end = time.perf_counter()
print(f'Time pcd indirect: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points1))
end = time.perf_counter()
print(f'Sanity 1 start: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_res_native = pcd1 + pcd2
end = time.perf_counter()
print(f'Time native: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_res = o3d.geometry.PointCloud(
o3d.utility.Vector3dVector(
np.concatenate(
(
np.asarray(pcd1.points),
np.asarray(pcd2.points),
),
axis=0,
)
)
)
end = time.perf_counter()
print(f'Time np: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_res = o3d.geometry.PointCloud(
o3d.utility.Vector3dVector(
np.concatenate(
(
points1,
points2,
),
axis=0,
)
)
)
end = time.perf_counter()
print(f'Time np base: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_res = o3d.geometry.PointCloud(
o3d.utility.Vector3dVector(
np.concatenate(
(
points1,
points2,
),
axis=0,
).astype(np.float64)
)
)
end = time.perf_counter()
print(f'Time np base copy: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_res_raw = np.concatenate(
(
points1,
points2,
),
axis=0,
).astype(np.float64)
end = time.perf_counter()
print(f'Time np raw: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
v = o3d.utility.Vector3dVector(pcd_res_raw)
end = time.perf_counter()
print(f'Time vector: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
o3d.geometry.PointCloud(v)
end = time.perf_counter()
print(f'Time pcd: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points3))
end = time.perf_counter()
print(f'Sanity: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points1))
end = time.perf_counter()
print(f'Sanity 1: {(end - start) * 1000:.2f}ms')

# test transform
transform = np.asarray(
[
[9.99994574e-01, -1.68241939e-03, 2.83219799e-03, 1.00692469e02],
[1.71576989e-03, 9.99928734e-01, -1.18145285e-02, 8.12111785e01],
[2.81211916e-03, -1.18193238e-02, -9.99926195e-01, 1.79968695e01],
[0.00000000e00, 0.00000000e00, 0.00000000e00, 1.00000000e00],
],
dtype=np.float64,
)

start = time.perf_counter()
pcd_res_native.transform(transform)
end = time.perf_counter()
print(f'Time native transform: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_homo = np.concatenate((pcd_res_raw, np.ones((len(pcd_res_raw), 1))), axis=1)
pcd_homo_transformed = pcd_homo @ transform.T[:, :3].copy()
end = time.perf_counter()
print(f'Time np transform: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_transformed = pcd_res_raw @ transform.T[:3, :3].copy() + transform.T[:3, 3].copy()
end = time.perf_counter()
print(f'Time np transform no homo: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_transformed = (transform[:3, :3] @ pcd_res_raw.T).T + transform.T[:3, 3].copy()
end = time.perf_counter()
print(f'Time np transform no homo TT: {(end - start) * 1000:.2f}ms')

import numba

@numba.njit(parallel=True, fastmath=True)
def transform_pointcloud(points, transform):
"""
Applies a 3x3 rotation + 3x1 translation to a Nx3 point cloud using Numba.

Args:
points (np.ndarray): Nx3 array of 3D points.
transform (np.ndarray): 4x4 transformation matrix.

Returns:
np.ndarray: Transformed Nx3 point cloud.
"""
return points @ transform.T[:3, :3].copy() + transform.T[:3, 3].copy()

start = time.perf_counter()
pcd_transformed = transform_pointcloud(pcd_res_raw, transform)
end = time.perf_counter()
print(f'Time numba 1: {(end - start) * 1000:.2f}ms')

start = time.perf_counter()
pcd_transformed = transform_pointcloud(pcd_res_raw, transform)
end = time.perf_counter()
print(f'Time numba 2: {(end - start) * 1000:.2f}ms')
```

### Error message

_No response_

### Expected behavior

_No response_

### Open3D, Python and System information

```markdown
- Operating system: Ubuntu 20.04 / macOS 10.15 / Windows 10 64-bit
- Python version: Python 3.8 / output from `import sys; print(sys.version)`
- Open3D version: output from python: `print(open3d.__version__)`
- System architecture: x86 / arm64 / apple-silicon / jetson / rpi
- Is this a remote workstation?: yes or no
- How did you install Open3D?: pip / conda / build from source
- Compiler version (if built from source): gcc 7.5 / clang 7.0
```

### Additional information

_No response_

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.