From 48ccf2a4ea0471919fae2cb42d8d0889bb9562e2 Mon Sep 17 00:00:00 2001 From: Tim Ohliger Date: Wed, 14 Aug 2024 22:01:17 +0200 Subject: [PATCH] Split pybind declarations and definitions (#6869) As explained in #6867 there are several instances where C++ types are shown in the Python binding documentation. Some of those instances can be solved by splitting the py::_class and py::enum_ declarations from the method/function definitions using .def(...). This ensures, that all types are properly declared before usage. --------- Co-authored-by: Sameer Sheorey --- CHANGELOG.md | 1 + cpp/pybind/camera/camera.cpp | 97 +- cpp/pybind/camera/camera.h | 3 +- cpp/pybind/core/blob.cpp | 4 +- cpp/pybind/core/core.cpp | 45 +- cpp/pybind/core/core.h | 39 +- cpp/pybind/core/cuda_utils.cpp | 6 +- cpp/pybind/core/device.cpp | 14 +- cpp/pybind/core/dtype.cpp | 8 +- cpp/pybind/core/hashmap.cpp | 12 +- cpp/pybind/core/kernel.cpp | 5 +- cpp/pybind/core/linalg.cpp | 2 +- .../core/nns/nearest_neighbor_search.cpp | 29 +- cpp/pybind/core/nns/nearest_neighbor_search.h | 3 +- cpp/pybind/core/scalar.cpp | 6 +- cpp/pybind/core/size_vector.cpp | 17 +- cpp/pybind/core/sycl_utils.cpp | 4 +- cpp/pybind/core/tensor.cpp | 6 +- cpp/pybind/core/tensor_function.cpp | 18 +- cpp/pybind/geometry/boundingvolume.cpp | 30 +- cpp/pybind/geometry/geometry.cpp | 134 +-- cpp/pybind/geometry/geometry.h | 46 +- cpp/pybind/geometry/halfedgetrianglemesh.cpp | 30 +- cpp/pybind/geometry/image.cpp | 28 +- cpp/pybind/geometry/kdtreeflann.cpp | 48 +- cpp/pybind/geometry/keypoint.cpp | 29 +- cpp/pybind/geometry/lineset.cpp | 10 +- cpp/pybind/geometry/meshbase.cpp | 18 +- cpp/pybind/geometry/octree.cpp | 106 ++- cpp/pybind/geometry/pointcloud.cpp | 11 +- cpp/pybind/geometry/tetramesh.cpp | 16 +- cpp/pybind/geometry/trianglemesh.cpp | 10 +- cpp/pybind/geometry/voxelgrid.cpp | 23 +- cpp/pybind/io/class_io.cpp | 4 +- cpp/pybind/io/io.cpp | 17 +- cpp/pybind/io/io.h | 16 +- cpp/pybind/io/rpc.cpp | 179 ++-- cpp/pybind/io/sensor.cpp | 38 +- cpp/pybind/ml/contrib/contrib.cpp | 10 +- cpp/pybind/ml/contrib/contrib.h | 8 +- cpp/pybind/ml/contrib/contrib_subsample.cpp | 2 +- cpp/pybind/ml/contrib/iou.cpp | 2 +- cpp/pybind/ml/ml.cpp | 9 +- cpp/pybind/ml/ml.h | 3 +- cpp/pybind/open3d_pybind.cpp | 29 +- cpp/pybind/pipelines/color_map/color_map.cpp | 57 +- cpp/pybind/pipelines/color_map/color_map.h | 5 +- .../pipelines/integration/integration.cpp | 102 ++- .../pipelines/integration/integration.h | 3 +- cpp/pybind/pipelines/odometry/odometry.cpp | 141 +-- cpp/pybind/pipelines/odometry/odometry.h | 3 +- cpp/pybind/pipelines/pipelines.cpp | 18 +- cpp/pybind/pipelines/pipelines.h | 5 +- cpp/pybind/pipelines/registration/feature.cpp | 39 +- .../registration/global_optimization.cpp | 121 ++- .../pipelines/registration/registration.cpp | 506 ++++++----- .../pipelines/registration/registration.h | 14 +- .../pipelines/registration/robust_kernels.cpp | 163 ++-- cpp/pybind/t/geometry/boundingvolume.cpp | 76 +- cpp/pybind/t/geometry/drawablegeometry.cpp | 9 +- cpp/pybind/t/geometry/geometry.cpp | 47 +- cpp/pybind/t/geometry/geometry.h | 35 +- cpp/pybind/t/geometry/image.cpp | 30 +- cpp/pybind/t/geometry/lineset.cpp | 7 +- cpp/pybind/t/geometry/pointcloud.cpp | 68 +- cpp/pybind/t/geometry/raycasting_scene.cpp | 44 +- cpp/pybind/t/geometry/tensormap.cpp | 11 +- cpp/pybind/t/geometry/trianglemesh.cpp | 8 +- cpp/pybind/t/geometry/voxel_block_grid.cpp | 7 +- cpp/pybind/t/io/class_io.cpp | 71 +- cpp/pybind/t/io/io.cpp | 11 +- cpp/pybind/t/io/io.h | 10 +- cpp/pybind/t/io/sensor.cpp | 206 +++-- cpp/pybind/t/pipelines/odometry/odometry.cpp | 259 +++--- cpp/pybind/t/pipelines/odometry/odometry.h | 3 +- cpp/pybind/t/pipelines/pipelines.cpp | 17 +- cpp/pybind/t/pipelines/pipelines.h | 5 +- .../t/pipelines/registration/feature.cpp | 25 +- .../t/pipelines/registration/registration.cpp | 283 +++--- .../t/pipelines/registration/registration.h | 8 +- .../pipelines/registration/robust_kernel.cpp | 24 +- cpp/pybind/t/pipelines/slac/slac.cpp | 139 +-- cpp/pybind/t/pipelines/slac/slac.h | 3 +- cpp/pybind/t/pipelines/slam/slam.cpp | 42 +- cpp/pybind/t/pipelines/slam/slam.h | 3 +- cpp/pybind/t/t.cpp | 16 +- cpp/pybind/t/t.h | 3 +- cpp/pybind/utility/eigen.cpp | 23 +- cpp/pybind/utility/logging.cpp | 19 +- cpp/pybind/utility/utility.cpp | 15 +- cpp/pybind/utility/utility.h | 10 +- cpp/pybind/visualization/app/viewer.cpp | 18 +- cpp/pybind/visualization/app/viewer.h | 3 +- cpp/pybind/visualization/gui/events.cpp | 239 ++--- cpp/pybind/visualization/gui/gui.cpp | 842 ++++++++++-------- cpp/pybind/visualization/gui/gui.h | 7 +- cpp/pybind/visualization/o3dvisualizer.cpp | 55 +- .../visualization/rendering/material.cpp | 8 +- cpp/pybind/visualization/rendering/material.h | 5 +- .../visualization/rendering/rendering.cpp | 264 +++--- .../visualization/rendering/rendering.h | 5 +- cpp/pybind/visualization/renderoption.cpp | 109 +-- cpp/pybind/visualization/utility.cpp | 65 +- cpp/pybind/visualization/viewcontrol.cpp | 9 +- cpp/pybind/visualization/visualization.cpp | 46 +- cpp/pybind/visualization/visualization.h | 24 +- cpp/pybind/visualization/visualizer.cpp | 63 +- .../webrtc_server/webrtc_window_system.cpp | 26 +- .../webrtc_server/webrtc_window_system.h | 3 +- docs/builddocs.rst | 19 + examples/python/benchmark/benchmark_fgr.py | 1 + examples/python/benchmark/benchmark_pre.py | 1 + examples/python/benchmark/benchmark_ransac.py | 1 + examples/python/benchmark/benchmark_tsdf.py | 2 +- 114 files changed, 3248 insertions(+), 2456 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed7a0c786cb..98836909e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix build with fmt v10.2.0 (#6783) - Fix segmentation fault (lambda reference capture) of VisualizerWithCustomAnimation::Play (PR #6804) - Add O3DVisualizer API to enable collapse control of verts in the side panel (PR #6865) +- Split pybind declarations/definitions to avoid C++ types in Python docs (PR #6869) - Fix minimal oriented bounding box of MeshBase derived classes and add new unit tests (PR #6898) ## 0.13 diff --git a/cpp/pybind/camera/camera.cpp b/cpp/pybind/camera/camera.cpp index fa4f201e70a..6f78b631f00 100644 --- a/cpp/pybind/camera/camera.cpp +++ b/cpp/pybind/camera/camera.cpp @@ -14,12 +14,48 @@ namespace open3d { namespace camera { -void pybind_camera_classes(py::module &m) { - // open3d.camera.PinholeCameraIntrinsic +void pybind_camera_declarations(py::module &m) { + py::module m_camera = m.def_submodule("camera"); py::class_ pinhole_intr( - m, "PinholeCameraIntrinsic", + m_camera, "PinholeCameraIntrinsic", "PinholeCameraIntrinsic class stores intrinsic camera matrix, and " "image height and width."); + // open3d.camera.PinholeCameraIntrinsicParameters + py::enum_ pinhole_intr_params( + m_camera, "PinholeCameraIntrinsicParameters", py::arithmetic(), + "PinholeCameraIntrinsicParameters"); + pinhole_intr_params + .value("PrimeSenseDefault", + PinholeCameraIntrinsicParameters::PrimeSenseDefault, + "Default camera intrinsic parameter for PrimeSense.") + .value("Kinect2DepthCameraDefault", + PinholeCameraIntrinsicParameters::Kinect2DepthCameraDefault, + "Default camera intrinsic parameter for Kinect2 depth " + "camera.") + .value("Kinect2ColorCameraDefault", + PinholeCameraIntrinsicParameters::Kinect2ColorCameraDefault, + "Default camera intrinsic parameter for Kinect2 color " + "camera.") + .export_values(); + pinhole_intr_params.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class that contains default camera intrinsic " + "parameters for different sensors."; + }), + py::none(), py::none(), ""); + py::class_ pinhole_param( + m_camera, "PinholeCameraParameters", + "Contains both intrinsic and extrinsic pinhole camera parameters."); + py::class_ pinhole_traj( + m_camera, "PinholeCameraTrajectory", + "Contains a list of ``PinholeCameraParameters``, useful to storing " + "trajectories."); +} +void pybind_camera_definitions(py::module &m) { + auto m_camera = static_cast(m.attr("camera")); + // open3d.camera.PinholeCameraIntrinsic + auto pinhole_intr = static_cast>( + m_camera.attr("PinholeCameraIntrinsic")); py::detail::bind_default_constructor(pinhole_intr); py::detail::bind_copy_functions(pinhole_intr); pinhole_intr @@ -64,8 +100,9 @@ void pybind_camera_classes(py::module &m) { std::string( ".\nAccess intrinsics with intrinsic_matrix."); }); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", "__init__"); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", + "__init__"); + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", "set_intrinsics", {{"width", "Width of the image."}, {"height", "Height of the image."}, @@ -73,41 +110,18 @@ void pybind_camera_classes(py::module &m) { {"fy", "Y-axis focal length."}, {"cx", "X-axis principle point."}, {"cy", "Y-axis principle point."}}); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", "get_focal_length"); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", "get_principal_point"); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", "get_skew"); - docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", "is_valid"); - - // open3d.camera.PinholeCameraIntrinsicParameters - py::enum_ pinhole_intr_params( - m, "PinholeCameraIntrinsicParameters", py::arithmetic(), - "PinholeCameraIntrinsicParameters"); - pinhole_intr_params - .value("PrimeSenseDefault", - PinholeCameraIntrinsicParameters::PrimeSenseDefault, - "Default camera intrinsic parameter for PrimeSense.") - .value("Kinect2DepthCameraDefault", - PinholeCameraIntrinsicParameters::Kinect2DepthCameraDefault, - "Default camera intrinsic parameter for Kinect2 depth " - "camera.") - .value("Kinect2ColorCameraDefault", - PinholeCameraIntrinsicParameters::Kinect2ColorCameraDefault, - "Default camera intrinsic parameter for Kinect2 color " - "camera.") - .export_values(); - pinhole_intr_params.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class that contains default camera intrinsic " - "parameters for different sensors."; - }), - py::none(), py::none(), ""); + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", + "get_skew"); + docstring::ClassMethodDocInject(m_camera, "PinholeCameraIntrinsic", + "is_valid"); // open3d.camera.PinholeCameraParameters - py::class_ pinhole_param( - m, "PinholeCameraParameters", - "Contains both intrinsic and extrinsic pinhole camera parameters."); + auto pinhole_param = static_cast>( + m_camera.attr("PinholeCameraParameters")); py::detail::bind_default_constructor( pinhole_param); py::detail::bind_copy_functions(pinhole_param); @@ -125,10 +139,8 @@ void pybind_camera_classes(py::module &m) { }); // open3d.camera.PinholeCameraTrajectory - py::class_ pinhole_traj( - m, "PinholeCameraTrajectory", - "Contains a list of ``PinholeCameraParameters``, useful to storing " - "trajectories."); + auto pinhole_traj = static_cast>( + m_camera.attr("PinholeCameraTrajectory")); py::detail::bind_default_constructor(pinhole_traj); py::detail::bind_copy_functions(pinhole_traj); pinhole_traj @@ -141,10 +153,5 @@ void pybind_camera_classes(py::module &m) { }); } -void pybind_camera(py::module &m) { - py::module m_submodule = m.def_submodule("camera"); - pybind_camera_classes(m_submodule); -} - } // namespace camera } // namespace open3d diff --git a/cpp/pybind/camera/camera.h b/cpp/pybind/camera/camera.h index 38ef9c76afc..b7de65643fe 100644 --- a/cpp/pybind/camera/camera.h +++ b/cpp/pybind/camera/camera.h @@ -12,7 +12,8 @@ namespace open3d { namespace camera { -void pybind_camera(py::module &m); +void pybind_camera_declarations(py::module &m); +void pybind_camera_definitions(py::module &m); } // namespace camera } // namespace open3d diff --git a/cpp/pybind/core/blob.cpp b/cpp/pybind/core/blob.cpp index 309e9381f9b..228347cf81b 100644 --- a/cpp/pybind/core/blob.cpp +++ b/cpp/pybind/core/blob.cpp @@ -14,7 +14,9 @@ namespace open3d { namespace core { -void pybind_core_blob(py::module &m) { py::class_ blob(m, "Blob"); } +void pybind_core_blob_declarations(py::module &m) { + py::class_ blob(m, "Blob"); +} } // namespace core } // namespace open3d diff --git a/cpp/pybind/core/core.cpp b/cpp/pybind/core/core.cpp index 3ce45511c0a..764852ae2e7 100644 --- a/cpp/pybind/core/core.cpp +++ b/cpp/pybind/core/core.cpp @@ -16,27 +16,42 @@ namespace open3d { namespace core { -void pybind_core(py::module& m) { +void pybind_core_declarations(py::module& m) { py::module m_core = m.def_submodule("core"); // opn3d::core namespace. - pybind_cuda_utils(m_core); - pybind_sycl_utils(m_core); - pybind_core_blob(m_core); - pybind_core_dtype(m_core); - pybind_core_device(m_core); - pybind_core_size_vector(m_core); - pybind_core_tensor(m_core); - pybind_core_tensor_function(m_core); - pybind_core_linalg(m_core); - pybind_core_kernel(m_core); - pybind_core_hashmap(m_core); - pybind_core_hashset(m_core); - pybind_core_scalar(m_core); + pybind_cuda_utils_declarations(m_core); + pybind_core_blob_declarations(m_core); + pybind_core_dtype_declarations(m_core); + pybind_core_device_declarations(m_core); + pybind_core_size_vector_declarations(m_core); + pybind_core_tensor_declarations(m_core); + pybind_core_kernel_declarations(m_core); + pybind_core_hashmap_declarations(m_core); + pybind_core_hashset_declarations(m_core); + pybind_core_scalar_declarations(m_core); // opn3d::core::nns namespace. py::module m_nns = m_core.def_submodule("nns"); - nns::pybind_core_nns(m_nns); + nns::pybind_core_nns_declarations(m_nns); +} + +void pybind_core_definitions(py::module& m) { + auto m_core = static_cast(m.attr("core")); + pybind_cuda_utils_definitions(m_core); + pybind_sycl_utils_definitions(m_core); + pybind_core_dtype_definitions(m_core); + pybind_core_device_definitions(m_core); + pybind_core_size_vector_definitions(m_core); + pybind_core_tensor_definitions(m_core); + pybind_core_tensor_function_definitions(m_core); + pybind_core_linalg_definitions(m_core); + pybind_core_kernel_definitions(m_core); + pybind_core_hashmap_definitions(m_core); + pybind_core_hashset_definitions(m_core); + pybind_core_scalar_definitions(m_core); + auto m_nns = static_cast(m_core.attr("nns")); + nns::pybind_core_nns_definitions(m_nns); } } // namespace core diff --git a/cpp/pybind/core/core.h b/cpp/pybind/core/core.h index 14897df8fdb..634b697a55a 100644 --- a/cpp/pybind/core/core.h +++ b/cpp/pybind/core/core.h @@ -13,21 +13,32 @@ namespace open3d { namespace core { -void pybind_core(py::module& m); -void pybind_cuda_utils(py::module& m); -void pybind_sycl_utils(py::module& m); -void pybind_core_blob(py::module& m); -void pybind_core_dtype(py::module& m); -void pybind_core_device(py::module& m); -void pybind_core_size_vector(py::module& m); -void pybind_core_tensor(py::module& m); +void pybind_core_declarations(py::module& m); +void pybind_cuda_utils_declarations(py::module& m); +void pybind_core_blob_declarations(py::module& m); +void pybind_core_dtype_declarations(py::module& m); +void pybind_core_device_declarations(py::module& m); +void pybind_core_size_vector_declarations(py::module& m); +void pybind_core_tensor_declarations(py::module& m); void pybind_core_tensor_accessor(py::class_& t); -void pybind_core_tensor_function(py::module& m); -void pybind_core_linalg(py::module& m); -void pybind_core_kernel(py::module& m); -void pybind_core_hashmap(py::module& m); -void pybind_core_hashset(py::module& m); -void pybind_core_scalar(py::module& m); +void pybind_core_tensor_function_definitions(py::module& m); +void pybind_core_kernel_declarations(py::module& m); +void pybind_core_hashmap_declarations(py::module& m); +void pybind_core_hashset_declarations(py::module& m); +void pybind_core_scalar_declarations(py::module& m); + +void pybind_core_definitions(py::module& m); +void pybind_cuda_utils_definitions(py::module& m); +void pybind_sycl_utils_definitions(py::module& m); +void pybind_core_dtype_definitions(py::module& m); +void pybind_core_device_definitions(py::module& m); +void pybind_core_size_vector_definitions(py::module& m); +void pybind_core_tensor_definitions(py::module& m); +void pybind_core_linalg_definitions(py::module& m); +void pybind_core_kernel_definitions(py::module& m); +void pybind_core_hashmap_definitions(py::module& m); +void pybind_core_hashset_definitions(py::module& m); +void pybind_core_scalar_definitions(py::module& m); } // namespace core } // namespace open3d diff --git a/cpp/pybind/core/cuda_utils.cpp b/cpp/pybind/core/cuda_utils.cpp index 3befab655c2..30b0f84511f 100644 --- a/cpp/pybind/core/cuda_utils.cpp +++ b/cpp/pybind/core/cuda_utils.cpp @@ -12,9 +12,11 @@ namespace open3d { namespace core { -void pybind_cuda_utils(py::module& m) { +void pybind_cuda_utils_declarations(py::module& m) { py::module m_cuda = m.def_submodule("cuda"); - +} +void pybind_cuda_utils_definitions(py::module& m) { + auto m_cuda = static_cast(m.attr("cuda")); m_cuda.def("device_count", cuda::DeviceCount, "Returns the number of available CUDA devices. Returns 0 if " "Open3D is not compiled with CUDA support."); diff --git a/cpp/pybind/core/device.cpp b/cpp/pybind/core/device.cpp index bcf11805173..f72f7ace754 100644 --- a/cpp/pybind/core/device.cpp +++ b/cpp/pybind/core/device.cpp @@ -14,10 +14,17 @@ namespace open3d { namespace core { -void pybind_core_device(py::module &m) { +void pybind_core_device_declarations(py::module &m) { py::class_ device( m, "Device", "Device context specifying device type and device id."); + py::enum_(device, "DeviceType") + .value("CPU", Device::DeviceType::CPU) + .value("CUDA", Device::DeviceType::CUDA) + .export_values(); +} +void pybind_core_device_definitions(py::module &m) { + auto device = static_cast>(m.attr("Device")); device.def(py::init<>()); device.def(py::init()); device.def(py::init()); @@ -41,11 +48,6 @@ void pybind_core_device(py::module &m) { return Device(t[0].cast(), t[1].cast()); })); - - py::enum_(device, "DeviceType") - .value("CPU", Device::DeviceType::CPU) - .value("CUDA", Device::DeviceType::CUDA) - .export_values(); } } // namespace core diff --git a/cpp/pybind/core/dtype.cpp b/cpp/pybind/core/dtype.cpp index e4ab4489c4b..5a17a6cb105 100644 --- a/cpp/pybind/core/dtype.cpp +++ b/cpp/pybind/core/dtype.cpp @@ -15,10 +15,14 @@ namespace open3d { namespace core { -void pybind_core_dtype(py::module &m) { - // open3d.core.Dtype class +void pybind_core_dtype_declarations(py::module &m) { py::class_> dtype(m, "Dtype", "Open3D data types."); +} +void pybind_core_dtype_definitions(py::module &m) { + // open3d.core.Dtype class + auto dtype = static_cast>>( + m.attr("Dtype")); dtype.def(py::init()); dtype.def_readonly_static("Undefined", &core::Undefined); dtype.def_readonly_static("Float32", &core::Float32); diff --git a/cpp/pybind/core/hashmap.cpp b/cpp/pybind/core/hashmap.cpp index 4c6740d1bfa..44220aaeae1 100644 --- a/cpp/pybind/core/hashmap.cpp +++ b/cpp/pybind/core/hashmap.cpp @@ -50,11 +50,13 @@ const std::unordered_map argument_docs = { {"values_buffer_id", "Index of the value buffer tensor."}, {"device_id", "Target CUDA device ID."}}; -void pybind_core_hashmap(py::module& m) { +void pybind_core_hashmap_declarations(py::module& m) { py::class_ hashmap(m, "HashMap", "A HashMap is an unordered map from key to " "value wrapped by Tensors."); - +} +void pybind_core_hashmap_definitions(py::module& m) { + auto hashmap = static_cast>(m.attr("HashMap")); hashmap.def(py::init(), "init_capacity"_a, "key_dtype"_a, "key_element_shape"_a, @@ -193,11 +195,13 @@ void pybind_core_hashmap(py::module& m) { hashmap.def_property_readonly("is_cuda", &HashMap::IsCUDA); } -void pybind_core_hashset(py::module& m) { +void pybind_core_hashset_declarations(py::module& m) { py::class_ hashset( m, "HashSet", "A HashSet is an unordered set of keys wrapped by Tensors."); - +} +void pybind_core_hashset_definitions(py::module& m) { + auto hashset = static_cast>(m.attr("HashSet")); hashset.def( py::init(), "init_capacity"_a, "key_dtype"_a, "key_element_shape"_a, diff --git a/cpp/pybind/core/kernel.cpp b/cpp/pybind/core/kernel.cpp index 1d083778997..abfa9785e63 100644 --- a/cpp/pybind/core/kernel.cpp +++ b/cpp/pybind/core/kernel.cpp @@ -14,8 +14,11 @@ namespace open3d { namespace core { -void pybind_core_kernel(py::module &m) { +void pybind_core_kernel_declarations(py::module &m) { py::module m_kernel = m.def_submodule("kernel"); +} +void pybind_core_kernel_definitions(py::module &m) { + auto m_kernel = static_cast(m.attr("kernel")); m_kernel.def("test_linalg_integration", &core::kernel::TestLinalgIntegration); } diff --git a/cpp/pybind/core/linalg.cpp b/cpp/pybind/core/linalg.cpp index 39ec10650f2..c225daf6861 100644 --- a/cpp/pybind/core/linalg.cpp +++ b/cpp/pybind/core/linalg.cpp @@ -21,7 +21,7 @@ namespace open3d { namespace core { -void pybind_core_linalg(py::module &m) { +void pybind_core_linalg_definitions(py::module &m) { m.def( "matmul", [](const Tensor &A, const Tensor &B) { diff --git a/cpp/pybind/core/nns/nearest_neighbor_search.cpp b/cpp/pybind/core/nns/nearest_neighbor_search.cpp index feab8bbcdbf..5512bccf3be 100644 --- a/cpp/pybind/core/nns/nearest_neighbor_search.cpp +++ b/cpp/pybind/core/nns/nearest_neighbor_search.cpp @@ -18,24 +18,17 @@ namespace open3d { namespace core { namespace nns { -void pybind_core_nns(py::module &m_nns) { - static const std::unordered_map - map_nearest_neighbor_search_method_docs = { - {"query_points", "The query tensor of shape {n_query, d}."}, - {"radii", - "Tensor of shape {n_query,} containing multiple radii, " - "one for each query point."}, - {"radius", "Radius value for radius search."}, - {"max_knn", - "Maximum number of neighbors to search per query point."}, - {"knn", "Number of neighbors to search per query point."}}; - +void pybind_core_nns_declarations(py::module &m_nns) { py::class_> nns(m_nns, "NearestNeighborSearch", "NearestNeighborSearch class for nearest neighbor search. " "Construct a NearestNeighborSearch object with input " "dataset_points of shape {n_dataset, d}."); - +} +void pybind_core_nns_definitions(py::module &m_nns) { + auto nns = static_cast>>( + m_nns.attr("NearestNeighborSearch")); // Constructors. nns.def(py::init(), "dataset_points"_a, "index_dtype"_a = core::Int64); @@ -91,6 +84,16 @@ void pybind_core_nns(py::module &m_nns) { "Perform hybrid search."); // Docstrings. + static const std::unordered_map + map_nearest_neighbor_search_method_docs = { + {"query_points", "The query tensor of shape {n_query, d}."}, + {"radii", + "Tensor of shape {n_query,} containing multiple radii, " + "one for each query point."}, + {"radius", "Radius value for radius search."}, + {"max_knn", + "Maximum number of neighbors to search per query point."}, + {"knn", "Number of neighbors to search per query point."}}; docstring::ClassMethodDocInject(m_nns, "NearestNeighborSearch", "knn_search", map_nearest_neighbor_search_method_docs); diff --git a/cpp/pybind/core/nns/nearest_neighbor_search.h b/cpp/pybind/core/nns/nearest_neighbor_search.h index a391160b525..278f240b64b 100644 --- a/cpp/pybind/core/nns/nearest_neighbor_search.h +++ b/cpp/pybind/core/nns/nearest_neighbor_search.h @@ -14,7 +14,8 @@ namespace open3d { namespace core { namespace nns { -void pybind_core_nns(py::module &m); +void pybind_core_nns_declarations(py::module &m); +void pybind_core_nns_definitions(py::module &m); } // namespace nns } // namespace core diff --git a/cpp/pybind/core/scalar.cpp b/cpp/pybind/core/scalar.cpp index 8a16a690f97..fc6512d09dd 100644 --- a/cpp/pybind/core/scalar.cpp +++ b/cpp/pybind/core/scalar.cpp @@ -12,10 +12,12 @@ namespace open3d { namespace core { -void pybind_core_scalar(py::module& m) { +void pybind_core_scalar_declarations(py::module& m) { py::class_ scalar( m, "Scalar", "A Scalar can store one of {double, int64, bool}."); - +} +void pybind_core_scalar_definitions(py::module& m) { + auto scalar = static_cast>(m.attr("Scalar")); scalar.def(py::init([](float val) { return Scalar(val); })); scalar.def(py::init([](double val) { return Scalar(val); })); scalar.def(py::init([](int8_t val) { return Scalar(val); })); diff --git a/cpp/pybind/core/size_vector.cpp b/cpp/pybind/core/size_vector.cpp index 434dcb6ff59..f9344908a3b 100644 --- a/cpp/pybind/core/size_vector.cpp +++ b/cpp/pybind/core/size_vector.cpp @@ -13,12 +13,17 @@ namespace open3d { namespace core { -void pybind_core_size_vector(py::module& m) { +void pybind_core_size_vector_declarations(py::module& m) { // bind_vector takes care of most common methods for Python list. auto sv = py::bind_vector( m, "SizeVector", "A vector of integers for specifying shape, strides, etc."); - + auto dsv = py::bind_vector( + m, "DynamicSizeVector", + "A vector of integers for specifying shape, strides, etc. Some " + "elements can be None."); +} +void pybind_core_size_vector_definitions(py::module& m) { // In Python, We want (3), (3,), [3] and [3,] to represent the same thing. // The following are all equivalent to core::SizeVector({3}): // - o3d.core.SizeVector(3) # int @@ -33,17 +38,15 @@ void pybind_core_size_vector(py::module& m) { // // The API difference is due to the NumPy convention which allows integer to // represent a 1-element tuple, and the C++ constructor for vectors. + auto sv = static_cast>(m.attr("SizeVector")); sv.def(py::init([](int64_t i) { return SizeVector({i}); })); py::implicitly_convertible(); // Allows tuple and list implicit conversions to SizeVector. py::implicitly_convertible(); py::implicitly_convertible(); - - auto dsv = py::bind_vector( - m, "DynamicSizeVector", - "A vector of integers for specifying shape, strides, etc. Some " - "elements can be None."); + auto dsv = static_cast>( + m.attr("DynamicSizeVector")); dsv.def("__repr__", [](const DynamicSizeVector& dsv) { return dsv.ToString(); }); } diff --git a/cpp/pybind/core/sycl_utils.cpp b/cpp/pybind/core/sycl_utils.cpp index b0b486ba6f0..0acf8c37a5b 100644 --- a/cpp/pybind/core/sycl_utils.cpp +++ b/cpp/pybind/core/sycl_utils.cpp @@ -12,7 +12,9 @@ namespace open3d { namespace core { -void pybind_sycl_utils(py::module& m) { m.def("sycl_demo", &sycl::SYCLDemo); } +void pybind_sycl_utils_definitions(py::module& m) { + m.def("sycl_demo", &sycl::SYCLDemo); +} } // namespace core } // namespace open3d diff --git a/cpp/pybind/core/tensor.cpp b/cpp/pybind/core/tensor.cpp index 56e1aaa2f2c..d81cccaf7de 100644 --- a/cpp/pybind/core/tensor.cpp +++ b/cpp/pybind/core/tensor.cpp @@ -250,11 +250,13 @@ static void BindTensorFullCreation(py::module& m, py::class_& tensor) { "device"_a = py::none()); } -void pybind_core_tensor(py::module& m) { +void pybind_core_tensor_declarations(py::module& m) { py::class_ tensor( m, "Tensor", "A Tensor is a view of a data Blob with shape, stride, data_ptr."); - +} +void pybind_core_tensor_definitions(py::module& m) { + auto tensor = static_cast>(m.attr("Tensor")); // o3c.Tensor(np.array([[0, 1, 2], [3, 4, 5]]), dtype=None, device=None). tensor.def(py::init([](const py::array& np_array, utility::optional dtype, diff --git a/cpp/pybind/core/tensor_function.cpp b/cpp/pybind/core/tensor_function.cpp index 78a6da686a1..01adb644ce0 100644 --- a/cpp/pybind/core/tensor_function.cpp +++ b/cpp/pybind/core/tensor_function.cpp @@ -13,7 +13,7 @@ namespace open3d { namespace core { -void pybind_core_tensor_function(py::module& m) { +void pybind_core_tensor_function_definitions(py::module& m) { m.def( "concatenate", [](const std::vector& tensors, @@ -27,7 +27,7 @@ void pybind_core_tensor_function(py::module& m) { axis into a new tensor. All the tensors must have same data-type, device, and number of dimensions. All dimensions must be the same, except the dimension along the axis the tensors are to be concatenated. -Using Concatenate for a single tensor, the tensor is split along its first +Using Concatenate for a single tensor, the tensor is split along its first dimension (length), and concatenated along the axis. This is the same as NumPy's semantics: @@ -59,17 +59,17 @@ This is the same as NumPy's semantics: } return core::Append(self, values); }, - R"(Appends the `values` tensor to the `self` tensor, along the + R"(Appends the `values` tensor to the `self` tensor, along the given axis and returns a new tensor. Both the tensors must have same data-type device, and number of dimensions. All dimensions must be the same, except the -dimension along the axis the tensors are to be appended. +dimension along the axis the tensors are to be appended. This is the same as NumPy's semantics: - https://numpy.org/doc/stable/reference/generated/numpy.append.html Returns: - A copy of the `self` tensor with `values` appended to axis. Note that - append does not occur in-place: a new array is allocated and filled. + A copy of the `self` tensor with `values` appended to axis. Note that + append does not occur in-place: a new array is allocated and filled. If axis is null, out is a flattened tensor. Example: @@ -78,20 +78,20 @@ This is the same as NumPy's semantics: [2 3], [4 5]] Tensor[shape={3, 2}, stride={2, 1}, Int64, CPU:0, 0x55555abc6b00] - + >>> o3d.core.append([[0, 1], [2, 3]], [[4, 5]]) [0 1 2 3 4 5] Tensor[shape={6}, stride={1}, Int64, CPU:0, 0x55555abc6b70])", "self"_a, "values"_a, "axis"_a = py::none()); m.def("maximum", &core::Maximum, - R"(Computes the element-wise maximum of input and other. The tensors + R"(Computes the element-wise maximum of input and other. The tensors must have same data type and device. If input.GetShape() != other.GetShape(), then they will be broadcasted to a common shape (which becomes the shape of the output).)", "input"_a, "other"_a); m.def("minimum", &core::Minimum, - R"(Computes the element-wise minimum of input and other. The tensors + R"(Computes the element-wise minimum of input and other. The tensors must have same data type and device. If input.GetShape() != other.GetShape(), then they will be broadcasted to a common shape (which becomes the shape of the output).)", diff --git a/cpp/pybind/geometry/boundingvolume.cpp b/cpp/pybind/geometry/boundingvolume.cpp index 719e4edfa55..e178adfe19b 100644 --- a/cpp/pybind/geometry/boundingvolume.cpp +++ b/cpp/pybind/geometry/boundingvolume.cpp @@ -16,12 +16,26 @@ namespace open3d { namespace geometry { -void pybind_boundingvolume(py::module &m) { +void pybind_boundingvolume_declarations(py::module &m) { py::class_, std::shared_ptr, Geometry3D> oriented_bounding_box(m, "OrientedBoundingBox", "Class that defines an oriented box that can " "be computed from 3D geometries."); + py::class_, + std::shared_ptr, Geometry3D> + axis_aligned_bounding_box(m, "AxisAlignedBoundingBox", + "Class that defines an axis_aligned box " + "that can be computed from 3D " + "geometries, The axis aligned bounding " + "box uses the coordinate axes for " + "bounding box generation."); +} +void pybind_boundingvolume_definitions(py::module &m) { + auto oriented_bounding_box = static_cast< + py::class_, + std::shared_ptr, Geometry3D>>( + m.attr("OrientedBoundingBox")); py::detail::bind_default_constructor( oriented_bounding_box); py::detail::bind_copy_functions(oriented_bounding_box); @@ -62,7 +76,7 @@ The returned bounding box is an approximation to the minimal bounding box. Args: points (open3d.utility.Vector3dVector): Input points. - robust (bool): If set to true uses a more robust method which works in + robust (bool): If set to true uses a more robust method which works in degenerate cases but introduces noise to the points coordinates. Returns: @@ -93,14 +107,10 @@ The returned bounding box is an approximation to the minimal bounding box. "AxisAlignedBoundingBox object from which OrientedBoundingBox is " "created."}}); - py::class_, - std::shared_ptr, Geometry3D> - axis_aligned_bounding_box(m, "AxisAlignedBoundingBox", - "Class that defines an axis_aligned box " - "that can be computed from 3D " - "geometries, The axis aligned bounding " - "box uses the coordinate axes for " - "bounding box generation."); + auto axis_aligned_bounding_box = static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("AxisAlignedBoundingBox")); py::detail::bind_default_constructor( axis_aligned_bounding_box); py::detail::bind_copy_functions( diff --git a/cpp/pybind/geometry/geometry.cpp b/cpp/pybind/geometry/geometry.cpp index 8158c0de25c..22b63c8184c 100644 --- a/cpp/pybind/geometry/geometry.cpp +++ b/cpp/pybind/geometry/geometry.cpp @@ -14,7 +14,39 @@ namespace open3d { namespace geometry { -void pybind_geometry_classes(py::module &m) { +void pybind_geometry_classes_declarations(py::module &m) { + py::class_, std::shared_ptr> + geometry(m, "Geometry", "The base geometry class."); + py::enum_ geometry_type(geometry, "Type", + py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + geometry_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for Geometry types."; + }), + py::none(), py::none(), ""); + + geometry_type.value("Unspecified", Geometry::GeometryType::Unspecified) + .value("PointCloud", Geometry::GeometryType::PointCloud) + .value("VoxelGrid", Geometry::GeometryType::VoxelGrid) + .value("LineSet", Geometry::GeometryType::LineSet) + .value("TriangleMesh", Geometry::GeometryType::TriangleMesh) + .value("HalfEdgeTriangleMesh", + Geometry::GeometryType::HalfEdgeTriangleMesh) + .value("Image", Geometry::GeometryType::Image) + .value("RGBDImage", Geometry::GeometryType::RGBDImage) + .value("TetraMesh", Geometry::GeometryType::TetraMesh) + .export_values(); + py::class_, + std::shared_ptr, Geometry> + geometry3d(m, "Geometry3D", + "The base geometry class for 3D geometries."); + py::class_, + std::shared_ptr, Geometry> + geometry2d(m, "Geometry2D", + "The base geometry class for 2D geometries."); +} +void pybind_geometry_classes_definitions(py::module &m) { // open3d.geometry functions m.def("get_rotation_matrix_from_xyz", &Geometry3D::GetRotationMatrixFromXYZ, "rotation"_a); @@ -34,8 +66,9 @@ void pybind_geometry_classes(py::module &m) { &Geometry3D::GetRotationMatrixFromQuaternion, "rotation"_a); // open3d.geometry.Geometry - py::class_, std::shared_ptr> - geometry(m, "Geometry", "The base geometry class."); + auto geometry = static_cast, + std::shared_ptr>>( + m.attr("Geometry")); geometry.def("clear", &Geometry::Clear, "Clear all elements in the geometry.") .def("is_empty", &Geometry::IsEmpty, @@ -48,33 +81,11 @@ void pybind_geometry_classes(py::module &m) { docstring::ClassMethodDocInject(m, "Geometry", "is_empty"); docstring::ClassMethodDocInject(m, "Geometry", "get_geometry_type"); docstring::ClassMethodDocInject(m, "Geometry", "dimension"); - - // open3d.geometry.Geometry.Type - py::enum_ geometry_type(geometry, "Type", - py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - geometry_type.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for Geometry types."; - }), - py::none(), py::none(), ""); - - geometry_type.value("Unspecified", Geometry::GeometryType::Unspecified) - .value("PointCloud", Geometry::GeometryType::PointCloud) - .value("VoxelGrid", Geometry::GeometryType::VoxelGrid) - .value("LineSet", Geometry::GeometryType::LineSet) - .value("TriangleMesh", Geometry::GeometryType::TriangleMesh) - .value("HalfEdgeTriangleMesh", - Geometry::GeometryType::HalfEdgeTriangleMesh) - .value("Image", Geometry::GeometryType::Image) - .value("RGBDImage", Geometry::GeometryType::RGBDImage) - .value("TetraMesh", Geometry::GeometryType::TetraMesh) - .export_values(); - - py::class_, - std::shared_ptr, Geometry> - geometry3d(m, "Geometry3D", - "The base geometry class for 3D geometries."); + // open3d.geometry.Geometry3D + auto geometry3d = + static_cast, + std::shared_ptr, Geometry>>( + m.attr("Geometry3D")); geometry3d .def("get_min_bound", &Geometry3D::GetMinBound, "Returns min bounds for geometry coordinates.") @@ -94,7 +105,7 @@ Computes the oriented bounding box based on the PCA of the convex hull. The returned bounding box is an approximation to the minimal bounding box. Args: - robust (bool): If set to true uses a more robust method which works in + robust (bool): If set to true uses a more robust method which works in degenerate cases but introduces noise to the points coordinates. Returns: @@ -192,10 +203,10 @@ at the end, return the box with the smallest volume {"center", "Rotation center used for transformation."}}); // open3d.geometry.Geometry2D - py::class_, - std::shared_ptr, Geometry> - geometry2d(m, "Geometry2D", - "The base geometry class for 2D geometries."); + auto geometry2d = + static_cast, + std::shared_ptr, Geometry>>( + m.attr("Geometry2D")); geometry2d .def("get_min_bound", &Geometry2D::GetMinBound, "Returns min bounds for geometry coordinates.") @@ -205,27 +216,38 @@ at the end, return the box with the smallest volume docstring::ClassMethodDocInject(m, "Geometry2D", "get_max_bound"); } -void pybind_geometry(py::module &m) { - py::module m_submodule = m.def_submodule("geometry"); - pybind_geometry_classes(m_submodule); - pybind_kdtreeflann(m_submodule); - pybind_pointcloud(m_submodule); - pybind_keypoint(m_submodule); - pybind_voxelgrid(m_submodule); - pybind_lineset(m_submodule); - pybind_meshbase(m_submodule); - pybind_trianglemesh(m_submodule); - pybind_halfedgetrianglemesh(m_submodule); - pybind_image(m_submodule); - pybind_tetramesh(m_submodule); - pybind_pointcloud_methods(m_submodule); - pybind_voxelgrid_methods(m_submodule); - pybind_meshbase_methods(m_submodule); - pybind_lineset_methods(m_submodule); - pybind_image_methods(m_submodule); - pybind_octree_methods(m_submodule); - pybind_octree(m_submodule); - pybind_boundingvolume(m_submodule); +void pybind_geometry_declarations(py::module &m) { + py::module m_geometry = m.def_submodule("geometry"); + pybind_geometry_classes_declarations(m_geometry); + pybind_kdtreeflann_declarations(m_geometry); + pybind_pointcloud_declarations(m_geometry); + pybind_keypoint_declarations(m_geometry); + pybind_voxelgrid_declarations(m_geometry); + pybind_lineset_declarations(m_geometry); + pybind_meshbase_declarations(m_geometry); + pybind_trianglemesh_declarations(m_geometry); + pybind_halfedgetrianglemesh_declarations(m_geometry); + pybind_image_declarations(m_geometry); + pybind_tetramesh_declarations(m_geometry); + pybind_octree_declarations(m_geometry); + pybind_boundingvolume_declarations(m_geometry); +} + +void pybind_geometry_definitions(py::module &m) { + auto m_geometry = static_cast(m.attr("geometry")); + pybind_geometry_classes_definitions(m_geometry); + pybind_kdtreeflann_definitions(m_geometry); + pybind_pointcloud_definitions(m_geometry); + pybind_keypoint_definitions(m_geometry); + pybind_voxelgrid_definitions(m_geometry); + pybind_lineset_definitions(m_geometry); + pybind_meshbase_definitions(m_geometry); + pybind_trianglemesh_definitions(m_geometry); + pybind_halfedgetrianglemesh_definitions(m_geometry); + pybind_image_definitions(m_geometry); + pybind_tetramesh_definitions(m_geometry); + pybind_octree_definitions(m_geometry); + pybind_boundingvolume_definitions(m_geometry); } } // namespace geometry diff --git a/cpp/pybind/geometry/geometry.h b/cpp/pybind/geometry/geometry.h index 41a9e31cd01..ec9d6f6f3fa 100644 --- a/cpp/pybind/geometry/geometry.h +++ b/cpp/pybind/geometry/geometry.h @@ -12,27 +12,33 @@ namespace open3d { namespace geometry { -void pybind_geometry(py::module &m); +void pybind_geometry_declarations(py::module &m); +void pybind_kdtreeflann_declarations(py::module &m); +void pybind_pointcloud_declarations(py::module &m); +void pybind_keypoint_declarations(py::module &m); +void pybind_voxelgrid_declarations(py::module &m); +void pybind_lineset_declarations(py::module &m); +void pybind_meshbase_declarations(py::module &m); +void pybind_trianglemesh_declarations(py::module &m); +void pybind_halfedgetrianglemesh_declarations(py::module &m); +void pybind_image_declarations(py::module &m); +void pybind_tetramesh_declarations(py::module &m); +void pybind_octree_declarations(py::module &m); +void pybind_boundingvolume_declarations(py::module &m); -void pybind_pointcloud(py::module &m); -void pybind_keypoint(py::module &m); -void pybind_voxelgrid(py::module &m); -void pybind_lineset(py::module &m); -void pybind_meshbase(py::module &m); -void pybind_trianglemesh(py::module &m); -void pybind_halfedgetrianglemesh(py::module &m); -void pybind_image(py::module &m); -void pybind_tetramesh(py::module &m); -void pybind_kdtreeflann(py::module &m); -void pybind_pointcloud_methods(py::module &m); -void pybind_voxelgrid_methods(py::module &m); -void pybind_meshbase_methods(py::module &m); -void pybind_trianglemesh_methods(py::module &m); -void pybind_lineset_methods(py::module &m); -void pybind_image_methods(py::module &m); -void pybind_octree_methods(py::module &m); -void pybind_octree(py::module &m); -void pybind_boundingvolume(py::module &m); +void pybind_geometry_definitions(py::module &m); +void pybind_kdtreeflann_definitions(py::module &m); +void pybind_pointcloud_definitions(py::module &m); +void pybind_keypoint_definitions(py::module &m); +void pybind_voxelgrid_definitions(py::module &m); +void pybind_lineset_definitions(py::module &m); +void pybind_meshbase_definitions(py::module &m); +void pybind_trianglemesh_definitions(py::module &m); +void pybind_halfedgetrianglemesh_definitions(py::module &m); +void pybind_image_definitions(py::module &m); +void pybind_tetramesh_definitions(py::module &m); +void pybind_octree_definitions(py::module &m); +void pybind_boundingvolume_definitions(py::module &m); } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/halfedgetrianglemesh.cpp b/cpp/pybind/geometry/halfedgetrianglemesh.cpp index 437c4f12dfd..ee269a33f84 100644 --- a/cpp/pybind/geometry/halfedgetrianglemesh.cpp +++ b/cpp/pybind/geometry/halfedgetrianglemesh.cpp @@ -16,11 +16,24 @@ namespace open3d { namespace geometry { -void pybind_half_edge(py::module &m) { +void pybind_half_edge(py::module &m) {} + +void pybind_halfedgetrianglemesh_declarations(py::module &m) { py::class_ half_edge( m, "HalfEdge", "HalfEdge class contains vertex, triangle info about a half edge, " "as well as relations of next and twin half edge."); + py::class_, + std::shared_ptr, MeshBase> + half_edge_triangle_mesh( + m, "HalfEdgeTriangleMesh", + "HalfEdgeTriangleMesh inherits TriangleMesh class with the " + "addition of HalfEdge data structure for each half edge in " + "the mesh as well as related functions."); +} +void pybind_halfedgetrianglemesh_definitions(py::module &m) { + auto half_edge = static_cast>( + m.attr("HalfEdge")); py::detail::bind_default_constructor( half_edge); py::detail::bind_copy_functions(half_edge); @@ -52,19 +65,12 @@ void pybind_half_edge(py::module &m) { "triangle_index", &HalfEdgeTriangleMesh::HalfEdge::triangle_index_, "int: Index of the triangle containing this half edge"); -} - -void pybind_halfedgetrianglemesh(py::module &m) { - pybind_half_edge(m); // open3d.geometry.HalfEdgeTriangleMesh - py::class_, - std::shared_ptr, MeshBase> - half_edge_triangle_mesh( - m, "HalfEdgeTriangleMesh", - "HalfEdgeTriangleMesh inherits TriangleMesh class with the " - "addition of HalfEdge data structure for each half edge in " - "the mesh as well as related functions."); + auto half_edge_triangle_mesh = static_cast< + py::class_, + std::shared_ptr, MeshBase>>( + m.attr("HalfEdgeTriangleMesh")); py::detail::bind_default_constructor( half_edge_triangle_mesh); py::detail::bind_copy_functions( diff --git a/cpp/pybind/geometry/image.cpp b/cpp/pybind/geometry/image.cpp index 70e63184e64..910826708de 100644 --- a/cpp/pybind/geometry/image.cpp +++ b/cpp/pybind/geometry/image.cpp @@ -37,7 +37,7 @@ static const std::unordered_map "When ``True``, image in the pyramid will first be filtered " "by a 3x3 Gaussian kernel before downsampling."}}; -void pybind_image(py::module &m) { +void pybind_image_declarations(py::module &m) { py::enum_ image_filter_type(m, "ImageFilterType"); image_filter_type.value("Gaussian3", Image::FilterType::Gaussian3) .value("Gaussian5", Image::FilterType::Gaussian5) @@ -50,11 +50,22 @@ void pybind_image(py::module &m) { return "Enum class for Image filter types."; }), py::none(), py::none(), ""); - py::class_, std::shared_ptr, Geometry2D> image(m, "Image", py::buffer_protocol(), "The image class stores image with customizable width, " "height, num of channels and bytes per channel."); + py::class_, std::shared_ptr, + Geometry2D> + rgbd_image(m, "RGBDImage", + "RGBDImage is for a pair of registered color and depth " + "images, viewed from the same view, of the same " + "resolution. If you have other format, convert it " + "first."); +} +void pybind_image_definitions(py::module &m) { + auto image = static_cast, + std::shared_ptr, Geometry2D>>( + m.attr("Image")); py::detail::bind_default_constructor(image); py::detail::bind_copy_functions(image); image.def(py::init([](py::buffer b) { @@ -204,13 +215,10 @@ void pybind_image(py::module &m) { docstring::ClassMethodDocInject(m, "Image", "filter_pyramid", map_shared_argument_docstrings); - py::class_, std::shared_ptr, - Geometry2D> - rgbd_image(m, "RGBDImage", - "RGBDImage is for a pair of registered color and depth " - "images, viewed from the same view, of the same " - "resolution. If you have other format, convert it " - "first."); + auto rgbd_image = + static_cast, + std::shared_ptr, Geometry2D>>( + m.attr("RGBDImage")); py::detail::bind_default_constructor(rgbd_image); rgbd_image .def_readwrite("color", &RGBDImage::color_, @@ -275,7 +283,5 @@ void pybind_image(py::module &m) { map_shared_argument_docstrings); } -void pybind_image_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/kdtreeflann.cpp b/cpp/pybind/geometry/kdtreeflann.cpp index 785aa2a09f9..42dfaba3795 100644 --- a/cpp/pybind/geometry/kdtreeflann.cpp +++ b/cpp/pybind/geometry/kdtreeflann.cpp @@ -14,15 +14,9 @@ namespace open3d { namespace geometry { -void pybind_kdtreeflann(py::module &m) { - // open3d.geometry.KDTreeSearchParam +void pybind_kdtreeflann_declarations(py::module &m) { py::class_ kdtreesearchparam( m, "KDTreeSearchParam", "Base class for KDTree search parameters."); - kdtreesearchparam.def("get_search_type", &KDTreeSearchParam::GetSearchType, - "Get the search type (KNN, Radius, Hybrid) for the " - "search parameter."); - docstring::ClassMethodDocInject(m, "KDTreeSearchParam", "get_search_type"); - // open3d.geometry.KDTreeSearchParam.Type py::enum_ kdtree_search_param_type( kdtreesearchparam, "Type", py::arithmetic()); @@ -36,11 +30,31 @@ void pybind_kdtreeflann(py::module &m) { return "Enum class for Geometry types."; }), py::none(), py::none(), ""); - - // open3d.geometry.KDTreeSearchParamKNN py::class_ kdtreesearchparam_knn( m, "KDTreeSearchParamKNN", kdtreesearchparam, "KDTree search parameters for pure KNN search."); + py::class_ kdtreesearchparam_radius( + m, "KDTreeSearchParamRadius", kdtreesearchparam, + "KDTree search parameters for pure radius search."); + py::class_ kdtreesearchparam_hybrid( + m, "KDTreeSearchParamHybrid", kdtreesearchparam, + "KDTree search parameters for hybrid KNN and radius search."); + py::class_> kdtreeflann( + m, "KDTreeFlann", "KDTree with FLANN for nearest neighbor search."); +} + +void pybind_kdtreeflann_definitions(py::module &m) { + // open3d.geometry.KDTreeSearchParam + auto kdtreesearchparam = static_cast>( + m.attr("KDTreeSearchParam")); + kdtreesearchparam.def("get_search_type", &KDTreeSearchParam::GetSearchType, + "Get the search type (KNN, Radius, Hybrid) for the " + "search parameter."); + docstring::ClassMethodDocInject(m, "KDTreeSearchParam", "get_search_type"); + + // open3d.geometry.KDTreeSearchParamKNN + auto kdtreesearchparam_knn = static_cast>( + m.attr("KDTreeSearchParamKNN")); kdtreesearchparam_knn.def(py::init(), "knn"_a = 30) .def("__repr__", [](const KDTreeSearchParamKNN ¶m) { @@ -53,9 +67,9 @@ void pybind_kdtreeflann(py::module &m) { "Number of the neighbors that will be searched."); // open3d.geometry.KDTreeSearchParamRadius - py::class_ kdtreesearchparam_radius( - m, "KDTreeSearchParamRadius", kdtreesearchparam, - "KDTree search parameters for pure radius search."); + auto kdtreesearchparam_radius = + static_cast>( + m.attr("KDTreeSearchParamRadius")); kdtreesearchparam_radius.def(py::init(), "radius"_a) .def("__repr__", [](const KDTreeSearchParamRadius ¶m) { @@ -68,9 +82,9 @@ void pybind_kdtreeflann(py::module &m) { "Search radius."); // open3d.geometry.KDTreeSearchParamHybrid - py::class_ kdtreesearchparam_hybrid( - m, "KDTreeSearchParamHybrid", kdtreesearchparam, - "KDTree search parameters for hybrid KNN and radius search."); + auto kdtreesearchparam_hybrid = + static_cast>( + m.attr("KDTreeSearchParamHybrid")); kdtreesearchparam_hybrid .def(py::init(), "radius"_a, "max_nn"_a) .def("__repr__", @@ -88,6 +102,8 @@ void pybind_kdtreeflann(py::module &m) { "At maximum, ``max_nn`` neighbors will be searched."); // open3d.geometry.KDTreeFlann + auto kdtreeflann = + static_cast>(m.attr("KDTreeFlann")); static const std::unordered_map map_kd_tree_flann_method_docs = { {"query", "The input query point."}, @@ -97,8 +113,6 @@ void pybind_kdtreeflann(py::module &m) { {"knn", "``knn`` neighbors will be searched."}, {"feature", "Feature data."}, {"data", "Matrix data."}}; - py::class_> kdtreeflann( - m, "KDTreeFlann", "KDTree with FLANN for nearest neighbor search."); kdtreeflann.def(py::init<>()) .def(py::init(), "data"_a) .def("set_matrix_data", &KDTreeFlann::SetMatrixData, diff --git a/cpp/pybind/geometry/keypoint.cpp b/cpp/pybind/geometry/keypoint.cpp index a4253670255..97865a7f36c 100644 --- a/cpp/pybind/geometry/keypoint.cpp +++ b/cpp/pybind/geometry/keypoint.cpp @@ -19,17 +19,21 @@ namespace open3d { namespace geometry { -void pybind_keypoint_methods(py::module &m) { - m.def("compute_iss_keypoints", &keypoint::ComputeISSKeypoints, - "Function that computes the ISS keypoints from an input point " - "cloud. This implements the keypoint detection modules " - "proposed in Yu Zhong, 'Intrinsic Shape Signatures: A Shape " - "Descriptor for 3D Object Recognition', 2009.", - "input"_a, "salient_radius"_a = 0.0, "non_max_radius"_a = 0.0, - "gamma_21"_a = 0.975, "gamma_32"_a = 0.975, "min_neighbors"_a = 5); - +void pybind_keypoint_declarations(py::module &m) { + py::module m_keypoint = m.def_submodule("keypoint", "Keypoint Detectors."); +} +void pybind_keypoint_definitions(py::module &m) { + auto m_keypoint = static_cast(m.attr("keypoint")); + m_keypoint.def( + "compute_iss_keypoints", &keypoint::ComputeISSKeypoints, + "Function that computes the ISS keypoints from an input point " + "cloud. This implements the keypoint detection modules " + "proposed in Yu Zhong, 'Intrinsic Shape Signatures: A Shape " + "Descriptor for 3D Object Recognition', 2009.", + "input"_a, "salient_radius"_a = 0.0, "non_max_radius"_a = 0.0, + "gamma_21"_a = 0.975, "gamma_32"_a = 0.975, "min_neighbors"_a = 5); docstring::FunctionDocInject( - m, "compute_iss_keypoints", + m_keypoint, "compute_iss_keypoints", {{"input", "The Input point cloud."}, {"salient_radius", "The radius of the spherical neighborhood used to detect " @@ -49,10 +53,5 @@ void pybind_keypoint_methods(py::module &m) { "keypoint"}}); } -void pybind_keypoint(py::module &m) { - py::module m_submodule = m.def_submodule("keypoint", "Keypoint Detectors."); - pybind_keypoint_methods(m_submodule); -} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/lineset.cpp b/cpp/pybind/geometry/lineset.cpp index 41e47a1c7ca..fe13fd0b848 100644 --- a/cpp/pybind/geometry/lineset.cpp +++ b/cpp/pybind/geometry/lineset.cpp @@ -16,13 +16,19 @@ namespace open3d { namespace geometry { -void pybind_lineset(py::module &m) { +void pybind_lineset_declarations(py::module &m) { py::class_, std::shared_ptr, Geometry3D> lineset(m, "LineSet", "LineSet define a sets of lines in 3D. A typical " "application is to display the point cloud correspondence " "pairs."); +} +void pybind_lineset_definitions(py::module &m) { + auto lineset = + static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("LineSet")); py::detail::bind_default_constructor(lineset); py::detail::bind_copy_functions(lineset); lineset.def(py::init &, @@ -125,7 +131,5 @@ void pybind_lineset(py::module &m) { {{"mesh", "The input tetra mesh."}}); } -void pybind_lineset_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/meshbase.cpp b/cpp/pybind/geometry/meshbase.cpp index cbc71520e4f..d3c3ec447e6 100644 --- a/cpp/pybind/geometry/meshbase.cpp +++ b/cpp/pybind/geometry/meshbase.cpp @@ -15,16 +15,13 @@ namespace open3d { namespace geometry { -void pybind_meshbase(py::module &m) { +void pybind_meshbase_declarations(py::module &m) { py::class_, std::shared_ptr, Geometry3D> meshbase(m, "MeshBase", "MeshBase class. Triangle mesh contains vertices. " "Optionally, the mesh " "may also contain vertex normals and vertex colors."); - py::detail::bind_default_constructor(meshbase); - py::detail::bind_copy_functions(meshbase); - py::enum_(m, "SimplificationContraction") .value("Average", MeshBase::SimplificationContraction::Average, @@ -33,7 +30,6 @@ void pybind_meshbase(py::module &m) { "The vertex positions are computed by minimizing the " "distance to the adjacent triangle planes.") .export_values(); - py::enum_(m, "FilterScope") .value("All", MeshBase::FilterScope::All, "All properties (color, normal, vertex position) are " @@ -45,7 +41,6 @@ void pybind_meshbase(py::module &m) { .value("Vertex", MeshBase::FilterScope::Vertex, "Only the vertex positions are filtered.") .export_values(); - py::enum_( m, "DeformAsRigidAsPossibleEnergy") .value("Spokes", MeshBase::DeformAsRigidAsPossibleEnergy::Spokes, @@ -55,6 +50,15 @@ void pybind_meshbase(py::module &m) { MeshBase::DeformAsRigidAsPossibleEnergy::Smoothed, "adds a rotation smoothing term to the rotations.") .export_values(); +} + +void pybind_meshbase_definitions(py::module &m) { + auto meshbase = + static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("MeshBase")); + py::detail::bind_default_constructor(meshbase); + py::detail::bind_copy_functions(meshbase); meshbase.def("__repr__", [](const MeshBase &mesh) { @@ -101,7 +105,5 @@ void pybind_meshbase(py::module &m) { docstring::ClassMethodDocInject(m, "MeshBase", "compute_convex_hull"); } -void pybind_meshbase_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/octree.cpp b/cpp/pybind/geometry/octree.cpp index e9ebafae522..9e4c2c62c71 100644 --- a/cpp/pybind/geometry/octree.cpp +++ b/cpp/pybind/geometry/octree.cpp @@ -33,14 +33,52 @@ static const std::unordered_map "bigger than the original point cloud bounds to accommodate " "all points."}}; -void pybind_octree(py::module &m) { - // OctreeNodeInfo +void pybind_octree_declarations(py::module &m) { // Binds std::shared_ptr<...> to avoid non-allocated free in python code py::class_> octree_node_info(m, "OctreeNodeInfo", "OctreeNode's information. OctreeNodeInfo is " "computed on the fly, " "not stored with the Node."); + py::class_, + std::shared_ptr> + octree_node(m, "OctreeNode", "The base class for octree node."); + py::class_, + std::shared_ptr, OctreeNode> + octree_internal_node(m, "OctreeInternalNode", + "OctreeInternalNode class, containing " + "OctreeNode children."); + py::class_, + std::shared_ptr, OctreeInternalNode> + octree_internal_point_node( + m, "OctreeInternalPointNode", + "OctreeInternalPointNode class is an " + "OctreeInternalNode with a list of point " + "indices (from point cloud) belonging to " + "children of this node."); + py::class_, + std::shared_ptr, OctreeNode> + octree_leaf_node(m, "OctreeLeafNode", "OctreeLeafNode base class."); + py::class_, + std::shared_ptr, OctreeLeafNode> + octree_color_leaf_node(m, "OctreeColorLeafNode", + "OctreeColorLeafNode class is an " + "OctreeLeafNode containing color."); + py::class_, + std::shared_ptr, OctreeLeafNode> + octree_point_color_leaf_node(m, "OctreePointColorLeafNode", + "OctreePointColorLeafNode class is an " + "OctreeLeafNode containing color."); + py::class_, std::shared_ptr, + Geometry3D> + octree(m, "Octree", "Octree datastructure."); +} +void pybind_octree_definitions(py::module &m) { + // OctreeNodeInfo + auto octree_node_info = static_cast< + py::class_>>( + m.attr("OctreeNodeInfo")); octree_node_info.def(py::init([](const Eigen::Vector3d &origin, double size, size_t depth, size_t child_index) { return new OctreeNodeInfo(origin, size, depth, @@ -75,20 +113,20 @@ void pybind_octree(py::module &m) { docstring::ClassMethodDocInject(m, "OctreeNodeInfo", "__init__"); // OctreeNode - py::class_, - std::shared_ptr> - octree_node(m, "OctreeNode", "The base class for octree node."); + auto octree_node = + static_cast, + std::shared_ptr>>( + m.attr("OctreeNode")); octree_node.def("__repr__", [](const OctreeNode &octree_node) { return "OctreeNode instance."; }); docstring::ClassMethodDocInject(m, "OctreeNode", "__init__"); // OctreeInternalNode - py::class_, - std::shared_ptr, OctreeNode> - octree_internal_node(m, "OctreeInternalNode", - "OctreeInternalNode class, containing " - "OctreeNode children."); + auto octree_internal_node = static_cast< + py::class_, + std::shared_ptr, OctreeNode>>( + m.attr("OctreeInternalNode")); octree_internal_node .def("__repr__", [](const OctreeInternalNode &internal_node) { @@ -122,14 +160,10 @@ void pybind_octree(py::module &m) { docstring::ClassMethodDocInject(m, "OctreeInternalNode", "__init__"); // OctreeInternalPointNode - py::class_, - std::shared_ptr, OctreeInternalNode> - octree_internal_point_node( - m, "OctreeInternalPointNode", - "OctreeInternalPointNode class is an " - "OctreeInternalNode with a list of point " - "indices (from point cloud) belonging to " - "children of this node."); + auto octree_internal_point_node = static_cast, + std::shared_ptr, OctreeInternalNode>>( + m.attr("OctreeInternalPointNode")); octree_internal_point_node .def("__repr__", [](const OctreeInternalPointNode &internal_point_node) { @@ -172,9 +206,10 @@ void pybind_octree(py::module &m) { docstring::ClassMethodDocInject(m, "OctreeInternalPointNode", "__init__"); // OctreeLeafNode - py::class_, - std::shared_ptr, OctreeNode> - octree_leaf_node(m, "OctreeLeafNode", "OctreeLeafNode base class."); + auto octree_leaf_node = static_cast< + py::class_, + std::shared_ptr, OctreeNode>>( + m.attr("OctreeLeafNode")); octree_leaf_node .def("__repr__", [](const OctreeLeafNode &leaf_node) { @@ -189,11 +224,10 @@ void pybind_octree(py::module &m) { docstring::ClassMethodDocInject(m, "OctreeLeafNode", "__init__"); // OctreeColorLeafNode - py::class_, - std::shared_ptr, OctreeLeafNode> - octree_color_leaf_node(m, "OctreeColorLeafNode", - "OctreeColorLeafNode class is an " - "OctreeLeafNode containing color."); + auto octree_color_leaf_node = static_cast, + std::shared_ptr, OctreeLeafNode>>( + m.attr("OctreeColorLeafNode")); octree_color_leaf_node .def("__repr__", [](const OctreeColorLeafNode &color_leaf_node) { @@ -223,12 +257,12 @@ void pybind_octree(py::module &m) { octree_color_leaf_node); // OctreePointColorLeafNode - py::class_, - std::shared_ptr, OctreeLeafNode> - octree_point_color_leaf_node(m, "OctreePointColorLeafNode", - "OctreePointColorLeafNode class is an " - "OctreeLeafNode containing color."); + auto octree_point_color_leaf_node = + static_cast, + std::shared_ptr, + OctreeLeafNode>>( + m.attr("OctreePointColorLeafNode")); octree_point_color_leaf_node .def("__repr__", [](const OctreePointColorLeafNode &color_leaf_node) { @@ -264,9 +298,9 @@ void pybind_octree(py::module &m) { octree_point_color_leaf_node); // Octree - py::class_, std::shared_ptr, - Geometry3D> - octree(m, "Octree", "Octree datastructure."); + auto octree = static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("Octree")); py::detail::bind_default_constructor(octree); py::detail::bind_copy_functions(octree); octree.def(py::init([](size_t max_depth) { return new Octree(max_depth); }), @@ -344,7 +378,5 @@ void pybind_octree(py::module &m) { {{"voxel_grid", "geometry.VoxelGrid: The source voxel grid."}}); } -void pybind_octree_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/pointcloud.cpp b/cpp/pybind/geometry/pointcloud.cpp index 2af8319e93d..9a77636f628 100644 --- a/cpp/pybind/geometry/pointcloud.cpp +++ b/cpp/pybind/geometry/pointcloud.cpp @@ -19,13 +19,20 @@ namespace open3d { namespace geometry { -void pybind_pointcloud(py::module &m) { +void pybind_pointcloud_declarations(py::module &m) { py::class_, std::shared_ptr, Geometry3D> pointcloud(m, "PointCloud", "PointCloud class. A point cloud consists of point " "coordinates, and optionally point colors and point " "normals."); +} + +void pybind_pointcloud_definitions(py::module &m) { + auto pointcloud = + static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("PointCloud")); py::detail::bind_default_constructor(pointcloud); py::detail::bind_copy_functions(pointcloud); pointcloud @@ -413,7 +420,5 @@ camera. Given depth value d at (u, v) image coordinate, the corresponding 3d poi {"extrnsic", "Extrinsic parameters of the camera."}}); } -void pybind_pointcloud_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/tetramesh.cpp b/cpp/pybind/geometry/tetramesh.cpp index 5ef8ce0d757..62e7ec7e807 100644 --- a/cpp/pybind/geometry/tetramesh.cpp +++ b/cpp/pybind/geometry/tetramesh.cpp @@ -15,16 +15,22 @@ namespace open3d { namespace geometry { -void pybind_tetramesh(py::module &m) { +void pybind_tetramesh_declarations(py::module &m) { py::class_, std::shared_ptr, MeshBase> trianglemesh(m, "TetraMesh", "TetraMesh class. Tetra mesh contains vertices " "and tetrahedra represented by the indices to the " "vertices."); - py::detail::bind_default_constructor(trianglemesh); - py::detail::bind_copy_functions(trianglemesh); - trianglemesh +} +void pybind_tetramesh_definitions(py::module &m) { + auto tetramesh = + static_cast, + std::shared_ptr, MeshBase>>( + m.attr("TetraMesh")); + py::detail::bind_default_constructor(tetramesh); + py::detail::bind_copy_functions(tetramesh); + tetramesh .def(py::init &, const std::vector &>(), @@ -95,7 +101,5 @@ void pybind_tetramesh(py::module &m) { {{"point_cloud", "A PointCloud."}}); } -void pybind_tetramesh_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/trianglemesh.cpp b/cpp/pybind/geometry/trianglemesh.cpp index 311aa311b86..1c0fbd8c365 100644 --- a/cpp/pybind/geometry/trianglemesh.cpp +++ b/cpp/pybind/geometry/trianglemesh.cpp @@ -16,7 +16,7 @@ namespace open3d { namespace geometry { -void pybind_trianglemesh(py::module &m) { +void pybind_trianglemesh_declarations(py::module &m) { py::class_, std::shared_ptr, MeshBase> trianglemesh(m, "TriangleMesh", @@ -24,6 +24,12 @@ void pybind_trianglemesh(py::module &m) { "and triangles represented by the indices to the " "vertices. Optionally, the mesh may also contain " "triangle normals, vertex normals and vertex colors."); +} +void pybind_trianglemesh_definitions(py::module &m) { + auto trianglemesh = + static_cast, + std::shared_ptr, MeshBase>>( + m.attr("TriangleMesh")); py::detail::bind_default_constructor(trianglemesh); py::detail::bind_copy_functions(trianglemesh); trianglemesh @@ -779,7 +785,5 @@ void pybind_trianglemesh(py::module &m) { {"scale", "Scale the complete Mobius strip."}}); } -void pybind_trianglemesh_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/geometry/voxelgrid.cpp b/cpp/pybind/geometry/voxelgrid.cpp index a50efa8c17b..591d8705af6 100644 --- a/cpp/pybind/geometry/voxelgrid.cpp +++ b/cpp/pybind/geometry/voxelgrid.cpp @@ -20,9 +20,18 @@ namespace open3d { namespace geometry { -void pybind_voxelgrid(py::module &m) { +void pybind_voxelgrid_declarations(py::module &m) { py::class_> voxel( m, "Voxel", "Base Voxel class, containing grid id and color"); + py::class_, std::shared_ptr, + Geometry3D> + voxelgrid(m, "VoxelGrid", + "VoxelGrid is a collection of voxels which are aligned " + "in grid."); +} +void pybind_voxelgrid_definitions(py::module &m) { + auto voxel = static_cast>>( + m.attr("Voxel")); py::detail::bind_default_constructor(voxel); py::detail::bind_copy_functions(voxel); voxel.def("__repr__", @@ -50,12 +59,10 @@ void pybind_voxelgrid(py::module &m) { .def_readwrite( "color", &Voxel::color_, "Float64 numpy array of shape (3,): Color of the voxel."); - - py::class_, std::shared_ptr, - Geometry3D> - voxelgrid(m, "VoxelGrid", - "VoxelGrid is a collection of voxels which are aligned " - "in grid."); + auto voxelgrid = + static_cast, + std::shared_ptr, Geometry3D>>( + m.attr("VoxelGrid")); py::detail::bind_default_constructor(voxelgrid); py::detail::bind_copy_functions(voxelgrid); voxelgrid @@ -230,7 +237,5 @@ void pybind_voxelgrid(py::module &m) { "Maximum boundary point for the VoxelGrid to create."}}); } -void pybind_voxelgrid_methods(py::module &m) {} - } // namespace geometry } // namespace open3d diff --git a/cpp/pybind/io/class_io.cpp b/cpp/pybind/io/class_io.cpp index 2ba0f6fad88..137d1784708 100644 --- a/cpp/pybind/io/class_io.cpp +++ b/cpp/pybind/io/class_io.cpp @@ -83,7 +83,7 @@ static const std::unordered_map "If set to true a progress bar is visualized in the console"}, }; -void pybind_class_io(py::module &m_io) { +void pybind_class_io_declarations(py::module &m_io) { py::enum_ geom_type(m_io, "FileGeometry", py::arithmetic()); // Trick to write docs without listing the members in the enum class again. geom_type.attr("__doc__") = docstring::static_property( @@ -96,6 +96,8 @@ void pybind_class_io(py::module &m_io) { .value("CONTAINS_LINES", FileGeometry::CONTAINS_LINES) .value("CONTAINS_TRIANGLES", FileGeometry::CONTAINS_TRIANGLES) .export_values(); +} +void pybind_class_io_definitions(py::module &m_io) { m_io.def( "read_file_geometry_type", &ReadFileGeometryType, "Returns the type of geometry of the file. This is a faster way of " diff --git a/cpp/pybind/io/io.cpp b/cpp/pybind/io/io.cpp index 63afc83e85a..27c6c32e705 100644 --- a/cpp/pybind/io/io.cpp +++ b/cpp/pybind/io/io.cpp @@ -12,12 +12,21 @@ namespace open3d { namespace io { -void pybind_io(py::module &m) { +void pybind_io_declarations(py::module &m) { py::module m_io = m.def_submodule("io"); - pybind_class_io(m_io); - pybind_rpc(m_io); + pybind_class_io_declarations(m_io); + pybind_rpc_declarations(m_io); #ifdef BUILD_AZURE_KINECT - pybind_sensor(m_io); + pybind_sensor_declarations(m_io); +#endif +} + +void pybind_io_definitions(py::module &m) { + auto m_io = static_cast(m.attr("io")); + pybind_class_io_definitions(m_io); + pybind_rpc_definitions(m_io); +#ifdef BUILD_AZURE_KINECT + pybind_sensor_definitions(m_io); #endif } diff --git a/cpp/pybind/io/io.h b/cpp/pybind/io/io.h index 6d54dfeada1..c08a1d1fa90 100644 --- a/cpp/pybind/io/io.h +++ b/cpp/pybind/io/io.h @@ -12,14 +12,18 @@ namespace open3d { namespace io { -void pybind_io(py::module& m); - -void pybind_class_io(py::module& m); - -void pybind_rpc(py::module& m); +void pybind_io_declarations(py::module& m); +void pybind_class_io_declarations(py::module& m); +void pybind_rpc_declarations(py::module& m); +#ifdef BUILD_AZURE_KINECT +void pybind_sensor_declarations(py::module& m); +#endif +void pybind_io_definitions(py::module& m); +void pybind_class_io_definitions(py::module& m); +void pybind_rpc_definitions(py::module& m); #ifdef BUILD_AZURE_KINECT -void pybind_sensor(py::module& m); +void pybind_sensor_definitions(py::module& m); #endif } // namespace io diff --git a/cpp/pybind/io/rpc.cpp b/cpp/pybind/io/rpc.cpp index 8d04d1265c9..590f2bd3d66 100644 --- a/cpp/pybind/io/rpc.cpp +++ b/cpp/pybind/io/rpc.cpp @@ -18,47 +18,58 @@ namespace open3d { namespace io { -void pybind_rpc(py::module& m_io) { - py::module m = m_io.def_submodule("rpc"); - - // this is to cleanly shutdown the zeromq context on windows. - auto atexit = py::module::import("atexit"); - atexit.attr("register")( - py::cpp_function([]() { rpc::DestroyZMQContext(); })); - +void pybind_rpc_declarations(py::module& m_io) { + py::module m_rpc = m_io.def_submodule("rpc"); py::class_>( - m, "_ConnectionBase"); - + m_rpc, "_ConnectionBase"); py::class_, - rpc::ConnectionBase>(m, "Connection", R"doc( + rpc::ConnectionBase>(m_rpc, "Connection", R"doc( The default connection class which uses a ZeroMQ socket. -)doc") - .def(py::init([](std::string address, int connect_timeout, - int timeout) { - return std::shared_ptr( - new rpc::Connection(address, connect_timeout, - timeout)); - }), - "Creates a connection object", - "address"_a = "tcp://127.0.0.1:51454", - "connect_timeout"_a = 5000, "timeout"_a = 10000); - +)doc"); py::class_, - rpc::ConnectionBase>(m, "BufferConnection", R"doc( + rpc::ConnectionBase>(m_rpc, "BufferConnection", R"doc( A connection writing to a memory buffer. -)doc") - .def(py::init<>()) +)doc"); + py::class_>( + m_rpc, "_DummyReceiver", + "Dummy receiver for the server side receiving requests from a " + "client."); +} +void pybind_rpc_definitions(py::module& m_io) { + auto m_rpc = static_cast(m_io.attr("rpc")); + // this is to cleanly shutdown the zeromq context on windows. + auto atexit = py::module::import("atexit"); + atexit.attr("register")( + py::cpp_function([]() { rpc::DestroyZMQContext(); })); + auto connection = static_cast< + py::class_, + rpc::ConnectionBase>>(m_rpc.attr("Connection")); + connection.def( + py::init([](std::string address, int connect_timeout, int timeout) { + return std::shared_ptr( + new rpc::Connection(address, connect_timeout, timeout)); + }), + "Creates a connection object", + "address"_a = "tcp://127.0.0.1:51454", "connect_timeout"_a = 5000, + "timeout"_a = 10000); + + auto buffer_connection = + static_cast, + rpc::ConnectionBase>>( + m_rpc.attr("BufferConnection")); + buffer_connection.def(py::init<>()) .def( "get_buffer", [](const rpc::BufferConnection& self) { return py::bytes(self.buffer().str()); }, "Returns a copy of the buffer."); - - py::class_>( - m, "_DummyReceiver", - "Dummy receiver for the server side receiving requests from a " - "client.") + auto dummy_receiver = + static_cast>>( + m_rpc.attr("_DummyReceiver")); + dummy_receiver .def(py::init([](std::string address, int timeout) { return std::shared_ptr( new rpc::DummyReceiver(address, timeout)); @@ -73,15 +84,15 @@ A connection writing to a memory buffer. "function blocks until the mainloop is done with processing " "messages that have already been received."); - m.def("destroy_zmq_context", &rpc::DestroyZMQContext, - "Destroys the ZMQ context."); + m_rpc.def("destroy_zmq_context", &rpc::DestroyZMQContext, + "Destroys the ZMQ context."); - m.def("set_point_cloud", &rpc::SetPointCloud, "pcd"_a, "path"_a = "", - "time"_a = 0, "layer"_a = "", - "connection"_a = std::shared_ptr(), - "Sends a point cloud message to a viewer."); + m_rpc.def("set_point_cloud", &rpc::SetPointCloud, "pcd"_a, "path"_a = "", + "time"_a = 0, "layer"_a = "", + "connection"_a = std::shared_ptr(), + "Sends a point cloud message to a viewer."); docstring::FunctionDocInject( - m, "set_point_cloud", + m_rpc, "set_point_cloud", { {"pcd", "Point cloud object."}, {"path", "A path descriptor, e.g., 'mygroup/points'."}, @@ -92,14 +103,14 @@ A connection writing to a memory buffer. "the connection."}, }); - m.def("set_triangle_mesh", - py::overload_cast>( - &rpc::SetTriangleMesh), - "mesh"_a, "path"_a = "", "time"_a = 0, "layer"_a = "", - "connection"_a = std::shared_ptr(), - R"doc(Sends a triangle mesh to a viewer. + m_rpc.def("set_triangle_mesh", + py::overload_cast>( + &rpc::SetTriangleMesh), + "mesh"_a, "path"_a = "", "time"_a = 0, "layer"_a = "", + "connection"_a = std::shared_ptr(), + R"doc(Sends a triangle mesh to a viewer. Args: mesh (o3d.geometry.TriangleMesh): The triangle mesh. path (str): The path in the scene graph. @@ -111,14 +122,14 @@ A connection writing to a memory buffer. Returns True if the data was successfully received. )doc"); - m.def("set_triangle_mesh", - py::overload_cast>( - &rpc::SetTriangleMesh), - "mesh"_a, "path"_a = "", "time"_a = 0, "layer"_a = "", - "connection"_a = std::shared_ptr(), - R"doc(Sends a triangle mesh to a viewer. + m_rpc.def("set_triangle_mesh", + py::overload_cast>( + &rpc::SetTriangleMesh), + "mesh"_a, "path"_a = "", "time"_a = 0, "layer"_a = "", + "connection"_a = std::shared_ptr(), + R"doc(Sends a triangle mesh to a viewer. Args: mesh (o3d.t.geometry.TriangleMesh): The triangle mesh. path (str): The path in the scene graph. @@ -130,23 +141,23 @@ A connection writing to a memory buffer. Returns True if the data was successfully received. )doc"); - m.def("set_mesh_data", &rpc::SetMeshData, "path"_a = "", "time"_a = 0, - "layer"_a = "", "vertices"_a = core::Tensor({0}, core::Float32), - "vertex_attributes"_a = std::map(), - "faces"_a = core::Tensor({0}, core::Int32), - "face_attributes"_a = std::map(), - "lines"_a = core::Tensor({0}, core::Int32), - "line_attributes"_a = std::map(), - "material"_a = "", - "material_scalar_attributes"_a = std::map(), - "material_vector_attributes"_a = - std::map(), - "texture_maps"_a = std::map(), - "o3d_type"_a = "", - "connection"_a = std::shared_ptr(), - "Sends a set_mesh_data message."); + m_rpc.def("set_mesh_data", &rpc::SetMeshData, "path"_a = "", "time"_a = 0, + "layer"_a = "", "vertices"_a = core::Tensor({0}, core::Float32), + "vertex_attributes"_a = std::map(), + "faces"_a = core::Tensor({0}, core::Int32), + "face_attributes"_a = std::map(), + "lines"_a = core::Tensor({0}, core::Int32), + "line_attributes"_a = std::map(), + "material"_a = "", + "material_scalar_attributes"_a = std::map(), + "material_vector_attributes"_a = + std::map(), + "texture_maps"_a = std::map(), + "o3d_type"_a = "", + "connection"_a = std::shared_ptr(), + "Sends a set_mesh_data message."); docstring::FunctionDocInject( - m, "set_mesh_data", + m_rpc, "set_mesh_data", { {"path", "A path descriptor, e.g., 'mygroup/points'."}, {"time", "The time associated with this data."}, @@ -181,12 +192,12 @@ A connection writing to a memory buffer. "the connection."}, }); - m.def("set_legacy_camera", &rpc::SetLegacyCamera, "camera"_a, "path"_a = "", - "time"_a = 0, "layer"_a = "", - "connection"_a = std::shared_ptr(), - "Sends a PinholeCameraParameters object."); + m_rpc.def("set_legacy_camera", &rpc::SetLegacyCamera, "camera"_a, + "path"_a = "", "time"_a = 0, "layer"_a = "", + "connection"_a = std::shared_ptr(), + "Sends a PinholeCameraParameters object."); docstring::FunctionDocInject( - m, "set_legacy_camera", + m_rpc, "set_legacy_camera", { {"path", "A path descriptor, e.g., 'mygroup/camera'."}, {"time", "The time associated with this data."}, @@ -196,11 +207,11 @@ A connection writing to a memory buffer. "the connection."}, }); - m.def("set_time", &rpc::SetTime, "time"_a, - "connection"_a = std::shared_ptr(), - "Sets the time in the external visualizer."); + m_rpc.def("set_time", &rpc::SetTime, "time"_a, + "connection"_a = std::shared_ptr(), + "Sets the time in the external visualizer."); docstring::FunctionDocInject( - m, "set_time", + m_rpc, "set_time", { {"time", "The time value to set."}, {"connection", @@ -208,11 +219,11 @@ A connection writing to a memory buffer. "the connection."}, }); - m.def("set_active_camera", &rpc::SetActiveCamera, "path"_a, - "connection"_a = std::shared_ptr(), - "Sets the object with the specified path as the active camera."); + m_rpc.def("set_active_camera", &rpc::SetActiveCamera, "path"_a, + "connection"_a = std::shared_ptr(), + "Sets the object with the specified path as the active camera."); docstring::FunctionDocInject( - m, "set_active_camera", + m_rpc, "set_active_camera", { {"path", "A path descriptor, e.g., 'mygroup/camera'."}, {"connection", @@ -220,8 +231,8 @@ A connection writing to a memory buffer. "the connection."}, }); - m.def("data_buffer_to_meta_geometry", &rpc::DataBufferToMetaGeometry, - "data"_a, R"doc( + m_rpc.def("data_buffer_to_meta_geometry", &rpc::DataBufferToMetaGeometry, + "data"_a, R"doc( This function returns the geometry, the path and the time stored in a SetMeshData message. data must contain the Request header message followed by the SetMeshData message. The function returns None for the geometry if not diff --git a/cpp/pybind/io/sensor.cpp b/cpp/pybind/io/sensor.cpp index 3b07e0d10a5..b9108d69116 100644 --- a/cpp/pybind/io/sensor.cpp +++ b/cpp/pybind/io/sensor.cpp @@ -16,7 +16,19 @@ namespace open3d { namespace io { -void pybind_sensor(py::module &m) { +void pybind_sensor_declarations(py::module &m) { + py::class_ azure_kinect_sensor_config( + m, "AzureKinectSensorConfig", "AzureKinect sensor configuration."); + py::class_ azure_kinect_mkv_metadata( + m, "AzureKinectMKVMetadata", "AzureKinect mkv metadata."); + py::class_ azure_kinect_sensor(m, "AzureKinectSensor", + "AzureKinect sensor."); + py::class_ azure_kinect_recorder( + m, "AzureKinectRecorder", "AzureKinect recorder."); + py::class_ azure_kinect_mkv_reader( + m, "AzureKinectMKVReader", "AzureKinect mkv file reader."); +} +void pybind_sensor_definitions(py::module &m) { static const std::unordered_map map_shared_argument_docstrings = { {"sensor_index", "The selected device index."}, @@ -29,8 +41,9 @@ void pybind_sensor(py::module &m) { "visualizer."}}; // Class kinect config - py::class_ azure_kinect_sensor_config( - m, "AzureKinectSensorConfig", "AzureKinect sensor configuration."); + auto azure_kinect_sensor_config = + static_cast>( + m.attr("AzureKinectSensorConfig")); py::detail::bind_default_constructor( azure_kinect_sensor_config); azure_kinect_sensor_config.def( @@ -39,9 +52,8 @@ void pybind_sensor(py::module &m) { return new AzureKinectSensorConfig(config); }), "config"_a); - - py::class_ azure_kinect_mkv_metadata( - m, "AzureKinectMKVMetadata", "AzureKinect mkv metadata."); + auto azure_kinect_mkv_metadata = static_cast>( + m.attr("AzureKinectMKVMetadata")); py::detail::bind_default_constructor( azure_kinect_mkv_metadata); azure_kinect_mkv_metadata @@ -53,9 +65,8 @@ void pybind_sensor(py::module &m) { "Length of the video (usec)"); // Class sensor - py::class_ azure_kinect_sensor(m, "AzureKinectSensor", - "AzureKinect sensor."); - + auto azure_kinect_sensor = static_cast>( + m.attr("AzureKinectSensor")); azure_kinect_sensor.def( py::init([](const AzureKinectSensorConfig &sensor_config) { return new AzureKinectSensor(sensor_config); @@ -78,9 +89,8 @@ void pybind_sensor(py::module &m) { map_shared_argument_docstrings); // Class recorder - py::class_ azure_kinect_recorder( - m, "AzureKinectRecorder", "AzureKinect recorder."); - + auto azure_kinect_recorder = static_cast>( + m.attr("AzureKinectRecorder")); azure_kinect_recorder.def( py::init([](const AzureKinectSensorConfig &sensor_config, size_t sensor_index) { @@ -113,8 +123,8 @@ void pybind_sensor(py::module &m) { map_shared_argument_docstrings); // Class mkv reader - py::class_ azure_kinect_mkv_reader( - m, "AzureKinectMKVReader", "AzureKinect mkv file reader."); + auto azure_kinect_mkv_reader = + static_cast>(m.attr("AzureKinectMKVReader")); azure_kinect_mkv_reader.def(py::init([]() { return MKVReader(); })); azure_kinect_mkv_reader .def("is_opened", &MKVReader::IsOpened, diff --git a/cpp/pybind/ml/contrib/contrib.cpp b/cpp/pybind/ml/contrib/contrib.cpp index 9ed422ee12d..bd3b87d45cd 100644 --- a/cpp/pybind/ml/contrib/contrib.cpp +++ b/cpp/pybind/ml/contrib/contrib.cpp @@ -15,11 +15,13 @@ namespace open3d { namespace ml { namespace contrib { -void pybind_contrib(py::module& m) { +void pybind_contrib_declarations(py::module& m) { py::module m_contrib = m.def_submodule("contrib"); - - pybind_contrib_subsample(m_contrib); - pybind_contrib_iou(m_contrib); +} +void pybind_contrib_definitions(py::module& m) { + auto m_contrib = static_cast(m.attr("contrib")); + pybind_contrib_subsample_definitions(m_contrib); + pybind_contrib_iou_definitions(m_contrib); } } // namespace contrib diff --git a/cpp/pybind/ml/contrib/contrib.h b/cpp/pybind/ml/contrib/contrib.h index 9caff96487b..1b13668843d 100644 --- a/cpp/pybind/ml/contrib/contrib.h +++ b/cpp/pybind/ml/contrib/contrib.h @@ -14,9 +14,11 @@ namespace open3d { namespace ml { namespace contrib { -void pybind_contrib(py::module &m); -void pybind_contrib_subsample(py::module &m_contrib); -void pybind_contrib_iou(py::module &m_contrib); +void pybind_contrib_declarations(py::module &m); + +void pybind_contrib_definitions(py::module &m); +void pybind_contrib_subsample_definitions(py::module &m_contrib); +void pybind_contrib_iou_definitions(py::module &m_contrib); } // namespace contrib } // namespace ml diff --git a/cpp/pybind/ml/contrib/contrib_subsample.cpp b/cpp/pybind/ml/contrib/contrib_subsample.cpp index b7946a69451..ce028afff07 100644 --- a/cpp/pybind/ml/contrib/contrib_subsample.cpp +++ b/cpp/pybind/ml/contrib/contrib_subsample.cpp @@ -343,7 +343,7 @@ const py::object Subsample(py::array points, } } -void pybind_contrib_subsample(py::module& m_contrib) { +void pybind_contrib_subsample_definitions(py::module& m_contrib) { m_contrib.def("subsample", &Subsample, "points"_a, "features"_a = py::none(), "classes"_a = py::none(), "sampleDl"_a = 0.1, "verbose"_a = 0); diff --git a/cpp/pybind/ml/contrib/iou.cpp b/cpp/pybind/ml/contrib/iou.cpp index 1f14af19814..58a83e4b593 100644 --- a/cpp/pybind/ml/contrib/iou.cpp +++ b/cpp/pybind/ml/contrib/iou.cpp @@ -117,7 +117,7 @@ py::array Iou3dCUDA(py::array boxes_a, py::array boxes_b) { } #endif -void pybind_contrib_iou(py::module& m_contrib) { +void pybind_contrib_iou_definitions(py::module& m_contrib) { m_contrib.def("iou_bev_cpu", &IouBevCPU, "boxes_a"_a, "boxes_b"_a); m_contrib.def("iou_3d_cpu", &Iou3dCPU, "boxes_a"_a, "boxes_b"_a); diff --git a/cpp/pybind/ml/ml.cpp b/cpp/pybind/ml/ml.cpp index de71caf71b0..c0e4c882241 100644 --- a/cpp/pybind/ml/ml.cpp +++ b/cpp/pybind/ml/ml.cpp @@ -13,9 +13,14 @@ namespace open3d { namespace ml { -void pybind_ml(py::module &m) { +void pybind_ml_declarations(py::module &m) { py::module m_ml = m.def_submodule("ml"); - contrib::pybind_contrib(m_ml); + contrib::pybind_contrib_declarations(m_ml); +} + +void pybind_ml_definitions(py::module &m) { + auto m_ml = static_cast(m.attr("ml")); + contrib::pybind_contrib_definitions(m_ml); } } // namespace ml diff --git a/cpp/pybind/ml/ml.h b/cpp/pybind/ml/ml.h index 3cbbc0e6a8e..454771411b0 100644 --- a/cpp/pybind/ml/ml.h +++ b/cpp/pybind/ml/ml.h @@ -12,7 +12,8 @@ namespace open3d { namespace ml { -void pybind_ml(py::module& m); +void pybind_ml_declarations(py::module& m); +void pybind_ml_definitions(py::module& m); } // namespace ml } // namespace open3d diff --git a/cpp/pybind/open3d_pybind.cpp b/cpp/pybind/open3d_pybind.cpp index a6b71969804..5b587408398 100644 --- a/cpp/pybind/open3d_pybind.cpp +++ b/cpp/pybind/open3d_pybind.cpp @@ -38,17 +38,26 @@ PYBIND11_MODULE(pybind, m) { // The binding order matters: if a class haven't been binded, binding the // user of this class will result in "could not convert default argument // into a Python object" error. - utility::pybind_utility(m); - - camera::pybind_camera(m); - core::pybind_core(m); + utility::pybind_utility_declarations(m); + camera::pybind_camera_declarations(m); + core::pybind_core_declarations(m); data::pybind_data(m); - geometry::pybind_geometry(m); - t::pybind_t(m); - ml::pybind_ml(m); - io::pybind_io(m); - pipelines::pybind_pipelines(m); - visualization::pybind_visualization(m); + geometry::pybind_geometry_declarations(m); + t::pybind_t_declarations(m); + ml::pybind_ml_declarations(m); + io::pybind_io_declarations(m); + pipelines::pybind_pipelines_declarations(m); + visualization::pybind_visualization_declarations(m); + + utility::pybind_utility_definitions(m); + camera::pybind_camera_definitions(m); + core::pybind_core_definitions(m); + geometry::pybind_geometry_definitions(m); + t::pybind_t_definitions(m); + ml::pybind_ml_definitions(m); + io::pybind_io_definitions(m); + pipelines::pybind_pipelines_definitions(m); + visualization::pybind_visualization_definitions(m); // pybind11 will internally manage the lifetime of default arguments for // function bindings. Since these objects will live longer than the memory diff --git a/cpp/pybind/pipelines/color_map/color_map.cpp b/cpp/pybind/pipelines/color_map/color_map.cpp index 141d0bdab28..dfc57470bb6 100644 --- a/cpp/pybind/pipelines/color_map/color_map.cpp +++ b/cpp/pybind/pipelines/color_map/color_map.cpp @@ -19,7 +19,19 @@ namespace open3d { namespace pipelines { namespace color_map { -void pybind_color_map_options(py::module &m) { +void pybind_color_map_declarations(py::module &m) { + py::module m_color_map = + m.def_submodule("color_map", "Color map optimization pipeline"); + py::class_ + rigid_optimizer_option(m_color_map, "RigidOptimizerOption", + "Rigid optimizer option class."); + py::class_ + non_rigid_optimizer_option(m_color_map, "NonRigidOptimizerOption", + "Non Rigid optimizer option class."); +} + +void pybind_color_map_definitions(py::module &m) { + auto m_color_map = static_cast(m.attr("color_map")); static std::unordered_map colormap_docstrings = { {"non_rigid_camera_coordinate", "bool: (Default ``False``) Set to ``True`` to enable non-rigid " @@ -86,10 +98,9 @@ void pybind_color_map_options(py::module &m) { "If specified, the intermediate results will be stored in in the " "debug output dir. Existing files will be overwritten if the " "names are the same."}}; - - py::class_ - rigid_optimizer_option(m, "RigidOptimizerOption", - "Rigid optimizer option class."); + auto rigid_optimizer_option = + static_cast>( + m_color_map.attr("RigidOptimizerOption")); rigid_optimizer_option.def( py::init([](int maximum_iteration, double maximum_allowable_depth, double depth_threshold_for_visibility_check, @@ -120,12 +131,11 @@ void pybind_color_map_options(py::module &m) { "image_boundary_margin"_a = 10, "invisible_vertex_color_knn"_a = 3, "debug_output_dir"_a = ""); - docstring::ClassMethodDocInject(m, "RigidOptimizerOption", "__init__", - colormap_docstrings); - - py::class_ - non_rigid_optimizer_option(m, "NonRigidOptimizerOption", - "Non Rigid optimizer option class."); + docstring::ClassMethodDocInject(m_color_map, "RigidOptimizerOption", + "__init__", colormap_docstrings); + auto non_rigid_optimizer_option = static_cast< + py::class_>( + m_color_map.attr("NonRigidOptimizerOption")); non_rigid_optimizer_option.def( py::init([](int number_of_vertical_anchors, double non_rigid_anchor_point_weight, @@ -164,23 +174,14 @@ void pybind_color_map_options(py::module &m) { "image_boundary_margin"_a = 10, "invisible_vertex_color_knn"_a = 3, "debug_output_dir"_a = ""); - docstring::ClassMethodDocInject(m, "NonRigidOptimizerOption", "__init__", - colormap_docstrings); -} - -void pybind_color_map_classes(py::module &m) { - m.def("run_rigid_optimizer", &pipelines::color_map::RunRigidOptimizer, - "Run rigid optimization."); - m.def("run_non_rigid_optimizer", - &pipelines::color_map::RunNonRigidOptimizer, - "Run non-rigid optimization."); -} - -void pybind_color_map(py::module &m) { - py::module m_submodule = - m.def_submodule("color_map", "Color map optimization pipeline"); - pybind_color_map_options(m_submodule); - pybind_color_map_classes(m_submodule); + docstring::ClassMethodDocInject(m_color_map, "NonRigidOptimizerOption", + "__init__", colormap_docstrings); + m_color_map.def("run_rigid_optimizer", + &pipelines::color_map::RunRigidOptimizer, + "Run rigid optimization."); + m_color_map.def("run_non_rigid_optimizer", + &pipelines::color_map::RunNonRigidOptimizer, + "Run non-rigid optimization."); } } // namespace color_map diff --git a/cpp/pybind/pipelines/color_map/color_map.h b/cpp/pybind/pipelines/color_map/color_map.h index 12bffb93c91..bea1a3c84a4 100644 --- a/cpp/pybind/pipelines/color_map/color_map.h +++ b/cpp/pybind/pipelines/color_map/color_map.h @@ -13,8 +13,9 @@ namespace open3d { namespace pipelines { namespace color_map { -void pybind_color_map(py::module &m); +void pybind_color_map_declarations(py::module &m); +void pybind_color_map_definitions(py::module &m); -} +} // namespace color_map } // namespace pipelines } // namespace open3d diff --git a/cpp/pybind/pipelines/integration/integration.cpp b/cpp/pybind/pipelines/integration/integration.cpp index d0482bcc8fe..1ffca9843c6 100644 --- a/cpp/pybind/pipelines/integration/integration.cpp +++ b/cpp/pybind/pipelines/integration/integration.cpp @@ -38,10 +38,12 @@ class PyTSDFVolume : public TSDFVolumeBase { } }; -void pybind_integration_classes(py::module &m) { +void pybind_integration_declarations(py::module &m) { + py::module m_integration = + m.def_submodule("integration", "Integration pipeline."); // open3d.integration.TSDFVolumeColorType py::enum_ tsdf_volume_color_type( - m, "TSDFVolumeColorType", py::arithmetic()); + m_integration, "TSDFVolumeColorType", py::arithmetic()); tsdf_volume_color_type.value("NoColor", TSDFVolumeColorType::NoColor) .value("RGB8", TSDFVolumeColorType::RGB8) .value("Gray32", TSDFVolumeColorType::Gray32) @@ -52,10 +54,8 @@ void pybind_integration_classes(py::module &m) { return "Enum class for TSDFVolumeColorType."; }), py::none(), py::none(), ""); - - // open3d.integration.TSDFVolume py::class_> tsdfvolume( - m, "TSDFVolume", R"(Base class of the Truncated + m_integration, "TSDFVolume", R"(Base class of the Truncated Signed Distance Function (TSDF) volume This volume is usually used to integrate surface data (e.g., a series of RGB-D images) into a Mesh or PointCloud. The basic technique is presented in the following paper: @@ -65,6 +65,40 @@ A volumetric method for building complex models from range images B. Curless and M. Levoy In SIGGRAPH, 1996)"); + py::class_, TSDFVolume> + uniform_tsdfvolume( + m_integration, "UniformTSDFVolume", + "UniformTSDFVolume implements the classic TSDF " + "volume with uniform voxel grid (Curless and Levoy 1996)."); + py::class_, TSDFVolume> + scalable_tsdfvolume(m_integration, "ScalableTSDFVolume", R"(The +ScalableTSDFVolume implements a more memory efficient data structure for +volumetric integration. + +This implementation is based on the following repository: +https://github.com/qianyizh/ElasticReconstruction/tree/master/Integrate + +An observed depth pixel gives two types of information: (a) an approximation +of the nearby surface, and (b) empty space from the camera to the surface. +They induce two core concepts of volumetric integration: weighted average of +a truncated signed distance function (TSDF), and carving. The weighted +average of TSDF is great in addressing the Gaussian noise along surface +normal and producing a smooth surface output. The carving is great in +removing outlier structures like floating noise pixels and bumps along +structure edges. + +Ref: Dense Scene Reconstruction with Points of Interest + +Q.-Y. Zhou and V. Koltun + +In SIGGRAPH, 2013)"); +} +void pybind_integration_definitions(py::module &m) { + auto m_integration = static_cast(m.attr("integration")); + // open3d.integration.TSDFVolume + auto tsdfvolume = + static_cast>>( + m_integration.attr("TSDFVolume")); tsdfvolume .def("reset", &TSDFVolume::Reset, "Function to reset the TSDFVolume") @@ -83,21 +117,21 @@ In SIGGRAPH, 1996)"); .def_readwrite("color_type", &TSDFVolume::color_type_, "integration.TSDFVolumeColorType: Color type of the " "TSDF volume."); - docstring::ClassMethodDocInject(m, "TSDFVolume", "extract_point_cloud"); - docstring::ClassMethodDocInject(m, "TSDFVolume", "extract_triangle_mesh"); + docstring::ClassMethodDocInject(m_integration, "TSDFVolume", + "extract_point_cloud"); + docstring::ClassMethodDocInject(m_integration, "TSDFVolume", + "extract_triangle_mesh"); docstring::ClassMethodDocInject( - m, "TSDFVolume", "integrate", + m_integration, "TSDFVolume", "integrate", {{"image", "RGBD image."}, {"intrinsic", "Pinhole camera intrinsic parameters."}, {"extrinsic", "Extrinsic parameters."}}); - docstring::ClassMethodDocInject(m, "TSDFVolume", "reset"); + docstring::ClassMethodDocInject(m_integration, "TSDFVolume", "reset"); // open3d.integration.UniformTSDFVolume: open3d.integration.TSDFVolume - py::class_, TSDFVolume> - uniform_tsdfvolume( - m, "UniformTSDFVolume", - "UniformTSDFVolume implements the classic TSDF " - "volume with uniform voxel grid (Curless and Levoy 1996)."); + auto uniform_tsdfvolume = static_cast, TSDFVolume>>( + m_integration.attr("UniformTSDFVolume")); py::detail::bind_copy_functions(uniform_tsdfvolume); uniform_tsdfvolume .def(py::init([](double length, int resolution, double sdf_trunc, @@ -142,32 +176,13 @@ In SIGGRAPH, 1996)"); .def_readwrite("resolution", &UniformTSDFVolume::resolution_, "Resolution over the total length, where " "``voxel_length = length / resolution``"); - docstring::ClassMethodDocInject(m, "UniformTSDFVolume", + docstring::ClassMethodDocInject(m_integration, "UniformTSDFVolume", "extract_voxel_point_cloud"); // open3d.integration.ScalableTSDFVolume: open3d.integration.TSDFVolume - py::class_, TSDFVolume> - scalable_tsdfvolume(m, "ScalableTSDFVolume", R"(The -ScalableTSDFVolume implements a more memory efficient data structure for -volumetric integration. - -This implementation is based on the following repository: -https://github.com/qianyizh/ElasticReconstruction/tree/master/Integrate - -An observed depth pixel gives two types of information: (a) an approximation -of the nearby surface, and (b) empty space from the camera to the surface. -They induce two core concepts of volumetric integration: weighted average of -a truncated signed distance function (TSDF), and carving. The weighted -average of TSDF is great in addressing the Gaussian noise along surface -normal and producing a smooth surface output. The carving is great in -removing outlier structures like floating noise pixels and bumps along -structure edges. - -Ref: Dense Scene Reconstruction with Points of Interest - -Q.-Y. Zhou and V. Koltun - -In SIGGRAPH, 2013)"); + auto scalable_tsdfvolume = static_cast, TSDFVolume>>( + m_integration.attr("ScalableTSDFVolume")); py::detail::bind_copy_functions(scalable_tsdfvolume); scalable_tsdfvolume .def(py::init([](double voxel_length, double sdf_trunc, @@ -193,21 +208,10 @@ In SIGGRAPH, 2013)"); &ScalableTSDFVolume::ExtractVoxelPointCloud, "Debug function to extract the voxel data into a point " "cloud."); - docstring::ClassMethodDocInject(m, "ScalableTSDFVolume", + docstring::ClassMethodDocInject(m_integration, "ScalableTSDFVolume", "extract_voxel_point_cloud"); } -void pybind_integration_methods(py::module &m) { - // Currently empty -} - -void pybind_integration(py::module &m) { - py::module m_submodule = - m.def_submodule("integration", "Integration pipeline."); - pybind_integration_classes(m_submodule); - pybind_integration_methods(m_submodule); -} - } // namespace integration } // namespace pipelines } // namespace open3d diff --git a/cpp/pybind/pipelines/integration/integration.h b/cpp/pybind/pipelines/integration/integration.h index ed316740bb5..47c2454a32e 100644 --- a/cpp/pybind/pipelines/integration/integration.h +++ b/cpp/pybind/pipelines/integration/integration.h @@ -13,7 +13,8 @@ namespace open3d { namespace pipelines { namespace integration { -void pybind_integration(py::module &m); +void pybind_integration_declarations(py::module &m); +void pybind_integration_definitions(py::module &m); } // namespace integration } // namespace pipelines diff --git a/cpp/pybind/pipelines/odometry/odometry.cpp b/cpp/pybind/pipelines/odometry/odometry.cpp index 972a4452aad..b365edfc7b7 100644 --- a/cpp/pybind/pipelines/odometry/odometry.cpp +++ b/cpp/pybind/pipelines/odometry/odometry.cpp @@ -41,10 +41,50 @@ class PyRGBDOdometryJacobian : public RGBDOdometryJacobianBase { } }; -void pybind_odometry_classes(py::module &m) { - // open3d.odometry.OdometryOption +void pybind_odometry_declarations(py::module &m) { + py::module m_odometry = m.def_submodule("odometry", "Odometry pipeline."); py::class_ odometry_option( - m, "OdometryOption", "Class that defines Odometry options."); + m_odometry, "OdometryOption", + "Class that defines Odometry options."); + py::class_> + jacobian( + m_odometry, "RGBDOdometryJacobian", + "Base class that computes Jacobian from two RGB-D images."); + py::class_, + RGBDOdometryJacobian> + jacobian_color(m_odometry, "RGBDOdometryJacobianFromColorTerm", + R"(Class to Compute Jacobian using color term. + +Energy: :math:`(I_p-I_q)^2.` + +Reference: + +F. Steinbrucker, J. Sturm, and D. Cremers. + +Real-time visual odometry from dense RGB-D images. + +In ICCV Workshops, 2011.)"); + py::class_, + RGBDOdometryJacobian> + jacobian_hybrid(m_odometry, "RGBDOdometryJacobianFromHybridTerm", + R"(Class to compute Jacobian using hybrid term + +Energy: :math:`(I_p-I_q)^2 + \lambda(D_p-D_q')^2` + +Reference: + +J. Park, Q.-Y. Zhou, and V. Koltun + +Anonymous submission.)"); +} +void pybind_odometry_definitions(py::module &m) { + auto m_odometry = static_cast(m.attr("odometry")); + // open3d.odometry.OdometryOption + auto odometry_option = static_cast>( + m_odometry.attr("OdometryOption")); odometry_option .def(py::init( [](std::vector iteration_number_per_pyramid_level, @@ -102,12 +142,10 @@ void pybind_odometry_classes(py::module &m) { }); // open3d.odometry.RGBDOdometryJacobian - py::class_> - jacobian( - m, "RGBDOdometryJacobian", - "Base class that computes Jacobian from two RGB-D images."); - + auto jacobian = static_cast< + py::class_>>( + m_odometry.attr("RGBDOdometryJacobian")); jacobian.def( "compute_jacobian_and_residual", &RGBDOdometryJacobian::ComputeJacobianAndResidual, @@ -121,21 +159,11 @@ void pybind_odometry_classes(py::module &m) { "corresps"_a); // open3d.odometry.RGBDOdometryJacobianFromColorTerm: RGBDOdometryJacobian - py::class_, - RGBDOdometryJacobian> - jacobian_color(m, "RGBDOdometryJacobianFromColorTerm", - R"(Class to Compute Jacobian using color term. - -Energy: :math:`(I_p-I_q)^2.` - -Reference: - -F. Steinbrucker, J. Sturm, and D. Cremers. - -Real-time visual odometry from dense RGB-D images. - -In ICCV Workshops, 2011.)"); + auto jacobian_color = static_cast, + RGBDOdometryJacobian>>( + m_odometry.attr("RGBDOdometryJacobianFromColorTerm")); py::detail::bind_default_constructor( jacobian_color); py::detail::bind_copy_functions( @@ -146,19 +174,11 @@ In ICCV Workshops, 2011.)"); }); // open3d.odometry.RGBDOdometryJacobianFromHybridTerm: RGBDOdometryJacobian - py::class_, - RGBDOdometryJacobian> - jacobian_hybrid(m, "RGBDOdometryJacobianFromHybridTerm", - R"(Class to compute Jacobian using hybrid term - -Energy: :math:`(I_p-I_q)^2 + \lambda(D_p-D_q')^2` - -Reference: - -J. Park, Q.-Y. Zhou, and V. Koltun - -Anonymous submission.)"); + auto jacobian_hybrid = static_cast, + RGBDOdometryJacobian>>( + m_odometry.attr("RGBDOdometryJacobianFromHybridTerm")); py::detail::bind_default_constructor( jacobian_hybrid); py::detail::bind_copy_functions( @@ -167,20 +187,18 @@ Anonymous submission.)"); "__repr__", [](const RGBDOdometryJacobianFromHybridTerm &te) { return std::string("RGBDOdometryJacobianFromHybridTerm"); }); -} - -void pybind_odometry_methods(py::module &m) { - m.def("compute_rgbd_odometry", &ComputeRGBDOdometry, - py::call_guard(), - "Function to estimate 6D rigid motion from two RGBD image pairs. " - "Output: (is_success, 4x4 motion matrix, 6x6 information matrix).", - "rgbd_source"_a, "rgbd_target"_a, - "pinhole_camera_intrinsic"_a = camera::PinholeCameraIntrinsic(), - "odo_init"_a = Eigen::Matrix4d::Identity(), - "jacobian"_a = RGBDOdometryJacobianFromHybridTerm(), - "option"_a = OdometryOption()); + m_odometry.def( + "compute_rgbd_odometry", &ComputeRGBDOdometry, + py::call_guard(), + "Function to estimate 6D rigid motion from two RGBD image pairs. " + "Output: (is_success, 4x4 motion matrix, 6x6 information matrix).", + "rgbd_source"_a, "rgbd_target"_a, + "pinhole_camera_intrinsic"_a = camera::PinholeCameraIntrinsic(), + "odo_init"_a = Eigen::Matrix4d::Identity(), + "jacobian"_a = RGBDOdometryJacobianFromHybridTerm(), + "option"_a = OdometryOption()); docstring::FunctionDocInject( - m, "compute_rgbd_odometry", + m_odometry, "compute_rgbd_odometry", { {"rgbd_source", "Source RGBD image."}, {"rgbd_target", "Target RGBD image."}, @@ -195,15 +213,16 @@ void pybind_odometry_methods(py::module &m) { {"option", "Odometry hyper parameters."}, }); - m.def("compute_correspondence", &ComputeCorrespondence, - py::call_guard(), - "Function to estimate point to point correspondences from two depth " - "images. A vector of u_s, v_s, u_t, v_t which maps the 2d " - "coordinates of source to target.", - "intrinsic_matrix"_a, "extrinsic"_a, "depth_s"_a, "depth_t"_a, - "option"_a = OdometryOption()); + m_odometry.def("compute_correspondence", &ComputeCorrespondence, + py::call_guard(), + "Function to estimate point to point correspondences from " + "two depth " + "images. A vector of u_s, v_s, u_t, v_t which maps the 2d " + "coordinates of source to target.", + "intrinsic_matrix"_a, "extrinsic"_a, "depth_s"_a, + "depth_t"_a, "option"_a = OdometryOption()); docstring::FunctionDocInject( - m, "compute_correspondence", + m_odometry, "compute_correspondence", { {"intrinsic_matrix", "Camera intrinsic parameters."}, {"extrinsic", @@ -214,12 +233,6 @@ void pybind_odometry_methods(py::module &m) { }); } -void pybind_odometry(py::module &m) { - py::module m_submodule = m.def_submodule("odometry", "Odometry pipeline."); - pybind_odometry_classes(m_submodule); - pybind_odometry_methods(m_submodule); -} - } // namespace odometry } // namespace pipelines } // namespace open3d diff --git a/cpp/pybind/pipelines/odometry/odometry.h b/cpp/pybind/pipelines/odometry/odometry.h index 48cd0edc657..0a7d2868969 100644 --- a/cpp/pybind/pipelines/odometry/odometry.h +++ b/cpp/pybind/pipelines/odometry/odometry.h @@ -13,7 +13,8 @@ namespace open3d { namespace pipelines { namespace odometry { -void pybind_odometry(py::module &m); +void pybind_odometry_declarations(py::module &m); +void pybind_odometry_definitions(py::module &m); } // namespace odometry } // namespace pipelines diff --git a/cpp/pybind/pipelines/pipelines.cpp b/cpp/pybind/pipelines/pipelines.cpp index aa696a50245..9b1f4422828 100644 --- a/cpp/pybind/pipelines/pipelines.cpp +++ b/cpp/pybind/pipelines/pipelines.cpp @@ -16,12 +16,20 @@ namespace open3d { namespace pipelines { -void pybind_pipelines(py::module& m) { +void pybind_pipelines_declarations(py::module& m) { py::module m_pipelines = m.def_submodule("pipelines"); - color_map::pybind_color_map(m_pipelines); - integration::pybind_integration(m_pipelines); - registration::pybind_registration(m_pipelines); - odometry::pybind_odometry(m_pipelines); + color_map::pybind_color_map_declarations(m_pipelines); + integration::pybind_integration_declarations(m_pipelines); + registration::pybind_registration_declarations(m_pipelines); + odometry::pybind_odometry_declarations(m_pipelines); +} + +void pybind_pipelines_definitions(py::module& m) { + auto m_pipelines = static_cast(m.attr("pipelines")); + color_map::pybind_color_map_definitions(m_pipelines); + integration::pybind_integration_definitions(m_pipelines); + registration::pybind_registration_definitions(m_pipelines); + odometry::pybind_odometry_definitions(m_pipelines); } } // namespace pipelines diff --git a/cpp/pybind/pipelines/pipelines.h b/cpp/pybind/pipelines/pipelines.h index 3f01c128cfe..7783055cff0 100644 --- a/cpp/pybind/pipelines/pipelines.h +++ b/cpp/pybind/pipelines/pipelines.h @@ -12,7 +12,8 @@ namespace open3d { namespace pipelines { -void pybind_pipelines(py::module& m); +void pybind_pipelines_declarations(py::module& m); +void pybind_pipelines_definitions(py::module& m); -} +} // namespace pipelines } // namespace open3d diff --git a/cpp/pybind/pipelines/registration/feature.cpp b/cpp/pybind/pipelines/registration/feature.cpp index bdc4b5d8e51..5896243b5c3 100644 --- a/cpp/pybind/pipelines/registration/feature.cpp +++ b/cpp/pybind/pipelines/registration/feature.cpp @@ -15,10 +15,15 @@ namespace open3d { namespace pipelines { namespace registration { -void pybind_feature(py::module &m) { - // open3d.registration.Feature +void pybind_feature_declarations(py::module &m_registration) { py::class_> feature( - m, "Feature", "Class to store featrues for registration."); + m_registration, "Feature", + "Class to store featrues for registration."); +} +void pybind_feature_definitions(py::module &m_registration) { + // open3d.registration.Feature + auto feature = static_cast>>( + m_registration.attr("Feature")); py::detail::bind_default_constructor(feature); py::detail::bind_copy_functions(feature); feature.def("resize", &Feature::Resize, "dim"_a, "n"_a, @@ -38,28 +43,26 @@ void pybind_feature(py::module &m) { std::string(" and num = ") + std::to_string(f.Num()) + std::string("\nAccess its data via data member."); }); - docstring::ClassMethodDocInject(m, "Feature", "dimension"); - docstring::ClassMethodDocInject(m, "Feature", "num"); - docstring::ClassMethodDocInject(m, "Feature", "resize", + docstring::ClassMethodDocInject(m_registration, "Feature", "dimension"); + docstring::ClassMethodDocInject(m_registration, "Feature", "num"); + docstring::ClassMethodDocInject(m_registration, "Feature", "resize", {{"dim", "Feature dimension per point."}, {"n", "Number of points."}}); -} - -void pybind_feature_methods(py::module &m) { - m.def("compute_fpfh_feature", &ComputeFPFHFeature, - "Function to compute FPFH feature for a point cloud", "input"_a, - "search_param"_a); + m_registration.def("compute_fpfh_feature", &ComputeFPFHFeature, + "Function to compute FPFH feature for a point cloud", + "input"_a, "search_param"_a); docstring::FunctionDocInject( - m, "compute_fpfh_feature", + m_registration, "compute_fpfh_feature", {{"input", "The Input point cloud."}, {"search_param", "KDTree KNN search parameter."}}); - m.def("correspondences_from_features", &CorrespondencesFromFeatures, - "Function to find nearest neighbor correspondences from features", - "source_features"_a, "target_features"_a, "mutual_filter"_a = false, - "mutual_consistency_ratio"_a = 0.1f); + m_registration.def( + "correspondences_from_features", &CorrespondencesFromFeatures, + "Function to find nearest neighbor correspondences from features", + "source_features"_a, "target_features"_a, "mutual_filter"_a = false, + "mutual_consistency_ratio"_a = 0.1f); docstring::FunctionDocInject( - m, "correspondences_from_features", + m_registration, "correspondences_from_features", {{"source_features", "The source features stored in (dim, N)."}, {"target_features", "The target features stored in (dim, M)."}, {"mutual_filter", diff --git a/cpp/pybind/pipelines/registration/global_optimization.cpp b/cpp/pybind/pipelines/registration/global_optimization.cpp index 58607de6597..b1c4d246c45 100644 --- a/cpp/pybind/pipelines/registration/global_optimization.cpp +++ b/cpp/pybind/pipelines/registration/global_optimization.cpp @@ -29,10 +29,50 @@ class PyGlobalOptimizationMethod : public GlobalOptimizationMethodBase { } }; -void pybind_global_optimization(py::module &m) { - // open3d.registration.PoseGraphNode +void pybind_global_optimization_declarations(py::module &m_registration) { py::class_> pose_graph_node( - m, "PoseGraphNode", "Node of ``PoseGraph``."); + m_registration, "PoseGraphNode", "Node of ``PoseGraph``."); + auto pose_graph_node_vector = py::bind_vector>( + m_registration, "PoseGraphNodeVector"); + py::class_> pose_graph_edge( + m_registration, "PoseGraphEdge", "Edge of ``PoseGraph``."); + auto pose_graph_edge_vector = py::bind_vector>( + m_registration, "PoseGraphEdgeVector"); + py::class_> pose_graph( + m_registration, "PoseGraph", + "Data structure defining the pose graph."); + py::class_> + global_optimization_method( + m_registration, "GlobalOptimizationMethod", + "Base class for global optimization method."); + py::class_, + GlobalOptimizationMethod> + global_optimization_method_lm( + m_registration, "GlobalOptimizationLevenbergMarquardt", + "Global optimization with Levenberg-Marquardt algorithm. " + "Recommended over the Gauss-Newton method since the LM has " + "better convergence characteristics."); + py::class_, + GlobalOptimizationMethod> + global_optimization_method_gn( + m_registration, "GlobalOptimizationGaussNewton", + "Global optimization with Gauss-Newton algorithm."); + py::class_ criteria( + m_registration, "GlobalOptimizationConvergenceCriteria", + "Convergence criteria of GlobalOptimization."); + py::class_ option( + m_registration, "GlobalOptimizationOption", + "Option for GlobalOptimization."); +} + +void pybind_global_optimization_definitions(py::module &m_registration) { + // open3d.registration.PoseGraphNode + auto pose_graph_node = static_cast< + py::class_>>( + m_registration.attr("PoseGraphNode")); py::detail::bind_default_constructor(pose_graph_node); py::detail::bind_copy_functions(pose_graph_node); pose_graph_node.def_readwrite("pose", &PoseGraphNode::pose_) @@ -49,8 +89,10 @@ void pybind_global_optimization(py::module &m) { }); // open3d.registration.PoseGraphNodeVector - auto pose_graph_node_vector = py::bind_vector>( - m, "PoseGraphNodeVector"); + auto pose_graph_node_vector = + static_cast>( + m_registration, "PoseGraphNodeVector"))>( + m_registration.attr("PoseGraphNodeVector")); pose_graph_node_vector.attr("__doc__") = docstring::static_property( py::cpp_function([](py::handle arg) -> std::string { return "Vector of PoseGraphNode"; @@ -58,8 +100,9 @@ void pybind_global_optimization(py::module &m) { py::none(), py::none(), ""); // open3d.registration.PoseGraphEdge - py::class_> pose_graph_edge( - m, "PoseGraphEdge", "Edge of ``PoseGraph``."); + auto pose_graph_edge = static_cast< + py::class_>>( + m_registration.attr("PoseGraphEdge")); py::detail::bind_default_constructor(pose_graph_edge); py::detail::bind_copy_functions(pose_graph_edge); pose_graph_edge @@ -109,8 +152,10 @@ void pybind_global_optimization(py::module &m) { }); // open3d.registration.PoseGraphEdgeVector - auto pose_graph_edge_vector = py::bind_vector>( - m, "PoseGraphEdgeVector"); + auto pose_graph_edge_vector = + static_cast>( + m_registration, "PoseGraphEdgeVector"))>( + m_registration.attr("PoseGraphEdgeVector")); pose_graph_edge_vector.attr("__doc__") = docstring::static_property( py::cpp_function([](py::handle arg) -> std::string { return "Vector of PoseGraphEdge"; @@ -118,8 +163,9 @@ void pybind_global_optimization(py::module &m) { py::none(), py::none(), ""); // open3d.registration.PoseGraph - py::class_> pose_graph( - m, "PoseGraph", "Data structure defining the pose graph."); + auto pose_graph = + static_cast>>( + m_registration.attr("PoseGraph")); py::detail::bind_default_constructor(pose_graph); py::detail::bind_copy_functions(pose_graph); pose_graph @@ -138,29 +184,25 @@ void pybind_global_optimization(py::module &m) { }); // open3d.registration.GlobalOptimizationMethod - py::class_> - global_optimization_method( - m, "GlobalOptimizationMethod", - "Base class for global optimization method."); + auto global_optimization_method = static_cast< + py::class_>>( + m_registration.attr("GlobalOptimizationMethod")); global_optimization_method.def("OptimizePoseGraph", &GlobalOptimizationMethod::OptimizePoseGraph, "pose_graph"_a, "criteria"_a, "option"_a, "Run pose graph optimization."); docstring::ClassMethodDocInject( - m, "GlobalOptimizationMethod", "OptimizePoseGraph", + m_registration, "GlobalOptimizationMethod", "OptimizePoseGraph", {{"pose_graph", "The pose graph to be optimized (in-place)."}, {"criteria", "Convergence criteria."}, {"option", "Global optimization options."}}); - py::class_, - GlobalOptimizationMethod> - global_optimization_method_lm( - m, "GlobalOptimizationLevenbergMarquardt", - "Global optimization with Levenberg-Marquardt algorithm. " - "Recommended over the Gauss-Newton method since the LM has " - "better convergence characteristics."); + auto global_optimization_method_lm = static_cast, + GlobalOptimizationMethod>>( + m_registration.attr("GlobalOptimizationLevenbergMarquardt")); py::detail::bind_default_constructor( global_optimization_method_lm); py::detail::bind_copy_functions( @@ -170,12 +212,11 @@ void pybind_global_optimization(py::module &m) { return std::string("GlobalOptimizationLevenbergMarquardt"); }); - py::class_, - GlobalOptimizationMethod> - global_optimization_method_gn( - m, "GlobalOptimizationGaussNewton", - "Global optimization with Gauss-Newton algorithm."); + auto global_optimization_method_gn = static_cast, + GlobalOptimizationMethod>>( + m_registration.attr("GlobalOptimizationGaussNewton")); py::detail::bind_default_constructor( global_optimization_method_gn); py::detail::bind_copy_functions( @@ -185,9 +226,10 @@ void pybind_global_optimization(py::module &m) { return std::string("GlobalOptimizationGaussNewton"); }); - py::class_ criteria( - m, "GlobalOptimizationConvergenceCriteria", - "Convergence criteria of GlobalOptimization."); + auto criteria = + static_cast>( + m_registration.attr( + "GlobalOptimizationConvergenceCriteria")); py::detail::bind_default_constructor( criteria); py::detail::bind_copy_functions( @@ -252,8 +294,8 @@ void pybind_global_optimization(py::module &m) { std::to_string(cr.lower_scale_factor_); }); - py::class_ option( - m, "GlobalOptimizationOption", "Option for GlobalOptimization."); + auto option = static_cast>( + m_registration.attr("GlobalOptimizationOption")); py::detail::bind_default_constructor(option); py::detail::bind_copy_functions(option); option.def_readwrite( @@ -302,10 +344,7 @@ void pybind_global_optimization(py::module &m) { std::string("\n> reference_node : ") + std::to_string(goo.reference_node_); }); -} - -void pybind_global_optimization_methods(py::module &m) { - m.def( + m_registration.def( "global_optimization", [](PoseGraph &pose_graph, const GlobalOptimizationMethod &method, const GlobalOptimizationConvergenceCriteria &criteria, @@ -315,7 +354,7 @@ void pybind_global_optimization_methods(py::module &m) { "Function to optimize PoseGraph", "pose_graph"_a, "method"_a, "criteria"_a, "option"_a); docstring::FunctionDocInject( - m, "global_optimization", + m_registration, "global_optimization", {{"pose_graph", "The pose_graph to be optimized (in-place)."}, {"method", "Global optimization method. Either " diff --git a/cpp/pybind/pipelines/registration/registration.cpp b/cpp/pybind/pipelines/registration/registration.cpp index cddcb7e7e80..b57836bdcee 100644 --- a/cpp/pybind/pipelines/registration/registration.cpp +++ b/cpp/pybind/pipelines/registration/registration.cpp @@ -63,16 +63,109 @@ class PyCorrespondenceChecker : public CorrespondenceCheckerBase { } }; -void pybind_registration_classes(py::module &m) { - // open3d.registration.ICPConvergenceCriteria +void pybind_registration_declarations(py::module &m) { + py::module m_registration = + m.def_submodule("registration", "Registration pipeline."); py::class_ convergence_criteria( - m, "ICPConvergenceCriteria", + m_registration, "ICPConvergenceCriteria", "Class that defines the convergence criteria of ICP. ICP " "algorithm " "stops if the relative change of fitness and rmse hit " "``relative_fitness`` and ``relative_rmse`` individually, " "or the " "iteration number exceeds ``max_iteration``."); + py::class_ ransac_criteria( + m_registration, "RANSACConvergenceCriteria", + "Class that defines the convergence criteria of " + "RANSAC. RANSAC algorithm stops if the iteration " + "number hits ``max_iteration``, or the fitness " + "measured during validation suggests that the " + "algorithm can be terminated early with some " + "``confidence``. Early termination takes place " + "when the number of iterations reaches ``k = " + "log(1 - confidence)/log(1 - fitness^{ransac_n})``, " + "where ``ransac_n`` is the number of points used " + "during a ransac iteration. Use confidence=1.0 " + "to avoid early termination."); + py::class_> + te(m_registration, "TransformationEstimation", + "Base class that estimates a transformation between two point " + "clouds. The virtual function ComputeTransformation() must be " + "implemented in subclasses."); + py::class_, + TransformationEstimation> + te_p2p(m_registration, "TransformationEstimationPointToPoint", + "Class to estimate a transformation for point to point " + "distance."); + py::class_, + TransformationEstimation> + te_p2l(m_registration, "TransformationEstimationPointToPlane", + "Class to estimate a transformation for point to plane " + "distance."); + py::class_< + TransformationEstimationForColoredICP, + PyTransformationEstimation, + TransformationEstimation> + te_col(m_registration, "TransformationEstimationForColoredICP", + "Class to estimate a transformation between two point " + "clouds using color information"); + py::class_, + TransformationEstimation> + te_gicp(m_registration, "TransformationEstimationForGeneralizedICP", + "Class to estimate a transformation for Generalized ICP."); + py::class_> + cc(m_registration, "CorrespondenceChecker", + "Base class that checks if two (small) point clouds can be " + "aligned. This class is used in feature based matching " + "algorithms (such as RANSAC and FastGlobalRegistration) to " + "prune out outlier correspondences. The virtual function " + "Check() must be implemented in subclasses."); + py::class_, + CorrespondenceChecker> + cc_el(m_registration, "CorrespondenceCheckerBasedOnEdgeLength", + "Check if two point clouds build the polygons with similar " + "edge lengths. That is, checks if the lengths of any two " + "arbitrary edges (line formed by two vertices) individually " + "drawn within the source point cloud and within the target " + "point cloud with correspondences are similar. The only " + "parameter similarity_threshold is a number between 0 " + "(loose) and 1 (strict)"); + py::class_, + CorrespondenceChecker> + cc_d(m_registration, "CorrespondenceCheckerBasedOnDistance", + "Class to check if aligned point clouds are close (less than " + "specified threshold)."); + py::class_, + CorrespondenceChecker> + cc_n(m_registration, "CorrespondenceCheckerBasedOnNormal", + "Class to check if two aligned point clouds have similar " + "normals. It considers vertex normal affinity of any " + "correspondences. It computes dot product of two normal " + "vectors. It takes radian value for the threshold."); + py::class_ fgr_option( + m_registration, "FastGlobalRegistrationOption", + "Options for FastGlobalRegistration."); + py::class_ registration_result( + m_registration, "RegistrationResult", + "Class that contains the registration results."); + pybind_feature_declarations(m_registration); + pybind_global_optimization_declarations(m_registration); + pybind_robust_kernels_declarations(m_registration); +} +void pybind_registration_definitions(py::module &m) { + auto m_registration = static_cast(m.attr("registration")); + // open3d.registration.ICPConvergenceCriteria + auto convergence_criteria = static_cast>( + m_registration.attr("ICPConvergenceCriteria")); py::detail::bind_copy_functions( convergence_criteria); convergence_criteria @@ -103,19 +196,8 @@ void pybind_registration_classes(py::module &m) { }); // open3d.registration.RANSACConvergenceCriteria - py::class_ ransac_criteria( - m, "RANSACConvergenceCriteria", - "Class that defines the convergence criteria of " - "RANSAC. RANSAC algorithm stops if the iteration " - "number hits ``max_iteration``, or the fitness " - "measured during validation suggests that the " - "algorithm can be terminated early with some " - "``confidence``. Early termination takes place " - "when the number of iterations reaches ``k = " - "log(1 - confidence)/log(1 - fitness^{ransac_n})``, " - "where ``ransac_n`` is the number of points used " - "during a ransac iteration. Use confidence=1.0 " - "to avoid early termination."); + auto ransac_criteria = static_cast>( + m_registration.attr("RANSACConvergenceCriteria")); py::detail::bind_copy_functions(ransac_criteria); ransac_criteria .def(py::init([](int max_iteration, double confidence) { @@ -139,12 +221,10 @@ void pybind_registration_classes(py::module &m) { }); // open3d.registration.TransformationEstimation - py::class_> - te(m, "TransformationEstimation", - "Base class that estimates a transformation between two point " - "clouds. The virtual function ComputeTransformation() must be " - "implemented in subclasses."); + auto te = static_cast< + py::class_>>( + m_registration.attr("TransformationEstimation")); te.def("compute_rmse", &TransformationEstimation::ComputeRMSE, "source"_a, "target"_a, "corres"_a, "Compute RMSE between source and target points cloud given " @@ -155,13 +235,14 @@ void pybind_registration_classes(py::module &m) { "Compute transformation from source to target point cloud given " "correspondences."); docstring::ClassMethodDocInject( - m, "TransformationEstimation", "compute_rmse", + m_registration, "TransformationEstimation", "compute_rmse", {{"source", "Source point cloud."}, {"target", "Target point cloud."}, {"corres", "Correspondence set between source and target point cloud."}}); docstring::ClassMethodDocInject( - m, "TransformationEstimation", "compute_transformation", + m_registration, "TransformationEstimation", + "compute_transformation", {{"source", "Source point cloud."}, {"target", "Target point cloud."}, {"corres", @@ -169,12 +250,11 @@ void pybind_registration_classes(py::module &m) { // open3d.registration.TransformationEstimationPointToPoint: // TransformationEstimation - py::class_, - TransformationEstimation> - te_p2p(m, "TransformationEstimationPointToPoint", - "Class to estimate a transformation for point to point " - "distance."); + auto te_p2p = static_cast, + TransformationEstimation>>( + m_registration.attr("TransformationEstimationPointToPoint")); py::detail::bind_copy_functions( te_p2p); te_p2p.def(py::init([](bool with_scaling) { @@ -205,12 +285,11 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. // open3d.registration.TransformationEstimationPointToPlane: // TransformationEstimation - py::class_, - TransformationEstimation> - te_p2l(m, "TransformationEstimationPointToPlane", - "Class to estimate a transformation for point to plane " - "distance."); + auto te_p2l = static_cast, + TransformationEstimation>>( + m_registration.attr("TransformationEstimationPointToPlane")); py::detail::bind_default_constructor( te_p2l); py::detail::bind_copy_functions( @@ -229,13 +308,11 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. "Robust Kernel used in the Optimization"); // open3d.registration.TransformationEstimationForColoredICP : - py::class_< + auto te_col = static_cast, - TransformationEstimation> - te_col(m, "TransformationEstimationForColoredICP", - "Class to estimate a transformation between two point " - "clouds using color information"); + TransformationEstimation>>( + m_registration.attr("TransformationEstimationForColoredICP")); py::detail::bind_default_constructor( te_col); py::detail::bind_copy_functions( @@ -274,12 +351,12 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. // open3d.registration.TransformationEstimationForGeneralizedICP: // TransformationEstimation - py::class_, - TransformationEstimation> - te_gicp(m, "TransformationEstimationForGeneralizedICP", - "Class to estimate a transformation for Generalized ICP."); + auto te_gicp = static_cast< + py::class_, + TransformationEstimation>>( + m_registration.attr("TransformationEstimationForGeneralizedICP")); py::detail::bind_default_constructor< TransformationEstimationForGeneralizedICP>(te_gicp); py::detail::bind_copy_functions( @@ -316,14 +393,10 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. "Robust Kernel used in the Optimization"); // open3d.registration.CorrespondenceChecker - py::class_> - cc(m, "CorrespondenceChecker", - "Base class that checks if two (small) point clouds can be " - "aligned. This class is used in feature based matching " - "algorithms (such as RANSAC and FastGlobalRegistration) to " - "prune out outlier correspondences. The virtual function " - "Check() must be implemented in subclasses."); + auto cc = static_cast< + py::class_>>( + m_registration.attr("CorrespondenceChecker")); cc.def("Check", &CorrespondenceChecker::Check, "source"_a, "target"_a, "corres"_a, "transformation"_a, "Function to check if two points can be aligned. The two input " @@ -335,7 +408,7 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. "the edge length checker. Some checkers do, e.g., the distance " "checker."); docstring::ClassMethodDocInject( - m, "CorrespondenceChecker", "Check", + m_registration, "CorrespondenceChecker", "Check", {{"source", "Source point cloud."}, {"target", "Target point cloud."}, {"corres", @@ -344,17 +417,11 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. // open3d.registration.CorrespondenceCheckerBasedOnEdgeLength: // CorrespondenceChecker - py::class_, - CorrespondenceChecker> - cc_el(m, "CorrespondenceCheckerBasedOnEdgeLength", - "Check if two point clouds build the polygons with similar " - "edge lengths. That is, checks if the lengths of any two " - "arbitrary edges (line formed by two vertices) individually " - "drawn within the source point cloud and within the target " - "point cloud with correspondences are similar. The only " - "parameter similarity_threshold is a number between 0 " - "(loose) and 1 (strict)"); + auto cc_el = static_cast, + CorrespondenceChecker>>( + m_registration.attr("CorrespondenceCheckerBasedOnEdgeLength")); py::detail::bind_copy_functions( cc_el); cc_el.def(py::init([](double similarity_threshold) { @@ -385,12 +452,11 @@ must hold true for all edges.)"); // open3d.registration.CorrespondenceCheckerBasedOnDistance: // CorrespondenceChecker - py::class_, - CorrespondenceChecker> - cc_d(m, "CorrespondenceCheckerBasedOnDistance", - "Class to check if aligned point clouds are close (less than " - "specified threshold)."); + auto cc_d = static_cast, + CorrespondenceChecker>>( + m_registration.attr("CorrespondenceCheckerBasedOnDistance")); py::detail::bind_copy_functions(cc_d); cc_d.def(py::init([](double distance_threshold) { return new CorrespondenceCheckerBasedOnDistance( @@ -412,14 +478,11 @@ must hold true for all edges.)"); // open3d.registration.CorrespondenceCheckerBasedOnNormal: // CorrespondenceChecker - py::class_, - CorrespondenceChecker> - cc_n(m, "CorrespondenceCheckerBasedOnNormal", - "Class to check if two aligned point clouds have similar " - "normals. It considers vertex normal affinity of any " - "correspondences. It computes dot product of two normal " - "vectors. It takes radian value for the threshold."); + auto cc_n = static_cast, + CorrespondenceChecker>>( + m_registration.attr("CorrespondenceCheckerBasedOnNormal")); py::detail::bind_copy_functions(cc_n); cc_n.def(py::init([](double normal_angle_threshold) { return new CorrespondenceCheckerBasedOnNormal( @@ -440,9 +503,8 @@ must hold true for all edges.)"); "Radian value for angle threshold."); // open3d.registration.FastGlobalRegistrationOption: - py::class_ fgr_option( - m, "FastGlobalRegistrationOption", - "Options for FastGlobalRegistration."); + auto fgr_option = static_cast>( + m_registration.attr("FastGlobalRegistrationOption")); py::detail::bind_copy_functions(fgr_option); fgr_option .def(py::init([](double division_factor, bool use_absolute_scale, @@ -509,9 +571,8 @@ must hold true for all edges.)"); }); // open3d.registration.RegistrationResult - py::class_ registration_result( - m, "RegistrationResult", - "Class that contains the registration results."); + auto registration_result = static_cast>( + m_registration.attr("RegistrationResult")); py::detail::bind_default_constructor( registration_result); py::detail::bind_copy_functions(registration_result); @@ -542,162 +603,161 @@ must hold true for all edges.)"); rr.fitness_, rr.inlier_rmse_, rr.correspondence_set_.size()); }); -} - -// Registration functions have similar arguments, sharing arg docstrings -static const std::unordered_map - map_shared_argument_docstrings = { - {"checkers", - "Vector of Checker class to check if two point " - "clouds can be aligned. One of " - "(``CorrespondenceCheckerBasedOnEdgeLength``, " - "``CorrespondenceCheckerBasedOnDistance``, " - "``CorrespondenceCheckerBasedOnNormal``)"}, - {"confidence", - "Desired probability of success for RANSAC. Used for " - "estimating early termination by k = log(1 - " - "confidence)/log(1 - inlier_ratio^{ransac_n}."}, - {"corres", - "o3d.utility.Vector2iVector that stores indices of " - "corresponding point or feature arrays."}, - {"criteria", "Convergence criteria"}, - {"estimation_method", - "Estimation method. One of " - "(``TransformationEstimationPointToPoint``, " - "``TransformationEstimationPointToPlane``, " - "``TransformationEstimationForGeneralizedICP``, " - "``TransformationEstimationForColoredICP``)"}, - {"init", "Initial transformation estimation"}, - {"lambda_geometric", "lambda_geometric value"}, - {"epsilon", "epsilon value"}, - {"kernel", "Robust Kernel used in the Optimization"}, - {"max_correspondence_distance", - "Maximum correspondence points-pair distance."}, - {"mutual_filter", - "Enables mutual filter such that the correspondence of the " - "source point's correspondence is itself."}, - {"option", "Registration option"}, - {"ransac_n", "Fit ransac with ``ransac_n`` correspondences"}, - {"source_feature", "Source point cloud feature."}, - {"source", "The source point cloud."}, - {"target_feature", "Target point cloud feature."}, - {"target", "The target point cloud."}, - {"transformation", - "The 4x4 transformation matrix to transform ``source`` to " - "``target``"}}; - -void pybind_registration_methods(py::module &m) { - m.def("evaluate_registration", &EvaluateRegistration, - py::call_guard(), - "Function for evaluating registration between point clouds", - "source"_a, "target"_a, "max_correspondence_distance"_a, - "transformation"_a = Eigen::Matrix4d::Identity()); - docstring::FunctionDocInject(m, "evaluate_registration", + // Registration functions have similar arguments, sharing arg docstrings + static const std::unordered_map + map_shared_argument_docstrings = { + {"checkers", + "Vector of Checker class to check if two point " + "clouds can be aligned. One of " + "(``CorrespondenceCheckerBasedOnEdgeLength``, " + "``CorrespondenceCheckerBasedOnDistance``, " + "``CorrespondenceCheckerBasedOnNormal``)"}, + {"confidence", + "Desired probability of success for RANSAC. Used for " + "estimating early termination by k = log(1 - " + "confidence)/log(1 - inlier_ratio^{ransac_n}."}, + {"corres", + "o3d.utility.Vector2iVector that stores indices of " + "corresponding point or feature arrays."}, + {"criteria", "Convergence criteria"}, + {"estimation_method", + "Estimation method. One of " + "(``TransformationEstimationPointToPoint``, " + "``TransformationEstimationPointToPlane``, " + "``TransformationEstimationForGeneralizedICP``, " + "``TransformationEstimationForColoredICP``)"}, + {"init", "Initial transformation estimation"}, + {"lambda_geometric", "lambda_geometric value"}, + {"epsilon", "epsilon value"}, + {"kernel", "Robust Kernel used in the Optimization"}, + {"max_correspondence_distance", + "Maximum correspondence points-pair distance."}, + {"mutual_filter", + "Enables mutual filter such that the correspondence of " + "the " + "source point's correspondence is itself."}, + {"option", "Registration option"}, + {"ransac_n", + "Fit ransac with ``ransac_n`` correspondences"}, + {"source_feature", "Source point cloud feature."}, + {"source", "The source point cloud."}, + {"target_feature", "Target point cloud feature."}, + {"target", "The target point cloud."}, + {"transformation", + "The 4x4 transformation matrix to transform ``source`` to " + "``target``"}}; + m_registration.def( + "evaluate_registration", &EvaluateRegistration, + py::call_guard(), + "Function for evaluating registration between point clouds", + "source"_a, "target"_a, "max_correspondence_distance"_a, + "transformation"_a = Eigen::Matrix4d::Identity()); + docstring::FunctionDocInject(m_registration, "evaluate_registration", map_shared_argument_docstrings); - m.def("registration_icp", &RegistrationICP, - py::call_guard(), - "Function for ICP registration", "source"_a, "target"_a, - "max_correspondence_distance"_a, - "init"_a = Eigen::Matrix4d::Identity(), - "estimation_method"_a = TransformationEstimationPointToPoint(false), - "criteria"_a = ICPConvergenceCriteria()); - docstring::FunctionDocInject(m, "registration_icp", + m_registration.def( + "registration_icp", &RegistrationICP, + py::call_guard(), + "Function for ICP registration", "source"_a, "target"_a, + "max_correspondence_distance"_a, + "init"_a = Eigen::Matrix4d::Identity(), + "estimation_method"_a = TransformationEstimationPointToPoint(false), + "criteria"_a = ICPConvergenceCriteria()); + docstring::FunctionDocInject(m_registration, "registration_icp", map_shared_argument_docstrings); - m.def("registration_colored_icp", &RegistrationColoredICP, - py::call_guard(), - "Function for Colored ICP registration", "source"_a, "target"_a, - "max_correspondence_distance"_a, - "init"_a = Eigen::Matrix4d::Identity(), - "estimation_method"_a = TransformationEstimationForColoredICP(0.968), - "criteria"_a = ICPConvergenceCriteria()); - docstring::FunctionDocInject(m, "registration_colored_icp", + m_registration.def("registration_colored_icp", &RegistrationColoredICP, + py::call_guard(), + "Function for Colored ICP registration", "source"_a, + "target"_a, "max_correspondence_distance"_a, + "init"_a = Eigen::Matrix4d::Identity(), + "estimation_method"_a = + TransformationEstimationForColoredICP(0.968), + "criteria"_a = ICPConvergenceCriteria()); + docstring::FunctionDocInject(m_registration, "registration_colored_icp", map_shared_argument_docstrings); - m.def("registration_generalized_icp", &RegistrationGeneralizedICP, - py::call_guard(), - "Function for Generalized ICP registration", "source"_a, "target"_a, - "max_correspondence_distance"_a, - "init"_a = Eigen::Matrix4d::Identity(), - "estimation_method"_a = - TransformationEstimationForGeneralizedICP(1e-3), - "criteria"_a = ICPConvergenceCriteria()); - docstring::FunctionDocInject(m, "registration_generalized_icp", + m_registration.def("registration_generalized_icp", + &RegistrationGeneralizedICP, + py::call_guard(), + "Function for Generalized ICP registration", "source"_a, + "target"_a, "max_correspondence_distance"_a, + "init"_a = Eigen::Matrix4d::Identity(), + "estimation_method"_a = + TransformationEstimationForGeneralizedICP(1e-3), + "criteria"_a = ICPConvergenceCriteria()); + docstring::FunctionDocInject(m_registration, "registration_generalized_icp", map_shared_argument_docstrings); - m.def("registration_ransac_based_on_correspondence", - &RegistrationRANSACBasedOnCorrespondence, - py::call_guard(), - "Function for global RANSAC registration based on a set of " - "correspondences", - "source"_a, "target"_a, "corres"_a, "max_correspondence_distance"_a, - "estimation_method"_a = TransformationEstimationPointToPoint(false), - "ransac_n"_a = 3, - "checkers"_a = std::vector< - std::reference_wrapper>(), - "criteria"_a = RANSACConvergenceCriteria(100000, 0.999)); - docstring::FunctionDocInject(m, + m_registration.def( + "registration_ransac_based_on_correspondence", + &RegistrationRANSACBasedOnCorrespondence, + py::call_guard(), + "Function for global RANSAC registration based on a set of " + "correspondences", + "source"_a, "target"_a, "corres"_a, "max_correspondence_distance"_a, + "estimation_method"_a = TransformationEstimationPointToPoint(false), + "ransac_n"_a = 3, + "checkers"_a = std::vector< + std::reference_wrapper>(), + "criteria"_a = RANSACConvergenceCriteria(100000, 0.999)); + docstring::FunctionDocInject(m_registration, "registration_ransac_based_on_correspondence", map_shared_argument_docstrings); - m.def("registration_ransac_based_on_feature_matching", - &RegistrationRANSACBasedOnFeatureMatching, - py::call_guard(), - "Function for global RANSAC registration based on feature matching", - "source"_a, "target"_a, "source_feature"_a, "target_feature"_a, - "mutual_filter"_a, "max_correspondence_distance"_a, - "estimation_method"_a = TransformationEstimationPointToPoint(false), - "ransac_n"_a = 3, - "checkers"_a = std::vector< - std::reference_wrapper>(), - "criteria"_a = RANSACConvergenceCriteria(100000, 0.999)); + m_registration.def( + "registration_ransac_based_on_feature_matching", + &RegistrationRANSACBasedOnFeatureMatching, + py::call_guard(), + "Function for global RANSAC registration based on feature matching", + "source"_a, "target"_a, "source_feature"_a, "target_feature"_a, + "mutual_filter"_a, "max_correspondence_distance"_a, + "estimation_method"_a = TransformationEstimationPointToPoint(false), + "ransac_n"_a = 3, + "checkers"_a = std::vector< + std::reference_wrapper>(), + "criteria"_a = RANSACConvergenceCriteria(100000, 0.999)); docstring::FunctionDocInject( - m, "registration_ransac_based_on_feature_matching", + m_registration, "registration_ransac_based_on_feature_matching", map_shared_argument_docstrings); - m.def("registration_fgr_based_on_correspondence", - &FastGlobalRegistrationBasedOnCorrespondence, - py::call_guard(), - "Function for fast global registration based on a set of " - "correspondences", - "source"_a, "target"_a, "corres"_a, - "option"_a = FastGlobalRegistrationOption()); - docstring::FunctionDocInject(m, "registration_fgr_based_on_correspondence", + m_registration.def( + "registration_fgr_based_on_correspondence", + &FastGlobalRegistrationBasedOnCorrespondence, + py::call_guard(), + "Function for fast global registration based on a set of " + "correspondences", + "source"_a, "target"_a, "corres"_a, + "option"_a = FastGlobalRegistrationOption()); + docstring::FunctionDocInject(m_registration, + "registration_fgr_based_on_correspondence", map_shared_argument_docstrings); - m.def("registration_fgr_based_on_feature_matching", - &FastGlobalRegistrationBasedOnFeatureMatching, - py::call_guard(), - "Function for fast global registration based on feature matching", - "source"_a, "target"_a, "source_feature"_a, "target_feature"_a, - "option"_a = FastGlobalRegistrationOption()); - docstring::FunctionDocInject(m, + m_registration.def( + "registration_fgr_based_on_feature_matching", + &FastGlobalRegistrationBasedOnFeatureMatching, + py::call_guard(), + "Function for fast global registration based on feature matching", + "source"_a, "target"_a, "source_feature"_a, "target_feature"_a, + "option"_a = FastGlobalRegistrationOption()); + docstring::FunctionDocInject(m_registration, "registration_fgr_based_on_feature_matching", map_shared_argument_docstrings); - m.def("get_information_matrix_from_point_clouds", - &GetInformationMatrixFromPointClouds, - py::call_guard(), - "Function for computing information matrix from transformation " - "matrix", - "source"_a, "target"_a, "max_correspondence_distance"_a, - "transformation"_a); - docstring::FunctionDocInject(m, "get_information_matrix_from_point_clouds", + m_registration.def( + "get_information_matrix_from_point_clouds", + &GetInformationMatrixFromPointClouds, + py::call_guard(), + "Function for computing information matrix from transformation " + "matrix", + "source"_a, "target"_a, "max_correspondence_distance"_a, + "transformation"_a); + docstring::FunctionDocInject(m_registration, + "get_information_matrix_from_point_clouds", map_shared_argument_docstrings); -} - -void pybind_registration(py::module &m) { - py::module m_submodule = - m.def_submodule("registration", "Registration pipeline."); - pybind_registration_classes(m_submodule); - pybind_registration_methods(m_submodule); - - pybind_feature(m_submodule); - pybind_feature_methods(m_submodule); - pybind_global_optimization(m_submodule); - pybind_global_optimization_methods(m_submodule); - pybind_robust_kernels(m_submodule); + pybind_feature_definitions(m_registration); + pybind_global_optimization_definitions(m_registration); + pybind_robust_kernels_definitions(m_registration); } } // namespace registration diff --git a/cpp/pybind/pipelines/registration/registration.h b/cpp/pybind/pipelines/registration/registration.h index 17eb18a3b02..1e98308e073 100644 --- a/cpp/pybind/pipelines/registration/registration.h +++ b/cpp/pybind/pipelines/registration/registration.h @@ -13,13 +13,15 @@ namespace open3d { namespace pipelines { namespace registration { -void pybind_registration(py::module &m); +void pybind_registration_declarations(py::module &m); +void pybind_feature_declarations(py::module &m_registration); +void pybind_global_optimization_declarations(py::module &m_registration); +void pybind_robust_kernels_declarations(py::module &m_registration); -void pybind_feature(py::module &m); -void pybind_feature_methods(py::module &m); -void pybind_global_optimization(py::module &m); -void pybind_global_optimization_methods(py::module &m); -void pybind_robust_kernels(py::module &m); +void pybind_registration_definitions(py::module &m); +void pybind_feature_definitions(py::module &m_registration); +void pybind_global_optimization_definitions(py::module &m_registration); +void pybind_robust_kernels_definitions(py::module &m_registration); } // namespace registration } // namespace pipelines diff --git a/cpp/pybind/pipelines/registration/robust_kernels.cpp b/cpp/pybind/pipelines/registration/robust_kernels.cpp index 5cd8bb83181..477c5812427 100644 --- a/cpp/pybind/pipelines/registration/robust_kernels.cpp +++ b/cpp/pybind/pipelines/registration/robust_kernels.cpp @@ -39,10 +39,9 @@ using PyCauchyLoss = PyRobustKernelT; using PyGMLoss = PyRobustKernelT; using PyTukeyLoss = PyRobustKernelT; -void pybind_robust_kernels(py::module &m) { - // open3d.registration.RobustKernel +void pybind_robust_kernels_declarations(py::module &m_registration) { py::class_, PyRobustKernel> rk( - m, "RobustKernel", + m_registration, "RobustKernel", R"( Base class that models a robust kernel for outlier rejection. The virtual function ``weight()`` must be implemented in derived classes. @@ -114,16 +113,8 @@ Philippe Babin et al. For more information please also see: **"Adaptive Robust Kernels for Non-Linear Least Squares Problems"**, by Nived Chebrolu et al. )"); - rk.def("weight", &RobustKernel::Weight, "residual"_a, - "Obtain the weight for the given residual according to the " - "robust kernel model."); - docstring::ClassMethodDocInject( - m, "RobustKernel", "weight", - {{"residual", "value obtained during the optimization problem"}}); - - // open3d.registration.L2Loss py::class_, PyL2Loss, RobustKernel> l2_loss( - m, "L2Loss", + m_registration, "L2Loss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is given by: @@ -133,16 +124,8 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: .. math:: w(r) = 1 )"); - py::detail::bind_default_constructor(l2_loss); - py::detail::bind_copy_functions(l2_loss); - l2_loss.def("__repr__", [](const L2Loss &rk) { - (void)rk; - return "RobustKernel::L2Loss"; - }); - - // open3d.registration.L1Loss:RobustKernel py::class_, PyL1Loss, RobustKernel> l1_loss( - m, "L1Loss", + m_registration, "L1Loss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is given by: @@ -152,16 +135,8 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: .. math:: w(r) = \frac{1}{|r|} )"); - py::detail::bind_default_constructor(l1_loss); - py::detail::bind_copy_functions(l1_loss); - l1_loss.def("__repr__", [](const L1Loss &rk) { - (void)rk; - return "RobustKernel::L1Loss"; - }); - - // open3d.registration.HuberLoss py::class_, PyHuberLoss, RobustKernel> - h_loss(m, "HuberLoss", + h_loss(m_registration, "HuberLoss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is: @@ -185,21 +160,9 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: \end{cases} \end{equation} )"); - py::detail::bind_copy_functions(h_loss); - h_loss.def(py::init( - [](double k) { return std::make_shared(k); }), - "k"_a) - .def("__repr__", - [](const HuberLoss &rk) { - return std::string("RobustKernel::HuberLoss with k=") + - std::to_string(rk.k_); - }) - .def_readwrite("k", &HuberLoss::k_, "Parameter of the loss"); - - // open3d.registration.CauchyLoss py::class_, PyCauchyLoss, RobustKernel> - c_loss(m, "CauchyLoss", + c_loss(m_registration, "CauchyLoss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is: @@ -217,21 +180,8 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: \frac{1}{1 + \left(\frac{r}{k}\right)^2} \end{equation} )"); - py::detail::bind_copy_functions(c_loss); - c_loss.def(py::init([](double k) { - return std::make_shared(k); - }), - "k"_a) - .def("__repr__", - [](const CauchyLoss &rk) { - return std::string("RobustKernel::CauchyLoss with k=") + - std::to_string(rk.k_); - }) - .def_readwrite("k", &CauchyLoss::k_, "Parameter of the loss."); - - // open3d.registration.GMLoss py::class_, PyGMLoss, RobustKernel> gm_loss( - m, "GMLoss", + m_registration, "GMLoss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is: @@ -249,19 +199,8 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: \frac{k}{\left(k + r^2\right)^2} \end{equation} )"); - py::detail::bind_copy_functions(gm_loss); - gm_loss.def(py::init([](double k) { return std::make_shared(k); }), - "k"_a) - .def("__repr__", - [](const GMLoss &rk) { - return std::string("RobustKernel::GMLoss with k=") + - std::to_string(rk.k_); - }) - .def_readwrite("k", &GMLoss::k_, "Parameter of the loss."); - - // open3d.registration.TukeyLoss:RobustKernel py::class_, PyTukeyLoss, RobustKernel> - t_loss(m, "TukeyLoss", + t_loss(m_registration, "TukeyLoss", R"( The loss :math:`\rho(r)` for a given residual ``r`` is: @@ -285,6 +224,92 @@ The weight :math:`w(r)` for a given residual ``r`` is given by: \end{cases} \end{equation} )"); +} +void pybind_robust_kernels_definitions(py::module &m_registration) { + // open3d.registration.RobustKernel + auto rk = + static_cast, + PyRobustKernel>>( + m_registration.attr("RobustKernel")); + rk.def("weight", &RobustKernel::Weight, "residual"_a, + "Obtain the weight for the given residual according to the " + "robust kernel model."); + docstring::ClassMethodDocInject( + m_registration, "RobustKernel", "weight", + {{"residual", "value obtained during the optimization problem"}}); + + // open3d.registration.L2Loss + auto l2_loss = static_cast, + PyL2Loss, RobustKernel>>( + m_registration.attr("L2Loss")); + py::detail::bind_default_constructor(l2_loss); + py::detail::bind_copy_functions(l2_loss); + l2_loss.def("__repr__", [](const L2Loss &rk) { + (void)rk; + return "RobustKernel::L2Loss"; + }); + + // open3d.registration.L1Loss:RobustKernel + auto l1_loss = static_cast, + PyL1Loss, RobustKernel>>( + m_registration.attr("L1Loss")); + py::detail::bind_default_constructor(l1_loss); + py::detail::bind_copy_functions(l1_loss); + l1_loss.def("__repr__", [](const L1Loss &rk) { + (void)rk; + return "RobustKernel::L1Loss"; + }); + + // open3d.registration.HuberLoss + auto h_loss = static_cast, + PyHuberLoss, RobustKernel>>( + m_registration.attr("HuberLoss")); + py::detail::bind_copy_functions(h_loss); + h_loss.def(py::init( + [](double k) { return std::make_shared(k); }), + "k"_a) + .def("__repr__", + [](const HuberLoss &rk) { + return std::string("RobustKernel::HuberLoss with k=") + + std::to_string(rk.k_); + }) + .def_readwrite("k", &HuberLoss::k_, "Parameter of the loss"); + + // open3d.registration.CauchyLoss + auto c_loss = + static_cast, + PyCauchyLoss, RobustKernel>>( + m_registration.attr("CauchyLoss")); + py::detail::bind_copy_functions(c_loss); + c_loss.def(py::init([](double k) { + return std::make_shared(k); + }), + "k"_a) + .def("__repr__", + [](const CauchyLoss &rk) { + return std::string("RobustKernel::CauchyLoss with k=") + + std::to_string(rk.k_); + }) + .def_readwrite("k", &CauchyLoss::k_, "Parameter of the loss."); + + // open3d.registration.GMLoss + auto gm_loss = static_cast, + PyGMLoss, RobustKernel>>( + m_registration.attr("GMLoss")); + py::detail::bind_copy_functions(gm_loss); + gm_loss.def(py::init([](double k) { return std::make_shared(k); }), + "k"_a) + .def("__repr__", + [](const GMLoss &rk) { + return std::string("RobustKernel::GMLoss with k=") + + std::to_string(rk.k_); + }) + .def_readwrite("k", &GMLoss::k_, "Parameter of the loss."); + + // open3d.registration.TukeyLoss:RobustKernel + auto t_loss = static_cast, + PyTukeyLoss, RobustKernel>>( + m_registration.attr("TukeyLoss")); py::detail::bind_copy_functions(t_loss); t_loss.def(py::init( [](double k) { return std::make_shared(k); }), diff --git a/cpp/pybind/t/geometry/boundingvolume.cpp b/cpp/pybind/t/geometry/boundingvolume.cpp index 946fb9617df..981b8f3dd46 100644 --- a/cpp/pybind/t/geometry/boundingvolume.cpp +++ b/cpp/pybind/t/geometry/boundingvolume.cpp @@ -17,7 +17,7 @@ namespace open3d { namespace t { namespace geometry { -void pybind_boundingvolume(py::module& m) { +void pybind_boundingvolume_declarations(py::module& m) { py::class_, std::shared_ptr, Geometry, DrawableGeometry> @@ -43,6 +43,37 @@ axes. - Value tensor must have shape {3,}. - Value tensor can only be float32 (default) or float64. - Value tensor can only be range [0.0, 1.0].)"); + py::class_, + std::shared_ptr, Geometry, DrawableGeometry> + obb(m, "OrientedBoundingBox", + R"(A bounding box oriented along an arbitrary frame of reference. +- (center, rotation, extent): The oriented bounding box is defined by its +center position, rotation maxtrix and extent. + - Usage + - OrientedBoundingBox::GetCenter() + - OrientedBoundingBox::SetCenter(const core::Tensor ¢er) + - OrientedBoundingBox::GetRotation() + - OrientedBoundingBox::SetRotation(const core::Tensor &rotation) + - Value tensor of center and extent must have shape {3,}. + - Value tensor of rotation must have shape {3, 3}. + - Value tensor must have the same data type and device. + - Value tensor can only be float32 (default) or float64. + - The device of the tensor determines the device of the box. + +- color: Color of the bounding box. + - Usage + - OrientedBoundingBox::GetColor() + - OrientedBoundingBox::SetColor(const core::Tensor &color) + - Value tensor must have shape {3,}. + - Value tensor can only be float32 (default) or float64. + - Value tensor can only be range [0.0, 1.0].)"); +} +void pybind_boundingvolume_definitions(py::module& m) { + auto aabb = static_cast, + std::shared_ptr, + Geometry, DrawableGeometry>>( + m.attr("AxisAlignedBoundingBox")); aabb.def(py::init(), "device"_a = core::Device("CPU:0"), "Construct an empty axis-aligned box on the provided " @@ -50,7 +81,7 @@ axes. aabb.def(py::init(), "min_bound"_a, "max_bound"_a, R"(Construct an axis-aligned box from min/max bound. -The axis-aligned box will be created on the device of the given bound +The axis-aligned box will be created on the device of the given bound tensor, which must be on the same device and have the same data type.)"); docstring::ClassMethodDocInject( m, "AxisAlignedBoundingBox", "__init__", @@ -125,7 +156,7 @@ translation is applied to make the box's center at the given translation.)", aabb.def("scale", &AxisAlignedBoundingBox::Scale, R"(Scale the axis-aligned box. If \f$mi\f$ is the min_bound and \f$ma\f$ is the max_bound of the axis aligned -bounding box, and \f$s\f$ and \f$c\f$ are the provided scaling factor and +bounding box, and \f$s\f$ and \f$c\f$ are the provided scaling factor and center respectively, then the new min_bound and max_bound are given by \f$mi = c + s (mi - c)\f$ and \f$ma = c + s (ma - c)\f$. The scaling center will be the box center if it is not specified.)", @@ -198,38 +229,17 @@ The scaling center will be the box center if it is not specified.)", {{"points", "A list of points with data type of float32 or float64 (N x 3 " "tensor)."}}); - - py::class_, - std::shared_ptr, Geometry, DrawableGeometry> - obb(m, "OrientedBoundingBox", - R"(A bounding box oriented along an arbitrary frame of reference. -- (center, rotation, extent): The oriented bounding box is defined by its -center position, rotation maxtrix and extent. - - Usage - - OrientedBoundingBox::GetCenter() - - OrientedBoundingBox::SetCenter(const core::Tensor ¢er) - - OrientedBoundingBox::GetRotation() - - OrientedBoundingBox::SetRotation(const core::Tensor &rotation) - - Value tensor of center and extent must have shape {3,}. - - Value tensor of rotation must have shape {3, 3}. - - Value tensor must have the same data type and device. - - Value tensor can only be float32 (default) or float64. - - The device of the tensor determines the device of the box. - -- color: Color of the bounding box. - - Usage - - OrientedBoundingBox::GetColor() - - OrientedBoundingBox::SetColor(const core::Tensor &color) - - Value tensor must have shape {3,}. - - Value tensor can only be float32 (default) or float64. - - Value tensor can only be range [0.0, 1.0].)"); + auto obb = static_cast, + std::shared_ptr, Geometry, DrawableGeometry>>( + m.attr("OrientedBoundingBox")); obb.def(py::init(), "device"_a = core::Device("CPU:0"), "Construct an empty OrientedBoundingBox on the provided device."); obb.def(py::init(), "center"_a, "rotation"_a, "extent"_a, - R"(Construct an OrientedBoundingBox from center, rotation and extent. -The OrientedBoundingBox will be created on the device of the given tensors, which + R"(Construct an OrientedBoundingBox from center, rotation and extent. +The OrientedBoundingBox will be created on the device of the given tensors, which must be on the same device and have the same data type.)"); docstring::ClassMethodDocInject( m, "OrientedBoundingBox", "__init__", @@ -295,7 +305,7 @@ must be on the same device and have the same data type.)"); obb.def("translate", &OrientedBoundingBox::Translate, R"(Translate the oriented box by the given translation. If relative is true, the translation is -added to the center of the box. If false, the center will be assigned to the +added to the center of the box. If false, the center will be assigned to the translation.)", "translation"_a, "relative"_a = true); obb.def("rotate", &OrientedBoundingBox::Rotate, @@ -309,7 +319,7 @@ The rotation center will be the box center if it is not specified.)", obb.def("scale", &OrientedBoundingBox::Scale, R"(Scale the axis-aligned box. If \f$mi\f$ is the min_bound and \f$ma\f$ is the max_bound of the axis aligned -bounding box, and \f$s\f$ and \f$c\f$ are the provided scaling factor and +bounding box, and \f$s\f$ and \f$c\f$ are the provided scaling factor and center respectively, then the new min_bound and max_bound are given by \f$mi = c + s (mi - c)\f$ and \f$ma = c + s (ma - c)\f$. The scaling center will be the box center if it is not specified.)", @@ -341,7 +351,7 @@ The scaling center will be the box center if it is not specified.)", obb.def_static("create_from_points", &OrientedBoundingBox::CreateFromPoints, R"(Creates an oriented bounding box using a PCA. Note that this is only an approximation to the minimum oriented bounding box -that could be computed for example with O'Rourke's algorithm +that could be computed for example with O'Rourke's algorithm (cf. http://cs.smith.edu/~jorourke/Papers/MinVolBox.pdf, https://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf) This is a wrapper for a CPU implementation.)", "points"_a, "robust"_a = false); diff --git a/cpp/pybind/t/geometry/drawablegeometry.cpp b/cpp/pybind/t/geometry/drawablegeometry.cpp index 7d4a11493fc..57ef37b2f3a 100644 --- a/cpp/pybind/t/geometry/drawablegeometry.cpp +++ b/cpp/pybind/t/geometry/drawablegeometry.cpp @@ -13,12 +13,17 @@ namespace open3d { namespace t { namespace geometry { -void pybind_drawable_geometry_class(py::module& m) { - // open3d.t.geometry.DrawableGeometry +void pybind_drawable_geometry_class_declarations(py::module& m) { py::class_> drawable_geometry( m, "DrawableGeometry", "Base class for geometry types which can be visualized."); +} +void pybind_drawable_geometry_class_definitions(py::module& m) { + // open3d.t.geometry.DrawableGeometry + auto drawable_geometry = static_cast< + py::class_>>( + m.attr("DrawableGeometry")); drawable_geometry.def("has_valid_material", &DrawableGeometry::HasMaterial, "Returns true if the geometry's material is valid."); drawable_geometry.def_property( diff --git a/cpp/pybind/t/geometry/geometry.cpp b/cpp/pybind/t/geometry/geometry.cpp index 9563fbb215f..47c13bebc25 100644 --- a/cpp/pybind/t/geometry/geometry.cpp +++ b/cpp/pybind/t/geometry/geometry.cpp @@ -14,11 +14,16 @@ namespace open3d { namespace t { namespace geometry { -void pybind_geometry_class(py::module& m) { - // open3d.t.geometry.Geometry +void pybind_geometry_class_declarations(py::module& m) { py::class_, std::shared_ptr> geometry(m, "Geometry", "The base geometry class."); +} +void pybind_geometry_class_definitions(py::module& m) { + // open3d.t.geometry.Geometry + auto geometry = static_cast, + std::shared_ptr>>( + m.attr("Geometry")); geometry.def("clear", &Geometry::Clear, "Clear all elements in the geometry."); geometry.def("is_empty", &Geometry::IsEmpty, @@ -33,20 +38,34 @@ void pybind_geometry_class(py::module& m) { docstring::ClassMethodDocInject(m, "Geometry", "is_empty"); } -void pybind_geometry(py::module& m) { - py::module m_submodule = m.def_submodule( +void pybind_geometry_declarations(py::module& m) { + py::module m_geometry = m.def_submodule( "geometry", "Tensor-based geometry defining module."); - pybind_geometry_class(m_submodule); - pybind_drawable_geometry_class(m_submodule); - pybind_tensormap(m_submodule); - pybind_pointcloud(m_submodule); - pybind_lineset(m_submodule); - pybind_trianglemesh(m_submodule); - pybind_image(m_submodule); - pybind_boundingvolume(m_submodule); - pybind_voxel_block_grid(m_submodule); - pybind_raycasting_scene(m_submodule); + pybind_geometry_class_declarations(m_geometry); + pybind_drawable_geometry_class_declarations(m_geometry); + pybind_tensormap_declarations(m_geometry); + pybind_pointcloud_declarations(m_geometry); + pybind_lineset_declarations(m_geometry); + pybind_trianglemesh_declarations(m_geometry); + pybind_image_declarations(m_geometry); + pybind_boundingvolume_declarations(m_geometry); + pybind_voxel_block_grid_declarations(m_geometry); + pybind_raycasting_scene_declarations(m_geometry); +} + +void pybind_geometry_definitions(py::module& m) { + auto m_geometry = static_cast(m.attr("geometry")); + pybind_geometry_class_definitions(m_geometry); + pybind_drawable_geometry_class_definitions(m_geometry); + pybind_tensormap_definitions(m_geometry); + pybind_pointcloud_definitions(m_geometry); + pybind_lineset_definitions(m_geometry); + pybind_trianglemesh_definitions(m_geometry); + pybind_image_definitions(m_geometry); + pybind_boundingvolume_definitions(m_geometry); + pybind_voxel_block_grid_definitions(m_geometry); + pybind_raycasting_scene_definitions(m_geometry); } } // namespace geometry diff --git a/cpp/pybind/t/geometry/geometry.h b/cpp/pybind/t/geometry/geometry.h index 6e1271083df..a1c9f81ea62 100644 --- a/cpp/pybind/t/geometry/geometry.h +++ b/cpp/pybind/t/geometry/geometry.h @@ -34,18 +34,29 @@ class PyGeometry : public GeometryBase { } }; -void pybind_geometry(py::module& m); -void pybind_geometry_class(py::module& m); -void pybind_drawable_geometry_class(py::module& m); -void pybind_tensormap(py::module& m); -void pybind_image(py::module& m); -void pybind_pointcloud(py::module& m); -void pybind_lineset(py::module& m); -void pybind_trianglemesh(py::module& m); -void pybind_image(py::module& m); -void pybind_boundingvolume(py::module& m); -void pybind_voxel_block_grid(py::module& m); -void pybind_raycasting_scene(py::module& m); +void pybind_geometry_declarations(py::module& m); +void pybind_geometry_class_declarations(py::module& m); +void pybind_drawable_geometry_class_declarations(py::module& m); +void pybind_tensormap_declarations(py::module& m); +void pybind_pointcloud_declarations(py::module& m); +void pybind_lineset_declarations(py::module& m); +void pybind_trianglemesh_declarations(py::module& m); +void pybind_image_declarations(py::module& m); +void pybind_boundingvolume_declarations(py::module& m); +void pybind_voxel_block_grid_declarations(py::module& m); +void pybind_raycasting_scene_declarations(py::module& m); + +void pybind_geometry_definitions(py::module& m); +void pybind_geometry_class_definitions(py::module& m); +void pybind_drawable_geometry_class_definitions(py::module& m); +void pybind_tensormap_definitions(py::module& m); +void pybind_pointcloud_definitions(py::module& m); +void pybind_lineset_definitions(py::module& m); +void pybind_trianglemesh_definitions(py::module& m); +void pybind_image_definitions(py::module& m); +void pybind_boundingvolume_definitions(py::module& m); +void pybind_voxel_block_grid_definitions(py::module& m); +void pybind_raycasting_scene_definitions(py::module& m); } // namespace geometry } // namespace t diff --git a/cpp/pybind/t/geometry/image.cpp b/cpp/pybind/t/geometry/image.cpp index b9fe3c20725..6f42c4eca35 100644 --- a/cpp/pybind/t/geometry/image.cpp +++ b/cpp/pybind/t/geometry/image.cpp @@ -53,12 +53,11 @@ static const std::unordered_map {"distance_sigma", "Standard deviation for the image pixel positions."}}; -void pybind_image(py::module &m) { +void pybind_image_declarations(py::module &m) { py::class_, std::shared_ptr, Geometry> image(m, "Image", py::buffer_protocol(), "The Image class stores image with customizable rols, cols, " "channels, dtype and device."); - py::enum_(m, "InterpType", "Interpolation type.") .value("Nearest", Image::InterpType::Nearest) .value("Linear", Image::InterpType::Linear) @@ -66,7 +65,19 @@ void pybind_image(py::module &m) { .value("Lanczos", Image::InterpType::Lanczos) .value("Super", Image::InterpType::Super) .export_values(); - + py::class_, std::shared_ptr, + Geometry> + rgbd_image( + m, "RGBDImage", + "RGBDImage is a pair of color and depth images. For most " + "processing, the image pair should be aligned (same " + "viewpoint and " + "resolution)."); +} +void pybind_image_definitions(py::module &m) { + auto image = static_cast, + std::shared_ptr, Geometry>>( + m.attr("Image")); // Constructors image.def(py::init(), "Row-major storage is used, similar to OpenCV. Use (row, col, " @@ -266,15 +277,10 @@ void pybind_image(py::module &m) { docstring::ClassMethodDocInject(m, "Image", "clear"); docstring::ClassMethodDocInject(m, "Image", "is_empty"); docstring::ClassMethodDocInject(m, "Image", "to_legacy"); - - py::class_, std::shared_ptr, - Geometry> - rgbd_image( - m, "RGBDImage", - "RGBDImage is a pair of color and depth images. For most " - "processing, the image pair should be aligned (same " - "viewpoint and " - "resolution)."); + auto rgbd_image = + static_cast, + std::shared_ptr, Geometry>>( + m.attr("RGBDImage")); rgbd_image // Constructors. .def(py::init<>(), "Construct an empty RGBDImage.") diff --git a/cpp/pybind/t/geometry/lineset.cpp b/cpp/pybind/t/geometry/lineset.cpp index 299deef36b0..fe5bdb8ad33 100644 --- a/cpp/pybind/t/geometry/lineset.cpp +++ b/cpp/pybind/t/geometry/lineset.cpp @@ -19,7 +19,7 @@ namespace open3d { namespace t { namespace geometry { -void pybind_lineset(py::module& m) { +void pybind_lineset_declarations(py::module& m) { py::class_, std::shared_ptr, Geometry, DrawableGeometry> line_set(m, "LineSet", R"( @@ -72,7 +72,12 @@ The attributes of the line set have different levels:: lineset.point.labels = o3d.core.Tensor(...) lineset.line.features = o3d.core.Tensor(...) )"); +} +void pybind_lineset_definitions(py::module& m) { + auto line_set = static_cast< + py::class_, std::shared_ptr, + Geometry, DrawableGeometry>>(m.attr("LineSet")); // Constructors. line_set.def(py::init(), "device"_a = core::Device("CPU:0"), diff --git a/cpp/pybind/t/geometry/pointcloud.cpp b/cpp/pybind/t/geometry/pointcloud.cpp index a36813fd353..ddcfd89d788 100644 --- a/cpp/pybind/t/geometry/pointcloud.cpp +++ b/cpp/pybind/t/geometry/pointcloud.cpp @@ -47,7 +47,7 @@ static const std::unordered_map "neighbors search radius parameter to use HybridSearch. " "[Recommended ~1.4x voxel size]."}}; -void pybind_pointcloud(py::module& m) { +void pybind_pointcloud_declarations(py::module& m) { py::class_, std::shared_ptr, Geometry, DrawableGeometry> pointcloud(m, "PointCloud", @@ -95,7 +95,12 @@ The attributes of the point cloud have different levels:: pcd.point.intensities = o3d.core.Tensor([0.3, 0.1, 0.4], dtype, device) pcd.point.labels = o3d.core.Tensor([3, 1, 4], o3d.core.int32, device) )"); - +} +void pybind_pointcloud_definitions(py::module& m) { + auto pointcloud = + static_cast, + std::shared_ptr, Geometry, + DrawableGeometry>>(m.attr("PointCloud")); // Constructors. pointcloud .def(py::init(), @@ -252,11 +257,10 @@ The attributes of the point cloud have different levels:: "by selecting the farthest point from previous selected " "points iteratively", "num_samples"_a); - pointcloud.def( - "remove_radius_outliers", &PointCloud::RemoveRadiusOutliers, - "nb_points"_a, "search_radius"_a, - R"(Remove points that have less than nb_points neighbors in a -sphere of a given search radius. + pointcloud.def("remove_radius_outliers", &PointCloud::RemoveRadiusOutliers, + "nb_points"_a, "search_radius"_a, + R"(Remove points that have less than nb_points neighbors in a +sphere of a given search radius. Args: nb_points: Number of neighbor points required within the radius. @@ -270,7 +274,7 @@ sphere of a given search radius. &PointCloud::RemoveStatisticalOutliers, "nb_neighbors"_a, "std_ratio"_a, R"(Remove points that are further away from their \p nb_neighbor -neighbors in average. This function is not recommended to use on GPU. +neighbors in average. This function is not recommended to use on GPU. Args: nb_neighbors: Number of neighbors around the target point. @@ -285,9 +289,9 @@ neighbors in average. This function is not recommended to use on GPU. pointcloud.def( "remove_non_finite_points", &PointCloud::RemoveNonFinitePoints, "remove_nan"_a = true, "remove_infinite"_a = true, - R"(Remove all points from the point cloud that have a nan entry, or -infinite value. It also removes the corresponding attributes. - + R"(Remove all points from the point cloud that have a nan entry, or +infinite value. It also removes the corresponding attributes. + Args: remove_nan: Remove NaN values from the PointCloud. remove_infinite: Remove infinite values from the PointCloud. @@ -326,31 +330,31 @@ infinite value. It also removes the corresponding attributes. "lambda"_a = 0.0, "cos_alpha_tol"_a = 1.0, R"(Function to consistently orient the normals of a point cloud based on tangent planes. -The algorithm is described in Hoppe et al., "Surface Reconstruction from Unorganized Points", 1992. +The algorithm is described in Hoppe et al., "Surface Reconstruction from Unorganized Points", 1992. Additional information about the choice of lambda and cos_alpha_tol for complex -point clouds can be found in Piazza, Valentini, Varetti, "Mesh Reconstruction from Point Cloud", 2023 +point clouds can be found in Piazza, Valentini, Varetti, "Mesh Reconstruction from Point Cloud", 2023 (https://eugeniovaretti.github.io/meshreco/Piazza_Valentini_Varetti_MeshReconstructionFromPointCloud_2023.pdf). Args: k (int): Number of neighbors to use for tangent plane estimation. - lambda (float): A non-negative real parameter that influences the distance - metric used to identify the true neighbors of a point in complex - geometries. It penalizes the distance between a point and the tangent - plane defined by the reference point and its normal vector, helping to - mitigate misclassification issues encountered with traditional + lambda (float): A non-negative real parameter that influences the distance + metric used to identify the true neighbors of a point in complex + geometries. It penalizes the distance between a point and the tangent + plane defined by the reference point and its normal vector, helping to + mitigate misclassification issues encountered with traditional Euclidean distance metrics. - cos_alpha_tol (float): Cosine threshold angle used to determine the - inclusion boundary of neighbors based on the direction of the normal + cos_alpha_tol (float): Cosine threshold angle used to determine the + inclusion boundary of neighbors based on the direction of the normal vector. Example: We use Bunny point cloud to compute its normals and orient them consistently. - The initial reconstruction adheres to Hoppe's algorithm (raw), whereas the - second reconstruction utilises the lambda and cos_alpha_tol parameters. + The initial reconstruction adheres to Hoppe's algorithm (raw), whereas the + second reconstruction utilises the lambda and cos_alpha_tol parameters. Due to the high density of the Bunny point cloud available in Open3D a larger - value of the parameter k is employed to test the algorithm. Usually you do - not have at disposal such a refined point clouds, thus you cannot find a - proper choice of k: refer to + value of the parameter k is employed to test the algorithm. Usually you do + not have at disposal such a refined point clouds, thus you cannot find a + proper choice of k: refer to https://eugeniovaretti.github.io/meshreco for these cases.:: import open3d as o3d @@ -690,13 +694,13 @@ The implementation is inspired by the PCL implementation. Reference: Args: angle (float): The rotation angle in degree. - + axis (open3d.core.Tensor): The rotation axis. - + resolution (int): The resolution defines the number of intermediate sweeps about the rotation axis. - translation (float): The translation along the rotation axis. + translation (float): The translation along the rotation axis. Returns: A line set with the result of the sweep operation. @@ -719,9 +723,9 @@ The implementation is inspired by the PCL implementation. Reference: R"(Sweeps the point cloud along a direction vector. Args: - + vector (open3d.core.Tensor): The direction vector. - + scale (float): Scalar factor which essentially scales the direction vector. Returns: @@ -731,7 +735,7 @@ The implementation is inspired by the PCL implementation. Reference: Example: This code generates a set of straight lines from a point cloud:: - + import open3d as o3d import numpy as np pcd = o3d.t.geometry.PointCloud(np.random.rand(10,3)) @@ -743,7 +747,7 @@ The implementation is inspired by the PCL implementation. Reference: pointcloud.def("pca_partition", &PointCloud::PCAPartition, "max_points"_a, R"(Partition the point cloud by recursively doing PCA. -This function creates a new point attribute with the name "partition_ids" storing +This function creates a new point attribute with the name "partition_ids" storing the partition id for each point. Args: diff --git a/cpp/pybind/t/geometry/raycasting_scene.cpp b/cpp/pybind/t/geometry/raycasting_scene.cpp index e7f6dcecbd5..b3e8983aaef 100644 --- a/cpp/pybind/t/geometry/raycasting_scene.cpp +++ b/cpp/pybind/t/geometry/raycasting_scene.cpp @@ -13,7 +13,7 @@ namespace open3d { namespace t { namespace geometry { -void pybind_raycasting_scene(py::module& m) { +void pybind_raycasting_scene_declarations(py::module& m) { py::class_ raycasting_scene(m, "RaycastingScene", R"doc( A scene class with basic ray casting and closest point queries. @@ -52,7 +52,11 @@ The following shows how to create a scene and compute ray intersections:: plt.imshow(ans['t_hit'].numpy()) )doc"); +} +void pybind_raycasting_scene_definitions(py::module& m) { + auto raycasting_scene = + static_cast>(m.attr("RaycastingScene")); // Constructors. raycasting_scene.def(py::init(), "nthreads"_a = 0, R"doc( Create a RaycastingScene. @@ -219,7 +223,7 @@ Lists the intersections of the rays with the scene:: # Calculate intersection coordinates using ray_ids c = rays[lx['ray_ids']][:,:3] + rays[lx['ray_ids']][:,3:]*lx['t_hit'][...,None] - + # Visualize the rays and intersections. lines = o3d.t.geometry.LineSet() lines.point.positions = np.hstack([orig,dest]).reshape(-1,3) @@ -234,35 +238,35 @@ Lists the intersections of the rays with the scene:: Float32 describing the rays; {..} can be any number of dimensions. The last dimension must be 6 and has the format [ox, oy, oz, dx, dy, dz] with [ox,oy,oz] as the origin and [dx,dy,dz] as the direction. It is not - necessary to normalize the direction although it should be normalised if + necessary to normalize the direction although it should be normalised if t_hit is to be calculated in coordinate units. nthreads (int): The number of threads to use. Set to 0 for automatic. Returns: The returned dictionary contains - + ray_splits A tensor with ray intersection splits. Can be used to iterate over all intersections for each ray. The shape is {num_rays + 1}. - + ray_ids A tensor with ray IDs. The shape is {num_intersections}. - + t_hit - A tensor with the distance to the hit. The shape is {num_intersections}. - + A tensor with the distance to the hit. The shape is {num_intersections}. + geometry_ids A tensor with the geometry IDs. The shape is {num_intersections}. primitive_ids A tensor with the primitive IDs, which corresponds to the triangle index. The shape is {num_intersections}. - - primitive_uvs - A tensor with the barycentric coordinates of the intersection points within + + primitive_uvs + A tensor with the barycentric coordinates of the intersection points within the triangles. The shape is {num_intersections, 2}. - - + + An example of using ray_splits:: ray_splits: [0, 2, 3, 6, 6, 8] # note that the length of this is num_rays+1 @@ -271,8 +275,8 @@ An example of using ray_splits:: for ray_id, (start, end) in enumerate(zip(ray_splits[:-1], ray_splits[1:])): for i,t in enumerate(t_hit[start:end]): print(f'ray {ray_id}, intersection {i} at {t}') - - + + )doc"); raycasting_scene.def("compute_closest_points", @@ -302,12 +306,12 @@ Computes the closest points on the surfaces of the scene. A tensor with the primitive IDs, which corresponds to the triangle index. The shape is {..}. - primitive_uvs - A tensor with the barycentric coordinates of the closest points within + primitive_uvs + A tensor with the barycentric coordinates of the closest points within the triangles. The shape is {.., 2}. - primitive_normals - A tensor with the normals of the closest triangle . The shape is + primitive_normals + A tensor with the normals of the closest triangle . The shape is {.., 3}. )doc"); @@ -353,7 +357,7 @@ the intersections of a rays starting at the query points. nsamples (int): The number of rays used for determining the inside. This must be an odd number. The default is 1. Use a higher value if you - notice sign flipping, which can occur when rays hit exactly an edge or + notice sign flipping, which can occur when rays hit exactly an edge or vertex in the scene. Returns: diff --git a/cpp/pybind/t/geometry/tensormap.cpp b/cpp/pybind/t/geometry/tensormap.cpp index 36fea31b472..45882dc3115 100644 --- a/cpp/pybind/t/geometry/tensormap.cpp +++ b/cpp/pybind/t/geometry/tensormap.cpp @@ -116,7 +116,11 @@ static py::class_ bind_tensor_map(py::handle scope, return cl; } -void pybind_tensormap(py::module &m) { +void pybind_tensormap_declarations(py::module &m) { + auto tm = bind_tensor_map( + m, "TensorMap", "Map of String to Tensor with a primary key."); +} +void pybind_tensormap_definitions(py::module &m) { // Bind to the generic dictionary interface such that it works the same as a // regular dictionary in Python, except that types are enforced. Supported // functions include `__bool__`, `__iter__`, `items`, `__getitem__`, @@ -124,9 +128,8 @@ void pybind_tensormap(py::module &m) { // The `__delitem__` function is removed from bind_map, in bind_tensor_map, // and defined in this function, to use TensorMap::Erase, in order to // protect users from deleting the `private_key`. - auto tm = bind_tensor_map( - m, "TensorMap", "Map of String to Tensor with a primary key."); - + auto tm = static_cast>>( + m.attr("TensorMap")); tm.def("__delitem__", [](TensorMap &m, const std::string &k) { return m.Erase(k); }); diff --git a/cpp/pybind/t/geometry/trianglemesh.cpp b/cpp/pybind/t/geometry/trianglemesh.cpp index 783e7c0489c..5c2de11fa18 100644 --- a/cpp/pybind/t/geometry/trianglemesh.cpp +++ b/cpp/pybind/t/geometry/trianglemesh.cpp @@ -19,7 +19,7 @@ namespace open3d { namespace t { namespace geometry { -void pybind_trianglemesh(py::module& m) { +void pybind_trianglemesh_declarations(py::module& m) { py::class_, std::shared_ptr, Geometry, DrawableGeometry> triangle_mesh(m, "TriangleMesh", @@ -77,7 +77,13 @@ The attributes of the triangle mesh have different levels:: pcd.vertex.labels = o3d.core.Tensor(...) pcd.triangle.features = o3d.core.Tensor(...) )"); +} +void pybind_trianglemesh_definitions(py::module& m) { + auto triangle_mesh = + static_cast, + std::shared_ptr, Geometry, + DrawableGeometry>>(m.attr("TriangleMesh")); // Constructors. triangle_mesh .def(py::init(), diff --git a/cpp/pybind/t/geometry/voxel_block_grid.cpp b/cpp/pybind/t/geometry/voxel_block_grid.cpp index ff47ffa0e85..9928fcde83f 100644 --- a/cpp/pybind/t/geometry/voxel_block_grid.cpp +++ b/cpp/pybind/t/geometry/voxel_block_grid.cpp @@ -17,14 +17,17 @@ namespace open3d { namespace t { namespace geometry { -void pybind_voxel_block_grid(py::module& m) { +void pybind_voxel_block_grid_declarations(py::module& m) { py::class_ vbg( m, "VoxelBlockGrid", "A voxel block grid is a sparse grid of voxel blocks. Each voxel " "block is a dense 3D array, preserving local data distribution. If " "the block_resolution is set to 1, then the VoxelBlockGrid " "degenerates to a sparse voxel grid."); - +} +void pybind_voxel_block_grid_definitions(py::module& m) { + auto vbg = + static_cast>(m.attr("VoxelBlockGrid")); vbg.def(py::init&, const std::vector&, const std::vector&, float, int64_t, diff --git a/cpp/pybind/t/io/class_io.cpp b/cpp/pybind/t/io/class_io.cpp index 9ec88f08475..756187a8aa4 100644 --- a/cpp/pybind/t/io/class_io.cpp +++ b/cpp/pybind/t/io/class_io.cpp @@ -67,7 +67,35 @@ static const std::unordered_map "If set to true a progress bar is visualized in the console."}, }; -void pybind_class_io(py::module &m_io) { +void pybind_class_io_declarations(py::module &m_io) { + py::class_ depth_noise_simulator( + m_io, "DepthNoiseSimulator", + R"(Simulate depth image noise from a given noise distortion model. The distortion model is based on *Teichman et. al. "Unsupervised intrinsic calibration of depth sensors via SLAM" RSS 2009*. Also see __ + +Example:: + + import open3d as o3d + + # Redwood Indoor LivingRoom1 (Augmented ICL-NUIM) + # http://redwood-data.org/indoor/ + data = o3d.data.RedwoodIndoorLivingRoom1() + noise_model_path = data.noise_model_path + im_src_path = data.depth_paths[0] + depth_scale = 1000.0 + + # Read clean depth image (uint16) + im_src = o3d.t.io.read_image(im_src_path) + + # Run noise model simulation + simulator = o3d.t.io.DepthNoiseSimulator(noise_model_path) + im_dst = simulator.simulate(im_src, depth_scale=depth_scale) + + # Save noisy depth image (uint16) + o3d.t.io.write_image("noisy_depth.png", im_dst) + )"); +} + +void pybind_class_io_definitions(py::module &m_io) { // open3d::t::geometry::Image m_io.def( "read_image", @@ -153,25 +181,25 @@ The following example reads a triangle mesh with the .ply extension:: Args: filename (str): Path to the mesh file. - enable_post_processing (bool): If True enables post-processing. - Post-processing will + enable_post_processing (bool): If True enables post-processing. + Post-processing will - triangulate meshes with polygonal faces - remove redundant materials - pretransform vertices - generate face normals if needed - + For more information see ASSIMPs documentation on the flags - `aiProcessPreset_TargetRealtime_Fast, aiProcess_RemoveRedundantMaterials, + `aiProcessPreset_TargetRealtime_Fast, aiProcess_RemoveRedundantMaterials, aiProcess_OptimizeMeshes, aiProcess_PreTransformVertices`. - + Note that identical vertices will always be joined regardless of whether - post-processing is enabled or not, which changes the number of vertices + post-processing is enabled or not, which changes the number of vertices in the mesh. The `ply`-format is not affected by the post-processing. print_progress (bool): If True print the reading progress to the terminal. - + Returns: Returns the mesh object. On failure an empty mesh is returned. )doc"); @@ -196,31 +224,8 @@ The following example reads a triangle mesh with the .ply extension:: map_shared_argument_docstrings); // DepthNoiseSimulator - py::class_ depth_noise_simulator( - m_io, "DepthNoiseSimulator", - R"(Simulate depth image noise from a given noise distortion model. The distortion model is based on *Teichman et. al. "Unsupervised intrinsic calibration of depth sensors via SLAM" RSS 2009*. Also see __ - -Example:: - - import open3d as o3d - - # Redwood Indoor LivingRoom1 (Augmented ICL-NUIM) - # http://redwood-data.org/indoor/ - data = o3d.data.RedwoodIndoorLivingRoom1() - noise_model_path = data.noise_model_path - im_src_path = data.depth_paths[0] - depth_scale = 1000.0 - - # Read clean depth image (uint16) - im_src = o3d.t.io.read_image(im_src_path) - - # Run noise model simulation - simulator = o3d.t.io.DepthNoiseSimulator(noise_model_path) - im_dst = simulator.simulate(im_src, depth_scale=depth_scale) - - # Save noisy depth image (uint16) - o3d.t.io.write_image("noisy_depth.png", im_dst) - )"); + auto depth_noise_simulator = static_cast>( + m_io.attr("DepthNoiseSimulator")); depth_noise_simulator.def(py::init([](const fs::path &fielname) { return DepthNoiseSimulator(fielname.string()); }), diff --git a/cpp/pybind/t/io/io.cpp b/cpp/pybind/t/io/io.cpp index 5cbdb441879..8282c3a94fb 100644 --- a/cpp/pybind/t/io/io.cpp +++ b/cpp/pybind/t/io/io.cpp @@ -13,11 +13,16 @@ namespace open3d { namespace t { namespace io { -void pybind_io(py::module& m) { +void pybind_io_declarations(py::module& m) { py::module m_io = m.def_submodule("io", "Tensor-based input-output handling module."); - pybind_class_io(m_io); - pybind_sensor(m_io); + pybind_class_io_declarations(m_io); + pybind_sensor_declarations(m_io); +} +void pybind_io_definitions(py::module& m) { + auto m_io = static_cast(m.attr("io")); + pybind_class_io_definitions(m_io); + pybind_sensor_definitions(m_io); } } // namespace io diff --git a/cpp/pybind/t/io/io.h b/cpp/pybind/t/io/io.h index dc3aef9327a..84561452aa3 100644 --- a/cpp/pybind/t/io/io.h +++ b/cpp/pybind/t/io/io.h @@ -13,9 +13,13 @@ namespace open3d { namespace t { namespace io { -void pybind_io(py::module& m); -void pybind_class_io(py::module& m); -void pybind_sensor(py::module& m); +void pybind_io_declarations(py::module& m); +void pybind_class_io_declarations(py::module& m); +void pybind_sensor_declarations(py::module& m); + +void pybind_io_definitions(py::module& m); +void pybind_class_io_definitions(py::module& m); +void pybind_sensor_definitions(py::module& m); } // namespace io } // namespace t diff --git a/cpp/pybind/t/io/sensor.cpp b/cpp/pybind/t/io/sensor.cpp index 241d763d3cc..b0d9cf13cc2 100644 --- a/cpp/pybind/t/io/sensor.cpp +++ b/cpp/pybind/t/io/sensor.cpp @@ -22,7 +22,98 @@ namespace open3d { namespace t { namespace io { -void pybind_sensor(py::module &m) { +// RGBD video reader trampoline +class PyRGBDVideoReader : public RGBDVideoReader { +public: + using RGBDVideoReader::RGBDVideoReader; + bool IsOpened() const override { + PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, ); + } + + bool IsEOF() const override { + PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, ); + } + + bool Open(const std::string &filename) override { + PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, Open, filename); + } + + void Close() override { PYBIND11_OVERLOAD_PURE(void, RGBDVideoReader, ); } + + RGBDVideoMetadata &GetMetadata() override { + PYBIND11_OVERLOAD_PURE(RGBDVideoMetadata &, RGBDVideoReader, ); + } + + const RGBDVideoMetadata &GetMetadata() const override { + PYBIND11_OVERLOAD_PURE(const RGBDVideoMetadata &, RGBDVideoReader, ); + } + + bool SeekTimestamp(uint64_t timestamp) override { + PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, SeekTimestamp, timestamp); + } + + uint64_t GetTimestamp() const override { + PYBIND11_OVERLOAD_PURE(uint64_t, RGBDVideoReader, ); + } + + t::geometry::RGBDImage NextFrame() override { + PYBIND11_OVERLOAD_PURE(t::geometry::RGBDImage, RGBDVideoReader, ); + } + + std::string GetFilename() const override { + PYBIND11_OVERLOAD_PURE(std::string, RGBDVideoReader, ); + } +}; + +void pybind_sensor_declarations(py::module &m) { + py::enum_(m, "SensorType", "Sensor type") + .value("AZURE_KINECT", SensorType::AZURE_KINECT) + .value("REAL_SENSE", SensorType::REAL_SENSE); + py::class_ rgbd_video_metadata(m, "RGBDVideoMetadata", + "RGBD Video metadata."); + py::class_> + rgbd_video_reader(m, "RGBDVideoReader", "RGBD Video file reader."); + py::class_ rgbd_sensor( + m, "RGBDSensor", "Interface class for control of RGBD cameras."); +#ifdef BUILD_LIBREALSENSE + py::class_, RGBDVideoReader> + rs_bag_reader( + m, "RSBagReader", + "RealSense Bag file reader.\n" + "\tOnly the first color and depth streams from the bag " + "file will be read.\n" + " - The streams must have the same frame rate.\n" + " - The color stream must have RGB 8 bit (RGB8/BGR8) pixel " + "format\n" + " - The depth stream must have 16 bit unsigned int (Z16) " + "pixel format\n" + "The output is synchronized color and depth frame pairs " + "with the depth frame aligned to the color frame. " + "Unsynchronized frames will be dropped. With alignment, " + "the depth and color frames have the same viewpoint and " + "resolution. See format documentation `here " + "`__" + "\n\n" + ".. warning:: A few frames may be dropped if user code " + "takes a long time (>10 frame intervals) to process a " + "frame."); + py::class_ realsense_sensor_config( + m, "RealSenseSensorConfig", "Configuration for a RealSense camera"); + py::class_ realsense_valid_configs( + m, "RealSenseValidConfigs", + "Store set of valid configuration options for a connected " + "RealSense device. From this structure, a user can construct a " + "RealSenseSensorConfig object meeting their specifications."); + py::class_ realsense_sensor( + m, "RealSenseSensor", + "RealSense camera discovery, configuration, streaming and " + "recording"); +#endif +} + +void pybind_sensor_definitions(py::module &m) { static const std::unordered_map map_shared_argument_docstrings = { {"timestamp", "Timestamp in the video (usec)."}, @@ -39,13 +130,9 @@ void pybind_sensor(py::module &m) { "Size of internal frame buffer, increase this if you " "experience frame drops."}}; - py::enum_(m, "SensorType", "Sensor type") - .value("AZURE_KINECT", SensorType::AZURE_KINECT) - .value("REAL_SENSE", SensorType::REAL_SENSE); - // Class RGBD video metadata - py::class_ rgbd_video_metadata(m, "RGBDVideoMetadata", - "RGBD Video metadata."); + auto rgbd_video_metadata = static_cast>( + m.attr("RGBDVideoMetadata")); rgbd_video_metadata.def(py::init<>()) .def_readwrite("intrinsics", &RGBDVideoMetadata::intrinsics_, "Shared intrinsics between RGB & depth") @@ -78,57 +165,11 @@ void pybind_sensor(py::module &m) { "Number of color channels.") .def("__repr__", &RGBDVideoMetadata::ToString); - // RGBD video reader trampoline - class PyRGBDVideoReader : public RGBDVideoReader { - public: - using RGBDVideoReader::RGBDVideoReader; - bool IsOpened() const override { - PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, ); - } - - bool IsEOF() const override { - PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, ); - } - - bool Open(const std::string &filename) override { - PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, Open, filename); - } - - void Close() override { - PYBIND11_OVERLOAD_PURE(void, RGBDVideoReader, ); - } - - RGBDVideoMetadata &GetMetadata() override { - PYBIND11_OVERLOAD_PURE(RGBDVideoMetadata &, RGBDVideoReader, ); - } - - const RGBDVideoMetadata &GetMetadata() const override { - PYBIND11_OVERLOAD_PURE(const RGBDVideoMetadata &, - RGBDVideoReader, ); - } - - bool SeekTimestamp(uint64_t timestamp) override { - PYBIND11_OVERLOAD_PURE(bool, RGBDVideoReader, SeekTimestamp, - timestamp); - } - - uint64_t GetTimestamp() const override { - PYBIND11_OVERLOAD_PURE(uint64_t, RGBDVideoReader, ); - } - - t::geometry::RGBDImage NextFrame() override { - PYBIND11_OVERLOAD_PURE(t::geometry::RGBDImage, RGBDVideoReader, ); - } - - std::string GetFilename() const override { - PYBIND11_OVERLOAD_PURE(std::string, RGBDVideoReader, ); - } - }; - // Class RGBD video reader - py::class_> - rgbd_video_reader(m, "RGBDVideoReader", "RGBD Video file reader."); + auto rgbd_video_reader = + static_cast>>( + m.attr("RGBDVideoReader")); rgbd_video_reader.def(py::init<>()) .def_static( "create", @@ -147,34 +188,15 @@ void pybind_sensor(py::module &m) { map_shared_argument_docstrings); // Class RGBD sensor - py::class_ rgbd_sensor( - m, "RGBDSensor", "Interface class for control of RGBD cameras."); + auto rgbd_sensor = + static_cast>(m.attr("RGBDSensor")); rgbd_sensor.def("__repr__", &RGBDSensor::ToString); #ifdef BUILD_LIBREALSENSE // Class RS bag reader - py::class_, RGBDVideoReader> - rs_bag_reader( - m, "RSBagReader", - "RealSense Bag file reader.\n" - "\tOnly the first color and depth streams from the bag " - "file will be read.\n" - " - The streams must have the same frame rate.\n" - " - The color stream must have RGB 8 bit (RGB8/BGR8) pixel " - "format\n" - " - The depth stream must have 16 bit unsigned int (Z16) " - "pixel format\n" - "The output is synchronized color and depth frame pairs " - "with the depth frame aligned to the color frame. " - "Unsynchronized frames will be dropped. With alignment, " - "the depth and color frames have the same viewpoint and " - "resolution. See format documentation `here " - "`__" - "\n\n" - ".. warning:: A few frames may be dropped if user code " - "takes a long time (>10 frame intervals) to process a " - "frame."); + auto rs_bag_reader = + static_cast, + RGBDVideoReader>>(m.attr("RSBagReader")); rs_bag_reader.def(py::init<>()) .def(py::init(), "buffer_size"_a = RSBagReader::DEFAULT_BUFFER_SIZE) @@ -218,19 +240,16 @@ void pybind_sensor(py::module &m) { map_shared_argument_docstrings); // Class RealSenseSensorConfig - py::class_ realsense_sensor_config( - m, "RealSenseSensorConfig", "Configuration for a RealSense camera"); - + auto realsense_sensor_config = + static_cast>( + m.attr("RealSenseSensorConfig")); realsense_sensor_config.def(py::init<>(), "Default config will be used") .def(py::init &>(), "config"_a, "Initialize config with a map"); - - py::class_ realsense_valid_configs( - m, "RealSenseValidConfigs", - "Store set of valid configuration options for a connected " - "RealSense device. From this structure, a user can construct a " - "RealSenseSensorConfig object meeting their specifications."); + auto realsense_valid_configs = + static_cast>( + m.attr("RealSenseValidConfigs")); realsense_valid_configs .def_readwrite("serial", &RealSenseValidConfigs::serial, "Device serial number.") @@ -241,10 +260,9 @@ void pybind_sensor(py::module &m) { "list of valid values."); // Class RealSenseSensor - py::class_ realsense_sensor( - m, "RealSenseSensor", - "RealSense camera discovery, configuration, streaming and " - "recording"); + auto realsense_sensor = + static_cast>( + m.attr("RealSenseSensor")); realsense_sensor.def(py::init<>(), "Initialize with default settings.") .def_static("list_devices", &RealSenseSensor::ListDevices, py::call_guard(), diff --git a/cpp/pybind/t/pipelines/odometry/odometry.cpp b/cpp/pybind/t/pipelines/odometry/odometry.cpp index a6744e6a0b9..120d5827522 100644 --- a/cpp/pybind/t/pipelines/odometry/odometry.cpp +++ b/cpp/pybind/t/pipelines/odometry/odometry.cpp @@ -15,20 +15,95 @@ namespace t { namespace pipelines { namespace odometry { -void pybind_odometry_classes(py::module &m) { - py::enum_(m, "Method", "Tensor odometry estimation method.") +// Odometry functions have similar arguments, sharing arg docstrings. +static const std::unordered_map + map_shared_argument_docstrings = { + {"criteria", "Odometry convergence criteria."}, + {"criteria_list", "List of Odometry convergence criteria."}, + {"depth_outlier_trunc", + "Depth difference threshold used to filter projective " + "associations."}, + {"depth_huber_delta", + "Huber norm parameter used in depth loss."}, + {"depth_scale", + "Converts depth pixel values to meters by dividing the scale " + "factor."}, + {"init_source_to_target", + "(4, 4) initial transformation matrix from source to target."}, + {"intrinsics", "(3, 3) intrinsic matrix for projection."}, + {"intensity_huber_delta", + "Huber norm parameter used in intensity loss."}, + {"method", + "Estimation method used to apply RGBD odometry. " + "One of (``PointToPlane``, ``Intensity``, ``Hybrid``)"}, + {"params", "Odometry loss parameters."}, + {"source", "The source RGBD image."}, + {"source_depth", + "(row, col, channel = 1) Float32 source depth image obtained " + "by PreprocessDepth before calling this function."}, + {"source_intensity", + "(row, col, channel = 1) Float32 source intensity image " + "obtained by RGBToGray before calling this function"}, + {"source_vertex_map", + "(row, col, channel = 3) Float32 source vertex image obtained " + "by CreateVertexMap before calling this function."}, + {"target", "The target RGBD image."}, + {"target_depth", + "(row, col, channel = 1) Float32 target depth image obtained " + "by PreprocessDepth before calling this function."}, + {"target_depth_dx", + "(row, col, channel = 1) Float32 target depth gradient image " + "along x-axis obtained by FilterSobel before calling this " + "function."}, + {"target_depth_dy", + "(row, col, channel = 1) Float32 target depth gradient image " + "along y-axis obtained by FilterSobel before calling this " + "function."}, + {"target_intensity", + "(row, col, channel = 1) Float32 target intensity image " + "obtained by RGBToGray before calling this function"}, + {"target_intensity_dx", + "(row, col, channel = 1) Float32 target intensity gradient " + "image along x-axis obtained by FilterSobel before calling " + "this function."}, + {"target_intensity_dy", + "(row, col, channel = 1) Float32 target intensity gradient " + "image along y-axis obtained by FilterSobel before calling " + "this function."}, + {"target_normal_map", + "(row, col, channel = 3) Float32 target normal image obtained " + "by CreateNormalMap before calling this function."}, + {"target_vertex_map", + "(row, col, channel = 3) Float32 target vertex image obtained " + "by CreateVertexMap before calling this function."}}; + +void pybind_odometry_declarations(py::module &m) { + py::module m_odometry = + m.def_submodule("odometry", "Tensor odometry pipeline."); + py::enum_(m_odometry, "Method", + "Tensor odometry estimation method.") .value("PointToPlane", Method::PointToPlane) .value("Intensity", Method::Intensity) .value("Hybrid", Method::Hybrid) .export_values(); - - // open3d.t.pipelines.odometry.OdometryConvergenceCriteria py::class_ odometry_convergence_criteria( - m, "OdometryConvergenceCriteria", + m_odometry, "OdometryConvergenceCriteria", "Convergence criteria of odometry. " "Odometry algorithm stops if the relative change of fitness and " "rmse hit ``relative_fitness`` and ``relative_rmse`` individually, " "or the iteration number exceeds ``max_iteration``."); + py::class_ odometry_result(m_odometry, "OdometryResult", + "Odometry results."); + py::class_ odometry_loss_params( + m_odometry, "OdometryLossParams", "Odometry loss parameters."); +} + +void pybind_odometry_definitions(py::module &m) { + auto m_odometry = static_cast(m.attr("odometry")); + // open3d.t.pipelines.odometry.OdometryConvergenceCriteria + auto odometry_convergence_criteria = + static_cast>( + m_odometry.attr("OdometryConvergenceCriteria")); py::detail::bind_copy_functions( odometry_convergence_criteria); odometry_convergence_criteria @@ -56,8 +131,8 @@ void pybind_odometry_classes(py::module &m) { }); // open3d.t.pipelines.odometry.OdometryResult - py::class_ odometry_result(m, "OdometryResult", - "Odometry results."); + auto odometry_result = static_cast>( + m_odometry.attr("OdometryResult")); py::detail::bind_copy_functions(odometry_result); odometry_result .def(py::init(), @@ -82,8 +157,8 @@ void pybind_odometry_classes(py::module &m) { }); // open3d.t.pipelines.odometry.OdometryLossParams - py::class_ odometry_loss_params( - m, "OdometryLossParams", "Odometry loss parameters."); + auto odometry_loss_params = static_cast>( + m_odometry.attr("OdometryLossParams")); py::detail::bind_copy_functions(odometry_loss_params); odometry_loss_params .def(py::init(), @@ -107,88 +182,25 @@ void pybind_odometry_classes(py::module &m) { olp.depth_outlier_trunc_, olp.depth_huber_delta_, olp.intensity_huber_delta_); }); -} - -// Odometry functions have similar arguments, sharing arg docstrings. -static const std::unordered_map - map_shared_argument_docstrings = { - {"criteria", "Odometry convergence criteria."}, - {"criteria_list", "List of Odometry convergence criteria."}, - {"depth_outlier_trunc", - "Depth difference threshold used to filter projective " - "associations."}, - {"depth_huber_delta", - "Huber norm parameter used in depth loss."}, - {"depth_scale", - "Converts depth pixel values to meters by dividing the scale " - "factor."}, - {"init_source_to_target", - "(4, 4) initial transformation matrix from source to target."}, - {"intrinsics", "(3, 3) intrinsic matrix for projection."}, - {"intensity_huber_delta", - "Huber norm parameter used in intensity loss."}, - {"method", - "Estimation method used to apply RGBD odometry. " - "One of (``PointToPlane``, ``Intensity``, ``Hybrid``)"}, - {"params", "Odometry loss parameters."}, - {"source", "The source RGBD image."}, - {"source_depth", - "(row, col, channel = 1) Float32 source depth image obtained " - "by PreprocessDepth before calling this function."}, - {"source_intensity", - "(row, col, channel = 1) Float32 source intensity image " - "obtained by RGBToGray before calling this function"}, - {"source_vertex_map", - "(row, col, channel = 3) Float32 source vertex image obtained " - "by CreateVertexMap before calling this function."}, - {"target", "The target RGBD image."}, - {"target_depth", - "(row, col, channel = 1) Float32 target depth image obtained " - "by PreprocessDepth before calling this function."}, - {"target_depth_dx", - "(row, col, channel = 1) Float32 target depth gradient image " - "along x-axis obtained by FilterSobel before calling this " - "function."}, - {"target_depth_dy", - "(row, col, channel = 1) Float32 target depth gradient image " - "along y-axis obtained by FilterSobel before calling this " - "function."}, - {"target_intensity", - "(row, col, channel = 1) Float32 target intensity image " - "obtained by RGBToGray before calling this function"}, - {"target_intensity_dx", - "(row, col, channel = 1) Float32 target intensity gradient " - "image along x-axis obtained by FilterSobel before calling " - "this function."}, - {"target_intensity_dy", - "(row, col, channel = 1) Float32 target intensity gradient " - "image along y-axis obtained by FilterSobel before calling " - "this function."}, - {"target_normal_map", - "(row, col, channel = 3) Float32 target normal image obtained " - "by CreateNormalMap before calling this function."}, - {"target_vertex_map", - "(row, col, channel = 3) Float32 target vertex image obtained " - "by CreateVertexMap before calling this function."}}; - -void pybind_odometry_methods(py::module &m) { - m.def("rgbd_odometry_multi_scale", &RGBDOdometryMultiScale, - py::call_guard(), - "Function for Multi Scale RGBD odometry.", "source"_a, "target"_a, - "intrinsics"_a, - "init_source_to_target"_a = - core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), - "depth_scale"_a = 1000.0f, "depth_max"_a = 3.0f, - "criteria_list"_a = - std::vector({10, 5, 3}), - "method"_a = Method::Hybrid, "params"_a = OdometryLossParams()); - docstring::FunctionDocInject(m, "rgbd_odometry_multi_scale", + m_odometry.def( + "rgbd_odometry_multi_scale", &RGBDOdometryMultiScale, + py::call_guard(), + "Function for Multi Scale RGBD odometry.", "source"_a, "target"_a, + "intrinsics"_a, + "init_source_to_target"_a = + core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), + "depth_scale"_a = 1000.0f, "depth_max"_a = 3.0f, + "criteria_list"_a = + std::vector({10, 5, 3}), + "method"_a = Method::Hybrid, "params"_a = OdometryLossParams()); + docstring::FunctionDocInject(m_odometry, "rgbd_odometry_multi_scale", map_shared_argument_docstrings); - m.def("compute_odometry_result_point_to_plane", - &ComputeOdometryResultPointToPlane, - py::call_guard(), - R"(Estimates the OdometryResult (4x4 rigid transformation T from + m_odometry.def( + "compute_odometry_result_point_to_plane", + &ComputeOdometryResultPointToPlane, + py::call_guard(), + R"(Estimates the OdometryResult (4x4 rigid transformation T from source to target, with inlier rmse and fitness). Performs one iteration of RGBD odometry using Loss function: :math:`[(V_p - V_q)^T N_p]^2` @@ -199,15 +211,18 @@ where, q is obtained by transforming p with init_source_to_target then projecting with intrinsics. Reference: KinectFusion, ISMAR 2011.)", - "source_vertex_map"_a, "target_vertex_map"_a, "target_normal_map"_a, - "intrinsics"_a, "init_source_to_target"_a, "depth_outlier_trunc"_a, - "depth_huber_delta"_a); - docstring::FunctionDocInject(m, "compute_odometry_result_point_to_plane", + "source_vertex_map"_a, "target_vertex_map"_a, "target_normal_map"_a, + "intrinsics"_a, "init_source_to_target"_a, "depth_outlier_trunc"_a, + "depth_huber_delta"_a); + docstring::FunctionDocInject(m_odometry, + "compute_odometry_result_point_to_plane", map_shared_argument_docstrings); - m.def("compute_odometry_result_intensity", &ComputeOdometryResultIntensity, - py::call_guard(), - R"(Estimates the OdometryResult (4x4 rigid transformation T from + m_odometry.def( + "compute_odometry_result_intensity", + &ComputeOdometryResultIntensity, + py::call_guard(), + R"(Estimates the OdometryResult (4x4 rigid transformation T from source to target, with inlier rmse and fitness). Performs one iteration of RGBD odometry using Loss function: :math:`(I_p - I_q)^2` @@ -219,17 +234,19 @@ projecting with intrinsics. Reference: Real-time visual odometry from dense RGB-D images, ICCV Workshops, 2017.)", - "source_depth"_a, "target_depth"_a, "source_intensity"_a, - "target_intensity"_a, "target_intensity_dx"_a, - "target_intensity_dy"_a, "source_vertex_map"_a, "intrinsics"_a, - "init_source_to_target"_a, "depth_outlier_trunc"_a, - "intensity_huber_delta"_a); - docstring::FunctionDocInject(m, "compute_odometry_result_intensity", + "source_depth"_a, "target_depth"_a, "source_intensity"_a, + "target_intensity"_a, "target_intensity_dx"_a, + "target_intensity_dy"_a, "source_vertex_map"_a, "intrinsics"_a, + "init_source_to_target"_a, "depth_outlier_trunc"_a, + "intensity_huber_delta"_a); + docstring::FunctionDocInject(m_odometry, + "compute_odometry_result_intensity", map_shared_argument_docstrings); - m.def("compute_odometry_result_hybrid", &ComputeOdometryResultHybrid, - py::call_guard(), - R"(Estimates the OdometryResult (4x4 rigid transformation T from + m_odometry.def( + "compute_odometry_result_hybrid", &ComputeOdometryResultHybrid, + py::call_guard(), + R"(Estimates the OdometryResult (4x4 rigid transformation T from source to target, with inlier rmse and fitness). Performs one iteration of RGBD odometry using Loss function: :math:`(I_p - I_q)^2 + \lambda(D_p - (D_q)')^2` @@ -242,27 +259,21 @@ q is obtained by transforming p with init_source_to_target then projecting with intrinsics. Reference: J. Park, Q.Y. Zhou, and V. Koltun, Colored Point Cloud Registration Revisited, ICCV, 2017.)", - "source_depth"_a, "target_depth"_a, "source_intensity"_a, - "target_intensity"_a, "target_depth_dx"_a, "target_depth_dy"_a, - "target_intensity_dx"_a, "target_intensity_dy"_a, - "source_vertex_map"_a, "intrinsics"_a, "init_source_to_target"_a, - "depth_outlier_trunc"_a, "depth_huber_delta"_a, - "intensity_huber_delta"_a); - docstring::FunctionDocInject(m, "compute_odometry_result_hybrid", + "source_depth"_a, "target_depth"_a, "source_intensity"_a, + "target_intensity"_a, "target_depth_dx"_a, "target_depth_dy"_a, + "target_intensity_dx"_a, "target_intensity_dy"_a, + "source_vertex_map"_a, "intrinsics"_a, "init_source_to_target"_a, + "depth_outlier_trunc"_a, "depth_huber_delta"_a, + "intensity_huber_delta"_a); + docstring::FunctionDocInject(m_odometry, "compute_odometry_result_hybrid", map_shared_argument_docstrings); - m.def("compute_odometry_information_matrix", - &ComputeOdometryInformationMatrix, - py::call_guard(), "source_depth"_a, - "target_depth"_a, "intrinsic"_a, "source_to_target"_a, - "dist_threshold"_a, "depth_scale"_a = 1000.0, "depth_max"_a = 3.0); -} - -void pybind_odometry(py::module &m) { - py::module m_submodule = - m.def_submodule("odometry", "Tensor odometry pipeline."); - pybind_odometry_classes(m_submodule); - pybind_odometry_methods(m_submodule); + m_odometry.def("compute_odometry_information_matrix", + &ComputeOdometryInformationMatrix, + py::call_guard(), "source_depth"_a, + "target_depth"_a, "intrinsic"_a, "source_to_target"_a, + "dist_threshold"_a, "depth_scale"_a = 1000.0, + "depth_max"_a = 3.0); } } // namespace odometry diff --git a/cpp/pybind/t/pipelines/odometry/odometry.h b/cpp/pybind/t/pipelines/odometry/odometry.h index 2a603765675..c8494d66654 100644 --- a/cpp/pybind/t/pipelines/odometry/odometry.h +++ b/cpp/pybind/t/pipelines/odometry/odometry.h @@ -14,7 +14,8 @@ namespace t { namespace pipelines { namespace odometry { -void pybind_odometry(py::module &m); +void pybind_odometry_declarations(py::module &m); +void pybind_odometry_definitions(py::module &m); } // namespace odometry } // namespace pipelines diff --git a/cpp/pybind/t/pipelines/pipelines.cpp b/cpp/pybind/t/pipelines/pipelines.cpp index 6407923bf91..7f3fbe4e551 100644 --- a/cpp/pybind/t/pipelines/pipelines.cpp +++ b/cpp/pybind/t/pipelines/pipelines.cpp @@ -17,13 +17,20 @@ namespace open3d { namespace t { namespace pipelines { -void pybind_pipelines(py::module& m) { +void pybind_pipelines_declarations(py::module& m) { py::module m_pipelines = m.def_submodule( "pipelines", "Tensor-based geometry processing pipelines."); - odometry::pybind_odometry(m_pipelines); - registration::pybind_registration(m_pipelines); - slac::pybind_slac(m_pipelines); - slam::pybind_slam(m_pipelines); + odometry::pybind_odometry_declarations(m_pipelines); + registration::pybind_registration_declarations(m_pipelines); + slac::pybind_slac_declarations(m_pipelines); + slam::pybind_slam_declarations(m_pipelines); +} +void pybind_pipelines_definitions(py::module& m) { + auto m_pipelines = static_cast(m.attr("pipelines")); + odometry::pybind_odometry_definitions(m_pipelines); + registration::pybind_registration_definitions(m_pipelines); + slac::pybind_slac_definitions(m_pipelines); + slam::pybind_slam_definitions(m_pipelines); } } // namespace pipelines diff --git a/cpp/pybind/t/pipelines/pipelines.h b/cpp/pybind/t/pipelines/pipelines.h index 015a3959b56..96635b839a1 100644 --- a/cpp/pybind/t/pipelines/pipelines.h +++ b/cpp/pybind/t/pipelines/pipelines.h @@ -13,8 +13,9 @@ namespace open3d { namespace t { namespace pipelines { -void pybind_pipelines(py::module& m); +void pybind_pipelines_declarations(py::module& m); +void pybind_pipelines_definitions(py::module& m); -} +} // namespace pipelines } // namespace t } // namespace open3d diff --git a/cpp/pybind/t/pipelines/registration/feature.cpp b/cpp/pybind/t/pipelines/registration/feature.cpp index 74de409115d..2317e5ae5cd 100644 --- a/cpp/pybind/t/pipelines/registration/feature.cpp +++ b/cpp/pybind/t/pipelines/registration/feature.cpp @@ -17,16 +17,16 @@ namespace t { namespace pipelines { namespace registration { -void pybind_feature(py::module &m) { - m.def("compute_fpfh_feature", &ComputeFPFHFeature, - py::call_guard(), - R"(Function to compute FPFH feature for a point cloud. +void pybind_feature_definitions(py::module &m_registration) { + m_registration.def("compute_fpfh_feature", &ComputeFPFHFeature, + py::call_guard(), + R"(Function to compute FPFH feature for a point cloud. It uses KNN search (Not recommended to use on GPU) if only max_nn parameter is provided, Radius search (Not recommended to use on GPU) if only radius parameter is provided, and Hybrid search (Recommended) if both are provided.)", - "input"_a, "max_nn"_a = 100, "radius"_a = py::none()); + "input"_a, "max_nn"_a = 100, "radius"_a = py::none()); docstring::FunctionDocInject( - m, "compute_fpfh_feature", + m_registration, "compute_fpfh_feature", {{"input", "The input point cloud with data type float32 or float64."}, {"max_nn", @@ -36,13 +36,14 @@ parameter is provided, and Hybrid search (Recommended) if both are provided.)", "[optional] Neighbor search radius parameter. [Recommended ~5x " "voxel size]"}}); - m.def("correspondences_from_features", &CorrespondencesFromFeatures, - py::call_guard(), - R"(Function to query nearest neighbors of source_features in target_features.)", - "source_features"_a, "target_features"_a, "mutual_filter"_a = false, - "mutual_consistency_ratio"_a = 0.1f); + m_registration.def( + "correspondences_from_features", &CorrespondencesFromFeatures, + py::call_guard(), + R"(Function to query nearest neighbors of source_features in target_features.)", + "source_features"_a, "target_features"_a, "mutual_filter"_a = false, + "mutual_consistency_ratio"_a = 0.1f); docstring::FunctionDocInject( - m, "correspondences_from_features", + m_registration, "correspondences_from_features", {{"source_features", "The source features in shape (N, dim)."}, {"target_features", "The target features in shape (M, dim)."}, {"mutual_filter", diff --git a/cpp/pybind/t/pipelines/registration/registration.cpp b/cpp/pybind/t/pipelines/registration/registration.cpp index 6dbe3f1d27a..10988a80894 100644 --- a/cpp/pybind/t/pipelines/registration/registration.cpp +++ b/cpp/pybind/t/pipelines/registration/registration.cpp @@ -46,14 +46,101 @@ class PyTransformationEstimation : public TransformationEstimationBase { } }; -void pybind_registration_classes(py::module &m) { - // open3d.t.pipelines.registration.ICPConvergenceCriteria +// Registration functions have similar arguments, sharing arg docstrings. +static const std::unordered_map + map_shared_argument_docstrings = { + {"correspondences", + "Tensor of type Int64 containing indices of corresponding " + "target points, where the value is the target index and the " + "index of the value itself is the source index. It contains " + "-1 as value at index with no correspondence."}, + {"criteria", "Convergence criteria"}, + {"criteria_list", + "List of Convergence criteria for each scale of multi-scale " + "icp."}, + {"estimation_method", + "Estimation method. One of " + "(``TransformationEstimationPointToPoint``, " + "``TransformationEstimationPointToPlane``, " + "``TransformationEstimationForColoredICP``, " + "``TransformationEstimationForGeneralizedICP``)"}, + {"init_source_to_target", "Initial transformation estimation"}, + {"max_correspondence_distance", + "Maximum correspondence points-pair distance."}, + {"max_correspondence_distances", + "o3d.utility.DoubleVector of maximum correspondence " + "points-pair distances for multi-scale icp."}, + {"option", "Registration option"}, + {"source", "The source point cloud."}, + {"target", "The target point cloud."}, + {"transformation", + "The 4x4 transformation matrix of type Float64 " + "to transform ``source`` to ``target``"}, + {"voxel_size", + "The input pointclouds will be down-sampled to this " + "`voxel_size` scale. If `voxel_size` < 0, original scale will " + "be used. However it is highly recommended to down-sample the " + "point-cloud for performance. By default original scale of " + "the point-cloud will be used."}, + {"voxel_sizes", + "o3d.utility.DoubleVector of voxel sizes in strictly " + "decreasing order, for multi-scale icp."}, + {"callback_after_iteration", + "Optional lambda function, saves string to tensor map of " + "attributes such as iteration_index, scale_index, " + "scale_iteration_index, inlier_rmse, fitness, transformation, " + "on CPU device, updated after each iteration."}}; + +void pybind_registration_declarations(py::module &m) { + py::module m_registration = m.def_submodule( + "registration", "Tensor-based registration pipeline."); py::class_ convergence_criteria( - m, "ICPConvergenceCriteria", + m_registration, "ICPConvergenceCriteria", "Convergence criteria of ICP. " "ICP algorithm stops if the relative change of fitness and rmse " "hit ``relative_fitness`` and ``relative_rmse`` individually, " "or the iteration number exceeds ``max_iteration``."); + py::class_ registration_result( + m_registration, "RegistrationResult", "Registration results."); + py::class_> + te(m_registration, "TransformationEstimation", + "Base class that estimates a transformation between two " + "point clouds. The virtual function ComputeTransformation() " + "must be implemented in subclasses."); + py::class_, + TransformationEstimation> + te_p2p(m_registration, "TransformationEstimationPointToPoint", + "Class to estimate a transformation for point to " + "point distance."); + py::class_, + TransformationEstimation> + te_p2l(m_registration, "TransformationEstimationPointToPlane", + "Class to estimate a transformation for point to " + "plane distance."); + py::class_< + TransformationEstimationForColoredICP, + PyTransformationEstimation, + TransformationEstimation> + te_col(m_registration, "TransformationEstimationForColoredICP", + "Class to estimate a transformation between two point " + "clouds using color information"); + py::class_< + TransformationEstimationForDopplerICP, + PyTransformationEstimation, + TransformationEstimation> + te_dop(m_registration, "TransformationEstimationForDopplerICP", + "Class to estimate a transformation between two point " + "clouds using color information"); + pybind_robust_kernel_declarations(m_registration); +} +void pybind_registration_definitions(py::module &m) { + auto m_registration = static_cast(m.attr("registration")); + // open3d.t.pipelines.registration.ICPConvergenceCriteria + auto convergence_criteria = static_cast>( + m_registration.attr("ICPConvergenceCriteria")); py::detail::bind_copy_functions( convergence_criteria); convergence_criteria @@ -80,8 +167,8 @@ void pybind_registration_classes(py::module &m) { }); // open3d.t.pipelines.registration.RegistrationResult - py::class_ registration_result(m, "RegistrationResult", - "Registration results."); + auto registration_result = static_cast>( + m_registration.attr("RegistrationResult")); py::detail::bind_default_constructor( registration_result); py::detail::bind_copy_functions(registration_result); @@ -124,12 +211,10 @@ void pybind_registration_classes(py::module &m) { }); // open3d.t.pipelines.registration.TransformationEstimation - py::class_> - te(m, "TransformationEstimation", - "Base class that estimates a transformation between two " - "point clouds. The virtual function ComputeTransformation() " - "must be implemented in subclasses."); + auto te = static_cast< + py::class_>>( + m_registration.attr("TransformationEstimation")); te.def("compute_rmse", &TransformationEstimation::ComputeRMSE, "source"_a, "target"_a, "correspondences"_a, "Compute RMSE between source and target points cloud given " @@ -142,7 +227,7 @@ void pybind_registration_classes(py::module &m) { "iteration"_a = 0, "Compute transformation from source to target point cloud given " "correspondences."); - docstring::ClassMethodDocInject(m, "TransformationEstimation", + docstring::ClassMethodDocInject(m_registration, "TransformationEstimation", "compute_rmse", {{"source", "Source point cloud."}, {"target", "Target point cloud."}, @@ -155,7 +240,8 @@ void pybind_registration_classes(py::module &m) { "index. It contains -1 as value " "at index with no correspondence."}}); docstring::ClassMethodDocInject( - m, "TransformationEstimation", "compute_transformation", + m_registration, "TransformationEstimation", + "compute_transformation", {{"source", "Source point cloud."}, {"target", "Target point cloud."}, {"correspondences", @@ -169,12 +255,11 @@ void pybind_registration_classes(py::module &m) { // open3d.t.pipelines.registration.TransformationEstimationPointToPoint // TransformationEstimation - py::class_, - TransformationEstimation> - te_p2p(m, "TransformationEstimationPointToPoint", - "Class to estimate a transformation for point to " - "point distance."); + auto te_p2p = static_cast, + TransformationEstimation>>( + m_registration.attr("TransformationEstimationPointToPoint")); py::detail::bind_copy_functions( te_p2p); te_p2p.def(py::init()) @@ -185,12 +270,11 @@ void pybind_registration_classes(py::module &m) { // open3d.t.pipelines.registration.TransformationEstimationPointToPlane // TransformationEstimation - py::class_, - TransformationEstimation> - te_p2l(m, "TransformationEstimationPointToPlane", - "Class to estimate a transformation for point to " - "plane distance."); + auto te_p2l = static_cast, + TransformationEstimation>>( + m_registration.attr("TransformationEstimationPointToPlane")); py::detail::bind_default_constructor( te_p2l); py::detail::bind_copy_functions( @@ -209,13 +293,11 @@ void pybind_registration_classes(py::module &m) { // open3d.t.pipelines.registration.TransformationEstimationForColoredICP // TransformationEstimation - py::class_< + auto te_col = static_cast, - TransformationEstimation> - te_col(m, "TransformationEstimationForColoredICP", - "Class to estimate a transformation between two point " - "clouds using color information"); + TransformationEstimation>>( + m_registration.attr("TransformationEstimationForColoredICP")); py::detail::bind_default_constructor( te_col); py::detail::bind_copy_functions( @@ -253,13 +335,11 @@ void pybind_registration_classes(py::module &m) { // open3d.t.pipelines.registration.TransformationEstimationForDopplerICP // TransformationEstimation - py::class_< + auto te_dop = static_cast, - TransformationEstimation> - te_dop(m, "TransformationEstimationForDopplerICP", - "Class to estimate a transformation between two point " - "clouds using color information"); + TransformationEstimation>>( + m_registration.attr("TransformationEstimationForDopplerICP")); py::detail::bind_default_constructor( te_dop); py::detail::bind_copy_functions( @@ -356,102 +436,53 @@ void pybind_registration_classes(py::module &m) { transform_vehicle_to_sensor_, "The 4x4 extrinsic transformation matrix between " "the vehicle and the sensor frames."); -} - -// Registration functions have similar arguments, sharing arg docstrings. -static const std::unordered_map - map_shared_argument_docstrings = { - {"correspondences", - "Tensor of type Int64 containing indices of corresponding " - "target points, where the value is the target index and the " - "index of the value itself is the source index. It contains " - "-1 as value at index with no correspondence."}, - {"criteria", "Convergence criteria"}, - {"criteria_list", - "List of Convergence criteria for each scale of multi-scale " - "icp."}, - {"estimation_method", - "Estimation method. One of " - "(``TransformationEstimationPointToPoint``, " - "``TransformationEstimationPointToPlane``, " - "``TransformationEstimationForColoredICP``, " - "``TransformationEstimationForGeneralizedICP``)"}, - {"init_source_to_target", "Initial transformation estimation"}, - {"max_correspondence_distance", - "Maximum correspondence points-pair distance."}, - {"max_correspondence_distances", - "o3d.utility.DoubleVector of maximum correspondence " - "points-pair distances for multi-scale icp."}, - {"option", "Registration option"}, - {"source", "The source point cloud."}, - {"target", "The target point cloud."}, - {"transformation", - "The 4x4 transformation matrix of type Float64 " - "to transform ``source`` to ``target``"}, - {"voxel_size", - "The input pointclouds will be down-sampled to this " - "`voxel_size` scale. If `voxel_size` < 0, original scale will " - "be used. However it is highly recommended to down-sample the " - "point-cloud for performance. By default original scale of " - "the point-cloud will be used."}, - {"voxel_sizes", - "o3d.utility.DoubleVector of voxel sizes in strictly " - "decreasing order, for multi-scale icp."}, - {"callback_after_iteration", - "Optional lambda function, saves string to tensor map of " - "attributes such as iteration_index, scale_index, " - "scale_iteration_index, inlier_rmse, fitness, transformation, " - "on CPU device, updated after each iteration."}}; - -void pybind_registration_methods(py::module &m) { - m.def("evaluate_registration", &EvaluateRegistration, - py::call_guard(), - "Function for evaluating registration between point clouds", - "source"_a, "target"_a, "max_correspondence_distance"_a, - "transformation"_a = - core::Tensor::Eye(4, core::Float64, core::Device("CPU:0"))); - docstring::FunctionDocInject(m, "evaluate_registration", + m_registration.def( + "evaluate_registration", &EvaluateRegistration, + py::call_guard(), + "Function for evaluating registration between point clouds", + "source"_a, "target"_a, "max_correspondence_distance"_a, + "transformation"_a = + core::Tensor::Eye(4, core::Float64, core::Device("CPU:0"))); + docstring::FunctionDocInject(m_registration, "evaluate_registration", map_shared_argument_docstrings); - m.def("icp", &ICP, py::call_guard(), - "Function for ICP registration", "source"_a, "target"_a, - "max_correspondence_distance"_a, - "init_source_to_target"_a = - core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), - "estimation_method"_a = TransformationEstimationPointToPoint(), - "criteria"_a = ICPConvergenceCriteria(), "voxel_size"_a = -1.0, - "callback_after_iteration"_a = py::none()); - docstring::FunctionDocInject(m, "icp", map_shared_argument_docstrings); - - m.def("multi_scale_icp", &MultiScaleICP, - py::call_guard(), - "Function for Multi-Scale ICP registration", "source"_a, "target"_a, - "voxel_sizes"_a, "criteria_list"_a, "max_correspondence_distances"_a, - "init_source_to_target"_a = - core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), - "estimation_method"_a = TransformationEstimationPointToPoint(), - "callback_after_iteration"_a = py::none()); - docstring::FunctionDocInject(m, "multi_scale_icp", + m_registration.def( + "icp", &ICP, py::call_guard(), + "Function for ICP registration", "source"_a, "target"_a, + "max_correspondence_distance"_a, + "init_source_to_target"_a = + core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), + "estimation_method"_a = TransformationEstimationPointToPoint(), + "criteria"_a = ICPConvergenceCriteria(), "voxel_size"_a = -1.0, + "callback_after_iteration"_a = py::none()); + docstring::FunctionDocInject(m_registration, "icp", map_shared_argument_docstrings); - m.def("get_information_matrix", &GetInformationMatrix, - py::call_guard(), - "Function for computing information matrix from transformation " - "matrix. Information matrix is tensor of shape {6, 6}, dtype Float64 " - "on CPU device.", - "source"_a, "target"_a, "max_correspondence_distance"_a, - "transformation"_a); - docstring::FunctionDocInject(m, "get_information_matrix", + m_registration.def( + "multi_scale_icp", &MultiScaleICP, + py::call_guard(), + "Function for Multi-Scale ICP registration", "source"_a, "target"_a, + "voxel_sizes"_a, "criteria_list"_a, + "max_correspondence_distances"_a, + "init_source_to_target"_a = + core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), + "estimation_method"_a = TransformationEstimationPointToPoint(), + "callback_after_iteration"_a = py::none()); + docstring::FunctionDocInject(m_registration, "multi_scale_icp", map_shared_argument_docstrings); -} - -void pybind_registration(py::module &m) { - py::module m_submodule = m.def_submodule( - "registration", "Tensor-based registration pipeline."); - pybind_registration_classes(m_submodule); - pybind_registration_methods(m_submodule); - pybind_feature(m_submodule); - pybind_robust_kernels(m_submodule); + m_registration.def( + "get_information_matrix", &GetInformationMatrix, + py::call_guard(), + "Function for computing information matrix from transformation " + "matrix. Information matrix is tensor of shape {6, 6}, dtype " + "Float64 " + "on CPU device.", + "source"_a, "target"_a, "max_correspondence_distance"_a, + "transformation"_a); + docstring::FunctionDocInject(m_registration, "get_information_matrix", + map_shared_argument_docstrings); + pybind_feature_definitions(m_registration); + pybind_robust_kernel_definitions(m_registration); } } // namespace registration diff --git a/cpp/pybind/t/pipelines/registration/registration.h b/cpp/pybind/t/pipelines/registration/registration.h index 671f6e96d0d..518e6b319ed 100644 --- a/cpp/pybind/t/pipelines/registration/registration.h +++ b/cpp/pybind/t/pipelines/registration/registration.h @@ -14,9 +14,11 @@ namespace t { namespace pipelines { namespace registration { -void pybind_feature(py::module &m); -void pybind_registration(py::module &m); -void pybind_robust_kernels(py::module &m); +void pybind_registration_declarations(py::module &m); +void pybind_robust_kernel_declarations(py::module &m_registration); +void pybind_registration_definitions(py::module &m); +void pybind_feature_definitions(py::module &m_registration); +void pybind_robust_kernel_definitions(py::module &m_registration); } // namespace registration } // namespace pipelines diff --git a/cpp/pybind/t/pipelines/registration/robust_kernel.cpp b/cpp/pybind/t/pipelines/registration/robust_kernel.cpp index 2b2e7f5bccc..49198c0fb4b 100644 --- a/cpp/pybind/t/pipelines/registration/robust_kernel.cpp +++ b/cpp/pybind/t/pipelines/registration/robust_kernel.cpp @@ -15,8 +15,11 @@ namespace t { namespace pipelines { namespace registration { -void pybind_robust_kernel_classes(py::module& m) { - py::enum_(m, "RobustKernelMethod", +void pybind_robust_kernel_declarations(py::module& m) { + py::module m_robust_kernel = m.def_submodule( + "robust_kernel", + "Tensor-based robust kernel for outlier rejection."); + py::enum_(m_robust_kernel, "RobustKernelMethod", "Robust kernel method for outlier rejection.") .value("L2Loss", RobustKernelMethod::L2Loss) .value("L1Loss", RobustKernelMethod::L1Loss) @@ -26,9 +29,7 @@ void pybind_robust_kernel_classes(py::module& m) { .value("TukeyLoss", RobustKernelMethod::TukeyLoss) .value("GeneralizedLoss", RobustKernelMethod::GeneralizedLoss) .export_values(); - - // open3d.t.pipelines.odometry.OdometryConvergenceCriteria - py::class_ robust_kernel(m, "RobustKernel", + py::class_ robust_kernel(m_robust_kernel, "RobustKernel", R"( Base class that models a robust kernel for outlier rejection. The virtual function ``weight()`` must be implemented in derived classes. @@ -100,6 +101,12 @@ Philippe Babin et al. For more information please also see: **"Adaptive Robust Kernels for Non-Linear Least Squares Problems"**, by Nived Chebrolu et al. )"); +} + +void pybind_robust_kernel_definitions(py::module& m) { + auto m_robust_kernel = static_cast(m.attr("robust_kernel")); + auto robust_kernel = static_cast>( + m_robust_kernel.attr("RobustKernel")); py::detail::bind_copy_functions(robust_kernel); robust_kernel .def(py::init([](const RobustKernelMethod type, @@ -124,13 +131,6 @@ Non-Linear Least Squares Problems"**, by Nived Chebrolu et al. }); } -void pybind_robust_kernels(py::module& m) { - py::module m_submodule = m.def_submodule( - "robust_kernel", - "Tensor-based robust kernel for outlier rejection."); - pybind_robust_kernel_classes(m_submodule); -} - } // namespace registration } // namespace pipelines } // namespace t diff --git a/cpp/pybind/t/pipelines/slac/slac.cpp b/cpp/pybind/t/pipelines/slac/slac.cpp index a2874e0b172..4510f78207a 100644 --- a/cpp/pybind/t/pipelines/slac/slac.cpp +++ b/cpp/pybind/t/pipelines/slac/slac.cpp @@ -18,10 +18,45 @@ namespace t { namespace pipelines { namespace slac { -void pybind_slac_classes(py::module &m) { +// SLAC functions have similar arguments, sharing arg docstrings. +static const std::unordered_map + map_shared_argument_docstrings = { + {"fnames_processed", + "List of filenames (str) for pre-processed pointcloud " + "fragments."}, + {"fragment_filenames", + "List of filenames (str) for pointcloud fragments."}, + {"fragment_pose_graph", "PoseGraph for pointcloud fragments"}, + {"params", + "slac_optimizer_params Parameters to tune in optimization."}, + {"debug_option", "debug options."}}; + +void pybind_slac_declarations(py::module &m) { + py::module m_slac = m.def_submodule( + "slac", + "Tensor-based Simultaneous Localisation and Calibration pipeline."); py::class_ slac_optimizer_params( - m, "slac_optimizer_params", + m_slac, "slac_optimizer_params", "SLAC parameters to tune in optimization."); + py::class_ slac_debug_option(m_slac, "slac_debug_option", + "SLAC debug options."); + py::class_ control_grid( + m_slac, "control_grid", + " ControlGrid is a spatially hashed voxel grid used for non-rigid " + "point cloud registration and TSDF integration. Each grid stores a " + "map from the initial grid location to the deformed location. You " + "can imagine a control grid as a jelly that is warped upon " + "perturbation with its overall shape preserved. " + "Reference: " + "https://github.com/qianyizh/ElasticReconstruction/blob/master/" + "FragmentOptimizer/OptApp.cpp " + "http://vladlen.info/papers/elastic-fragments.pdf. "); +} + +void pybind_slac_definitions(py::module &m) { + auto m_slac = static_cast(m.attr("slac")); + auto slac_optimizer_params = static_cast>( + m_slac.attr("slac_optimizer_params")); py::detail::bind_copy_functions(slac_optimizer_params); slac_optimizer_params .def(py::init slac_debug_option(m, "slac_debug_option", - "SLAC debug options."); + auto slac_debug_option = static_cast>( + m_slac.attr("slac_debug_option")); py::detail::bind_copy_functions(slac_debug_option); slac_debug_option .def(py::init(), "debug"_a = false, @@ -88,18 +122,8 @@ void pybind_slac_classes(py::module &m) { debug_option.debug_, debug_option.debug_start_node_idx_); }); - - py::class_ control_grid( - m, "control_grid", - " ControlGrid is a spatially hashed voxel grid used for non-rigid " - "point cloud registration and TSDF integration. Each grid stores a " - "map from the initial grid location to the deformed location. You " - "can imagine a control grid as a jelly that is warped upon " - "perturbation with its overall shape preserved. " - "Reference: " - "https://github.com/qianyizh/ElasticReconstruction/blob/master/" - "FragmentOptimizer/OptApp.cpp " - "http://vladlen.info/papers/elastic-fragments.pdf. "); + auto control_grid = + static_cast>(m_slac.attr("control_grid")); py::detail::bind_copy_functions(control_grid); control_grid.def(py::init<>()) .def(py::init(), "grid_size"_a, @@ -205,64 +229,43 @@ void pybind_slac_classes(py::module &m) { "anchor_idx={:d}].", control_grid.Size(), control_grid.GetAnchorIdx()); }); -} - -// SLAC functions have similar arguments, sharing arg docstrings. -static const std::unordered_map - map_shared_argument_docstrings = { - {"fnames_processed", - "List of filenames (str) for pre-processed pointcloud " - "fragments."}, - {"fragment_filenames", - "List of filenames (str) for pointcloud fragments."}, - {"fragment_pose_graph", "PoseGraph for pointcloud fragments"}, - {"params", - "slac_optimizer_params Parameters to tune in optimization."}, - {"debug_option", "debug options."}}; - -void pybind_slac_methods(py::module &m) { - m.def("save_correspondences_for_pointclouds", - &SaveCorrespondencesForPointClouds, - py::call_guard(), - "Read pose graph containing loop closures and odometry to compute " - "correspondences. Uses aggressive pruning -- reject any suspicious " - "pair.", - "fnames_processed"_a, "fragment_pose_graph"_a, - "params"_a = SLACOptimizerParams(), - "debug_option"_a = SLACDebugOption()); - docstring::FunctionDocInject(m, "save_correspondences_for_pointclouds", + m_slac.def( + "save_correspondences_for_pointclouds", + &SaveCorrespondencesForPointClouds, + py::call_guard(), + "Read pose graph containing loop closures and odometry to compute " + "correspondences. Uses aggressive pruning -- reject any suspicious " + "pair.", + "fnames_processed"_a, "fragment_pose_graph"_a, + "params"_a = SLACOptimizerParams(), + "debug_option"_a = SLACDebugOption()); + docstring::FunctionDocInject(m_slac, "save_correspondences_for_pointclouds", map_shared_argument_docstrings); - m.def("run_slac_optimizer_for_fragments", &RunSLACOptimizerForFragments, - "Simultaneous Localization and Calibration: Self-Calibration of " - "Consumer Depth Cameras, CVPR 2014 Qian-Yi Zhou and Vladlen Koltun " - "Estimate a shared control grid for all fragments for scene " - "reconstruction, implemented in " - "https://github.com/qianyizh/ElasticReconstruction. ", - "fragment_filenames"_a, "fragment_pose_graph"_a, - "params"_a = SLACOptimizerParams(), - "debug_option"_a = SLACDebugOption()); - docstring::FunctionDocInject(m, "run_slac_optimizer_for_fragments", + m_slac.def( + "run_slac_optimizer_for_fragments", &RunSLACOptimizerForFragments, + "Simultaneous Localization and Calibration: Self-Calibration of " + "Consumer Depth Cameras, CVPR 2014 Qian-Yi Zhou and Vladlen Koltun " + "Estimate a shared control grid for all fragments for scene " + "reconstruction, implemented in " + "https://github.com/qianyizh/ElasticReconstruction. ", + "fragment_filenames"_a, "fragment_pose_graph"_a, + "params"_a = SLACOptimizerParams(), + "debug_option"_a = SLACDebugOption()); + docstring::FunctionDocInject(m_slac, "run_slac_optimizer_for_fragments", map_shared_argument_docstrings); - m.def("run_rigid_optimizer_for_fragments", &RunRigidOptimizerForFragments, - "Extended ICP to simultaneously align multiple point clouds with " - "dense pairwise point-to-plane distances.", - "fragment_filenames"_a, "fragment_pose_graph"_a, - "params"_a = SLACOptimizerParams(), - "debug_option"_a = SLACDebugOption()); - docstring::FunctionDocInject(m, "run_rigid_optimizer_for_fragments", + m_slac.def( + "run_rigid_optimizer_for_fragments", &RunRigidOptimizerForFragments, + "Extended ICP to simultaneously align multiple point clouds with " + "dense pairwise point-to-plane distances.", + "fragment_filenames"_a, "fragment_pose_graph"_a, + "params"_a = SLACOptimizerParams(), + "debug_option"_a = SLACDebugOption()); + docstring::FunctionDocInject(m_slac, "run_rigid_optimizer_for_fragments", map_shared_argument_docstrings); } -void pybind_slac(py::module &m) { - py::module m_submodule = m.def_submodule( - "slac", - "Tensor-based Simultaneous Localisation and Calibration pipeline."); - pybind_slac_classes(m_submodule); - pybind_slac_methods(m_submodule); -} - } // namespace slac } // namespace pipelines } // namespace t diff --git a/cpp/pybind/t/pipelines/slac/slac.h b/cpp/pybind/t/pipelines/slac/slac.h index aa161593640..c3e734086b4 100644 --- a/cpp/pybind/t/pipelines/slac/slac.h +++ b/cpp/pybind/t/pipelines/slac/slac.h @@ -14,7 +14,8 @@ namespace t { namespace pipelines { namespace slac { -void pybind_slac(py::module &m); +void pybind_slac_declarations(py::module &m); +void pybind_slac_definitions(py::module &m); } // namespace slac } // namespace pipelines diff --git a/cpp/pybind/t/pipelines/slam/slam.cpp b/cpp/pybind/t/pipelines/slam/slam.cpp index 5a2cf260d60..c7ea2940c81 100644 --- a/cpp/pybind/t/pipelines/slam/slam.cpp +++ b/cpp/pybind/t/pipelines/slam/slam.cpp @@ -54,10 +54,18 @@ static const std::unordered_map "--trunc_voxel_multiplier=8 with --voxel_size=0.006(m) " "creates a truncation distance of 0.048(m)."}}; -void pybind_slam_model(py::module &m) { - py::class_ model(m, "Model", "Volumetric model for Dense SLAM."); +void pybind_slam_declarations(py::module &m) { + py::module m_slam = m.def_submodule("slam", "Tensor DenseSLAM pipeline."); + py::class_ model(m_slam, "Model", + "Volumetric model for Dense SLAM."); + py::class_ frame(m_slam, "Frame", + "A frame container that stores a map from keys " + "(color, depth) to tensor images."); +} +void pybind_slam_definitions(py::module &m) { + auto m_slam = static_cast(m.attr("slam")); + auto model = static_cast>(m_slam.attr("Model")); py::detail::bind_copy_functions(model); - model.def(py::init<>()); model.def(py::init(), "Constructor of a VoxelBlockGrid", "voxel_size"_a, @@ -65,7 +73,7 @@ void pybind_slam_model(py::module &m) { "transformation"_a = core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), "device"_a = core::Device("CUDA:0")); - docstring::ClassMethodDocInject(m, "Model", "__init__", + docstring::ClassMethodDocInject(m_slam, "Model", "__init__", map_shared_argument_docstrings); model.def("get_current_frame_pose", &Model::GetCurrentFramePose); @@ -77,7 +85,7 @@ void pybind_slam_model(py::module &m) { "model_frame"_a, "depth_scale"_a = 1000.0, "depth_min"_a = 0.1, "depth_max"_a = 3.0, "trunc_voxel_multiplier"_a = 8.0, "enable_color"_a = false, "weight_threshold"_a = -1.0); - docstring::ClassMethodDocInject(m, "Model", "synthesize_model_frame", + docstring::ClassMethodDocInject(m_slam, "Model", "synthesize_model_frame", map_shared_argument_docstrings); model.def( @@ -89,7 +97,7 @@ void pybind_slam_model(py::module &m) { "method"_a = odometry::Method::PointToPlane, "criteria"_a = (std::vector){ 6, 3, 1}); - docstring::ClassMethodDocInject(m, "Model", "track_frame_to_model", + docstring::ClassMethodDocInject(m_slam, "Model", "track_frame_to_model", map_shared_argument_docstrings); model.def("integrate", &Model::Integrate, @@ -97,21 +105,21 @@ void pybind_slam_model(py::module &m) { "Integrate an input frame to a volume.", "input_frame"_a, "depth_scale"_a = 1000.0, "depth_max"_a = 3.0, "trunc_voxel_multiplier"_a = 8.0); - docstring::ClassMethodDocInject(m, "Model", "integrate", + docstring::ClassMethodDocInject(m_slam, "Model", "integrate", map_shared_argument_docstrings); model.def("extract_pointcloud", &Model::ExtractPointCloud, py::call_guard(), "Extract point cloud from the volumetric model.", "weight_threshold"_a = 3.0, "estimated_number"_a = -1); - docstring::ClassMethodDocInject(m, "Model", "extract_pointcloud", + docstring::ClassMethodDocInject(m_slam, "Model", "extract_pointcloud", map_shared_argument_docstrings); model.def("extract_trianglemesh", &Model::ExtractTriangleMesh, py::call_guard(), "Extract triangle mesh from the volumetric model.", "weight_threshold"_a = 3.0, "estimated_number"_a = -1); - docstring::ClassMethodDocInject(m, "Model", "extract_trianglemesh", + docstring::ClassMethodDocInject(m_slam, "Model", "extract_trianglemesh", map_shared_argument_docstrings); model.def( @@ -127,17 +135,12 @@ void pybind_slam_model(py::module &m) { "frame to the world frame."); model.def_readwrite("frame_id", &Model::frame_id_, "Get the current frame index in a sequence."); -} - -void pybind_slam_frame(py::module &m) { - py::class_ frame(m, "Frame", - "A frame container that stores a map from keys " - "(color, depth) to tensor images."); + auto frame = static_cast>(m_slam.attr("Frame")); py::detail::bind_copy_functions(frame); frame.def(py::init(), "height"_a, "width"_a, "intrinsics"_a, "device"_a); - docstring::ClassMethodDocInject(m, "Frame", "__init__", + docstring::ClassMethodDocInject(m_slam, "Frame", "__init__", map_shared_argument_docstrings); frame.def("height", &Frame::GetHeight); @@ -153,13 +156,6 @@ void pybind_slam_frame(py::module &m) { "Get a 2D image from from the given key in the map."); } -void pybind_slam(py::module &m) { - py::module m_submodule = - m.def_submodule("slam", "Tensor DenseSLAM pipeline."); - pybind_slam_model(m_submodule); - pybind_slam_frame(m_submodule); -} - } // namespace slam } // namespace pipelines } // namespace t diff --git a/cpp/pybind/t/pipelines/slam/slam.h b/cpp/pybind/t/pipelines/slam/slam.h index eea3c937abf..335e03c65fd 100644 --- a/cpp/pybind/t/pipelines/slam/slam.h +++ b/cpp/pybind/t/pipelines/slam/slam.h @@ -14,7 +14,8 @@ namespace t { namespace pipelines { namespace slam { -void pybind_slam(py::module &m); +void pybind_slam_declarations(py::module &m); +void pybind_slam_definitions(py::module &m); } // namespace slam } // namespace pipelines diff --git a/cpp/pybind/t/t.cpp b/cpp/pybind/t/t.cpp index b3a166efc1c..48a6dd9b811 100644 --- a/cpp/pybind/t/t.cpp +++ b/cpp/pybind/t/t.cpp @@ -15,11 +15,17 @@ namespace open3d { namespace t { -void pybind_t(py::module& m) { - py::module m_submodule = m.def_submodule("t"); - pipelines::pybind_pipelines(m_submodule); - geometry::pybind_geometry(m_submodule); - io::pybind_io(m_submodule); +void pybind_t_declarations(py::module& m) { + py::module m_t = m.def_submodule("t"); + pipelines::pybind_pipelines_declarations(m_t); + geometry::pybind_geometry_declarations(m_t); + io::pybind_io_declarations(m_t); +} +void pybind_t_definitions(py::module& m) { + auto m_t = static_cast(m.attr("t")); + pipelines::pybind_pipelines_definitions(m_t); + geometry::pybind_geometry_definitions(m_t); + io::pybind_io_definitions(m_t); } } // namespace t diff --git a/cpp/pybind/t/t.h b/cpp/pybind/t/t.h index c941372b5b8..95a050b4c02 100644 --- a/cpp/pybind/t/t.h +++ b/cpp/pybind/t/t.h @@ -12,7 +12,8 @@ namespace open3d { namespace t { -void pybind_t(py::module& m); +void pybind_t_declarations(py::module& m); +void pybind_t_definitions(py::module& m); } // namespace t } // namespace open3d diff --git a/cpp/pybind/utility/eigen.cpp b/cpp/pybind/utility/eigen.cpp index f23e94060f3..6220d7e14f8 100644 --- a/cpp/pybind/utility/eigen.cpp +++ b/cpp/pybind/utility/eigen.cpp @@ -306,8 +306,17 @@ py::class_ pybind_eigen_vector_of_matrix( namespace open3d { namespace utility { -void pybind_eigen(py::module &m) { +void pybind_eigen_declarations(py::module &m) { auto intvector = pybind_eigen_vector_of_scalar(m, "IntVector"); + auto doublevector = + pybind_eigen_vector_of_scalar(m, "DoubleVector"); + auto vector3dvector = pybind_eigen_vector_of_vector( + m, "Vector3dVector", "std::vector", + py::py_array_to_vectors_double); +} +void pybind_eigen_definitions(py::module &m) { + auto intvector = static_cast( + m, "IntVector"))>(m.attr("IntVector")); intvector.attr("__doc__") = docstring::static_property( py::cpp_function([](py::handle arg) -> std::string { return R"(Convert int32 numpy array of shape ``(n,)`` to Open3D format.)"; @@ -315,16 +324,18 @@ void pybind_eigen(py::module &m) { py::none(), py::none(), ""); auto doublevector = - pybind_eigen_vector_of_scalar(m, "DoubleVector"); + static_cast( + m, "DoubleVector"))>(m.attr("DoubleVector")); doublevector.attr("__doc__") = docstring::static_property( py::cpp_function([](py::handle arg) -> std::string { return R"(Convert float64 numpy array of shape ``(n,)`` to Open3D format.)"; }), py::none(), py::none(), ""); - - auto vector3dvector = pybind_eigen_vector_of_vector( - m, "Vector3dVector", "std::vector", - py::py_array_to_vectors_double); + auto vector3dvector = + static_cast( + m, "Vector3dVector", "std::vector", + py::py_array_to_vectors_double))>( + m.attr("Vector3dVector")); vector3dvector.attr("__doc__") = docstring::static_property( py::cpp_function([](py::handle arg) -> std::string { return R"(Convert float64 numpy array of shape ``(n, 3)`` to Open3D format. diff --git a/cpp/pybind/utility/logging.cpp b/cpp/pybind/utility/logging.cpp index 2be3832c883..8d50af1213f 100644 --- a/cpp/pybind/utility/logging.cpp +++ b/cpp/pybind/utility/logging.cpp @@ -13,7 +13,7 @@ namespace open3d { namespace utility { -void pybind_logging(py::module& m) { +void pybind_logging_declarations(py::module& m) { py::enum_ vl(m, "VerbosityLevel", py::arithmetic(), "VerbosityLevel"); vl.value("Error", VerbosityLevel::Error) @@ -27,7 +27,13 @@ void pybind_logging(py::module& m) { return "Enum class for VerbosityLevel."; }), py::none(), py::none(), ""); - + py::class_ verbosity_context_manager( + m, "VerbosityContextManager", + "A context manager to " + "temporally change the " + "verbosity level of Open3D"); +} +void pybind_logging_definitions(py::module& m) { m.def("set_verbosity_level", &SetVerbosityLevel, "Set global verbosity level of Open3D", py::arg("verbosity_level")); docstring::FunctionDocInject( @@ -44,11 +50,10 @@ void pybind_logging(py::module& m) { utility::LogInfo("Resetting default logger to print to terminal."); utility::Logger::GetInstance().ResetPrintFunction(); }); - - py::class_(m, "VerbosityContextManager", - "A context manager to " - "temporally change the " - "verbosity level of Open3D") + auto verbosity_context_manager = + static_cast>( + m.attr("VerbosityContextManager")); + verbosity_context_manager .def(py::init(), "Create a VerbosityContextManager with a given VerbosityLevel", "level"_a) diff --git a/cpp/pybind/utility/utility.cpp b/cpp/pybind/utility/utility.cpp index 315aa0a2125..1e26aa1bfb9 100644 --- a/cpp/pybind/utility/utility.cpp +++ b/cpp/pybind/utility/utility.cpp @@ -13,11 +13,16 @@ namespace open3d { namespace utility { -void pybind_utility(py::module &m) { - py::module m_submodule = m.def_submodule("utility"); - pybind_eigen(m_submodule); - pybind_logging(m_submodule); - random::pybind_random(m_submodule); +void pybind_utility_declarations(py::module &m) { + py::module m_utility = m.def_submodule("utility"); + pybind_eigen_declarations(m_utility); + pybind_logging_declarations(m_utility); + random::pybind_random(m_utility); +} +void pybind_utility_definitions(py::module &m) { + auto m_utility = static_cast(m.attr("utility")); + pybind_eigen_definitions(m_utility); + pybind_logging_definitions(m_utility); } } // namespace utility diff --git a/cpp/pybind/utility/utility.h b/cpp/pybind/utility/utility.h index eeb40cb4e8f..2f61661674d 100644 --- a/cpp/pybind/utility/utility.h +++ b/cpp/pybind/utility/utility.h @@ -12,10 +12,12 @@ namespace open3d { namespace utility { -void pybind_utility(py::module &m); - -void pybind_eigen(py::module &m); -void pybind_logging(py::module &m); +void pybind_utility_declarations(py::module &m); +void pybind_eigen_declarations(py::module &m); +void pybind_logging_declarations(py::module &m); +void pybind_utility_definitions(py::module &m); +void pybind_eigen_definitions(py::module &m); +void pybind_logging_definitions(py::module &m); namespace random { void pybind_random(py::module &m); diff --git a/cpp/pybind/visualization/app/viewer.cpp b/cpp/pybind/visualization/app/viewer.cpp index 524d154d45e..f6be9c4d158 100644 --- a/cpp/pybind/visualization/app/viewer.cpp +++ b/cpp/pybind/visualization/app/viewer.cpp @@ -14,8 +14,13 @@ namespace open3d { namespace visualization { namespace app { -static void pybind_app_functions(py::module &m) { - m.def( +void pybind_app_declarations(py::module &m) { + py::module m_app = m.def_submodule( + "app", "Functionality for running the open3d viewer."); +} +void pybind_app_definitions(py::module &m) { + auto m_app = static_cast(m.attr("app")); + m_app.def( "run_viewer", [](const std::vector &args) { const char **argv = new const char *[args.size()]; @@ -26,21 +31,14 @@ static void pybind_app_functions(py::module &m) { delete[] argv; }, "args"_a); - docstring::FunctionDocInject( - m, "run_viewer", + m_app, "run_viewer", {{"args", "List of arguments containing the path of the calling program " "(which should be in the same directory as the gui resources " "folder) and the optional path of the geometry to visualize."}}); } -void pybind_app(py::module &m) { - py::module m_submodule = m.def_submodule( - "app", "Functionality for running the open3d viewer."); - pybind_app_functions(m_submodule); -} - } // namespace app } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/app/viewer.h b/cpp/pybind/visualization/app/viewer.h index 8c62fdc71a0..b39310b6081 100644 --- a/cpp/pybind/visualization/app/viewer.h +++ b/cpp/pybind/visualization/app/viewer.h @@ -13,7 +13,8 @@ namespace open3d { namespace visualization { namespace app { -void pybind_app(py::module &m); +void pybind_app_declarations(py::module &m); +void pybind_app_definitions(py::module &m); } // namespace app } // namespace visualization diff --git a/cpp/pybind/visualization/gui/events.cpp b/cpp/pybind/visualization/gui/events.cpp index d35d1f7bf6d..f6a3876e8ee 100644 --- a/cpp/pybind/visualization/gui/events.cpp +++ b/cpp/pybind/visualization/gui/events.cpp @@ -15,9 +15,9 @@ namespace open3d { namespace visualization { namespace gui { -void pybind_gui_events(py::module& m) { - py::enum_ buttons(m, "MouseButton", "Mouse button identifiers", - py::arithmetic()); +void pybind_gui_events_declarations(py::module& m_gui) { + py::enum_ buttons( + m_gui, "MouseButton", "Mouse button identifiers", py::arithmetic()); buttons.value("NONE", MouseButton::NONE) .value("LEFT", MouseButton::LEFT) .value("MIDDLE", MouseButton::MIDDLE) @@ -25,17 +25,15 @@ void pybind_gui_events(py::module& m) { .value("BUTTON4", MouseButton::BUTTON4) .value("BUTTON5", MouseButton::BUTTON5) .export_values(); - - py::enum_ key_mod(m, "KeyModifier", "Key modifier identifiers", - py::arithmetic()); + py::enum_ key_mod( + m_gui, "KeyModifier", "Key modifier identifiers", py::arithmetic()); key_mod.value("NONE", KeyModifier::NONE) .value("SHIFT", KeyModifier::SHIFT) .value("CTRL", KeyModifier::CTRL) .value("ALT", KeyModifier::ALT) .value("META", KeyModifier::META) .export_values(); - - py::class_ mouse_event(m, "MouseEvent", + py::class_ mouse_event(m_gui, "MouseEvent", "Object that stores mouse events"); py::enum_ mouse_event_type(mouse_event, "Type", py::arithmetic()); @@ -45,115 +43,7 @@ void pybind_gui_events(py::module& m) { .value("BUTTON_UP", MouseEvent::Type::BUTTON_UP) .value("WHEEL", MouseEvent::Type::WHEEL) .export_values(); - mouse_event.def_readwrite("type", &MouseEvent::type, "Mouse event type") - .def_readwrite("x", &MouseEvent::x, - "x coordinate of the mouse event") - .def_readwrite("y", &MouseEvent::y, - "y coordinate of the mouse event") - .def( - "is_modifier_down", - [](const MouseEvent& e, KeyModifier mod) { - return ((e.modifiers & int(mod)) != 0); - }, - "Convenience function to more easily deterimine if a " - "modifier " - "key is down") - .def( - "is_button_down", - [](const MouseEvent& e, MouseButton b) { - if (e.type == MouseEvent::Type::WHEEL) { - return false; - } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { - return (e.button.button == b); - } else { - return ((e.move.buttons & int(b)) != 0); - } - }, - "Convenience function to more easily deterimine if a mouse " - "button is pressed") - .def_readwrite("modifiers", &MouseEvent::modifiers, - "ORed mouse modifiers") - .def_property( - "buttons", - [](const MouseEvent& e) -> int { - if (e.type == MouseEvent::Type::WHEEL) { - return int(MouseButton::NONE); - } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { - return int(e.button.button); - } else { - return e.move.buttons; - } - }, - [](MouseEvent& e, int new_value) { - if (e.type == MouseEvent::Type::WHEEL) { - ; // no button value for wheel events - } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { - e.button.button = MouseButton(new_value); - } else { - e.move.buttons = new_value; - } - }, - "ORed mouse buttons") - .def_property( - "wheel_dx", - [](const MouseEvent& e) -> int { - if (e.type == MouseEvent::Type::WHEEL) { - return e.wheel.dx; - } else { - return 0; - } - }, - [](MouseEvent& e, int new_value) { - if (e.type == MouseEvent::Type::WHEEL) { - e.wheel.dx = new_value; - } else { - utility::LogWarning( - "Cannot set MouseEvent.wheel_dx unless " - "MouseEvent.type == MouseEvent.Type.WHEEL"); - } - }, - "Mouse wheel horizontal motion") - .def_property( - "wheel_dy", - [](const MouseEvent& e) -> int { - if (e.type == MouseEvent::Type::WHEEL) { - return e.wheel.dy; - } else { - return 0; - } - }, - [](MouseEvent& e, int new_value) { - if (e.type == MouseEvent::Type::WHEEL) { - e.wheel.dy = new_value; - } else { - utility::LogWarning( - "Cannot set MouseEvent.wheel_dy unless " - "MouseEvent.type == MouseEvent.Type.WHEEL"); - } - }, - "Mouse wheel vertical motion") - .def_property( - "wheel_is_trackpad", - [](const MouseEvent& e) -> bool { - if (e.type == MouseEvent::Type::WHEEL) { - return e.wheel.isTrackpad; - } else { - return false; - } - }, - [](MouseEvent& e, bool new_value) { - if (e.type == MouseEvent::Type::WHEEL) { - e.wheel.isTrackpad = new_value; - } else { - utility::LogWarning( - "Cannot set MouseEvent.wheel_is_trackpad " - "unless MouseEvent.type == " - "MouseEvent.Type.WHEEL"); - } - }, - "Is mouse wheel event from a trackpad"); - - py::enum_ key_name(m, "KeyName", + py::enum_ key_name(m_gui, "KeyName", "Names of keys. Used by KeyEvent.key"); key_name.value("NONE", KeyName::KEY_NONE) .value("BACKSPACE", KeyName::KEY_BACKSPACE) @@ -260,14 +150,125 @@ void pybind_gui_events(py::module& m) { .value("F12", KeyName::KEY_F12) .value("UNKNOWN", KeyName::KEY_UNKNOWN) .export_values(); - - py::class_ key_event(m, "KeyEvent", + py::class_ key_event(m_gui, "KeyEvent", "Object that stores key events"); py::enum_ key_event_type(key_event, "Type", py::arithmetic()); key_event_type.value("DOWN", KeyEvent::Type::DOWN) .value("UP", KeyEvent::Type::UP) .export_values(); +} +void pybind_gui_events_definitions(py::module& m_gui) { + auto mouse_event = + static_cast>(m_gui.attr("MouseEvent")); + mouse_event.def_readwrite("type", &MouseEvent::type, "Mouse event type") + .def_readwrite("x", &MouseEvent::x, + "x coordinate of the mouse event") + .def_readwrite("y", &MouseEvent::y, + "y coordinate of the mouse event") + .def( + "is_modifier_down", + [](const MouseEvent& e, KeyModifier mod) { + return ((e.modifiers & int(mod)) != 0); + }, + "Convenience function to more easily deterimine if a " + "modifier " + "key is down") + .def( + "is_button_down", + [](const MouseEvent& e, MouseButton b) { + if (e.type == MouseEvent::Type::WHEEL) { + return false; + } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { + return (e.button.button == b); + } else { + return ((e.move.buttons & int(b)) != 0); + } + }, + "Convenience function to more easily deterimine if a mouse " + "button is pressed") + .def_readwrite("modifiers", &MouseEvent::modifiers, + "ORed mouse modifiers") + .def_property( + "buttons", + [](const MouseEvent& e) -> int { + if (e.type == MouseEvent::Type::WHEEL) { + return int(MouseButton::NONE); + } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { + return int(e.button.button); + } else { + return e.move.buttons; + } + }, + [](MouseEvent& e, int new_value) { + if (e.type == MouseEvent::Type::WHEEL) { + ; // no button value for wheel events + } else if (e.type == MouseEvent::Type::BUTTON_DOWN) { + e.button.button = MouseButton(new_value); + } else { + e.move.buttons = new_value; + } + }, + "ORed mouse buttons") + .def_property( + "wheel_dx", + [](const MouseEvent& e) -> int { + if (e.type == MouseEvent::Type::WHEEL) { + return e.wheel.dx; + } else { + return 0; + } + }, + [](MouseEvent& e, int new_value) { + if (e.type == MouseEvent::Type::WHEEL) { + e.wheel.dx = new_value; + } else { + utility::LogWarning( + "Cannot set MouseEvent.wheel_dx unless " + "MouseEvent.type == MouseEvent.Type.WHEEL"); + } + }, + "Mouse wheel horizontal motion") + .def_property( + "wheel_dy", + [](const MouseEvent& e) -> int { + if (e.type == MouseEvent::Type::WHEEL) { + return e.wheel.dy; + } else { + return 0; + } + }, + [](MouseEvent& e, int new_value) { + if (e.type == MouseEvent::Type::WHEEL) { + e.wheel.dy = new_value; + } else { + utility::LogWarning( + "Cannot set MouseEvent.wheel_dy unless " + "MouseEvent.type == MouseEvent.Type.WHEEL"); + } + }, + "Mouse wheel vertical motion") + .def_property( + "wheel_is_trackpad", + [](const MouseEvent& e) -> bool { + if (e.type == MouseEvent::Type::WHEEL) { + return e.wheel.isTrackpad; + } else { + return false; + } + }, + [](MouseEvent& e, bool new_value) { + if (e.type == MouseEvent::Type::WHEEL) { + e.wheel.isTrackpad = new_value; + } else { + utility::LogWarning( + "Cannot set MouseEvent.wheel_is_trackpad " + "unless MouseEvent.type == " + "MouseEvent.Type.WHEEL"); + } + }, + "Is mouse wheel event from a trackpad"); + auto key_event = static_cast>(m_gui.attr("KeyEvent")); key_event.def_readwrite("type", &KeyEvent::type, "Key event type") .def_readwrite("key", &KeyEvent::key, "This is the actual key that was pressed, not the " diff --git a/cpp/pybind/visualization/gui/gui.cpp b/cpp/pybind/visualization/gui/gui.cpp index 9f086e50ef5..708d2c14e2b 100644 --- a/cpp/pybind/visualization/gui/gui.cpp +++ b/cpp/pybind/visualization/gui/gui.cpp @@ -173,17 +173,366 @@ std::shared_ptr RenderToDepthImageWithoutWindow( enum class EventCallbackResult { IGNORED = 0, HANDLED, CONSUMED }; -void pybind_gui_classes(py::module &m) { - // ---- FontStyle ---- - py::enum_ font_style(m, "FontStyle", "Font style"); +class PyImageWidget : public ImageWidget { + using Super = ImageWidget; + +public: + PyImageWidget() : Super() {} + /// Uses image from the specified path. Each ImageWidget will use one + /// draw call. + explicit PyImageWidget(const char *image_path) : Super(image_path) {} + /// Uses existing image. Each ImageWidget will use one draw call. + explicit PyImageWidget(std::shared_ptr image) + : Super(image) {} + /// Uses existing image. Each ImageWidget will use one draw call. + explicit PyImageWidget(std::shared_ptr image) + : Super(image) {} + /// Uses an existing texture, using texture coordinates + /// (u0, v0) to (u1, v1). Does not deallocate texture on destruction. + /// This is useful for using an icon atlas to reduce draw calls. + explicit PyImageWidget( + open3d::visualization::rendering::TextureHandle texture_id, + float u0 = 0.0f, + float v0 = 0.0f, + float u1 = 1.0f, + float v1 = 1.0f) + : Super(texture_id, u0, v0, u1, v1) {} + + ~PyImageWidget() = default; + + void SetOnMouse(std::function f) { on_mouse_ = f; } + void SetOnKey(std::function f) { on_key_ = f; } + + Widget::EventResult Mouse(const MouseEvent &e) override { + if (on_mouse_) { + switch (EventCallbackResult(on_mouse_(e))) { + case EventCallbackResult::CONSUMED: + return Widget::EventResult::CONSUMED; + case EventCallbackResult::HANDLED: { + auto result = Super::Mouse(e); + if (result == Widget::EventResult::IGNORED) { + result = Widget::EventResult::CONSUMED; + } + return result; + } + case EventCallbackResult::IGNORED: + default: + return Super::Mouse(e); + } + } else { + return Super::Mouse(e); + } + } + + Widget::EventResult Key(const KeyEvent &e) override { + if (on_key_) { + switch (EventCallbackResult(on_key_(e))) { + case EventCallbackResult::CONSUMED: + return Widget::EventResult::CONSUMED; + case EventCallbackResult::HANDLED: { + auto result = Super::Key(e); + if (result == Widget::EventResult::IGNORED) { + result = Widget::EventResult::CONSUMED; + } + return result; + } + case EventCallbackResult::IGNORED: + default: + return Super::Key(e); + } + } else { + return Super::Key(e); + } + } + +private: + std::function on_mouse_; + std::function on_key_; +}; + +class PySceneWidget : public SceneWidget { + using Super = SceneWidget; + +public: + void SetOnMouse(std::function f) { on_mouse_ = f; } + void SetOnKey(std::function f) { on_key_ = f; } + + Widget::EventResult Mouse(const MouseEvent &e) override { + if (on_mouse_) { + switch (EventCallbackResult(on_mouse_(e))) { + case EventCallbackResult::CONSUMED: + return Widget::EventResult::CONSUMED; + case EventCallbackResult::HANDLED: { + auto result = Super::Mouse(e); + if (result == Widget::EventResult::IGNORED) { + result = Widget::EventResult::CONSUMED; + } + return result; + } + case EventCallbackResult::IGNORED: + default: + return Super::Mouse(e); + } + } else { + return Super::Mouse(e); + } + } + + Widget::EventResult Key(const KeyEvent &e) override { + if (on_key_) { + switch (EventCallbackResult(on_key_(e))) { + case EventCallbackResult::CONSUMED: + return Widget::EventResult::CONSUMED; + case EventCallbackResult::HANDLED: { + auto result = Super::Key(e); + if (result == Widget::EventResult::IGNORED) { + result = Widget::EventResult::CONSUMED; + } + return result; + } + case EventCallbackResult::IGNORED: + default: + return Super::Key(e); + } + } else { + return Super::Key(e); + } + } + +private: + std::function on_mouse_; + std::function on_key_; +}; + +void pybind_gui_declarations(py::module &m) { + py::module m_gui = m.def_submodule("gui"); + pybind_gui_events_declarations(m_gui); + py::enum_ font_style(m_gui, "FontStyle", "Font style"); font_style.value("NORMAL", FontStyle::NORMAL) .value("BOLD", FontStyle::BOLD) .value("ITALIC", FontStyle::ITALIC) .value("BOLD_ITALIC", FontStyle::BOLD_ITALIC); - - // ---- FontDescription ---- - py::class_ fd(m, "FontDescription", + py::class_ fd(m_gui, "FontDescription", "Class to describe a custom font"); + py::class_ application(m_gui, "Application", + "Global application singleton. This " + "owns the menubar, windows, and event " + "loop"); + py::class_ lc( + m_gui, "LayoutContext", + "Context passed to Window's on_layout callback"); + // Pybind appears to need to know about the base class. It doesn't have + // to be named the same as the C++ class, though. The holder object cannot + // be a shared_ptr or we can crash (see comment for UnownedPointer). + py::class_> window_base( + m_gui, "WindowBase", "Application window"); + py::class_, Window> window( + m_gui, "Window", + "Application window. Create with " + "Application.instance.create_window()."); + py::class_> menu(m_gui, "Menu", + "A menu, possibly a menu tree"); + py::class_ color(m_gui, "Color", "Stores color for gui classes"); + py::class_ theme(m_gui, "Theme", + "Theme parameters such as colors used for drawing " + "widgets (read-only)"); + py::class_ rect(m_gui, "Rect", "Represents a widget frame"); + py::class_ size(m_gui, "Size", "Size object"); + py::class_> widget(m_gui, "Widget", + "Base widget class"); + py::enum_ widget_event_callback_result( + widget, "EventCallbackResult", "Returned by event handlers", + py::arithmetic()); + widget_event_callback_result + .value("IGNORED", EventCallbackResult::IGNORED, + "Event handler ignored the event, widget will " + "handle event normally") + .value("HANDLED", EventCallbackResult::HANDLED, + "Event handler handled the event, but widget " + "will still handle the event normally. This is " + "useful when you are augmenting base " + "functionality") + .value("CONSUMED", EventCallbackResult::CONSUMED, + "Event handler consumed the event, event " + "handling stops, widget will not handle the " + "event. This is useful when you are replacing " + "functionality") + .export_values(); + py::class_ constraints( + widget, "Constraints", + "Constraints object for Widget.calc_preferred_size()"); + py::class_, Widget> widgetProxy( + m_gui, "WidgetProxy", + "Widget container to delegate any widget dynamically." + " Widget can not be managed dynamically. Although it is allowed" + " to add more child widgets, it's impossible to replace some child" + " with new on or remove children. WidgetProxy is designed to solve" + " this problem." + " When WidgetProxy is created, it's invisible and disabled, so it" + " won't be drawn or layout, seeming like it does not exist. When" + " a widget is set by set_widget, all Widget's APIs will be" + " conducted to that child widget. It looks like WidgetProxy is" + " that widget." + " At any time, a new widget could be set, to replace the old one." + " and the old widget will be destroyed." + " Due to the content changing after a new widget is set or cleared," + " a relayout of Window might be called after set_widget." + " The delegated widget could be retrieved by get_widget in case" + " you need to access it directly, like get check status of a" + " CheckBox. API other than set_widget and get_widget has" + " completely same functions as Widget."); + py::class_, WidgetProxy> + widget_stack(m_gui, "WidgetStack", + "A widget stack saves all widgets pushed into by " + "push_widget and always shows the top one. The " + "WidgetStack is a subclass of WidgetProxy, in other" + "words, the topmost widget will delegate itself to " + "WidgetStack. pop_widget will remove the topmost " + "widget and callback set by set_on_top taking the " + "new topmost widget will be called. The WidgetStack " + "disappears in GUI if there is no widget in stack."); + py::class_, Widget> button(m_gui, "Button", + "Button"); + py::class_, Widget> checkbox( + m_gui, "Checkbox", "Checkbox"); + py::class_, Widget> coloredit( + m_gui, "ColorEdit", "Color picker"); + py::class_, Widget> combobox( + m_gui, "Combobox", "Exclusive selection from a pull-down menu"); + py::class_, Widget> radiobtn( + m_gui, "RadioButton", "Exclusive selection from radio button list"); + py::enum_ radiobtn_type(radiobtn, "Type", + py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + radiobtn_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for RadioButton types."; + }), + py::none(), py::none(), ""); + radiobtn_type.value("VERT", RadioButton::Type::VERT) + .value("HORIZ", RadioButton::Type::HORIZ) + .export_values(); + py::class_> uiimage( + m_gui, "UIImage", + "A bitmap suitable for displaying with ImageWidget"); + py::enum_ uiimage_scaling(uiimage, "Scaling", + py::arithmetic()); + uiimage_scaling.value("NONE", UIImage::Scaling::NONE) + .value("ANY", UIImage::Scaling::ANY) + .value("ASPECT", UIImage::Scaling::ASPECT); + py::class_, Widget> + imagewidget(m_gui, "ImageWidget", "Displays a bitmap"); + py::class_, Widget> label(m_gui, "Label", + "Displays text"); + py::class_> label3d( + m_gui, "Label3D", "Displays text in a 3D scene"); + py::class_, Widget> listview( + m_gui, "ListView", "Displays a list of text"); + py::class_, Widget> numedit( + m_gui, "NumberEdit", "Allows the user to enter a number."); + py::enum_ numedit_type(numedit, "Type", py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + numedit_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for NumberEdit types."; + }), + py::none(), py::none(), ""); + numedit_type.value("INT", NumberEdit::Type::INT) + .value("DOUBLE", NumberEdit::Type::DOUBLE) + .export_values(); + py::class_, Widget> progress( + m_gui, "ProgressBar", "Displays a progress bar"); + py::class_, Widget> scene( + m_gui, "SceneWidget", "Displays 3D content"); + py::enum_ scene_ctrl(scene, "Controls", + py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + scene_ctrl.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class describing mouse interaction."; + }), + py::none(), py::none(), ""); + scene_ctrl.value("ROTATE_CAMERA", SceneWidget::Controls::ROTATE_CAMERA) + .value("ROTATE_CAMERA_SPHERE", + SceneWidget::Controls::ROTATE_CAMERA_SPHERE) + .value("FLY", SceneWidget::Controls::FLY) + .value("ROTATE_SUN", SceneWidget::Controls::ROTATE_SUN) + .value("ROTATE_IBL", SceneWidget::Controls::ROTATE_IBL) + .value("ROTATE_MODEL", SceneWidget::Controls::ROTATE_MODEL) + .value("PICK_POINTS", SceneWidget::Controls::PICK_POINTS) + .export_values(); + py::class_, Widget> slider( + m_gui, "Slider", "A slider widget for visually selecting numbers"); + py::enum_ slider_type(slider, "Type", py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + slider_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for Slider types."; + }), + py::none(), py::none(), ""); + slider_type.value("INT", Slider::Type::INT) + .value("DOUBLE", Slider::Type::DOUBLE) + .export_values(); + py::class_, Widget> stacked( + m_gui, "StackedWidget", "Like a TabControl but without the tabs"); + py::class_, Widget> tabctrl( + m_gui, "TabControl", "Tab control"); + py::class_, Widget> textedit( + m_gui, "TextEdit", "Allows the user to enter or modify text"); + py::class_, Widget> toggle( + m_gui, "ToggleSwitch", "ToggleSwitch"); + py::class_, Widget> treeview( + m_gui, "TreeView", "Hierarchical list"); + py::class_, + Widget> + checkable_cell(m_gui, "CheckableTextTreeCell", + "TreeView cell with a checkbox and text"); + py::class_, Widget> lut_cell( + m_gui, "LUTTreeCell", + "TreeView cell with checkbox, text, and color edit"); + py::class_, Widget> + colormap_cell(m_gui, "ColormapTreeCell", + "TreeView cell with a number edit and color edit"); + py::class_, Widget> vectoredit( + m_gui, "VectorEdit", "Allows the user to edit a 3-space vector"); + py::class_> margins(m_gui, "Margins", + "Margins for layouts"); + py::class_, Widget> layout1d( + m_gui, "Layout1D", "Layout base class"); + py::class_, Layout1D> vlayout(m_gui, "Vert", + "Vertical layout"); + py::class_, Vert> + collapsable(m_gui, "CollapsableVert", + "Vertical layout with title, whose contents are " + "collapsable"); + py::class_, Vert> slayout( + m_gui, "ScrollableVert", "Scrollable vertical layout"); + py::class_, Layout1D> hlayout( + m_gui, "Horiz", "Horizontal layout"); + py::class_, Widget> vgrid(m_gui, "VGrid", + "Grid layout"); + py::class_, Widget> dialog(m_gui, "Dialog", + "Dialog"); + py::class_, Dialog> filedlg( + m_gui, "FileDialog", "File picker dialog"); + py::enum_ filedlg_mode(filedlg, "Mode", py::arithmetic()); + // Trick to write docs without listing the members in the enum class again. + filedlg_mode.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for FileDialog modes."; + }), + py::none(), py::none(), ""); + filedlg_mode.value("OPEN", FileDialog::Mode::OPEN) + .value("SAVE", FileDialog::Mode::SAVE) + .value("OPEN_DIR", FileDialog::Mode::OPEN_DIR) + .export_values(); +} +void pybind_gui_definitions(py::module &m) { + auto m_gui = static_cast(m.attr("gui")); + pybind_gui_events_definitions(m_gui); + // ---- FontDescription ---- + auto fd = static_cast>( + m_gui.attr("FontDescription")); fd.attr("SANS_SERIF") = FontDescription::SANS_SERIF; fd.attr("MONOSPACE") = FontDescription::MONOSPACE; fd.def(py::init(), @@ -222,10 +571,8 @@ void pybind_gui_classes(py::module &m) { "font."); // ---- Application ---- - py::class_ application(m, "Application", - "Global application singleton. This " - "owns the menubar, windows, and event " - "loop"); + auto application = + static_cast>(m_gui.attr("Application")); application.attr("DEFAULT_FONT_ID") = Application::DEFAULT_FONT_ID; application .def("__repr__", @@ -376,9 +723,8 @@ void pybind_gui_classes(py::module &m) { "resources directory"); // ---- LayoutContext ---- - py::class_ lc( - m, "LayoutContext", - "Context passed to Window's on_layout callback"); + auto lc = + static_cast>(m_gui.attr("LayoutContext")); // lc.def_readonly("theme", &LayoutContext::theme); // Pybind can't return a reference (since Theme is a unique_ptr), so // return a copy instead. @@ -388,15 +734,9 @@ void pybind_gui_classes(py::module &m) { }); // ---- Window ---- - // Pybind appears to need to know about the base class. It doesn't have - // to be named the same as the C++ class, though. The holder object cannot - // be a shared_ptr or we can crash (see comment for UnownedPointer). - py::class_> window_base( - m, "WindowBase", "Application window"); - py::class_, Window> window( - m, "Window", - "Application window. Create with " - "Application.instance.create_window()."); + auto window = + static_cast, Window>>( + m_gui.attr("Window")); window.def("__repr__", [](const PyWindow &w) { return "Application window"; }) .def( @@ -489,8 +829,8 @@ void pybind_gui_classes(py::module &m) { "Gets the rendering.Renderer object for the Window"); // ---- Menu ---- - py::class_> menu(m, "Menu", - "A menu, possibly a menu tree"); + auto menu = static_cast>>( + m_gui.attr("Menu")); menu.def(py::init<>()) .def( "add_item", @@ -526,7 +866,7 @@ void pybind_gui_classes(py::module &m) { "Sets menu item (un)checked"); // ---- Color ---- - py::class_ color(m, "Color", "Stores color for gui classes"); + auto color = static_cast>(m_gui.attr("Color")); color.def(py::init([](float r, float g, float b, float a) { return new Color(r, g, b, a); }), @@ -554,9 +894,7 @@ void pybind_gui_classes(py::module &m) { // ---- Theme ---- // Note: no constructor because themes are created by Open3D - py::class_ theme(m, "Theme", - "Theme parameters such as colors used for drawing " - "widgets (read-only)"); + auto theme = static_cast>(m_gui.attr("Theme")); theme.def_readonly("font_size", &Theme::font_size, "Font size (which is also the conventional size of the " "em unit) (read-only)") @@ -569,7 +907,7 @@ void pybind_gui_classes(py::module &m) { "(read-only)"); // ---- Rect ---- - py::class_ rect(m, "Rect", "Represents a widget frame"); + auto rect = static_cast>(m_gui.attr("Rect")); rect.def(py::init<>()) .def(py::init()) .def(py::init([](float x, float y, float w, float h) { @@ -593,7 +931,7 @@ void pybind_gui_classes(py::module &m) { .def("get_bottom", &Rect::GetBottom); // ---- Size ---- - py::class_ size(m, "Size", "Size object"); + auto size = static_cast>(m_gui.attr("Size")); size.def(py::init<>()) .def(py::init()) .def(py::init([](float w, float h) { @@ -617,34 +955,13 @@ void pybind_gui_classes(py::module &m) { // 1) adding an object to multiple objects will cause multiple shared_ptrs // to think they own it, leading to a double-free and crash, and // 2) if the object is never added, the memory will be leaked. - py::class_> widget(m, "Widget", - "Base widget class"); - py::enum_ widget_event_callback_result( - widget, "EventCallbackResult", "Returned by event handlers", - py::arithmetic()); - widget_event_callback_result - .value("IGNORED", EventCallbackResult::IGNORED, - "Event handler ignored the event, widget will " - "handle event normally") - .value("HANDLED", EventCallbackResult::HANDLED, - "Event handler handled the event, but widget " - "will still handle the event normally. This is " - "useful when you are augmenting base " - "functionality") - .value("CONSUMED", EventCallbackResult::CONSUMED, - "Event handler consumed the event, event " - "handling stops, widget will not handle the " - "event. This is useful when you are replacing " - "functionality") - .export_values(); - - py::class_ constraints( - widget, "Constraints", - "Constraints object for Widget.calc_preferred_size()"); + auto widget = static_cast>>( + m_gui.attr("Widget")); + auto constraints = static_cast>( + widget.attr("Constraints")); constraints.def(py::init<>()) .def_readwrite("width", &Widget::Constraints::width) .def_readwrite("height", &Widget::Constraints::height); - widget.def(py::init<>()) .def("__repr__", [](const Widget &w) { @@ -682,26 +999,9 @@ void pybind_gui_classes(py::module &m) { "properly"); // ---- WidgetProxy ---- - py::class_, Widget> widgetProxy( - m, "WidgetProxy", - "Widget container to delegate any widget dynamically." - " Widget can not be managed dynamically. Although it is allowed" - " to add more child widgets, it's impossible to replace some child" - " with new on or remove children. WidgetProxy is designed to solve" - " this problem." - " When WidgetProxy is created, it's invisible and disabled, so it" - " won't be drawn or layout, seeming like it does not exist. When" - " a widget is set by set_widget, all Widget's APIs will be" - " conducted to that child widget. It looks like WidgetProxy is" - " that widget." - " At any time, a new widget could be set, to replace the old one." - " and the old widget will be destroyed." - " Due to the content changing after a new widget is set or cleared," - " a relayout of Window might be called after set_widget." - " The delegated widget could be retrieved by get_widget in case" - " you need to access it directly, like get check status of a" - " CheckBox. API other than set_widget and get_widget has" - " completely same functions as Widget."); + auto widgetProxy = static_cast< + py::class_, Widget>>( + m_gui.attr("WidgetProxy")); widgetProxy.def(py::init<>(), "Creates a widget proxy") .def("__repr__", [](const WidgetProxy &c) { @@ -730,17 +1030,10 @@ void pybind_gui_classes(py::module &m) { "if there is none."); // ---- WidgetStack ---- - py::class_, WidgetProxy> - widgetStack(m, "WidgetStack", - "A widget stack saves all widgets pushed into by " - "push_widget and always shows the top one. The " - "WidgetStack is a subclass of WidgetProxy, in other" - "words, the topmost widget will delegate itself to " - "WidgetStack. pop_widget will remove the topmost " - "widget and callback set by set_on_top taking the " - "new topmost widget will be called. The WidgetStack " - "disappears in GUI if there is no widget in stack."); - widgetStack + auto widget_stack = static_cast< + py::class_, WidgetProxy>>( + m_gui.attr("WidgetStack")); + widget_stack .def(py::init<>(), "Creates a widget stack. The widget stack without any" "widget will not be shown in GUI until set_widget is" @@ -766,8 +1059,9 @@ void pybind_gui_classes(py::module &m) { "out. It won't be called if a widget is pushed into stack" "by set_widget."); // ---- Button ---- - py::class_, Widget> button(m, "Button", - "Button"); + auto button = + static_cast, Widget>>( + m_gui.attr("Button")); button.def(py::init(), "Creates a button with the given text") .def("__repr__", [](const Button &b) { @@ -823,8 +1117,9 @@ void pybind_gui_classes(py::module &m) { "Calls passed function when button is pressed"); // ---- Checkbox ---- - py::class_, Widget> checkbox( - m, "Checkbox", "Checkbox"); + auto checkbox = + static_cast, Widget>>( + m_gui.attr("Checkbox")); checkbox.def(py::init(), "Creates a checkbox with the given text") .def("__repr__", @@ -842,8 +1137,9 @@ void pybind_gui_classes(py::module &m) { "Calls passed function when checkbox changes state"); // ---- ColorEdit ---- - py::class_, Widget> coloredit( - m, "ColorEdit", "Color picker"); + auto coloredit = static_cast< + py::class_, Widget>>( + m_gui.attr("ColorEdit")); coloredit.def(py::init<>()) .def("__repr__", [](const ColorEdit &c) { @@ -864,8 +1160,9 @@ void pybind_gui_classes(py::module &m) { "Calls f(Color) when color changes by user input"); // ---- Combobox ---- - py::class_, Widget> combobox( - m, "Combobox", "Exclusive selection from a pull-down menu"); + auto combobox = + static_cast, Widget>>( + m_gui.attr("Combobox")); combobox.def(py::init<>(), "Creates an empty combobox. Use add_item() to add items") .def("clear_items", &Combobox::ClearItems, "Removes all items") @@ -902,19 +1199,9 @@ void pybind_gui_classes(py::module &m) { "respectively"); // ---- RadioButton ---- - py::class_, Widget> radiobtn( - m, "RadioButton", "Exclusive selection from radio button list"); - py::enum_ radiobtn_type(radiobtn, "Type", - py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - radiobtn_type.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for RadioButton types."; - }), - py::none(), py::none(), ""); - radiobtn_type.value("VERT", RadioButton::Type::VERT) - .value("HORIZ", RadioButton::Type::HORIZ) - .export_values(); + auto radiobtn = static_cast< + py::class_, Widget>>( + m_gui.attr("RadioButton")); radiobtn.def(py::init(), "Creates an empty radio buttons. Use set_items() to add items") .def("set_items", &RadioButton::SetItems, @@ -930,95 +1217,8 @@ void pybind_gui_classes(py::module &m) { "Calls f(new_idx) when user changes selection"); // ---- ImageWidget ---- - class PyImageWidget : public ImageWidget { - using Super = ImageWidget; - - public: - PyImageWidget() : Super() {} - /// Uses image from the specified path. Each ImageWidget will use one - /// draw call. - explicit PyImageWidget(const char *image_path) : Super(image_path) {} - /// Uses existing image. Each ImageWidget will use one draw call. - explicit PyImageWidget(std::shared_ptr image) - : Super(image) {} - /// Uses existing image. Each ImageWidget will use one draw call. - explicit PyImageWidget( - std::shared_ptr image) - : Super(image) {} - /// Uses an existing texture, using texture coordinates - /// (u0, v0) to (u1, v1). Does not deallocate texture on destruction. - /// This is useful for using an icon atlas to reduce draw calls. - explicit PyImageWidget( - open3d::visualization::rendering::TextureHandle texture_id, - float u0 = 0.0f, - float v0 = 0.0f, - float u1 = 1.0f, - float v1 = 1.0f) - : Super(texture_id, u0, v0, u1, v1) {} - - ~PyImageWidget() = default; - - void SetOnMouse(std::function f) { - on_mouse_ = f; - } - void SetOnKey(std::function f) { on_key_ = f; } - - Widget::EventResult Mouse(const MouseEvent &e) override { - if (on_mouse_) { - switch (EventCallbackResult(on_mouse_(e))) { - case EventCallbackResult::CONSUMED: - return Widget::EventResult::CONSUMED; - case EventCallbackResult::HANDLED: { - auto result = Super::Mouse(e); - if (result == Widget::EventResult::IGNORED) { - result = Widget::EventResult::CONSUMED; - } - return result; - } - case EventCallbackResult::IGNORED: - default: - return Super::Mouse(e); - } - } else { - return Super::Mouse(e); - } - } - - Widget::EventResult Key(const KeyEvent &e) override { - if (on_key_) { - switch (EventCallbackResult(on_key_(e))) { - case EventCallbackResult::CONSUMED: - return Widget::EventResult::CONSUMED; - case EventCallbackResult::HANDLED: { - auto result = Super::Key(e); - if (result == Widget::EventResult::IGNORED) { - result = Widget::EventResult::CONSUMED; - } - return result; - } - case EventCallbackResult::IGNORED: - default: - return Super::Key(e); - } - } else { - return Super::Key(e); - } - } - - private: - std::function on_mouse_; - std::function on_key_; - }; - - py::class_> uiimage( - m, "UIImage", "A bitmap suitable for displaying with ImageWidget"); - - py::enum_ uiimage_scaling(uiimage, "Scaling", - py::arithmetic()); - uiimage_scaling.value("NONE", UIImage::Scaling::NONE) - .value("ANY", UIImage::Scaling::ANY) - .value("ASPECT", UIImage::Scaling::ASPECT); - + auto uiimage = static_cast>>( + m_gui.attr("UIImage")); uiimage.def(py::init<>([](const char *path) { return new UIImage(path); }), "Creates a UIImage from the image at the specified path") .def(py::init<>([](std::shared_ptr image) { @@ -1032,9 +1232,9 @@ void pybind_gui_classes(py::module &m) { "gui.UIImage.Scaling.ANY: scaled to fit\n" "gui.UIImage.Scaling.ASPECT: scaled to fit but " "keeping the image's aspect ratio"); - - py::class_, Widget> - imagewidget(m, "ImageWidget", "Displays a bitmap"); + auto imagewidget = static_cast< + py::class_, Widget>>( + m_gui.attr("ImageWidget")); imagewidget .def(py::init<>([]() { return new PyImageWidget(); }), "Creates an ImageWidget with no image") @@ -1099,8 +1299,8 @@ void pybind_gui_classes(py::module &m) { "as you will exhaust internal texture resources."); // ---- Label ---- - py::class_, Widget> label(m, "Label", - "Displays text"); + auto label = static_cast, Widget>>( + m_gui.attr("Label")); label.def(py::init([](const char *title = "") { return new Label(title); }), "Creates a Label with the given text") .def("__repr__", @@ -1123,8 +1323,8 @@ void pybind_gui_classes(py::module &m) { "Application.add_font()"); // ---- Label3D ---- - py::class_> label3d( - m, "Label3D", "Displays text in a 3D scene"); + auto label3d = static_cast>>( + m_gui.attr("Label3D")); label3d.def(py::init([](const char *text = "", const Eigen::Vector3f &pos = {0.f, 0.f, 0.f}) { return new Label3D(pos, text); @@ -1147,8 +1347,9 @@ void pybind_gui_classes(py::module &m) { "blurry text as the underlying font is not resized."); // ---- ListView ---- - py::class_, Widget> listview( - m, "ListView", "Displays a list of text"); + auto listview = + static_cast, Widget>>( + m_gui.attr("ListView")); listview.def(py::init<>(), "Creates an empty list") .def("__repr__", [](const ListView &lv) { @@ -1176,19 +1377,9 @@ void pybind_gui_classes(py::module &m) { "selection"); // ---- NumberEdit ---- - py::class_, Widget> numedit( - m, "NumberEdit", "Allows the user to enter a number."); - py::enum_ numedit_type(numedit, "Type", py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - numedit_type.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for NumberEdit types."; - }), - py::none(), py::none(), ""); - numedit_type.value("INT", NumberEdit::Type::INT) - .value("DOUBLE", NumberEdit::Type::DOUBLE) - .export_values(); - + auto numedit = static_cast< + py::class_, Widget>>( + m_gui.attr("NumberEdit")); numedit.def(py::init(), "Creates a NumberEdit that is either integers (INT) or " "floating point (DOUBLE). The initial value is 0 and the " @@ -1237,8 +1428,9 @@ void pybind_gui_classes(py::module &m) { "Sets the preferred width of the NumberEdit"); // ---- ProgressBar---- - py::class_, Widget> progress( - m, "ProgressBar", "Displays a progress bar"); + auto progress = static_cast< + py::class_, Widget>>( + m_gui.attr("ProgressBar")); progress.def(py::init<>()) .def("__repr__", [](const ProgressBar &pb) { @@ -1253,82 +1445,9 @@ void pybind_gui_classes(py::module &m) { "The value of the progress bar, ranges from 0.0 to 1.0"); // ---- SceneWidget ---- - class PySceneWidget : public SceneWidget { - using Super = SceneWidget; - - public: - void SetOnMouse(std::function f) { - on_mouse_ = f; - } - void SetOnKey(std::function f) { on_key_ = f; } - - Widget::EventResult Mouse(const MouseEvent &e) override { - if (on_mouse_) { - switch (EventCallbackResult(on_mouse_(e))) { - case EventCallbackResult::CONSUMED: - return Widget::EventResult::CONSUMED; - case EventCallbackResult::HANDLED: { - auto result = Super::Mouse(e); - if (result == Widget::EventResult::IGNORED) { - result = Widget::EventResult::CONSUMED; - } - return result; - } - case EventCallbackResult::IGNORED: - default: - return Super::Mouse(e); - } - } else { - return Super::Mouse(e); - } - } - - Widget::EventResult Key(const KeyEvent &e) override { - if (on_key_) { - switch (EventCallbackResult(on_key_(e))) { - case EventCallbackResult::CONSUMED: - return Widget::EventResult::CONSUMED; - case EventCallbackResult::HANDLED: { - auto result = Super::Key(e); - if (result == Widget::EventResult::IGNORED) { - result = Widget::EventResult::CONSUMED; - } - return result; - } - case EventCallbackResult::IGNORED: - default: - return Super::Key(e); - } - } else { - return Super::Key(e); - } - } - - private: - std::function on_mouse_; - std::function on_key_; - }; - - py::class_, Widget> scene( - m, "SceneWidget", "Displays 3D content"); - py::enum_ scene_ctrl(scene, "Controls", - py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - scene_ctrl.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class describing mouse interaction."; - }), - py::none(), py::none(), ""); - scene_ctrl.value("ROTATE_CAMERA", SceneWidget::Controls::ROTATE_CAMERA) - .value("ROTATE_CAMERA_SPHERE", - SceneWidget::Controls::ROTATE_CAMERA_SPHERE) - .value("FLY", SceneWidget::Controls::FLY) - .value("ROTATE_SUN", SceneWidget::Controls::ROTATE_SUN) - .value("ROTATE_IBL", SceneWidget::Controls::ROTATE_IBL) - .value("ROTATE_MODEL", SceneWidget::Controls::ROTATE_MODEL) - .value("PICK_POINTS", SceneWidget::Controls::PICK_POINTS) - .export_values(); - + auto scene = static_cast< + py::class_, Widget>>( + m_gui.attr("SceneWidget")); scene.def(py::init<>(), "Creates an empty SceneWidget. Assign a Scene with the 'scene' " "property") @@ -1396,19 +1515,9 @@ void pybind_gui_classes(py::module &m) { "Removes the 3D text label from the scene"); // ---- Slider ---- - py::class_, Widget> slider( - m, "Slider", "A slider widget for visually selecting numbers"); - py::enum_ slider_type(slider, "Type", py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - slider_type.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for Slider types."; - }), - py::none(), py::none(), ""); - slider_type.value("INT", Slider::Type::INT) - .value("DOUBLE", Slider::Type::DOUBLE) - .export_values(); - + auto slider = + static_cast, Widget>>( + m_gui.attr("Slider")); slider.def(py::init(), "Creates a NumberEdit that is either integers (INT) or " "floating point (DOUBLE). The initial value is 0 and the limits " @@ -1445,16 +1554,18 @@ void pybind_gui_classes(py::module &m) { "changes widget's value"); // ---- StackedWidget ---- - py::class_, Widget> stacked( - m, "StackedWidget", "Like a TabControl but without the tabs"); + auto stacked = static_cast< + py::class_, Widget>>( + m_gui.attr("StackedWidget")); stacked.def(py::init<>()) .def_property("selected_index", &StackedWidget::GetSelectedIndex, &StackedWidget::SetSelectedIndex, "Selects the index of the child to display"); // ---- TabControl ---- - py::class_, Widget> tabctrl( - m, "TabControl", "Tab control"); + auto tabctrl = static_cast< + py::class_, Widget>>( + m_gui.attr("TabControl")); tabctrl.def(py::init<>()) .def( "add_tab", @@ -1476,8 +1587,9 @@ void pybind_gui_classes(py::module &m) { "different tab"); // ---- TextEdit ---- - py::class_, Widget> textedit( - m, "TextEdit", "Allows the user to enter or modify text"); + auto textedit = + static_cast, Widget>>( + m_gui.attr("TextEdit")); textedit.def(py::init<>(), "Creates a TextEdit widget with an initial value of an empty " "string.") @@ -1504,8 +1616,9 @@ void pybind_gui_classes(py::module &m) { "user completes text editing"); // ---- ToggleSwitch ---- - py::class_, Widget> toggle( - m, "ToggleSwitch", "ToggleSwitch"); + auto toggle = static_cast< + py::class_, Widget>>( + m_gui.attr("ToggleSwitch")); toggle.def(py::init(), "Creates a toggle switch with the given text") .def("__repr__", @@ -1523,8 +1636,9 @@ void pybind_gui_classes(py::module &m) { "state."); // ---- TreeView ---- - py::class_, Widget> treeview( - m, "TreeView", "Hierarchical list"); + auto treeview = + static_cast, Widget>>( + m_gui.attr("TreeView")); treeview.def(py::init<>(), "Creates an empty TreeView widget") .def("__repr__", [](const TreeView &tv) { @@ -1572,10 +1686,10 @@ void pybind_gui_classes(py::module &m) { "changes the selection."); // ---- TreeView cells ---- - py::class_, - Widget> - checkable_cell(m, "CheckableTextTreeCell", - "TreeView cell with a checkbox and text"); + auto checkable_cell = static_cast< + py::class_, Widget>>( + m_gui.attr("CheckableTextTreeCell")); checkable_cell .def(py::init<>([](const char *text, bool checked, std::function on_toggled) { @@ -1592,10 +1706,9 @@ void pybind_gui_classes(py::module &m) { .def_property_readonly("label", &CheckableTextTreeCell::GetLabel, "Returns the label widget " "(property is read-only)"); - - py::class_, Widget> lut_cell( - m, "LUTTreeCell", - "TreeView cell with checkbox, text, and color edit"); + auto lut_cell = static_cast< + py::class_, Widget>>( + m_gui.attr("LUTTreeCell")); lut_cell.def(py::init<>([](const char *text, bool checked, const Color &color, std::function on_enabled, @@ -1618,10 +1731,10 @@ void pybind_gui_classes(py::module &m) { .def_property_readonly("color_edit", &LUTTreeCell::GetColorEdit, "Returns the ColorEdit widget " "(property is read-only)"); - - py::class_, Widget> - colormap_cell(m, "ColormapTreeCell", - "TreeView cell with a number edit and color edit"); + auto colormap_cell = + static_cast, Widget>>( + m_gui.attr("ColormapTreeCell")); colormap_cell .def(py::init<>([](float value, const Color &color, std::function on_value_changed, @@ -1645,8 +1758,9 @@ void pybind_gui_classes(py::module &m) { "(property is read-only)"); // ---- VectorEdit ---- - py::class_, Widget> vectoredit( - m, "VectorEdit", "Allows the user to edit a 3-space vector"); + auto vectoredit = static_cast< + py::class_, Widget>>( + m_gui.attr("VectorEdit")); vectoredit.def(py::init<>()) .def("__repr__", [](const VectorEdit &ve) { @@ -1665,8 +1779,8 @@ void pybind_gui_classes(py::module &m) { "changes the value of a component"); // ---- Margins ---- - py::class_> margins(m, "Margins", - "Margins for layouts"); + auto margins = static_cast>>( + m_gui.attr("Margins")); margins.def(py::init([](int left, int top, int right, int bottom) { return new Margins(left, top, right, bottom); }), @@ -1698,8 +1812,9 @@ void pybind_gui_classes(py::module &m) { .def("get_vert", &Margins::GetVert); // ---- Layout1D ---- - py::class_, Widget> layout1d( - m, "Layout1D", "Layout base class"); + auto layout1d = + static_cast, Widget>>( + m_gui.attr("Layout1D")); layout1d // TODO: write the proper constructor // .def(py::init([]() { return new Layout1D(Layout1D::VERT, @@ -1717,8 +1832,9 @@ void pybind_gui_classes(py::module &m) { "extra space as there is available in the layout"); // ---- Vert ---- - py::class_, Layout1D> vlayout(m, "Vert", - "Vertical layout"); + auto vlayout = + static_cast, Layout1D>>( + m_gui.attr("Vert")); vlayout.def(py::init([](int spacing, const Margins &margins) { return new Vert(spacing, margins); }), @@ -1740,10 +1856,9 @@ void pybind_gui_classes(py::module &m) { "Sets the preferred width of the layout"); // ---- CollapsableVert ---- - py::class_, Vert> - collapsable(m, "CollapsableVert", - "Vertical layout with title, whose contents are " - "collapsable"); + auto collapsable = static_cast< + py::class_, Vert>>( + m_gui.attr("CollapsableVert")); collapsable .def(py::init([](const char *text, int spacing, const Margins &margins) { @@ -1782,8 +1897,9 @@ void pybind_gui_classes(py::module &m) { "Application.add_font()"); // ---- ScrollableVert ---- - py::class_, Vert> slayout( - m, "ScrollableVert", "Scrollable vertical layout"); + auto slayout = static_cast< + py::class_, Vert>>( + m_gui.attr("ScrollableVert")); slayout.def(py::init([](int spacing, const Margins &margins) { return new ScrollableVert(spacing, margins); }), @@ -1803,8 +1919,9 @@ void pybind_gui_classes(py::module &m) { "is the margins. Both default to 0."); // ---- Horiz ---- - py::class_, Layout1D> hlayout( - m, "Horiz", "Horizontal layout"); + auto hlayout = + static_cast, Layout1D>>( + m_gui.attr("Horiz")); hlayout.def(py::init([](int spacing, const Margins &margins) { return new Horiz(spacing, margins); }), @@ -1828,8 +1945,8 @@ void pybind_gui_classes(py::module &m) { "Sets the preferred height of the layout"); // ---- VGrid ---- - py::class_, Widget> vgrid(m, "VGrid", - "Grid layout"); + auto vgrid = static_cast, Widget>>( + m_gui.attr("VGrid")); vgrid.def(py::init([](int n_cols, int spacing, const Margins &margins) { return new VGrid(n_cols, spacing, margins); }), @@ -1863,25 +1980,16 @@ void pybind_gui_classes(py::module &m) { "Sets the preferred width of the layout"); // ---- Dialog ---- - py::class_, Widget> dialog(m, "Dialog", - "Dialog"); + auto dialog = + static_cast, Widget>>( + m_gui.attr("Dialog")); dialog.def(py::init(), "Creates a dialog with the given title"); // ---- FileDialog ---- - py::class_, Dialog> filedlg( - m, "FileDialog", "File picker dialog"); - py::enum_ filedlg_mode(filedlg, "Mode", py::arithmetic()); - // Trick to write docs without listing the members in the enum class again. - filedlg_mode.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for FileDialog modes."; - }), - py::none(), py::none(), ""); - filedlg_mode.value("OPEN", FileDialog::Mode::OPEN) - .value("SAVE", FileDialog::Mode::SAVE) - .value("OPEN_DIR", FileDialog::Mode::OPEN_DIR) - .export_values(); + auto filedlg = static_cast< + py::class_, Dialog>>( + m_gui.attr("FileDialog")); filedlg.def(py::init(), "Creates either an open or save file dialog. The first " "parameter is either FileDialog.OPEN or FileDialog.SAVE. The " @@ -1900,12 +2008,6 @@ void pybind_gui_classes(py::module &m) { "Done callback; required"); } -void pybind_gui(py::module &m) { - py::module m_gui = m.def_submodule("gui"); - pybind_gui_events(m_gui); - pybind_gui_classes(m_gui); -} - } // namespace gui } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/gui/gui.h b/cpp/pybind/visualization/gui/gui.h index 7e5a7972c26..c2a10b990d3 100644 --- a/cpp/pybind/visualization/gui/gui.h +++ b/cpp/pybind/visualization/gui/gui.h @@ -30,10 +30,11 @@ std::shared_ptr RenderToDepthImageWithoutWindow( int height, bool z_in_view_space = false); -void pybind_gui(py::module &m); +void pybind_gui_declarations(py::module &m); +void pybind_gui_definitions(py::module &m); -void pybind_gui_events(py::module &m); -void pybind_gui_classes(py::module &m); +void pybind_gui_events_declarations(py::module &m); +void pybind_gui_events_definitions(py::module &m); } // namespace gui } // namespace visualization diff --git a/cpp/pybind/visualization/o3dvisualizer.cpp b/cpp/pybind/visualization/o3dvisualizer.cpp index 0859658a081..ba422cc9cc7 100644 --- a/cpp/pybind/visualization/o3dvisualizer.cpp +++ b/cpp/pybind/visualization/o3dvisualizer.cpp @@ -21,32 +21,10 @@ namespace visualization { using namespace visualizer; -void pybind_o3dvisualizer(py::module& m) { +void pybind_o3dvisualizer_declarations(py::module& m) { py::class_ selected_index( m, "SelectedIndex", "Information about a point or vertex that was selected"); - selected_index - .def("__repr__", - [](const O3DVisualizerSelections::SelectedIndex& idx) { - std::stringstream s; - s << "{ index: " << idx.index << ", order: " << idx.order - << ", point: (" << idx.point.x() << ", " << idx.point.y() - << ", " << idx.point.z() << ") }"; - return s.str(); - }) - .def_readonly("index", - &O3DVisualizerSelections::SelectedIndex::index, - "The index of this point in the point/vertex " - "array") - .def_readonly("order", - &O3DVisualizerSelections::SelectedIndex::order, - "A monotonically increasing value that can be " - "used to determine in what order the points " - "were selected") - .def_readonly("point", - &O3DVisualizerSelections::SelectedIndex::point, - "The (x, y, z) value of this point"); - py::class_, gui::Window> o3dvis(m, "O3DVisualizer", "Visualization object used by draw()"); @@ -75,6 +53,37 @@ void pybind_o3dvisualizer(py::module& m) { o3dvis, "DrawObject", "Information about an object that is drawn. Do not modify this, it " "can lead to unexpected results."); +} +void pybind_o3dvisualizer_definitions(py::module& m) { + auto selected_index = + static_cast>( + m.attr("SelectedIndex")); + selected_index + .def("__repr__", + [](const O3DVisualizerSelections::SelectedIndex& idx) { + std::stringstream s; + s << "{ index: " << idx.index << ", order: " << idx.order + << ", point: (" << idx.point.x() << ", " << idx.point.y() + << ", " << idx.point.z() << ") }"; + return s.str(); + }) + .def_readonly("index", + &O3DVisualizerSelections::SelectedIndex::index, + "The index of this point in the point/vertex " + "array") + .def_readonly("order", + &O3DVisualizerSelections::SelectedIndex::order, + "A monotonically increasing value that can be " + "used to determine in what order the points " + "were selected") + .def_readonly("point", + &O3DVisualizerSelections::SelectedIndex::point, + "The (x, y, z) value of this point"); + auto o3dvis = + static_cast, + gui::Window>>(m.attr("O3DVisualizer")); + auto drawobj = static_cast>( + o3dvis.attr("DrawObject")); drawobj.def_readonly("name", &O3DVisualizer::DrawObject::name, "The name of the object") .def_property_readonly( diff --git a/cpp/pybind/visualization/rendering/material.cpp b/cpp/pybind/visualization/rendering/material.cpp index 3e109dcdbb3..15ac1a08b09 100644 --- a/cpp/pybind/visualization/rendering/material.cpp +++ b/cpp/pybind/visualization/rendering/material.cpp @@ -27,18 +27,20 @@ namespace open3d { namespace visualization { namespace rendering { -void pybind_material(py::module& m) { +void pybind_material_declarations(py::module& m) { py::bind_map>( m, "TextureMaps"); py::bind_map>(m, "ScalarProperties"); py::bind_map(m, "VectorProperties"); - py::class_> mat( m, "Material", "Properties (texture maps, scalar and vector) related to " "visualization. Materials are optionally set for 3D geometries " "such as TriangleMesh, LineSets, and PointClouds"); - +} +void pybind_material_definitions(py::module& m) { + auto mat = static_cast>>( + m.attr("Material")); mat.def(py::init<>()) .def(py::init(), "", "mat"_a) .def(py::init(), "", "material_name"_a) diff --git a/cpp/pybind/visualization/rendering/material.h b/cpp/pybind/visualization/rendering/material.h index dd9e7404759..6e157cd74a1 100644 --- a/cpp/pybind/visualization/rendering/material.h +++ b/cpp/pybind/visualization/rendering/material.h @@ -13,8 +13,9 @@ namespace open3d { namespace visualization { namespace rendering { -void pybind_material(py::module &m); +void pybind_material_declarations(py::module &m); +void pybind_material_definitions(py::module &m); -} +} // namespace rendering } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/rendering/rendering.cpp b/cpp/pybind/visualization/rendering/rendering.cpp index b243531d859..f670289f420 100644 --- a/cpp/pybind/visualization/rendering/rendering.cpp +++ b/cpp/pybind/visualization/rendering/rendering.cpp @@ -109,10 +109,124 @@ class PyOffscreenRenderer { Open3DScene *scene_; }; -void pybind_rendering_classes(py::module &m) { +void pybind_rendering_declarations(py::module &m) { + py::module m_rendering = m.def_submodule("rendering"); py::class_ renderer( - m, "Renderer", + m_rendering, "Renderer", "Renderer class that manages 3D resources. Get from gui.Window."); + // It would be nice to have this inherit from Renderer, but the problem is + // that Python needs to own this class and Python needs to not own Renderer, + // and pybind does not let us mix the two styles of ownership. + py::class_> + offscreen(m_rendering, "OffscreenRenderer", + "Renderer instance that can be used for rendering to an " + "image"); + py::class_> cam(m_rendering, "Camera", + "Camera object"); + py::enum_ fov_type(cam, "FovType", py::arithmetic(), + "Enum class for Camera field of view " + "types."); + fov_type.value("Vertical", Camera::FovType::Vertical) + .value("Horizontal", Camera::FovType::Horizontal) + .export_values(); + + py::enum_ proj_type(cam, "Projection", py::arithmetic(), + "Enum class for Camera projection " + "types."); + proj_type.value("Perspective", Camera::Projection::Perspective) + .value("Ortho", Camera::Projection::Ortho) + .export_values(); + py::class_> gradient( + m_rendering, "Gradient", + "Manages a gradient for the unlitGradient shader." + "In gradient mode, the array of points specifies points along " + "the gradient, from 0 to 1 (inclusive). These do need to be " + "evenly spaced." + "Simple greyscale:" + " [ ( 0.0, black )," + " ( 1.0, white ) ]" + "Rainbow (note the gaps around green):" + " [ ( 0.000, blue )," + " ( 0.125, cornflower blue )," + " ( 0.250, cyan )," + " ( 0.500, green )," + " ( 0.750, yellow )," + " ( 0.875, orange )," + " ( 1.000, red ) ]" + "The gradient will generate a largish texture, so it should " + "be fairly smooth, but the boundaries may not be exactly as " + "specified due to quantization imposed by the fixed size of " + "the texture." + " The points *must* be sorted from the smallest value to the " + "largest. The values must be in the range [0, 1]."); + py::enum_ gradient_mode(gradient, "Mode", py::arithmetic()); + gradient_mode.value("GRADIENT", Gradient::Mode::kGradient) + .value("LUT", Gradient::Mode::kLUT) + .export_values(); + py::class_ gpt(gradient, "Point"); + py::class_ mat( + m_rendering, "MaterialRecord", + "Describes the real-world, physically based (PBR) " + "material used to render a geometry"); + py::class_> tri_model( + m_rendering, "TriangleMeshModel", + "A list of geometry.TriangleMesh and Material that can describe a " + "complex model with multiple meshes, such as might be stored in an " + "FBX, OBJ, or GLTF file"); + py::class_ tri_model_info(tri_model, + "MeshInfo", ""); + py::class_ color_grading( + m_rendering, "ColorGrading", + "Parameters to control color grading options"); + py::enum_ cgp_quality( + color_grading, "Quality", + "Quality level of color grading operations"); + cgp_quality.value("LOW", ColorGradingParams::Quality::kLow) + .value("MEDIUM", ColorGradingParams::Quality::kMedium) + .value("HIGH", ColorGradingParams::Quality::kHigh) + .value("ULTRA", ColorGradingParams::Quality::kUltra); + py::enum_ cgp_tone( + color_grading, "ToneMapping", + "Specifies the tone-mapping algorithm"); + cgp_tone.value("LINEAR", ColorGradingParams::ToneMapping::kLinear) + .value("ACES_LEGACY", ColorGradingParams::ToneMapping::kAcesLegacy) + .value("ACES", ColorGradingParams::ToneMapping::kAces) + .value("FILMIC", ColorGradingParams::ToneMapping::kFilmic) + .value("UCHIMURA", ColorGradingParams::ToneMapping::kUchimura) + .value("REINHARD", ColorGradingParams::ToneMapping::kReinhard) + .value("DISPLAY_RANGE", + ColorGradingParams::ToneMapping::kDisplayRange); + py::class_> view(m_rendering, "View", + "Low-level view class"); + py::enum_ shadow_type( + view, "ShadowType", "Available shadow mapping algorithm options"); + shadow_type.value("PCF", View::ShadowType::kPCF) + .value("VSM", View::ShadowType::kVSM); + py::class_> scene(m_rendering, "Scene", + "Low-level rendering scene"); + py::enum_ ground_plane( + scene, "GroundPlane", py::arithmetic(), + "Plane on which to show ground plane: XZ, XY, or YZ"); + ground_plane.value("XZ", Scene::GroundPlane::XZ) + .value("XY", Scene::GroundPlane::XY) + .value("YZ", Scene::GroundPlane::YZ) + .export_values(); + py::class_> o3dscene( + m_rendering, "Open3DScene", "High-level scene for rending"); + py::enum_ lighting( + o3dscene, "LightingProfile", py::arithmetic(), + "Enum for conveniently setting lighting"); + lighting.value("HARD_SHADOWS", Open3DScene::LightingProfile::HARD_SHADOWS) + .value("DARK_SHADOWS", Open3DScene::LightingProfile::DARK_SHADOWS) + .value("MED_SHADOWS", Open3DScene::LightingProfile::MED_SHADOWS) + .value("SOFT_SHADOWS", Open3DScene::LightingProfile::SOFT_SHADOWS) + .value("NO_SHADOWS", Open3DScene::LightingProfile::NO_SHADOWS) + .export_values(); +} +void pybind_rendering_definitions(py::module &m) { + auto m_rendering = static_cast(m.attr("rendering")); + auto renderer = + static_cast>(m_rendering.attr("Renderer")); renderer.def("set_clear_color", &Renderer::SetClearColor, "Sets the background color for the renderer, [r, g, b, a]. " "Applies to everything being rendered, so it essentially acts " @@ -143,14 +257,10 @@ void pybind_rendering_classes(py::module &m) { "Deletes the texture. This does not remove the texture from " "any existing materials or GUI widgets, and must be done " "prior to this call."); - - // It would be nice to have this inherit from Renderer, but the problem is - // that Python needs to own this class and Python needs to not own Renderer, - // and pybind does not let us mix the two styles of ownership. - py::class_> - offscreen(m, "OffscreenRenderer", - "Renderer instance that can be used for rendering to an " - "image"); + auto offscreen = + static_cast>>( + m_rendering.attr("OffscreenRenderer")); offscreen .def(py::init([](int w, int h, const std::string &resource_path) { return std::make_shared( @@ -201,22 +311,8 @@ void pybind_rendering_classes(py::module &m) { "camera)."); // ---- Camera ---- - py::class_> cam(m, "Camera", - "Camera object"); - py::enum_ fov_type(cam, "FovType", py::arithmetic(), - "Enum class for Camera field of view " - "types."); - fov_type.value("Vertical", Camera::FovType::Vertical) - .value("Horizontal", Camera::FovType::Horizontal) - .export_values(); - - py::enum_ proj_type(cam, "Projection", py::arithmetic(), - "Enum class for Camera projection " - "types."); - proj_type.value("Perspective", Camera::Projection::Perspective) - .value("Ortho", Camera::Projection::Ortho) - .export_values(); - + auto cam = static_cast>>( + m_rendering.attr("Camera")); cam.def("set_projection", (void (Camera::*)(double, double, double, double, Camera::FovType)) & @@ -279,34 +375,10 @@ void pybind_rendering_classes(py::module &m) { "Returns the model matrix of the camera"); // ---- Gradient ---- - py::class_> gradient( - m, "Gradient", - "Manages a gradient for the unlitGradient shader." - "In gradient mode, the array of points specifies points along " - "the gradient, from 0 to 1 (inclusive). These do need to be " - "evenly spaced." - "Simple greyscale:" - " [ ( 0.0, black )," - " ( 1.0, white ) ]" - "Rainbow (note the gaps around green):" - " [ ( 0.000, blue )," - " ( 0.125, cornflower blue )," - " ( 0.250, cyan )," - " ( 0.500, green )," - " ( 0.750, yellow )," - " ( 0.875, orange )," - " ( 1.000, red ) ]" - "The gradient will generate a largish texture, so it should " - "be fairly smooth, but the boundaries may not be exactly as " - "specified due to quantization imposed by the fixed size of " - "the texture." - " The points *must* be sorted from the smallest value to the " - "largest. The values must be in the range [0, 1]."); - py::enum_ gradient_mode(gradient, "Mode", py::arithmetic()); - gradient_mode.value("GRADIENT", Gradient::Mode::kGradient) - .value("LUT", Gradient::Mode::kLUT) - .export_values(); - py::class_ gpt(gradient, "Point"); + auto gradient = + static_cast>>( + m_rendering.attr("Gradient")); + auto gpt = static_cast>(gradient.attr("Point")); gpt.def(py::init()) .def("__repr__", [](const Gradient::Point &p) { @@ -326,10 +398,8 @@ void pybind_rendering_classes(py::module &m) { .def_property("mode", &Gradient::GetMode, &Gradient::SetMode); // ---- Material ---- - py::class_ mat( - m, "MaterialRecord", - "Describes the real-world, physically based (PBR) " - "material used to render a geometry"); + auto mat = static_cast>( + m_rendering.attr("MaterialRecord")); mat.def(py::init<>()) .def_readwrite("has_alpha", &MaterialRecord::has_alpha) .def_readwrite("base_color", &MaterialRecord::base_color) @@ -375,13 +445,11 @@ void pybind_rendering_classes(py::module &m) { .def_readwrite("shader", &MaterialRecord::shader); // ---- TriangleMeshModel ---- - py::class_> tri_model( - m, "TriangleMeshModel", - "A list of geometry.TriangleMesh and Material that can describe a " - "complex model with multiple meshes, such as might be stored in an " - "FBX, OBJ, or GLTF file"); - py::class_ tri_model_info(tri_model, - "MeshInfo", ""); + auto tri_model = static_cast< + py::class_>>( + m_rendering.attr("TriangleMeshModel")); + auto tri_model_info = static_cast>( + tri_model.attr("MeshInfo")); tri_model_info .def(py::init([](std::shared_ptr mesh, const std::string &name, @@ -397,29 +465,8 @@ void pybind_rendering_classes(py::module &m) { .def_readwrite("materials", &TriangleMeshModel::materials_); // ---- ColorGradingParams --- - py::class_ color_grading( - m, "ColorGrading", "Parameters to control color grading options"); - - py::enum_ cgp_quality( - color_grading, "Quality", - "Quality level of color grading operations"); - cgp_quality.value("LOW", ColorGradingParams::Quality::kLow) - .value("MEDIUM", ColorGradingParams::Quality::kMedium) - .value("HIGH", ColorGradingParams::Quality::kHigh) - .value("ULTRA", ColorGradingParams::Quality::kUltra); - - py::enum_ cgp_tone( - color_grading, "ToneMapping", - "Specifies the tone-mapping algorithm"); - cgp_tone.value("LINEAR", ColorGradingParams::ToneMapping::kLinear) - .value("ACES_LEGACY", ColorGradingParams::ToneMapping::kAcesLegacy) - .value("ACES", ColorGradingParams::ToneMapping::kAces) - .value("FILMIC", ColorGradingParams::ToneMapping::kFilmic) - .value("UCHIMURA", ColorGradingParams::ToneMapping::kUchimura) - .value("REINHARD", ColorGradingParams::ToneMapping::kReinhard) - .value("DISPLAY_RANGE", - ColorGradingParams::ToneMapping::kDisplayRange); - + auto color_grading = static_cast>( + m_rendering.attr("ColorGrading")); color_grading .def(py::init([](ColorGradingParams::Quality q, ColorGradingParams::ToneMapping algorithm) { @@ -443,14 +490,8 @@ void pybind_rendering_classes(py::module &m) { "Tint on the green/magenta axis. Ranges from -1.0 to 1.0."); // ---- View ---- - py::class_> view(m, "View", - "Low-level view class"); - // ---- Shadow Types ---- - py::enum_ shadow_type( - view, "ShadowType", "Available shadow mapping algorithm options"); - shadow_type.value("PCF", View::ShadowType::kPCF) - .value("VSM", View::ShadowType::kVSM); - + auto view = static_cast>>( + m_rendering.attr("View")); view.def("set_color_grading", &View::SetColorGrading, "Sets the parameters to be used for the color grading algorithms") .def("set_post_processing", &View::SetPostProcessing, @@ -483,15 +524,8 @@ void pybind_rendering_classes(py::module &m) { "Returns the Camera associated with this View."); // ---- Scene ---- - py::class_> scene(m, "Scene", - "Low-level rendering scene"); - py::enum_ ground_plane( - scene, "GroundPlane", py::arithmetic(), - "Plane on which to show ground plane: XZ, XY, or YZ"); - ground_plane.value("XZ", Scene::GroundPlane::XZ) - .value("XY", Scene::GroundPlane::XY) - .value("YZ", Scene::GroundPlane::YZ) - .export_values(); + auto scene = static_cast>>( + m_rendering.attr("Scene")); scene.def("add_camera", &Scene::AddCamera, "name"_a, "camera"_a, "Adds a camera to the scene") .def("remove_camera", &Scene::RemoveCamera, "name"_a, @@ -606,18 +640,9 @@ void pybind_rendering_classes(py::module &m) { scene.attr("UPDATE_UV0_FLAG") = py::int_(Scene::kUpdateUv0Flag); // ---- Open3DScene ---- - py::class_> o3dscene( - m, "Open3DScene", "High-level scene for rending"); - py::enum_ lighting( - o3dscene, "LightingProfile", py::arithmetic(), - "Enum for conveniently setting lighting"); - lighting.value("HARD_SHADOWS", Open3DScene::LightingProfile::HARD_SHADOWS) - .value("DARK_SHADOWS", Open3DScene::LightingProfile::DARK_SHADOWS) - .value("MED_SHADOWS", Open3DScene::LightingProfile::MED_SHADOWS) - .value("SOFT_SHADOWS", Open3DScene::LightingProfile::SOFT_SHADOWS) - .value("NO_SHADOWS", Open3DScene::LightingProfile::NO_SHADOWS) - .export_values(); - + auto o3dscene = + static_cast>>( + m_rendering.attr("Open3DScene")); o3dscene.def(py::init()) .def("show_skybox", &Open3DScene::ShowSkybox, "enable"_a, "Toggles display of the skybox") @@ -715,11 +740,6 @@ void pybind_rendering_classes(py::module &m) { "is important"); } -void pybind_rendering(py::module &m) { - py::module m_rendering = m.def_submodule("rendering"); - pybind_rendering_classes(m_rendering); -} - } // namespace rendering } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/rendering/rendering.h b/cpp/pybind/visualization/rendering/rendering.h index 007ba2fbce2..0e5dd6bc035 100644 --- a/cpp/pybind/visualization/rendering/rendering.h +++ b/cpp/pybind/visualization/rendering/rendering.h @@ -13,8 +13,9 @@ namespace open3d { namespace visualization { namespace rendering { -void pybind_rendering(py::module &m); +void pybind_rendering_declarations(py::module &m); +void pybind_rendering_definitions(py::module &m); -} +} // namespace rendering } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/renderoption.cpp b/cpp/pybind/visualization/renderoption.cpp index 903e2db6135..c2a3424c0b4 100644 --- a/cpp/pybind/visualization/renderoption.cpp +++ b/cpp/pybind/visualization/renderoption.cpp @@ -15,10 +15,63 @@ namespace open3d { namespace visualization { -void pybind_renderoption(py::module &m) { - // open3d.visualization.RenderOption +void pybind_renderoption_declarations(py::module &m) { py::class_> renderoption( m, "RenderOption", "Defines rendering options for visualizer."); + // This is a nested class, but now it's bind to the module + // o3d.visualization.PointColorOption + py::enum_ enum_point_color_option( + m, "PointColorOption", py::arithmetic(), "PointColorOption"); + enum_point_color_option.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for point color for ``PointCloud``."; + }), + py::none(), py::none(), ""); + enum_point_color_option + .value("Default", RenderOption::PointColorOption::Default) + .value("Color", RenderOption::PointColorOption::Color) + .value("XCoordinate", RenderOption::PointColorOption::XCoordinate) + .value("YCoordinate", RenderOption::PointColorOption::YCoordinate) + .value("ZCoordinate", RenderOption::PointColorOption::ZCoordinate) + .value("Normal", RenderOption::PointColorOption::Normal) + .export_values(); + // This is a nested class, but now it's bind to the module + // o3d.visualization.MeshShadeOption + py::enum_ enum_mesh_shade_option( + m, "MeshShadeOption", py::arithmetic(), "MeshShadeOption"); + enum_mesh_shade_option.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for mesh shading for ``TriangleMesh``."; + }), + py::none(), py::none(), ""); + enum_mesh_shade_option + .value("Default", RenderOption::MeshShadeOption::FlatShade) + .value("Color", RenderOption::MeshShadeOption::SmoothShade) + .export_values(); + + // This is a nested class, but now it's bind to the module + // o3d.visualization.MeshColorOption + py::enum_ enum_mesh_clor_option( + m, "MeshColorOption", py::arithmetic(), "MeshColorOption"); + enum_mesh_clor_option.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for color for ``TriangleMesh``."; + }), + py::none(), py::none(), ""); + enum_mesh_clor_option + .value("Default", RenderOption::MeshColorOption::Default) + .value("Color", RenderOption::MeshColorOption::Color) + .value("XCoordinate", RenderOption::MeshColorOption::XCoordinate) + .value("YCoordinate", RenderOption::MeshColorOption::YCoordinate) + .value("ZCoordinate", RenderOption::MeshColorOption::ZCoordinate) + .value("Normal", RenderOption::MeshColorOption::Normal) + .export_values(); +} +void pybind_renderoption_definitions(py::module &m) { + // open3d.visualization.RenderOption + auto renderoption = static_cast< + py::class_>>( + m.attr("RenderOption")); py::detail::bind_default_constructor(renderoption); renderoption .def("__repr__", @@ -77,59 +130,7 @@ void pybind_renderoption(py::module &m) { {{"filename", "Path to file."}}); docstring::ClassMethodDocInject(m, "RenderOption", "save_to_json", {{"filename", "Path to file."}}); - - // This is a nested class, but now it's bind to the module - // o3d.visualization.PointColorOption - py::enum_ enum_point_color_option( - m, "PointColorOption", py::arithmetic(), "PointColorOption"); - enum_point_color_option.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for point color for ``PointCloud``."; - }), - py::none(), py::none(), ""); - enum_point_color_option - .value("Default", RenderOption::PointColorOption::Default) - .value("Color", RenderOption::PointColorOption::Color) - .value("XCoordinate", RenderOption::PointColorOption::XCoordinate) - .value("YCoordinate", RenderOption::PointColorOption::YCoordinate) - .value("ZCoordinate", RenderOption::PointColorOption::ZCoordinate) - .value("Normal", RenderOption::PointColorOption::Normal) - .export_values(); - - // This is a nested class, but now it's bind to the module - // o3d.visualization.MeshShadeOption - py::enum_ enum_mesh_shade_option( - m, "MeshShadeOption", py::arithmetic(), "MeshShadeOption"); - enum_mesh_shade_option.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for mesh shading for ``TriangleMesh``."; - }), - py::none(), py::none(), ""); - enum_mesh_shade_option - .value("Default", RenderOption::MeshShadeOption::FlatShade) - .value("Color", RenderOption::MeshShadeOption::SmoothShade) - .export_values(); - - // This is a nested class, but now it's bind to the module - // o3d.visualization.MeshColorOption - py::enum_ enum_mesh_clor_option( - m, "MeshColorOption", py::arithmetic(), "MeshColorOption"); - enum_mesh_clor_option.attr("__doc__") = docstring::static_property( - py::cpp_function([](py::handle arg) -> std::string { - return "Enum class for color for ``TriangleMesh``."; - }), - py::none(), py::none(), ""); - enum_mesh_clor_option - .value("Default", RenderOption::MeshColorOption::Default) - .value("Color", RenderOption::MeshColorOption::Color) - .value("XCoordinate", RenderOption::MeshColorOption::XCoordinate) - .value("YCoordinate", RenderOption::MeshColorOption::YCoordinate) - .value("ZCoordinate", RenderOption::MeshColorOption::ZCoordinate) - .value("Normal", RenderOption::MeshColorOption::Normal) - .export_values(); } -void pybind_renderoption_method(py::module &m) {} - } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/utility.cpp b/cpp/pybind/visualization/utility.cpp index 603e78bb947..99cb4323d60 100644 --- a/cpp/pybind/visualization/utility.cpp +++ b/cpp/pybind/visualization/utility.cpp @@ -18,10 +18,15 @@ namespace open3d { namespace visualization { -void pybind_visualization_utility(py::module &m) { +void pybind_visualization_utility_declarations(py::module &m) { py::class_ selection_volume( m, "SelectionPolygonVolume", "Select a polygon volume for cropping."); +} + +void pybind_visualization_utility_definitions(py::module &m) { + auto selection_volume = static_cast>( + m.attr("SelectionPolygonVolume")); py::detail::bind_default_constructor( selection_volume); py::detail::bind_copy_functions(selection_volume); @@ -75,36 +80,34 @@ void pybind_visualization_utility(py::module &m) { docstring::ClassMethodDocInject(m, "SelectionPolygonVolume", "crop_in_polygon", {{"input", "The input point cloud xyz."}}); -} - -// Visualization util functions have similar arguments, sharing arg docstrings -static const std::unordered_map - map_shared_argument_docstrings = { - {"callback_function", - "Call back function to be triggered at a key press event."}, - {"filename", "The file path."}, - {"geometry_list", "List of geometries to be visualized."}, - {"height", "The height of the visualization window."}, - {"key_to_callback", "Map of key to call back functions."}, - {"left", "The left margin of the visualization window."}, - {"optional_view_trajectory_json_file", - "Camera trajectory json file path for custom animation."}, - {"top", "The top margin of the visualization window."}, - {"width", "The width of the visualization window."}, - {"point_show_normal", - "Visualize point normals if set to true."}, - {"mesh_show_wireframe", - "Visualize mesh wireframe if set to true."}, - {"mesh_show_back_face", - "Visualize also the back face of the mesh triangles."}, - {"window_name", - "The displayed title of the visualization window."}, - {"lookat", "The lookat vector of the camera."}, - {"up", "The up vector of the camera."}, - {"front", "The front vector of the camera."}, - {"zoom", "The zoom of the camera."}}; - -void pybind_visualization_utility_methods(py::module &m) { + // Visualization util functions have similar arguments, sharing arg + // docstrings + static const std::unordered_map + map_shared_argument_docstrings = { + {"callback_function", + "Call back function to be triggered at a key press " + "event."}, + {"filename", "The file path."}, + {"geometry_list", "List of geometries to be visualized."}, + {"height", "The height of the visualization window."}, + {"key_to_callback", "Map of key to call back functions."}, + {"left", "The left margin of the visualization window."}, + {"optional_view_trajectory_json_file", + "Camera trajectory json file path for custom animation."}, + {"top", "The top margin of the visualization window."}, + {"width", "The width of the visualization window."}, + {"point_show_normal", + "Visualize point normals if set to true."}, + {"mesh_show_wireframe", + "Visualize mesh wireframe if set to true."}, + {"mesh_show_back_face", + "Visualize also the back face of the mesh triangles."}, + {"window_name", + "The displayed title of the visualization window."}, + {"lookat", "The lookat vector of the camera."}, + {"up", "The up vector of the camera."}, + {"front", "The front vector of the camera."}, + {"zoom", "The zoom of the camera."}}; m.def( "draw_geometries", [](const std::vector> diff --git a/cpp/pybind/visualization/viewcontrol.cpp b/cpp/pybind/visualization/viewcontrol.cpp index 80a8aaa7895..695f7d7c626 100644 --- a/cpp/pybind/visualization/viewcontrol.cpp +++ b/cpp/pybind/visualization/viewcontrol.cpp @@ -29,9 +29,14 @@ static const std::unordered_map {"z_far", "The depth of the far z-plane of the visualizer."}, }; -void pybind_viewcontrol(py::module &m) { +void pybind_viewcontrol_declarations(py::module &m) { py::class_, std::shared_ptr> viewcontrol(m, "ViewControl", "View controller for visualizer."); +} +void pybind_viewcontrol_definitions(py::module &m) { + auto viewcontrol = static_cast, + std::shared_ptr>>( + m.attr("ViewControl")); py::detail::bind_default_constructor(viewcontrol); viewcontrol .def("__repr__", @@ -123,7 +128,5 @@ void pybind_viewcontrol(py::module &m) { map_view_control_docstrings); } -void pybind_viewcontrol_method(py::module &m) {} - } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/visualization.cpp b/cpp/pybind/visualization/visualization.cpp index b6e3455fa6d..d143683996b 100644 --- a/cpp/pybind/visualization/visualization.cpp +++ b/cpp/pybind/visualization/visualization.cpp @@ -19,27 +19,41 @@ namespace open3d { namespace visualization { -void pybind_visualization(py::module &m) { +void pybind_visualization_declarations(py::module &m) { py::module m_visualization = m.def_submodule("visualization"); - pybind_renderoption(m_visualization); - pybind_viewcontrol(m_visualization); - pybind_visualizer(m_visualization); - pybind_visualization_utility(m_visualization); - pybind_renderoption_method(m_visualization); - pybind_viewcontrol_method(m_visualization); - pybind_visualizer_method(m_visualization); - pybind_visualization_utility_methods(m_visualization); - rendering::pybind_material(m_visualization); // For RPC serialization - + pybind_renderoption_declarations(m_visualization); + pybind_viewcontrol_declarations(m_visualization); + pybind_visualizer_declarations(m_visualization); + pybind_visualization_utility_declarations(m_visualization); + // For RPC serialization + rendering::pybind_material_declarations(m_visualization); #ifdef BUILD_GUI - rendering::pybind_rendering(m_visualization); - gui::pybind_gui(m_visualization); - pybind_o3dvisualizer(m_visualization); - app::pybind_app(m_visualization); + rendering::pybind_rendering_declarations(m_visualization); + gui::pybind_gui_declarations(m_visualization); + pybind_o3dvisualizer_declarations(m_visualization); + app::pybind_app_declarations(m_visualization); +#endif +#ifdef BUILD_WEBRTC + webrtc_server::pybind_webrtc_server_declarations(m_visualization); #endif +} +void pybind_visualization_definitions(py::module &m) { + auto m_visualization = static_cast(m.attr("visualization")); + pybind_renderoption_definitions(m_visualization); + pybind_viewcontrol_definitions(m_visualization); + pybind_visualizer_definitions(m_visualization); + pybind_visualization_utility_definitions(m_visualization); + // For RPC serialization + rendering::pybind_material_definitions(m_visualization); +#ifdef BUILD_GUI + rendering::pybind_rendering_definitions(m_visualization); + gui::pybind_gui_definitions(m_visualization); + pybind_o3dvisualizer_definitions(m_visualization); + app::pybind_app_definitions(m_visualization); +#endif #ifdef BUILD_WEBRTC - webrtc_server::pybind_webrtc_server(m_visualization); + webrtc_server::pybind_webrtc_server_definitions(m_visualization); #endif } diff --git a/cpp/pybind/visualization/visualization.h b/cpp/pybind/visualization/visualization.h index de6d7aa5757..73dd95c952d 100644 --- a/cpp/pybind/visualization/visualization.h +++ b/cpp/pybind/visualization/visualization.h @@ -53,19 +53,19 @@ std::shared_ptr TakeOwnership(UnownedPointer x) { // Please update docs/python_api_in/open3d.visualization.rst when adding / // reorganizing submodules to open3d.visualization. -void pybind_visualization(py::module &m); +void pybind_visualization_declarations(py::module &m); +void pybind_renderoption_declarations(py::module &m); +void pybind_viewcontrol_declarations(py::module &m); +void pybind_visualizer_declarations(py::module &m); +void pybind_visualization_utility_declarations(py::module &m); +void pybind_o3dvisualizer_declarations(py::module &m); -void pybind_renderoption(py::module &m); -void pybind_viewcontrol(py::module &m); -void pybind_visualizer(py::module &m); -void pybind_visualization_utility(py::module &m); - -void pybind_renderoption_method(py::module &m); -void pybind_viewcontrol_method(py::module &m); -void pybind_visualizer_method(py::module &m); -void pybind_visualization_utility_methods(py::module &m); - -void pybind_o3dvisualizer(py::module &m); +void pybind_visualization_definitions(py::module &m); +void pybind_renderoption_definitions(py::module &m); +void pybind_viewcontrol_definitions(py::module &m); +void pybind_visualizer_definitions(py::module &m); +void pybind_visualization_utility_definitions(py::module &m); +void pybind_o3dvisualizer_definitions(py::module &m); } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/visualizer.cpp b/cpp/pybind/visualization/visualizer.cpp index 990727dd45c..579db2cadb6 100644 --- a/cpp/pybind/visualization/visualizer.cpp +++ b/cpp/pybind/visualization/visualizer.cpp @@ -38,9 +38,32 @@ static const std::unordered_map {"reset_bounding_box", "Set to ``False`` to keep current viewpoint"}}; -void pybind_visualizer(py::module &m) { +void pybind_visualizer_declarations(py::module &m) { py::class_, std::shared_ptr> visualizer(m, "Visualizer", "The main Visualizer class."); + py::class_, + std::shared_ptr> + visualizer_key(m, "VisualizerWithKeyCallback", visualizer, + "Visualizer with custom key callback capabilities."); + py::class_, + std::shared_ptr> + visualizer_edit(m, "VisualizerWithEditing", visualizer, + "Visualizer with editing capabilities."); + py::class_, + std::shared_ptr> + visualizer_vselect( + m, "VisualizerWithVertexSelection", visualizer, + "Visualizer with vertex selection capabilities."); + py::class_ + visualizer_vselect_pickedpoint(m, "PickedPoint"); +} + +void pybind_visualizer_definitions(py::module &m) { + auto visualizer = static_cast, + std::shared_ptr>>( + m.attr("Visualizer")); py::detail::bind_default_constructor(visualizer); visualizer .def("__repr__", @@ -145,12 +168,11 @@ void pybind_visualizer(py::module &m) { "Set the current view status from a json string of " "ViewTrajectory.", "view_status_str"_a); - - py::class_, - std::shared_ptr> - visualizer_key(m, "VisualizerWithKeyCallback", visualizer, - "Visualizer with custom key callback capabilities."); + auto visualizer_key = + static_cast, + std::shared_ptr>>( + m.attr("VisualizerWithKeyCallback")); py::detail::bind_default_constructor( visualizer_key); visualizer_key @@ -212,10 +234,11 @@ void pybind_visualizer(py::module &m) { "key `__.", "callback_func"_a); - py::class_, - std::shared_ptr> - visualizer_edit(m, "VisualizerWithEditing", visualizer, - "Visualizer with editing capabilities."); + auto visualizer_edit = + static_cast, + std::shared_ptr>>( + m.attr("VisualizerWithEditing")); py::detail::bind_default_constructor( visualizer_edit); visualizer_edit @@ -232,12 +255,11 @@ void pybind_visualizer(py::module &m) { &VisualizerWithEditing::GetCroppedGeometry, "Function to get cropped geometry"); - py::class_, - std::shared_ptr> - visualizer_vselect( - m, "VisualizerWithVertexSelection", visualizer, - "Visualizer with vertex selection capabilities."); + auto visualizer_vselect = static_cast< + py::class_, + std::shared_ptr>>( + m.attr("VisualizerWithVertexSelection")); py::detail::bind_default_constructor( visualizer_vselect); visualizer_vselect.def(py::init<>()) @@ -279,8 +301,9 @@ void pybind_visualizer(py::module &m) { "Registers a function to be called after selection moves", "f"_a); - py::class_ - visualizer_vselect_pickedpoint(m, "PickedPoint"); + auto visualizer_vselect_pickedpoint = + static_cast>( + m.attr("PickedPoint")); visualizer_vselect_pickedpoint.def(py::init<>()) .def_readwrite("index", &VisualizerWithVertexSelection::PickedPoint::index) @@ -337,7 +360,5 @@ void pybind_visualizer(py::module &m) { map_visualizer_docstrings); } -void pybind_visualizer_method(py::module &m) {} - } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/webrtc_server/webrtc_window_system.cpp b/cpp/pybind/visualization/webrtc_server/webrtc_window_system.cpp index 84ae5d391c2..cd499897358 100644 --- a/cpp/pybind/visualization/webrtc_server/webrtc_window_system.cpp +++ b/cpp/pybind/visualization/webrtc_server/webrtc_window_system.cpp @@ -15,8 +15,15 @@ namespace open3d { namespace visualization { namespace webrtc_server { -static void pybind_webrtc_server_functions(py::module &m) { - m.def( +void pybind_webrtc_server_declarations(py::module &m) { + py::module m_submodule = m.def_submodule( + "webrtc_server", + "Functionality for remote visualization over WebRTC."); +} + +void pybind_webrtc_server_definitions(py::module &m) { + auto m_webrtc_server = static_cast(m.attr("webrtc_server")); + m_webrtc_server.def( "call_http_api", [](const std::string &entry_point, const std::string &query_string, const std::string &data) { @@ -27,18 +34,18 @@ static void pybind_webrtc_server_functions(py::module &m) { "Emulates Open3D WebRTCWindowSystem's HTTP API calls. This is used " "when the HTTP handshake server is disabled (e.g. in Jupyter), and " "handshakes are done by this function."); - m.def( + m_webrtc_server.def( "enable_webrtc", []() { WebRTCWindowSystem::GetInstance()->EnableWebRTC(); }, "Use WebRTC streams to display rendered gui window."); - m.def( + m_webrtc_server.def( "disable_http_handshake", []() { WebRTCWindowSystem::GetInstance()->DisableHttpHandshake(); }, "Disables the HTTP handshake server. In Jupyter environment, " "WebRTC handshake is performed by call_http_api() with " "Jupyter's own COMMS interface, thus the HTTP server shall " "be turned off."); - m.def( + m_webrtc_server.def( "register_data_channel_message_callback", [](const std::string &class_name, std::function callback) { @@ -78,7 +85,7 @@ if it is not empty. )"); docstring::FunctionDocInject( - m, "register_data_channel_message_callback", + m_webrtc_server, "register_data_channel_message_callback", {{"class_name", "The value of of the ``class_name`` property of the JSON " "object."}, @@ -90,13 +97,6 @@ if it is not empty. "value of a slider) and return a ``string``."}}); } -void pybind_webrtc_server(py::module &m) { - py::module m_submodule = m.def_submodule( - "webrtc_server", - "Functionality for remote visualization over WebRTC."); - pybind_webrtc_server_functions(m_submodule); -} - } // namespace webrtc_server } // namespace visualization } // namespace open3d diff --git a/cpp/pybind/visualization/webrtc_server/webrtc_window_system.h b/cpp/pybind/visualization/webrtc_server/webrtc_window_system.h index cae067d7e85..45b820ccaf1 100644 --- a/cpp/pybind/visualization/webrtc_server/webrtc_window_system.h +++ b/cpp/pybind/visualization/webrtc_server/webrtc_window_system.h @@ -13,7 +13,8 @@ namespace open3d { namespace visualization { namespace webrtc_server { -void pybind_webrtc_server(py::module &m); +void pybind_webrtc_server_declarations(py::module &m); +void pybind_webrtc_server_definitions(py::module &m); } // namespace webrtc_server } // namespace visualization diff --git a/docs/builddocs.rst b/docs/builddocs.rst index 2f8e9539957..42d83349bbb 100644 --- a/docs/builddocs.rst +++ b/docs/builddocs.rst @@ -97,3 +97,22 @@ Open ``docs/_out/html/index.html`` in a web browser to preview the docs. .. code-block:: bash google-chrome docs/_out/html/index.html + + +Create Python stubs (type hints) for type checking and autocomplete +------------------------------------------------------------------- + +You can get type checking and auto-complete features in editors and IDES (e.g. + VS Code, PyCharm, etc.) using type hints produced from Open3D. These can be + created with the pybind11-stubgen tool and placed alongside the Open3D files: + +.. code-block:: bash + + # Install open3d and pybind11-stubgen + pip install pybind11-stubgen open3d + # Print location of install open3d library + pip show open3d + # This outputs a line like: + # Location: path/to/venv/site-packages + # Create stubs and place them next to Open3D files + pybind11-stubgen -o --root-suffix "" open3d \ No newline at end of file diff --git a/examples/python/benchmark/benchmark_fgr.py b/examples/python/benchmark/benchmark_fgr.py index 57b02bd09b1..788476a9fca 100644 --- a/examples/python/benchmark/benchmark_fgr.py +++ b/examples/python/benchmark/benchmark_fgr.py @@ -8,6 +8,7 @@ import os import sys import numpy as np +import open3d as o3d pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(pyexample_path) diff --git a/examples/python/benchmark/benchmark_pre.py b/examples/python/benchmark/benchmark_pre.py index 3d949cc42c3..bc1bca4a651 100644 --- a/examples/python/benchmark/benchmark_pre.py +++ b/examples/python/benchmark/benchmark_pre.py @@ -8,6 +8,7 @@ import os import sys import pickle +import open3d as o3d pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(pyexample_path) diff --git a/examples/python/benchmark/benchmark_ransac.py b/examples/python/benchmark/benchmark_ransac.py index 08d833cb9eb..05c0c6b8a0b 100644 --- a/examples/python/benchmark/benchmark_ransac.py +++ b/examples/python/benchmark/benchmark_ransac.py @@ -8,6 +8,7 @@ import os import sys import numpy as np +import open3d as o3d pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(pyexample_path) diff --git a/examples/python/benchmark/benchmark_tsdf.py b/examples/python/benchmark/benchmark_tsdf.py index 44f38d45370..c915a6e908d 100644 --- a/examples/python/benchmark/benchmark_tsdf.py +++ b/examples/python/benchmark/benchmark_tsdf.py @@ -5,11 +5,11 @@ # SPDX-License-Identifier: MIT # ---------------------------------------------------------------------------- -import open3d as o3d import numpy as np import time import os import sys +import open3d as o3d pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(pyexample_path)