Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions cpp/pybind/utility/eigen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ std::vector<EigenVector> py_array_to_vectors_double(
throw py::cast_error();
}
std::vector<EigenVector> eigen_vectors(array.shape(0));
auto array_unchecked = array.mutable_unchecked<2>();
auto array_unchecked = array.unchecked<2>();
for (auto i = 0; i < array_unchecked.shape(0); ++i) {
// The EigenVector here must be a double-typed eigen vector, since only
// open3d::Vector3dVector binds to py_array_to_vectors_double.
// Therefore, we can use the memory map directly.
eigen_vectors[i] = Eigen::Map<EigenVector>(&array_unchecked(i, 0));
// Vector2dVector and Vector3dVector both use double-typed Eigen
// vectors. Copy through a const map to also accept read-only arrays.
eigen_vectors[i] =
Eigen::Map<const EigenVector>(&array_unchecked(i, 0));
}
return eigen_vectors;
}
Expand All @@ -67,9 +67,10 @@ std::vector<EigenVector> py_array_to_vectors_int(py::array_t<int> array) {
throw py::cast_error();
}
std::vector<EigenVector> eigen_vectors(array.shape(0));
auto array_unchecked = array.mutable_unchecked<2>();
auto array_unchecked = array.unchecked<2>();
for (auto i = 0; i < array_unchecked.shape(0); ++i) {
eigen_vectors[i] = Eigen::Map<EigenVector>(&array_unchecked(i, 0));
eigen_vectors[i] =
Eigen::Map<const EigenVector>(&array_unchecked(i, 0));
}
return eigen_vectors;
}
Expand All @@ -86,9 +87,10 @@ py_array_to_vectors_int_eigen_allocator(py::array_t<int> array) {
throw py::cast_error();
}
std::vector<EigenVector, EigenAllocator> eigen_vectors(array.shape(0));
auto array_unchecked = array.mutable_unchecked<2>();
auto array_unchecked = array.unchecked<2>();
for (auto i = 0; i < array_unchecked.shape(0); ++i) {
eigen_vectors[i] = Eigen::Map<EigenVector>(&array_unchecked(i, 0));
eigen_vectors[i] =
Eigen::Map<const EigenVector>(&array_unchecked(i, 0));
}
return eigen_vectors;
}
Expand All @@ -105,9 +107,10 @@ py_array_to_vectors_int64_eigen_allocator(py::array_t<int64_t> array) {
throw py::cast_error();
}
std::vector<EigenVector, EigenAllocator> eigen_vectors(array.shape(0));
auto array_unchecked = array.mutable_unchecked<2>();
auto array_unchecked = array.unchecked<2>();
for (auto i = 0; i < array_unchecked.shape(0); ++i) {
eigen_vectors[i] = Eigen::Map<EigenVector>(&array_unchecked(i, 0));
eigen_vectors[i] =
Eigen::Map<const EigenVector>(&array_unchecked(i, 0));
}
return eigen_vectors;
}
Expand Down Expand Up @@ -361,7 +364,8 @@ Example usage
pcd = open3d.geometry.PointCloud()
np_points = np.random.rand(100, 3)

# From numpy to Open3D
# From numpy to Open3D: the input is copied and may be read-only.
np_points.setflags(write=False)
pcd.points = open3d.utility.Vector3dVector(np_points)

# From Open3D to numpy
Expand Down
47 changes: 47 additions & 0 deletions python/test/test_open3d_eigen.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,53 @@ def run_test(input_array):
run_test(input_array)


@pytest.mark.parametrize("vector_name, width, dtype", [
("Vector2dVector", 2, np.float64),
("Vector3dVector", 3, np.float64),
("Vector2iVector", 2, np.int32),
("Vector3iVector", 3, np.int32),
("Vector4iVector", 4, np.int32),
])
@pytest.mark.parametrize("writeable", [True, False],
ids=["writable", "readonly"])
@pytest.mark.parametrize(
"layout",
["c", "empty", "fortran", "column_slice", "reversed", "converted"])
def test_eigen_vector_numpy_input_is_copied(vector_name, width, dtype,
writeable, layout):
input_array = np.arange(4 * width, dtype=dtype).reshape(4, width)
if layout == "empty":
input_array = input_array[:0]
elif layout == "fortran":
input_array = np.asfortranarray(input_array)
elif layout == "column_slice":
input_array = np.arange(8 * width, dtype=dtype).reshape(4, 2 * width)
input_array = input_array[:, ::2]
elif layout == "reversed":
input_array = input_array[::-1, ::-1]
elif layout == "converted":
converted_dtype = np.float32 if dtype == np.float64 else np.int64
input_array = input_array.astype(converted_dtype)
input_array.setflags(write=writeable)
expected = input_array.copy()

vector = getattr(o3d.utility, vector_name)(input_array)
output_array = np.asarray(vector)

assert output_array.dtype == np.dtype(dtype)
np.testing.assert_array_equal(output_array, expected)
assert not np.shares_memory(output_array, input_array)
assert input_array.flags.writeable == writeable
np.testing.assert_array_equal(input_array, expected)

# Open3D owns the copy even when the NumPy input is read-only.
output_array[...] = -1
np.testing.assert_array_equal(np.asarray(vector),
np.full_like(expected, -1))
np.testing.assert_array_equal(input_array, expected)
assert input_array.flags.writeable == writeable


# Run with pytest -s to show output
def test_benchmark():
vector_size = int(2e6)
Expand Down
Loading