diff --git a/docs/conf.py b/docs/conf.py index a31514eabab..a941ec9b088 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,6 +23,7 @@ import sphinx_rtd_theme import sys import os +import re # Import open3d raw python package with the highest priority # This is a trick to show open3d.open3d as open3d in the docs @@ -176,5 +177,45 @@ 'Miscellaneous'), ] -# added by Jaesik to list Python members using the source order -autodoc_member_order = 'bysource' +# Version 0: Added by Jaesik to list Python members using the source order +# Version 1: Changed to 'groupwise': __init__ first, then methods, then +# properties. Within each, sorted alphabetically. +autodoc_member_order = 'groupwise' + + +def is_enum_class(func, func_name): + def import_from_str(class_name): + components = class_name.split('.') + mod = __import__(components[0]) + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + is_enum = False + try: + if func_name == "name" and "self: handle" in func.__doc__: + is_enum = True + else: + pattern = re.escape(func_name) + r"\(self: ([a-zA-Z0-9_\.]*).*\)" + m = re.match(pattern, func.__doc__) + if m: + c_name = m.groups()[0] + c = import_from_str(c_name) + if hasattr(c, "__entries"): + is_enum = True + except: + pass + return is_enum + + +# Keep the __init__ function doc +def skip(app, what, name, obj, would_skip, options): + if name in {"__init__", "name"}: + if is_enum_class(obj, name): + return True + else: + return False + return would_skip + +def setup(app): + app.connect("autodoc-skip-member", skip) diff --git a/docs/python_api/camera.rst b/docs/python_api/camera.rst index f4645feb37a..334c3c3c1c1 100644 --- a/docs/python_api/camera.rst +++ b/docs/python_api/camera.rst @@ -9,3 +9,4 @@ open3d.camera .. automodule:: open3d.camera :members: :undoc-members: + diff --git a/src/Open3D/ColorMap/ColorMapOptimization.h b/src/Open3D/ColorMap/ColorMapOptimization.h index 1222f4a48b2..c1bdb03fe12 100644 --- a/src/Open3D/ColorMap/ColorMapOptimization.h +++ b/src/Open3D/ColorMap/ColorMapOptimization.h @@ -49,6 +49,8 @@ namespace color_map { class ColorMapOptimizationOption { public: ColorMapOptimizationOption( + // Attention: when you update the defaults, update the docstrings in + // Python/color_map/color_map.cpp bool non_rigid_camera_coordinate = false, int number_of_vertical_anchors = 16, double non_rigid_anchor_point_weight = 0.316, diff --git a/src/Open3D/Geometry/HalfEdgeTriangleMesh.h b/src/Open3D/Geometry/HalfEdgeTriangleMesh.h index 769ee78cbaa..022ffa16554 100644 --- a/src/Open3D/Geometry/HalfEdgeTriangleMesh.h +++ b/src/Open3D/Geometry/HalfEdgeTriangleMesh.h @@ -68,7 +68,7 @@ class HalfEdgeTriangleMesh : public TriangleMesh { /// True if half-edges have already been computed bool HasHalfEdges() const; - /// Query manifold boundary half edges + /// Query manifold boundary half edges from a starting vertex /// If query vertex is not on boundary, empty vector will be returned std::vector BoundaryHalfEdgesFromVertex(int vertex_index) const; diff --git a/src/Open3D/Geometry/LineSet.h b/src/Open3D/Geometry/LineSet.h index b92f2998088..a4b07f01367 100644 --- a/src/Open3D/Geometry/LineSet.h +++ b/src/Open3D/Geometry/LineSet.h @@ -62,8 +62,9 @@ class LineSet : public Geometry3D { } std::pair GetLineCoordinate( - size_t i) const { - return std::make_pair(points_[lines_[i][0]], points_[lines_[i][1]]); + size_t line_index) const { + return std::make_pair(points_[lines_[line_index][0]], + points_[lines_[line_index][1]]); } public: diff --git a/src/Open3D/Odometry/RGBDOdometryJacobian.h b/src/Open3D/Odometry/RGBDOdometryJacobian.h index c453cd3206e..de00c3fdb24 100644 --- a/src/Open3D/Odometry/RGBDOdometryJacobian.h +++ b/src/Open3D/Odometry/RGBDOdometryJacobian.h @@ -73,7 +73,7 @@ class RGBDOdometryJacobian { const CorrespondenceSetPixelWise &corresps) const = 0; }; -/// Function to Compute Jacobian using color term +/// Class to compute Jacobian using color term /// Energy: (I_p-I_q)^2 /// reference: /// F. Steinbrucker, J. Sturm, and D. Cremers. @@ -99,7 +99,7 @@ class RGBDOdometryJacobianFromColorTerm : public RGBDOdometryJacobian { const CorrespondenceSetPixelWise &corresps) const override; }; -/// Function to Compute Jacobian using hybrid term +/// Class to compute Jacobian using hybrid term /// Energy: (I_p-I_q)^2 + lambda(D_p-(D_q)')^2 /// reference: /// J. Park, Q.-Y. Zhou, and V. Koltun diff --git a/src/Open3D/Registration/FastGlobalRegistration.h b/src/Open3D/Registration/FastGlobalRegistration.h index 61075247967..dd2f560a0e1 100644 --- a/src/Open3D/Registration/FastGlobalRegistration.h +++ b/src/Open3D/Registration/FastGlobalRegistration.h @@ -70,7 +70,7 @@ class FastGlobalRegistrationOption { bool decrease_mu_; // Maximum correspondence distance (also see comment of USE_ABSOLUTE_SCALE) double maximum_correspondence_distance_; - // Maximum number of iteration + // Maximum number of iterations int iteration_number_; // Similarity measure used for tuples of feature points. double tuple_scale_; diff --git a/src/Open3D/Registration/Registration.h b/src/Open3D/Registration/Registration.h index 032e2f27971..19b52ecc61d 100644 --- a/src/Open3D/Registration/Registration.h +++ b/src/Open3D/Registration/Registration.h @@ -81,7 +81,7 @@ class RANSACConvergenceCriteria { int max_validation_; }; -/// Class that contains the registration result +/// Class that contains the registration results class RegistrationResult { public: RegistrationResult( diff --git a/src/Python/camera/camera.cpp b/src/Python/camera/camera.cpp index a3f81a44966..5df55501168 100644 --- a/src/Python/camera/camera.cpp +++ b/src/Python/camera/camera.cpp @@ -27,13 +27,17 @@ #include #include +#include "Python/docstring.h" #include "Python/camera/camera.h" using namespace open3d; void pybind_camera_classes(py::module &m) { + // open3d.camera.PinholeCameraIntrinsic py::class_ pinhole_intr( - m, "PinholeCameraIntrinsic", "PinholeCameraIntrinsic"); + m, "PinholeCameraIntrinsic", + "PinholeCameraIntrinsic class stores intrinsic camera matrix, and " + "image height and width."); py::detail::bind_default_constructor( pinhole_intr); py::detail::bind_copy_functions( @@ -48,20 +52,32 @@ void pybind_camera_classes(py::module &m) { .def(py::init([](camera::PinholeCameraIntrinsicParameters param) { return new camera::PinholeCameraIntrinsic(param); }), - "param"_a) + "param"_a); + pinhole_intr .def("set_intrinsics", &camera::PinholeCameraIntrinsic::SetIntrinsics, "width"_a, - "height"_a, "fx"_a, "fy"_a, "cx"_a, "cy"_a) + "height"_a, "fx"_a, "fy"_a, "cx"_a, "cy"_a, + "Set camera intrinsic parmeters.") .def("get_focal_length", - &camera::PinholeCameraIntrinsic::GetFocalLength) + &camera::PinholeCameraIntrinsic::GetFocalLength, + "Returns the focal length.") .def("get_principal_point", - &camera::PinholeCameraIntrinsic::GetPrincipalPoint) - .def("get_skew", &camera::PinholeCameraIntrinsic::GetSkew) - .def("is_valid", &camera::PinholeCameraIntrinsic::IsValid) - .def_readwrite("width", &camera::PinholeCameraIntrinsic::width_) - .def_readwrite("height", &camera::PinholeCameraIntrinsic::height_) + &camera::PinholeCameraIntrinsic::GetPrincipalPoint, + "Returns the principle point.") + .def("get_skew", &camera::PinholeCameraIntrinsic::GetSkew, + "Returns the skew.") + .def("is_valid", &camera::PinholeCameraIntrinsic::IsValid, + "Returns True iff both the width and height are greater than " + "0.") + .def_readwrite("width", &camera::PinholeCameraIntrinsic::width_, + "int: Width of the image.") + .def_readwrite("height", &camera::PinholeCameraIntrinsic::height_, + "int: Height of the image.") .def_readwrite("intrinsic_matrix", - &camera::PinholeCameraIntrinsic::intrinsic_matrix_) + &camera::PinholeCameraIntrinsic::intrinsic_matrix_, + "3x3 numpy array: Intrinsic camera matrix ``[[fx, " + "0, cx], [0, fy, " + "cy], [0, 0, 1]]``") .def("__repr__", [](const camera::PinholeCameraIntrinsic &c) { return std::string( "camera::PinholeCameraIntrinsic with width = ") + @@ -71,45 +87,83 @@ void pybind_camera_classes(py::module &m) { std::string( ".\nAccess intrinsics with intrinsic_matrix."); }); - py::enum_( + docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", "__init__"); + docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", + "set_intrinsics", + {{"width", "Width of the image."}, + {"height", "Height of the image."}, + {"fx", "X-axis focal length"}, + {"fy", "Y-axis focal length."}, + {"cx", "X-axis principle point."}, + {"cy", "Y-axis principle point."}}); + docstring::ClassMethodDocInject(m, "PinholeCameraIntrinsic", + "get_focal_length"); + docstring::ClassMethodDocInject(m, "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") + "PinholeCameraIntrinsicParameters"); + pinhole_intr_params .value("PrimeSenseDefault", - camera::PinholeCameraIntrinsicParameters::PrimeSenseDefault) + camera::PinholeCameraIntrinsicParameters::PrimeSenseDefault, + "Default camera intrinsic parameter for PrimeSense.") .value("Kinect2DepthCameraDefault", camera::PinholeCameraIntrinsicParameters:: - Kinect2DepthCameraDefault) + Kinect2DepthCameraDefault, + "Default camera intrinsic parameter for Kinect2 depth " + "camera.") .value("Kinect2ColorCameraDefault", camera::PinholeCameraIntrinsicParameters:: - Kinect2ColorCameraDefault) + 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"; }), + py::none(), py::none(), ""); + // open3d.camera.PinholeCameraParameters py::class_ pinhole_param( - m, "PinholeCameraParameters", "PinholeCameraParameters"); + m, "PinholeCameraParameters", + "Contains both intrinsic and extrinsic pinhole camera parameters."); py::detail::bind_default_constructor( pinhole_param); py::detail::bind_copy_functions( pinhole_param); pinhole_param .def_readwrite("intrinsic", - &camera::PinholeCameraParameters::intrinsic_) + &camera::PinholeCameraParameters::intrinsic_, + "``open3d.camera.PinholeCameraIntrinsic``: " + "PinholeCameraIntrinsic " + "object.") .def_readwrite("extrinsic", - &camera::PinholeCameraParameters::extrinsic_) + &camera::PinholeCameraParameters::extrinsic_, + "4x4 numpy array: Camera extrinsic parameters.") .def("__repr__", [](const camera::PinholeCameraParameters &c) { return std::string("camera::PinholeCameraParameters class.\n") + std::string( "Access its data via intrinsic and extrinsic."); }); + // open3d.camera.PinholeCameraTrajectory py::class_ pinhole_traj( - m, "PinholeCameraTrajectory", "PinholeCameraTrajectory"); + m, "PinholeCameraTrajectory", + "Contains a list of ``PinholeCameraParameters``, useful to storing " + "trajectories."); py::detail::bind_default_constructor( pinhole_traj); py::detail::bind_copy_functions( pinhole_traj); pinhole_traj .def_readwrite("parameters", - &camera::PinholeCameraTrajectory::parameters_) + &camera::PinholeCameraTrajectory::parameters_, + "``List(open3d.camera.PinholeCameraIntrinsic)``: " + "List of PinholeCameraIntrinsic objects.") .def("__repr__", [](const camera::PinholeCameraTrajectory &c) { return std::string("camera::PinholeCameraTrajectory class.\n") + std::string("Access its data via camera_parameters."); diff --git a/src/Python/color_map/color_map.cpp b/src/Python/color_map/color_map.cpp index 99ce2d5d4a8..5ff7f05228e 100644 --- a/src/Python/color_map/color_map.cpp +++ b/src/Python/color_map/color_map.cpp @@ -41,34 +41,96 @@ void pybind_color_map_classes(py::module &m) { py::detail::bind_default_constructor( color_map_optimization_option); color_map_optimization_option - .def_readwrite("non_rigid_camera_coordinate", - &color_map::ColorMapOptimizationOption:: - non_rigid_camera_coordinate_) - .def_readwrite("number_of_vertical_anchors", - &color_map::ColorMapOptimizationOption:: - number_of_vertical_anchors_) - .def_readwrite("non_rigid_anchor_point_weight", - &color_map::ColorMapOptimizationOption:: - non_rigid_anchor_point_weight_) + .def_readwrite( + "non_rigid_camera_coordinate", + &color_map::ColorMapOptimizationOption:: + non_rigid_camera_coordinate_, + "bool: (Default ``False``) Set to ``True`` to enable " + "non-rigid optimization (optimizing camera " + "extrinsic params and image " + "wrapping field for color assignment), set to " + "``False`` to only enable rigid optimization " + "(optimize camera extrinsic params).") + .def_readwrite( + "number_of_vertical_anchors", + &color_map::ColorMapOptimizationOption:: + number_of_vertical_anchors_, + "int: (Default ``16``) Number of vertical anchor points " + "for image wrapping field. The number of horizontal anchor " + "points is computed automatically based on the " + "number of vertical anchor points. This option is " + "only used when non-rigid optimization is enabled.") + .def_readwrite( + "non_rigid_anchor_point_weight", + &color_map::ColorMapOptimizationOption:: + non_rigid_anchor_point_weight_, + "float: (Default ``0.316``) Additional regularization " + "terms added to non-rigid regularization. A higher value " + "results gives more conservative updates. If the residual " + "error does not stably decrease, it is mainly because " + "images are being bended abruptly. In this case, consider " + "making iteration more conservative by increasing " + "the value. This option is only used when non-rigid " + "optimization is enabled.") .def_readwrite( "maximum_iteration", - &color_map::ColorMapOptimizationOption::maximum_iteration_) - .def_readwrite("maximum_allowable_depth", - &color_map::ColorMapOptimizationOption:: - maximum_allowable_depth_) - .def_readwrite("depth_threshold_for_visiblity_check", - &color_map::ColorMapOptimizationOption:: - depth_threshold_for_visiblity_check_) - .def_readwrite("depth_threshold_for_discontinuity_check", - &color_map::ColorMapOptimizationOption:: - depth_threshold_for_discontinuity_check_) + &color_map::ColorMapOptimizationOption::maximum_iteration_, + "int: (Default ``300``) Number of iterations for " + "optimization steps.") + .def_readwrite( + "maximum_allowable_depth", + &color_map::ColorMapOptimizationOption:: + maximum_allowable_depth_, + "float: (Default ``2.5``) Parameter for point visibility " + "check. Points with depth larger than " + "``maximum_allowable_depth`` in a RGB-D will be marked as " + "invisible for the camera producing that RGB-D image. " + "Select a proper value to include necessary points while " + "ignoring unwanted points such as the background.") + .def_readwrite( + "depth_threshold_for_visiblity_check", + &color_map::ColorMapOptimizationOption:: + depth_threshold_for_visiblity_check_, + "float: (Default ``0.03``) Parameter for point visibility " + "check. When the difference of a point's depth " + "value in the RGB-D image and the point's depth " + "value in the 3D mesh is greater than " + "``depth_threshold_for_visiblity_check``, the " + "point is mark as invisible to the camera producing " + "the RGB-D image.") + .def_readwrite( + "depth_threshold_for_discontinuity_check", + &color_map::ColorMapOptimizationOption:: + depth_threshold_for_discontinuity_check_, + "float: (Default ``0.1``) Parameter for point visibility " + "check. It's often desirable to ignore points where there " + "are abrupt change in depth value. First the depth " + "gradient image is computed, points are considered " + "to be invisible if the depth gradient magnitude is " + "larger than ``depth_threshold_for_discontinuity_check``.") .def_readwrite( "half_dilation_kernel_size_for_discontinuity_map", &color_map::ColorMapOptimizationOption:: - half_dilation_kernel_size_for_discontinuity_map_) - .def_readwrite("image_boundary_margin", - &color_map::ColorMapOptimizationOption:: - image_boundary_margin_) + half_dilation_kernel_size_for_discontinuity_map_, + "int: (Default ``3``) Parameter for point visibility " + "check. Related to " + "``depth_threshold_for_discontinuity_check``, " + "when boundary points are detected, dilation is performed " + "to ignore points near the object boundary. " + "``half_dilation_kernel_size_for_discontinuity_map`` " + "specifies the half-kernel size for the dilation applied " + "on the visibility mask image.") + .def_readwrite( + "image_boundary_margin", + &color_map::ColorMapOptimizationOption:: + image_boundary_margin_, + "int: (Default ``10``) If a projected 3D point onto a 2D " + "image lies in the image border within " + "``image_boundary_margin``, the 3D point is " + "cosidered invisible from the camera producing the " + "image. This parmeter is not used for visibility " + "check, but used when computing the final color " + "assignment after color map optimization.") .def("__repr__", [](const color_map::ColorMapOptimizationOption &to) { return std::string( diff --git a/src/Python/docstring.cpp b/src/Python/docstring.cpp index 9dc327a305b..ff2633096d9 100644 --- a/src/Python/docstring.cpp +++ b/src/Python/docstring.cpp @@ -37,6 +37,50 @@ namespace open3d { namespace docstring { +// ref: enum_base in pybind11.h +py::handle static_property = + py::handle((PyObject*)py::detail::get_internals().static_property_type); + +void ClassMethodDocInject(py::module& pybind_module, + const std::string& class_name, + const std::string& function_name, + const std::unordered_map& + map_parameter_body_docs) { + // Get function + PyObject* module = pybind_module.ptr(); + PyObject* class_obj = PyObject_GetAttrString(module, class_name.c_str()); + PyObject* class_method_obj = + PyObject_GetAttrString(class_obj, function_name.c_str()); + if (Py_TYPE(class_method_obj) != &PyInstanceMethod_Type) { + return; + } + PyInstanceMethodObject* class_method = + (PyInstanceMethodObject*)class_method_obj; + PyObject* f_obj = class_method->func; + if (Py_TYPE(f_obj) != &PyCFunction_Type) { + return; + } + PyCFunctionObject* f = (PyCFunctionObject*)f_obj; + + // TODO: parse __init__ separately, currently __init__ can be overloaded + // which might cause parsing error. So they are skipped. + if (function_name == "__init__") { + return; + } + + // Parse existing docstring to FunctionDoc + FunctionDoc fd(f->m_ml->ml_doc); + + // Inject docstring + for (ArgumentDoc& ad : fd.argument_docs_) { + if (map_parameter_body_docs.find(ad.name_) != + map_parameter_body_docs.end()) { + ad.body_ = map_parameter_body_docs.at(ad.name_); + } + } + f->m_ml->ml_doc = strdup(fd.ToGoogleDocString().c_str()); +} + void FunctionDocInject(py::module& pybind_module, const std::string& function_name, const std::unordered_map& @@ -150,10 +194,14 @@ std::string FunctionDoc::ToGoogleDocString() const { } // Arguments - if (argument_docs_.size() != 0) { + if (argument_docs_.size() != 0 && + !(argument_docs_.size() == 1 && argument_docs_[0].name_ == "self")) { rc << std::endl; rc << "Args:" << std::endl; for (const ArgumentDoc& argument_doc : argument_docs_) { + if (argument_doc.name_ == "self") { + continue; + } rc << indent << argument_doc.name_ << " (" << argument_doc.type_; if (argument_doc.default_ != "") { rc << ", optional"; diff --git a/src/Python/docstring.h b/src/Python/docstring.h index 0f94852c4b2..dd810b4e279 100644 --- a/src/Python/docstring.h +++ b/src/Python/docstring.h @@ -125,11 +125,25 @@ class FunctionDoc { std::string pybind_doc_ = ""; }; -/// Parse pybind docstring to FunctionDoc and inject argument docstrings +/// Parse pybind docstring to FunctionDoc and inject argument docstrings for +/// functions void FunctionDocInject( py::module& pybind_module, const std::string& function_name, const std::unordered_map& map_parameter_docs = std::unordered_map()); + +/// Parse pybind docstring to FunctionDoc and inject argument docstrings for +/// class methods +void ClassMethodDocInject( + py::module& pybind_module, + const std::string& class_name, + const std::string& function_name, + const std::unordered_map& + map_parameter_body_docs = + std::unordered_map()); + +extern py::handle static_property; + } // namespace docstring } // namespace open3d diff --git a/src/Python/geometry/geometry.cpp b/src/Python/geometry/geometry.cpp index ac22eeddfb1..1235999a3c8 100644 --- a/src/Python/geometry/geometry.cpp +++ b/src/Python/geometry/geometry.cpp @@ -32,15 +32,34 @@ using namespace open3d; void pybind_geometry_classes(py::module &m) { + // open3d.geometry.Geometry py::class_, std::shared_ptr> - geometry(m, "Geometry", "Geometry"); - geometry.def("clear", &geometry::Geometry::Clear) - .def("is_empty", &geometry::Geometry::IsEmpty) - .def("get_geometry_type", &geometry::Geometry::GetGeometryType) - .def("dimension", &geometry::Geometry::Dimension); - py::enum_(geometry, "Type", - py::arithmetic()) + geometry(m, "Geometry", "The base geometry class."); + geometry.def("clear", &geometry::Geometry::Clear, + "Clear all elements in the geometry.") + .def("is_empty", &geometry::Geometry::IsEmpty, + "Returns ``True`` iff the geometry is empty.") + .def("get_geometry_type", &geometry::Geometry::GetGeometryType, + "Returns one of registered geometry types.") + .def("dimension", &geometry::Geometry::Dimension, + "Returns whether the geometry is 2D or 3D."); + docstring::ClassMethodDocInject(m, "Geometry", "clear"); + 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::Geometry::GeometryType::Unspecified) .value("PointCloud", geometry::Geometry::GeometryType::PointCloud) .value("VoxelGrid", geometry::Geometry::GeometryType::VoxelGrid) @@ -52,18 +71,35 @@ void pybind_geometry_classes(py::module &m) { .value("Image", geometry::Geometry::GeometryType::Image) .export_values(); + // open3d.geometry.Geometry3D py::class_, std::shared_ptr, geometry::Geometry> - geometry3d(m, "Geometry3D", "Geometry3D"); - geometry3d.def("get_min_bound", &geometry::Geometry3D::GetMinBound) - .def("get_max_bound", &geometry::Geometry3D::GetMaxBound) - .def("transform", &geometry::Geometry3D::Transform); + geometry3d(m, "Geometry3D", + "The base geometry class for 3D geometries."); + geometry3d + .def("get_min_bound", &geometry::Geometry3D::GetMinBound, + "Returns min bounds for geometry coordinates.") + .def("get_max_bound", &geometry::Geometry3D::GetMaxBound, + "Returns max bounds for geometry coordinates.") + .def("transform", &geometry::Geometry3D::Transform, + "Apply transformation (4x4 matrix) to the geometry " + "coordinates."); + docstring::ClassMethodDocInject(m, "Geometry3D", "get_min_bound"); + docstring::ClassMethodDocInject(m, "Geometry3D", "get_max_bound"); + docstring::ClassMethodDocInject(m, "Geometry3D", "transform"); + // open3d.geometry.Geometry2D py::class_, std::shared_ptr, geometry::Geometry> - geometry2d(m, "Geometry2D", "Geometry2D"); - geometry2d.def("get_min_bound", &geometry::Geometry2D::GetMinBound) - .def("get_max_bound", &geometry::Geometry2D::GetMaxBound); + geometry2d(m, "Geometry2D", + "The base geometry class for 2D geometries."); + geometry2d + .def("get_min_bound", &geometry::Geometry2D::GetMinBound, + "Returns min bounds for geometry coordinates.") + .def("get_max_bound", &geometry::Geometry2D::GetMaxBound, + "Returns max bounds for geometry coordinates."); + docstring::ClassMethodDocInject(m, "Geometry2D", "get_min_bound"); + docstring::ClassMethodDocInject(m, "Geometry2D", "get_max_bound"); } void pybind_geometry(py::module &m) { diff --git a/src/Python/geometry/halfedgetrianglemesh.cpp b/src/Python/geometry/halfedgetrianglemesh.cpp index 90e302dfb35..d4063d72732 100644 --- a/src/Python/geometry/halfedgetrianglemesh.cpp +++ b/src/Python/geometry/halfedgetrianglemesh.cpp @@ -35,7 +35,9 @@ using namespace open3d; void pybind_half_edge(py::module &m) { py::class_ half_edge( - m, "HalfEdge", "HalfEdge"); + m, "HalfEdge", + "HalfEdge class contains vertex, triangle info about a half edge, " + "as well as relations of next and twin half edge."); py::detail::bind_default_constructor< geometry::HalfEdgeTriangleMesh::HalfEdge>(half_edge); py::detail::bind_copy_functions( @@ -52,28 +54,39 @@ void pybind_half_edge(py::module &m) { return repr.str(); }) .def("is_boundary", - &geometry::HalfEdgeTriangleMesh::HalfEdge::IsBoundary) - .def_readwrite("next", - &geometry::HalfEdgeTriangleMesh::HalfEdge::next_) + &geometry::HalfEdgeTriangleMesh::HalfEdge::IsBoundary, + "Returns ``True`` iff the half edge is the boundary (has not " + "twin, i.e. twin index == -1).") + .def_readwrite( + "next", &geometry::HalfEdgeTriangleMesh::HalfEdge::next_, + "int: Index of the next HalfEdge in the same triangle.") .def_readwrite("twin", - &geometry::HalfEdgeTriangleMesh::HalfEdge::twin_) + &geometry::HalfEdgeTriangleMesh::HalfEdge::twin_, + "int: Index of the twin HalfEdge") .def_readwrite( "vertex_indices", - &geometry::HalfEdgeTriangleMesh::HalfEdge::vertex_indices_) + &geometry::HalfEdgeTriangleMesh::HalfEdge::vertex_indices_, + "List(int) of length 2: Index of the ordered vertices " + "forming this half edge") .def_readwrite( "triangle_index", - &geometry::HalfEdgeTriangleMesh::HalfEdge::triangle_index_); + &geometry::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, geometry::TriangleMesh> - half_edge_triangle_mesh(m, "HalfEdgeTriangleMesh", - "HalfEdgeTriangleMesh"); + 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."); py::detail::bind_default_constructor( half_edge_triangle_mesh); py::detail::bind_copy_functions( @@ -89,22 +102,47 @@ void pybind_halfedgetrianglemesh(py::module &m) { " triangles."; }) .def("compute_half_edges", - &geometry::HalfEdgeTriangleMesh::ComputeHalfEdges) + &geometry::HalfEdgeTriangleMesh::ComputeHalfEdges, + "Compute and update half edges, half edge can only be " + "computed if the mesh is a manifold. Returns ``True`` if half " + "edges are computed.") .def("has_half_edges", - &geometry::HalfEdgeTriangleMesh::HasHalfEdges) + &geometry::HalfEdgeTriangleMesh::HasHalfEdges, + "Returns ``True`` if half-edges have already been computed.") .def("boundary_half_edges_from_vertex", &geometry::HalfEdgeTriangleMesh::BoundaryHalfEdgesFromVertex, - "vertex_index"_a) + "vertex_index"_a, + "Query manifold boundary half edges from a starting vertex. " + "If query vertex is not on boundary, empty vector will be " + "returned.") .def("boundary_vertices_from_vertex", &geometry::HalfEdgeTriangleMesh::BoundaryVerticesFromVertex, - "vertex_index"_a) + "vertex_index"_a + "Query manifold boundary vertices from a starting vertex. If " + "query vertex is not on boundary, empty vector will be " + "returned.") .def("get_boundaries", - &geometry::HalfEdgeTriangleMesh::GetBoundaries) + &geometry::HalfEdgeTriangleMesh::GetBoundaries, + "Returns a vector of boundaries. A boundary is a vector of " + "vertices.") .def_readwrite("half_edges", - &geometry::HalfEdgeTriangleMesh::half_edges_) + &geometry::HalfEdgeTriangleMesh::half_edges_, + "List of HalfEdge in the mesh") .def_readwrite("ordered_half_edge_from_vertex", &geometry::HalfEdgeTriangleMesh:: - ordered_half_edge_from_vertex_); + ordered_half_edge_from_vertex_, + "Counter-clockwise ordered half-edges started from " + "each vertex"); + docstring::ClassMethodDocInject(m, "HalfEdgeTriangleMesh", + "boundary_half_edges_from_vertex"); + docstring::ClassMethodDocInject(m, "HalfEdgeTriangleMesh", + "boundary_vertices_from_vertex"); + docstring::ClassMethodDocInject(m, "HalfEdgeTriangleMesh", + "compute_half_edges"); + docstring::ClassMethodDocInject(m, "HalfEdgeTriangleMesh", + "get_boundaries"); + docstring::ClassMethodDocInject(m, "HalfEdgeTriangleMesh", + "has_half_edges"); m.def("create_half_edge_mesh_from_mesh", &geometry::CreateHalfEdgeMeshFromMesh, "mesh"_a, diff --git a/src/Python/geometry/image.cpp b/src/Python/geometry/image.cpp index 6572fae335c..9e9dc4a546f 100644 --- a/src/Python/geometry/image.cpp +++ b/src/Python/geometry/image.cpp @@ -59,7 +59,9 @@ static const std::unordered_map void pybind_image(py::module &m) { py::class_, std::shared_ptr, geometry::Geometry2D> - image(m, "Image", py::buffer_protocol(), "Image"); + image(m, "Image", py::buffer_protocol(), + "The image class stores image with customizable width, " + "height, num of channels and bytes per channel."); py::detail::bind_default_constructor(image); py::detail::bind_copy_functions(image); image.def(py::init([](py::buffer b) { @@ -77,14 +79,12 @@ void pybind_image(py::module &m) { bytes_per_channel = 4; } else { throw std::runtime_error( - "geometry::Image can only be initialized from buffer " - "of uint8, " + "Image can only be initialized from buffer of uint8, " "uint16, or float!"); } if (info.strides[info.ndim - 1] != bytes_per_channel) { throw std::runtime_error( - "geometry::Image can only be initialized from c-style " - "buffer."); + "Image can only be initialized from c-style buffer."); } if (info.ndim == 2) { num_of_channels = 1; @@ -113,8 +113,7 @@ void pybind_image(py::module &m) { break; default: throw std::runtime_error( - "geometry::Image has unrecognized " - "bytes_per_channel."); + "Image has unrecognized bytes_per_channel."); break; } if (img.num_of_channels_ == 1) { @@ -143,7 +142,7 @@ void pybind_image(py::module &m) { } }) .def("__repr__", [](const geometry::Image &img) { - return std::string("geometry::Image of size ") + + return std::string("Image of size ") + std::to_string(img.width_) + std::string("x") + std::to_string(img.height_) + ", with " + std::to_string(img.num_of_channels_) + @@ -153,12 +152,19 @@ void pybind_image(py::module &m) { }); py::class_> - rgbd_image(m, "RGBDImage", "RGBDImage"); + 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."); py::detail::bind_default_constructor(rgbd_image); - rgbd_image.def_readwrite("color", &geometry::RGBDImage::color_) - .def_readwrite("depth", &geometry::RGBDImage::depth_) + rgbd_image + .def_readwrite("color", &geometry::RGBDImage::color_, + "open3d.geometry.Image: The color image.") + .def_readwrite("depth", &geometry::RGBDImage::depth_, + "open3d.geometry.Image: The depth image.") .def("__repr__", [](const geometry::RGBDImage &rgbd_image) { - return std::string("geometry::RGBDImage of size \n") + + return std::string("RGBDImage of size \n") + std::string("Color image : ") + std::to_string(rgbd_image.color_.width_) + std::string("x") + @@ -176,13 +182,21 @@ void pybind_image(py::module &m) { } void pybind_image_methods(py::module &m) { - py::enum_(m, "ImageFilterType") - .value("Gaussian3", geometry::Image::FilterType::Gaussian3) + py::enum_ image_filter_type(m, + "ImageFilterType"); + image_filter_type.value("Gaussian3", geometry::Image::FilterType::Gaussian3) .value("Gaussian5", geometry::Image::FilterType::Gaussian5) .value("Gaussian7", geometry::Image::FilterType::Gaussian7) .value("Sobel3dx", geometry::Image::FilterType::Sobel3Dx) .value("Sobel3dy", geometry::Image::FilterType::Sobel3Dy) .export_values(); + // Trick to write docs without listing the members in the enum class again. + image_filter_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for Image filter types."; + }), + py::none(), py::none(), ""); + m.def("filter_image", [](const geometry::Image &input, geometry::Image::FilterType filter_type) { @@ -196,7 +210,7 @@ void pybind_image_methods(py::module &m) { return *output; } }, - "Function to filter geometry::Image", "image"_a, "filter_type"_a); + "Function to filter Image", "image"_a, "filter_type"_a); docstring::FunctionDocInject(m, "filter_image", map_shared_argument_docstrings); @@ -215,8 +229,8 @@ void pybind_image_methods(py::module &m) { return output; } }, - "Function to create geometry::ImagePyramid", "image"_a, - "num_of_levels"_a, "with_gaussian_filter"_a); + "Function to create ImagePyramid", "image"_a, "num_of_levels"_a, + "with_gaussian_filter"_a); docstring::FunctionDocInject(m, "create_image_pyramid", map_shared_argument_docstrings); @@ -226,37 +240,44 @@ void pybind_image_methods(py::module &m) { auto output = geometry::FilterImagePyramid(input, filter_type); return output; }, - "Function to filter geometry::ImagePyramid", "image_pyramid"_a, + "Function to filter ImagePyramid", "image_pyramid"_a, "filter_type"_a); docstring::FunctionDocInject(m, "filter_image_pyramid", map_shared_argument_docstrings); m.def("create_rgbd_image_from_color_and_depth", &geometry::CreateRGBDImageFromColorAndDepth, - "Function to make geometry::RGBDImage", "color"_a, "depth"_a, - "depth_scale"_a = 1000.0, "depth_trunc"_a = 3.0, + "Function to make RGBDImage from color and depth image", "color"_a, + "depth"_a, "depth_scale"_a = 1000.0, "depth_trunc"_a = 3.0, "convert_rgb_to_intensity"_a = true); docstring::FunctionDocInject(m, "create_rgbd_image_from_color_and_depth", map_shared_argument_docstrings); + m.def("create_rgbd_image_from_redwood_format", + &geometry::CreateRGBDImageFromRedwoodFormat, + "Function to make RGBDImage (for Redwood format)", "color"_a, + "depth"_a, "convert_rgb_to_intensity"_a = true); + docstring::FunctionDocInject(m, "create_rgbd_image_from_redwood_format", + map_shared_argument_docstrings); + m.def("create_rgbd_image_from_tum_format", &geometry::CreateRGBDImageFromTUMFormat, - "Function to make geometry::RGBDImage (for TUM format)", "color"_a, - "depth"_a, "convert_rgb_to_intensity"_a = true); + "Function to make RGBDImage (for TUM format)", "color"_a, "depth"_a, + "convert_rgb_to_intensity"_a = true); docstring::FunctionDocInject(m, "create_rgbd_image_from_tum_format", map_shared_argument_docstrings); m.def("create_rgbd_image_from_sun_format", &geometry::CreateRGBDImageFromSUNFormat, - "Function to make geometry::RGBDImage (for SUN format)", "color"_a, - "depth"_a, "convert_rgb_to_intensity"_a = true); + "Function to make RGBDImage (for SUN format)", "color"_a, "depth"_a, + "convert_rgb_to_intensity"_a = true); docstring::FunctionDocInject(m, "create_rgbd_image_from_sun_format", map_shared_argument_docstrings); m.def("create_rgbd_image_from_nyu_format", &geometry::CreateRGBDImageFromNYUFormat, - "Function to make geometry::RGBDImage (for NYU format)", "color"_a, - "depth"_a, "convert_rgb_to_intensity"_a = true); + "Function to make RGBDImage (for NYU format)", "color"_a, "depth"_a, + "convert_rgb_to_intensity"_a = true); docstring::FunctionDocInject(m, "create_rgbd_image_from_nyu_format", map_shared_argument_docstrings); } diff --git a/src/Python/geometry/kdtreeflann.cpp b/src/Python/geometry/kdtreeflann.cpp index d33494a029f..771e09e8005 100644 --- a/src/Python/geometry/kdtreeflann.cpp +++ b/src/Python/geometry/kdtreeflann.cpp @@ -33,22 +33,35 @@ using namespace open3d; void pybind_kdtreeflann(py::module &m) { + // open3d.geometry.KDTreeSearchParam py::class_ kdtreesearchparam( - m, "KDTreeSearchParam", "KDTreeSearchParam"); + m, "KDTreeSearchParam", "Base class for KDTree search parameters."); kdtreesearchparam.def("get_search_type", - &geometry::KDTreeSearchParam::GetSearchType); - py::enum_(kdtreesearchparam, - "Type", py::arithmetic()) + &geometry::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()); + kdtree_search_param_type .value("KNNSearch", geometry::KDTreeSearchParam::SearchType::Knn) .value("RadiusSearch", geometry::KDTreeSearchParam::SearchType::Radius) .value("HybridSearch", geometry::KDTreeSearchParam::SearchType::Hybrid) .export_values(); + kdtree_search_param_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for Geometry types."; + }), + py::none(), py::none(), ""); + // open3d.geometry.KDTreeSearchParamKNN py::class_ kdtreesearchparam_knn( m, "KDTreeSearchParamKNN", kdtreesearchparam, - "KDTreeSearchParamKNN"); + "KDTree search parameters for pure KNN search."); kdtreesearchparam_knn.def(py::init(), "knn"_a = 30) .def("__repr__", [](const geometry::KDTreeSearchParamKNN ¶m) { @@ -57,11 +70,13 @@ void pybind_kdtreeflann(py::module &m) { "= ") + std::to_string(param.knn_); }) - .def_readwrite("knn", &geometry::KDTreeSearchParamKNN::knn_); + .def_readwrite("knn", &geometry::KDTreeSearchParamKNN::knn_, + "``knn`` neighbors will be searched."); + // open3d.geometry.KDTreeSearchParamRadius py::class_ kdtreesearchparam_radius( m, "KDTreeSearchParamRadius", kdtreesearchparam, - "KDTreeSearchParamRadius"); + "KDTree search parameters for pure radius search."); kdtreesearchparam_radius.def(py::init(), "radius"_a) .def("__repr__", [](const geometry::KDTreeSearchParamRadius ¶m) { @@ -71,11 +86,13 @@ void pybind_kdtreeflann(py::module &m) { std::to_string(param.radius_); }) .def_readwrite("radius", - &geometry::KDTreeSearchParamRadius::radius_); + &geometry::KDTreeSearchParamRadius::radius_, + "Search radius."); + // open3d.geometry.KDTreeSearchParamHybrid py::class_ kdtreesearchparam_hybrid( m, "KDTreeSearchParamHybrid", kdtreesearchparam, - "KDTreeSearchParamHybrid"); + "KDTree search parameters for hybrid KNN and radius search."); kdtreesearchparam_hybrid .def(py::init(), "radius"_a, "max_nn"_a) .def("__repr__", @@ -87,12 +104,25 @@ void pybind_kdtreeflann(py::module &m) { " and max_nn = " + std::to_string(param.max_nn_); }) .def_readwrite("radius", - &geometry::KDTreeSearchParamHybrid::radius_) - .def_readwrite("max_nn", - &geometry::KDTreeSearchParamHybrid::max_nn_); + &geometry::KDTreeSearchParamHybrid::radius_, + "Search radius.") + .def_readwrite( + "max_nn", &geometry::KDTreeSearchParamHybrid::max_nn_, + "At maximum, ``max_nn`` neighbors will be searched."); + // open3d.geometry.KDTreeFlann + static const std::unordered_map + map_kd_tree_flann_method_docs = { + {"query", "The input query point."}, + {"radius", "Search radius."}, + {"max_nn", + "At maximum, ``max_nn`` neighbors will be searched."}, + {"knn", "``knn`` neighbors will be searched."}, + {"feature", "Feature data."}, + {"data", "Matrix data."}}; py::class_> - kdtreeflann(m, "KDTreeFlann", "KDTreeFlann"); + kdtreeflann(m, "KDTreeFlann", + "KDTree with FLANN for nearest neighbor search."); kdtreeflann.def(py::init<>()) .def(py::init(), "data"_a) .def("set_matrix_data", &geometry::KDTreeFlann::SetMatrixData, @@ -219,4 +249,26 @@ void pybind_kdtreeflann(py::module &m) { return std::make_tuple(k, indices, distance2); }, "query"_a, "radius"_a, "max_nn"_a); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_hybrid_vector_3d", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_hybrid_vector_xd", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_knn_vector_3d", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_knn_vector_xd", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_radius_vector_3d", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_radius_vector_xd", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_vector_3d", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "search_vector_xd", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "set_feature", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "set_geometry", + map_kd_tree_flann_method_docs); + docstring::ClassMethodDocInject(m, "KDTreeFlann", "set_matrix_data", + map_kd_tree_flann_method_docs); } diff --git a/src/Python/geometry/lineset.cpp b/src/Python/geometry/lineset.cpp index 3a6087d1b48..1c6726409c1 100644 --- a/src/Python/geometry/lineset.cpp +++ b/src/Python/geometry/lineset.cpp @@ -35,7 +35,10 @@ using namespace open3d; void pybind_lineset(py::module &m) { py::class_, std::shared_ptr, geometry::Geometry3D> - lineset(m, "LineSet", "LineSet"); + lineset(m, "LineSet", + "LineSet define a sets of lines in 3D. A typical " + "application is to display the point cloud correspondence " + "paris."); py::detail::bind_default_constructor(lineset); py::detail::bind_copy_functions(lineset); lineset.def("__repr__", @@ -45,11 +48,31 @@ void pybind_lineset(py::module &m) { }) .def(py::self + py::self) .def(py::self += py::self) - .def("has_points", &geometry::LineSet::HasPoints) - .def("has_lines", &geometry::LineSet::HasLines) - .def("has_colors", &geometry::LineSet::HasColors) - .def("normalize_normals", &geometry::LineSet::GetLineCoordinate) - .def_readwrite("points", &geometry::LineSet::points_) - .def_readwrite("lines", &geometry::LineSet::lines_) - .def_readwrite("colors", &geometry::LineSet::colors_); + .def("has_points", &geometry::LineSet::HasPoints, + "Returns ``True`` if the object contains points.") + .def("has_lines", &geometry::LineSet::HasLines, + "Returns ``True`` if the object contains lines.") + .def("has_colors", &geometry::LineSet::HasColors, + "Returns ``True`` if the object's lines contain contain " + "colors.") + .def("get_line_coordinate", &geometry::LineSet::GetLineCoordinate, + "line_index"_a) + .def_readwrite("points", &geometry::LineSet::points_, + "``float64`` array of shape ``(num_points, 3)``, " + "use ``numpy.asarray()`` to access data: Points " + "coordinates.") + .def_readwrite("lines", &geometry::LineSet::lines_, + "``int`` array of shape ``(num_lines, 2)``, use " + "``numpy.asarray()`` to access data: Lines denoted " + "by the index of points forming the line.") + .def_readwrite( + "colors", &geometry::LineSet::colors_, + "``float64`` array of shape ``(num_lines, 3)``, " + "range ``[0, 1]`` , use ``numpy.asarray()`` to access " + "data: RGB colors of lines."); + docstring::ClassMethodDocInject(m, "LineSet", "has_colors"); + docstring::ClassMethodDocInject(m, "LineSet", "has_lines"); + docstring::ClassMethodDocInject(m, "LineSet", "has_points"); + docstring::ClassMethodDocInject(m, "LineSet", "get_line_coordinate", + {{"line_index", "Index of the line."}}); } diff --git a/src/Python/geometry/pointcloud.cpp b/src/Python/geometry/pointcloud.cpp index 181f103369f..71d13b8e49a 100644 --- a/src/Python/geometry/pointcloud.cpp +++ b/src/Python/geometry/pointcloud.cpp @@ -38,7 +38,10 @@ using namespace open3d; void pybind_pointcloud(py::module &m) { py::class_, std::shared_ptr, geometry::Geometry3D> - pointcloud(m, "PointCloud", "PointCloud"); + pointcloud(m, "PointCloud", + "PointCloud class. A point cloud consists of point " + "coordinates, and optionally point colors and point " + "normals."); py::detail::bind_default_constructor(pointcloud); py::detail::bind_copy_functions(pointcloud); pointcloud @@ -49,15 +52,36 @@ void pybind_pointcloud(py::module &m) { }) .def(py::self + py::self) .def(py::self += py::self) - .def("has_points", &geometry::PointCloud::HasPoints) - .def("has_normals", &geometry::PointCloud::HasNormals) - .def("has_colors", &geometry::PointCloud::HasColors) - .def("normalize_normals", &geometry::PointCloud::NormalizeNormals) + .def("has_points", &geometry::PointCloud::HasPoints, + "Returns ``True`` if the point cloud contains points.") + .def("has_normals", &geometry::PointCloud::HasNormals, + "Returns ``True`` if the point cloud contains point normals.") + .def("has_colors", &geometry::PointCloud::HasColors, + "Returns ``True`` if the point cloud contains point colors.") + .def("normalize_normals", &geometry::PointCloud::NormalizeNormals, + "Normalize point normals to length 1.") .def("paint_uniform_color", - &geometry::PointCloud::PaintUniformColor) - .def_readwrite("points", &geometry::PointCloud::points_) - .def_readwrite("normals", &geometry::PointCloud::normals_) - .def_readwrite("colors", &geometry::PointCloud::colors_); + &geometry::PointCloud::PaintUniformColor, "color"_a, + "Assign uniform color to all points.") + .def_readwrite("points", &geometry::PointCloud::points_, + "``float64`` array of shape ``(num_points, 3)``, " + "use ``numpy.asarray()`` to access data: Points " + "coordinates.") + .def_readwrite("normals", &geometry::PointCloud::normals_, + "``float64`` array of shape ``(num_points, 3)``, " + "use ``numpy.asarray()`` to access data: Points " + "normals.") + .def_readwrite( + "colors", &geometry::PointCloud::colors_, + "``float64`` array of shape ``(num_points, 3)``, " + "range ``[0, 1]`` , use ``numpy.asarray()`` to access " + "data: RGB colors of points."); + docstring::ClassMethodDocInject(m, "PointCloud", "has_colors"); + docstring::ClassMethodDocInject(m, "PointCloud", "has_normals"); + docstring::ClassMethodDocInject(m, "PointCloud", "has_points"); + docstring::ClassMethodDocInject(m, "PointCloud", "normalize_normals"); + docstring::ClassMethodDocInject(m, "PointCloud", "paint_uniform_color", + {{"color", "RGB color."}}); } void pybind_pointcloud_methods(py::module &m) { diff --git a/src/Python/geometry/trianglemesh.cpp b/src/Python/geometry/trianglemesh.cpp index 0a8940a0f66..bc103642658 100644 --- a/src/Python/geometry/trianglemesh.cpp +++ b/src/Python/geometry/trianglemesh.cpp @@ -35,7 +35,11 @@ using namespace open3d; void pybind_trianglemesh(py::module &m) { py::class_, std::shared_ptr, geometry::Geometry3D> - trianglemesh(m, "TriangleMesh", "TriangleMesh"); + trianglemesh(m, "TriangleMesh", + "TriangleMesh class. Triangle mesh contains vertices " + "and triangles represented by the indices to the " + "vertices. Optionally, the mesh may also contain " + "triangle normals, vertex normals and vertex colors."); py::detail::bind_default_constructor(trianglemesh); py::detail::bind_copy_functions(trianglemesh); trianglemesh @@ -66,28 +70,76 @@ void pybind_trianglemesh(py::module &m) { .def("purge", &geometry::TriangleMesh::Purge, "Function to remove duplicated and non-manifold " "vertices/triangles") - .def("has_vertices", &geometry::TriangleMesh::HasVertices) - .def("has_triangles", &geometry::TriangleMesh::HasTriangles) + .def("has_vertices", &geometry::TriangleMesh::HasVertices, + "Returns ``True`` if the mesh contains vertices.") + .def("has_triangles", &geometry::TriangleMesh::HasTriangles, + "Returns ``True`` if the mesh contains triangles.") .def("has_vertex_normals", - &geometry::TriangleMesh::HasVertexNormals) - .def("has_vertex_colors", &geometry::TriangleMesh::HasVertexColors) + &geometry::TriangleMesh::HasVertexNormals, + "Returns ``True`` if the mesh contains vertex normals.") + .def("has_vertex_colors", &geometry::TriangleMesh::HasVertexColors, + "Returns ``True`` if the mesh contains vertex colors.") .def("has_triangle_normals", - &geometry::TriangleMesh::HasTriangleNormals) + &geometry::TriangleMesh::HasTriangleNormals, + "Returns ``True`` if the mesh contains triangle normals.") .def("has_adjacency_list", - &geometry::TriangleMesh::HasAdjacencyList) - .def("normalize_normals", &geometry::TriangleMesh::NormalizeNormals) + &geometry::TriangleMesh::HasAdjacencyList, + "Returns ``True`` if the mesh contains adjacency normals.") + .def("normalize_normals", &geometry::TriangleMesh::NormalizeNormals, + "Normalize both triangle normals and vertex normals to legnth " + "1.") .def("paint_uniform_color", - &geometry::TriangleMesh::PaintUniformColor) - .def_readwrite("vertices", &geometry::TriangleMesh::vertices_) + &geometry::TriangleMesh::PaintUniformColor, + "Assign uniform color to all vertices.") + .def_readwrite("vertices", &geometry::TriangleMesh::vertices_, + "``float64`` array of shape ``(num_vertices, 3)``, " + "use ``numpy.asarray()`` to access data: Vertex " + "coordinates.") .def_readwrite("vertex_normals", - &geometry::TriangleMesh::vertex_normals_) - .def_readwrite("vertex_colors", - &geometry::TriangleMesh::vertex_colors_) - .def_readwrite("triangles", &geometry::TriangleMesh::triangles_) + &geometry::TriangleMesh::vertex_normals_, + "``float64`` array of shape ``(num_vertices, 3)``, " + "use ``numpy.asarray()`` to access data: Vertex " + "normals.") + .def_readwrite( + "vertex_colors", &geometry::TriangleMesh::vertex_colors_, + "``float64`` array of shape ``(num_vertices, 3)``, " + "range ``[0, 1]`` , use ``numpy.asarray()`` to access " + "data: RGB colors of vertices.") + .def_readwrite("triangles", &geometry::TriangleMesh::triangles_, + "``int`` array of shape ``(num_triangles, 3)``, use " + "``numpy.asarray()`` to access data: List of " + "triangles denoted by the index of points forming " + "the triangle.") .def_readwrite("triangle_normals", - &geometry::TriangleMesh::triangle_normals_) - .def_readwrite("adjacency_list", - &geometry::TriangleMesh::adjacency_list_); + &geometry::TriangleMesh::triangle_normals_, + "``float64`` array of shape ``(num_triangles, 3)``, " + "use ``numpy.asarray()`` to access data: Triangle " + "normals.") + .def_readwrite( + "adjacency_list", &geometry::TriangleMesh::adjacency_list_, + "List of Sets: The set ``adjacency_list[i]`` contains the " + "indices of adjacent vertices of vertex i."); + docstring::ClassMethodDocInject(m, "TriangleMesh", + "compute_adjacency_list"); + docstring::ClassMethodDocInject(m, "TriangleMesh", + "compute_triangle_normals"); + docstring::ClassMethodDocInject(m, "TriangleMesh", + "compute_vertex_normals"); + docstring::ClassMethodDocInject(m, "TriangleMesh", "has_adjacency_list"); + docstring::ClassMethodDocInject( + m, "TriangleMesh", "has_triangle_normals", + {{"normalized", + "Set to ``True`` to normalize the normal to length 1."}}); + docstring::ClassMethodDocInject(m, "TriangleMesh", "has_triangles"); + docstring::ClassMethodDocInject(m, "TriangleMesh", "has_vertex_colors"); + docstring::ClassMethodDocInject( + m, "TriangleMesh", "has_vertex_normals", + {{"normalized", + "Set to ``True`` to normalize the normal to length 1."}}); + docstring::ClassMethodDocInject(m, "TriangleMesh", "has_vertices"); + docstring::ClassMethodDocInject(m, "TriangleMesh", "normalize_normals"); + docstring::ClassMethodDocInject(m, "TriangleMesh", "paint_uniform_color"); + docstring::ClassMethodDocInject(m, "TriangleMesh", "purge"); } void pybind_trianglemesh_methods(py::module &m) { diff --git a/src/Python/geometry/voxelgrid.cpp b/src/Python/geometry/voxelgrid.cpp index f71b75f281a..f965c8f32c0 100644 --- a/src/Python/geometry/voxelgrid.cpp +++ b/src/Python/geometry/voxelgrid.cpp @@ -36,7 +36,9 @@ using namespace open3d; void pybind_voxelgrid(py::module &m) { py::class_, std::shared_ptr, geometry::Geometry3D> - voxelgrid(m, "VoxelGrid"); + voxelgrid(m, "VoxelGrid", + "VoxelGrid is a collection of voxels which are aligned " + "in grid."); py::detail::bind_default_constructor(voxelgrid); py::detail::bind_copy_functions(voxelgrid); voxelgrid @@ -48,12 +50,25 @@ void pybind_voxelgrid(py::module &m) { }) .def(py::self + py::self) .def(py::self += py::self) - .def("has_voxels", &geometry::VoxelGrid::HasVoxels) - .def("has_colors", &geometry::VoxelGrid::HasColors) - .def_readwrite("voxels", &geometry::VoxelGrid::voxels_) - .def_readwrite("colors", &geometry::VoxelGrid::colors_) - .def_readwrite("origin", &geometry::VoxelGrid::origin_) + .def("has_voxels", &geometry::VoxelGrid::HasVoxels, + "Returns ``True`` if the voxel grid contains voxels.") + .def("has_colors", &geometry::VoxelGrid::HasColors, + "Returns ``True`` if the voxel grid contains voxel colors.") + .def_readwrite("voxels", &geometry::VoxelGrid::voxels_, + "``int`` array of shape ``(num_voxels, 3)``: " + "Voxel coordinates. use ``numpy.asarray()`` to " + "access data.") + .def_readwrite( + "colors", &geometry::VoxelGrid::colors_, + "``float64`` array of shape ``(num_voxels, 3)``, " + "range ``[0, 1]`` , use ``numpy.asarray()`` to access " + "data: RGB colors of voxels.") + .def_readwrite("origin", &geometry::VoxelGrid::origin_, + "``float64`` vector of length 3: Coorindate of the " + "origin point.") .def_readwrite("voxel_size", &geometry::VoxelGrid::voxel_size_); + docstring::ClassMethodDocInject(m, "VoxelGrid", "has_colors"); + docstring::ClassMethodDocInject(m, "VoxelGrid", "has_voxels"); } void pybind_voxelgrid_methods(py::module &m) { diff --git a/src/Python/integration/integration.cpp b/src/Python/integration/integration.cpp index b28363e9e3e..fdbe683b424 100644 --- a/src/Python/integration/integration.cpp +++ b/src/Python/integration/integration.cpp @@ -55,15 +55,32 @@ class PyTSDFVolume : public TSDFVolumeBase { }; void pybind_integration_classes(py::module &m) { - py::enum_(m, "TSDFVolumeColorType", - py::arithmetic()) - .value("None", integration::TSDFVolumeColorType::None) + // open3d.integration.TSDFVolumeColorType + py::enum_ tsdf_volume_color_type( + m, "TSDFVolumeColorType", py::arithmetic()); + tsdf_volume_color_type.value("None", integration::TSDFVolumeColorType::None) .value("RGB8", integration::TSDFVolumeColorType::RGB8) .value("Gray32", integration::TSDFVolumeColorType::Gray32) .export_values(); + // Trick to write docs without listing the members in the enum class again. + tsdf_volume_color_type.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for TSDFVolumeColorType."; + }), + py::none(), py::none(), ""); + // open3d.integration.TSDFVolume py::class_> - tsdfvolume(m, "TSDFVolume", "TSDFVolume"); + tsdfvolume(m, "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: + +A volumetric method for building complex models from range images + +B. Curless and M. Levoy + +In SIGGRAPH, 1996)"); tsdfvolume .def("reset", &integration::TSDFVolume::Reset, "Function to reset the integration::TSDFVolume") @@ -77,14 +94,31 @@ void pybind_integration_classes(py::module &m) { &integration::TSDFVolume::ExtractTriangleMesh, "Function to extract a triangle mesh") .def_readwrite("voxel_length", - &integration::TSDFVolume::voxel_length_) - .def_readwrite("sdf_trunc", &integration::TSDFVolume::sdf_trunc_) - .def_readwrite("color_type", &integration::TSDFVolume::color_type_); + &integration::TSDFVolume::voxel_length_, + "float: Voxel size.") + .def_readwrite("sdf_trunc", &integration::TSDFVolume::sdf_trunc_, + "float: Truncation value for signed distance " + "function (SDF).") + .def_readwrite("color_type", &integration::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, "TSDFVolume", "integrate", + {{"image", "RGBD image."}, + {"intrinsic", "Pinhole camera intrinsic parameters."}, + {"extrinsic", "Extrinsic parameters."}}); + docstring::ClassMethodDocInject(m, "TSDFVolume", "reset"); + // open3d.integration.UniformTSDFVolume: open3d.integration.TSDFVolume py::class_, integration::TSDFVolume> - uniform_tsdfvolume(m, "UniformTSDFVolume", "UniformTSDFVolume"); + uniform_tsdfvolume( + m, "UniformTSDFVolume", + "UniformTSDFVolume implements the classic TSDF " + "volume with uniform voxel grid (Curless and Levoy 1996)."); py::detail::bind_copy_functions( uniform_tsdfvolume); uniform_tsdfvolume @@ -104,15 +138,40 @@ void pybind_integration_classes(py::module &m) { : std::string("with color.")); }) // todo: extend .def("extract_voxel_point_cloud", - &integration::UniformTSDFVolume::ExtractVoxelPointCloud) - .def_readwrite("length", &integration::UniformTSDFVolume::length_) + &integration::UniformTSDFVolume::ExtractVoxelPointCloud, + "Debug function to extract the voxel data into a point cloud.") + .def_readwrite("length", &integration::UniformTSDFVolume::length_, + "Total length, where ``voxel_length = length / " + "resolution``.") .def_readwrite("resolution", - &integration::UniformTSDFVolume::resolution_); + &integration::UniformTSDFVolume::resolution_, + "Resolution over the total length, where " + "``voxel_length = length / resolution``"); + docstring::ClassMethodDocInject(m, "UniformTSDFVolume", + "extract_voxel_point_cloud"); + // open3d.integration.ScalableTSDFVolume: open3d.integration.TSDFVolume py::class_, integration::TSDFVolume> - scalable_tsdfvolume(m, "ScalableTSDFVolume", "ScalableTSDFVolume"); + scalable_tsdfvolume(m, "ScalableTSDFVolume", R"(The +ScalableTSDFVolume implements a more memory efficient data structure for +volumetric integration. + +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)"); py::detail::bind_copy_functions( scalable_tsdfvolume); scalable_tsdfvolume @@ -136,7 +195,11 @@ void pybind_integration_classes(py::module &m) { : std::string("with color.")); }) .def("extract_voxel_point_cloud", - &integration::ScalableTSDFVolume::ExtractVoxelPointCloud); + &integration::ScalableTSDFVolume::ExtractVoxelPointCloud, + "Debug function to extract the voxel data into a point " + "cloud."); + docstring::ClassMethodDocInject(m, "ScalableTSDFVolume", + "extract_voxel_point_cloud"); } void pybind_integration_methods(py::module &m) { diff --git a/src/Python/odometry/odometry.cpp b/src/Python/odometry/odometry.cpp index f36e77fb73b..7850050a78d 100644 --- a/src/Python/odometry/odometry.cpp +++ b/src/Python/odometry/odometry.cpp @@ -59,8 +59,9 @@ class PyRGBDOdometryJacobian : public RGBDOdometryJacobianBase { }; void pybind_odometry_classes(py::module &m) { - py::class_ odometry_option(m, "OdometryOption", - "OdometryOption"); + // open3d.odometry.OdometryOption + py::class_ odometry_option( + m, "OdometryOption", "Class that defines Odometry options."); odometry_option .def(py::init( [](std::vector iteration_number_per_pyramid_level, @@ -76,11 +77,26 @@ void pybind_odometry_classes(py::module &m) { "max_depth"_a = 4.0) .def_readwrite("iteration_number_per_pyramid_level", &odometry::OdometryOption:: - iteration_number_per_pyramid_level_) + iteration_number_per_pyramid_level_, + "List(int): Iteration number per image pyramid " + "level, typically larger image in the pyramid have " + "lower interation number to reduce computation " + "time.") .def_readwrite("max_depth_diff", - &odometry::OdometryOption::max_depth_diff_) - .def_readwrite("min_depth", &odometry::OdometryOption::min_depth_) - .def_readwrite("max_depth", &odometry::OdometryOption::max_depth_) + &odometry::OdometryOption::max_depth_diff_, + "Maximum depth difference to be considered as " + "correspondence. In depth image domain, if two " + "aligned pixels have a depth difference less than " + "specified value, they are considered as a " + "correspondence. Larger value induce more " + "aggressive search, but it is prone to unstable " + "result.") + .def_readwrite("min_depth", &odometry::OdometryOption::min_depth_, + "Pixels that has smaller than specified depth " + "values are ignored.") + .def_readwrite("max_depth", &odometry::OdometryOption::max_depth_, + "Pixels that has larger than specified depth values " + "are ignored.") .def("__repr__", [](const odometry::OdometryOption &c) { int num_pyramid_level = (int)c.iteration_number_per_pyramid_level_.size(); @@ -104,18 +120,30 @@ void pybind_odometry_classes(py::module &m) { std::to_string(c.max_depth_); }); + // open3d.odometry.RGBDOdometryJacobian py::class_> - jacobian(m, "RGBDOdometryJacobian", "RGBDOdometryJacobian"); - jacobian.def("compute_jacobian_and_residual", - &odometry::RGBDOdometryJacobian::ComputeJacobianAndResidual); + jacobian( + m, "RGBDOdometryJacobian", + "Base class that computes Jacobian from two RGB-D images."); + // open3d.odometry.RGBDOdometryJacobianFromColorTerm: RGBDOdometryJacobian py::class_< odometry::RGBDOdometryJacobianFromColorTerm, PyRGBDOdometryJacobian, odometry::RGBDOdometryJacobian> jacobian_color(m, "RGBDOdometryJacobianFromColorTerm", - "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::detail::bind_default_constructor< odometry::RGBDOdometryJacobianFromColorTerm>(jacobian_color); py::detail::bind_copy_functions< @@ -126,12 +154,21 @@ void pybind_odometry_classes(py::module &m) { return std::string("RGBDOdometryJacobianFromColorTerm"); }); + // open3d.odometry.RGBDOdometryJacobianFromHybridTerm: RGBDOdometryJacobian py::class_, odometry::RGBDOdometryJacobian> jacobian_hybrid(m, "RGBDOdometryJacobianFromHybridTerm", - "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.)"); py::detail::bind_default_constructor< odometry::RGBDOdometryJacobianFromHybridTerm>(jacobian_hybrid); py::detail::bind_copy_functions< diff --git a/src/Python/open3d_pybind.h b/src/Python/open3d_pybind.h index f997daa3467..12b9adcf8be 100644 --- a/src/Python/open3d_pybind.h +++ b/src/Python/open3d_pybind.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include diff --git a/src/Python/registration/feature.cpp b/src/Python/registration/feature.cpp index fbb274ddf31..224decf3dc6 100644 --- a/src/Python/registration/feature.cpp +++ b/src/Python/registration/feature.cpp @@ -33,14 +33,20 @@ using namespace open3d; void pybind_feature(py::module &m) { + // open3d.registration.Feature py::class_> - feature(m, "Feature", "Feature"); + feature(m, "Feature", "Class to store featrues for registration."); py::detail::bind_default_constructor(feature); py::detail::bind_copy_functions(feature); - feature.def("resize", ®istration::Feature::Resize, "dim"_a, "n"_a) - .def("dimension", ®istration::Feature::Dimension) - .def("num", ®istration::Feature::Num) - .def_readwrite("data", ®istration::Feature::data_) + feature.def("resize", ®istration::Feature::Resize, "dim"_a, "n"_a, + "Resize feature data buffer to ``dim x n``.") + .def("dimension", ®istration::Feature::Dimension, + "Returns feature dimensions per point.") + .def("num", ®istration::Feature::Num, + "Returns number of points.") + .def_readwrite("data", ®istration::Feature::data_, + "``dim x n`` float64 numpy array: Data buffer " + "storing features.") .def("__repr__", [](const registration::Feature &f) { return std::string( "registration::Feature class with dimension " @@ -49,6 +55,11 @@ 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", + {{"dim", "Feature dimension per point."}, + {"n", "Number of points."}}); } void pybind_feature_methods(py::module &m) { diff --git a/src/Python/registration/global_optimization.cpp b/src/Python/registration/global_optimization.cpp index 7cfa10d4654..8563c67d321 100644 --- a/src/Python/registration/global_optimization.cpp +++ b/src/Python/registration/global_optimization.cpp @@ -50,9 +50,10 @@ class PyGlobalOptimizationMethod : public GlobalOptimizationMethodBase { }; void pybind_global_optimization(py::module &m) { + // open3d.registration.PoseGraphNode py::class_> - pose_graph_node(m, "PoseGraphNode", "PoseGraphNode"); + pose_graph_node(m, "PoseGraphNode", "Node of ``PostGraph``."); py::detail::bind_default_constructor( pose_graph_node); py::detail::bind_copy_functions( @@ -68,29 +69,39 @@ void pybind_global_optimization(py::module &m) { "registration::PoseGraphNode, access pose to get its " "current pose."); }); + + // open3d.registration.PoseGraphNodeVector py::bind_vector>( m, "PoseGraphNodeVector"); + // open3d.registration.PoseGraphEdge py::class_> - pose_graph_edge(m, "PoseGraphEdge", "PoseGraphEdge"); + pose_graph_edge(m, "PoseGraphEdge", "Edge of ``PostGraph``."); py::detail::bind_default_constructor( pose_graph_edge); py::detail::bind_copy_functions( pose_graph_edge); pose_graph_edge .def_readwrite("source_node_id", - ®istration::PoseGraphEdge::source_node_id_) + ®istration::PoseGraphEdge::source_node_id_, + "int: Source ``PoseGraphNode`` id.") .def_readwrite("target_node_id", - ®istration::PoseGraphEdge::target_node_id_) - .def_readwrite("transformation", - ®istration::PoseGraphEdge::transformation_) + ®istration::PoseGraphEdge::target_node_id_, + "int: Target ``PoseGraphNode`` id.") + .def_readwrite( + "transformation", + ®istration::PoseGraphEdge::transformation_, + "``4 x 4`` float64 numpy array: Transformation matrix.") .def_readwrite("information", - ®istration::PoseGraphEdge::information_) + ®istration::PoseGraphEdge::information_, + "``6 x 6`` float64 numpy array: Information matrix.") .def_readwrite("uncertain", - ®istration::PoseGraphEdge::uncertain_) + ®istration::PoseGraphEdge::uncertain_, + "bool: Whether the edge is uncertain.") .def_readwrite("confidence", - ®istration::PoseGraphEdge::confidence_) + ®istration::PoseGraphEdge::confidence_, + "float from 0 to 1: Confidence value of the edge.") .def(py::init([](int source_node_id, int target_node_id, Eigen::Matrix4d transformation, Eigen::Matrix6d information, bool uncertain, @@ -112,16 +123,25 @@ void pybind_global_optimization(py::module &m) { ", access transformation to get relative " "transformation"); }); + + // open3d.registration.PoseGraphEdgeVector py::bind_vector>( m, "PoseGraphEdgeVector"); + // open3d.registration.PoseGraph py::class_> - pose_graph(m, "PoseGraph"); + pose_graph(m, "PoseGraph", + "Data structure defining the pose graph."); py::detail::bind_default_constructor(pose_graph); py::detail::bind_copy_functions(pose_graph); - pose_graph.def_readwrite("nodes", ®istration::PoseGraph::nodes_) - .def_readwrite("edges", ®istration::PoseGraph::edges_) + pose_graph + .def_readwrite( + "nodes", ®istration::PoseGraph::nodes_, + "``List(PostGraphNode)``: List of ``PostGraphNode``.") + .def_readwrite( + "edges", ®istration::PoseGraph::edges_, + "``List(PostGraphEdge)``: List of ``PostGraphEdge``.") .def("__repr__", [](const registration::PoseGraph &rr) { return std::string("registration::PoseGraph with ") + std::to_string(rr.nodes_.size()) + @@ -130,14 +150,23 @@ void pybind_global_optimization(py::module &m) { std::string(" edges."); }); + // open3d.registration.GlobalOptimizationMethod py::class_< registration::GlobalOptimizationMethod, PyGlobalOptimizationMethod> - global_optimization_method(m, "GlobalOptimizationMethod", - "GlobalOptimizationMethod"); + global_optimization_method( + m, "GlobalOptimizationMethod", + "Base class for global optimization method."); global_optimization_method.def( "OptimizePoseGraph", - ®istration::GlobalOptimizationMethod::OptimizePoseGraph); + ®istration::GlobalOptimizationMethod::OptimizePoseGraph, + "pose_graph"_a, "criteria"_a, "option"_a, + "Run pose graph optimization."); + docstring::ClassMethodDocInject( + m, "GlobalOptimizationMethod", "OptimizePoseGraph", + {{"pose_graph", "The pose graph to be optimized (in-place)."}, + {"criteria", "Convergence criteria."}, + {"option", "Global optimization options."}}); py::class_ global_optimization_method_lm( m, "GlobalOptimizationLevenbergMarquardt", - "GlobalOptimizationLevenbergMarquardt"); + "Global optimization with Levenberg-Marquardt algorithm. " + "Recommended over the Gauss-Newton method since the LM has " + "better convergence characteristics."); py::detail::bind_default_constructor< registration::GlobalOptimizationLevenbergMarquardt>( global_optimization_method_lm); @@ -162,8 +193,9 @@ void pybind_global_optimization(py::module &m) { PyGlobalOptimizationMethod< registration::GlobalOptimizationGaussNewton>, registration::GlobalOptimizationMethod> - global_optimization_method_gn(m, "GlobalOptimizationGaussNewton", - "GlobalOptimizationGaussNewton"); + global_optimization_method_gn( + m, "GlobalOptimizationGaussNewton", + "Global optimization with Gauss-Newton algorithm."); py::detail::bind_default_constructor< registration::GlobalOptimizationGaussNewton>( global_optimization_method_gn); @@ -186,35 +218,44 @@ void pybind_global_optimization(py::module &m) { criteria.def_readwrite( "max_iteration", ®istration::GlobalOptimizationConvergenceCriteria:: - max_iteration_) + max_iteration_, + "int: Maximum iteration number.") .def_readwrite( "min_relative_increment", ®istration::GlobalOptimizationConvergenceCriteria:: - min_relative_increment_) + min_relative_increment_, + "float: Minimum relative increments.") .def_readwrite( "min_relative_residual_increment", ®istration::GlobalOptimizationConvergenceCriteria:: - min_relative_residual_increment_) + min_relative_residual_increment_, + "float: Minimum relative residual increments.") .def_readwrite( "min_right_term", ®istration::GlobalOptimizationConvergenceCriteria:: - min_right_term_) + min_right_term_, + "float: Minimum right term value.") .def_readwrite( "min_residual", ®istration::GlobalOptimizationConvergenceCriteria:: - min_residual_) + min_residual_, + "float: Minimum residual value.") .def_readwrite( "max_iteration_lm", ®istration::GlobalOptimizationConvergenceCriteria:: - max_iteration_lm_) + max_iteration_lm_, + "int: Maximum iteration number for Levenberg Marquardt " + "method.") .def_readwrite( "upper_scale_factor", ®istration::GlobalOptimizationConvergenceCriteria:: - upper_scale_factor_) + upper_scale_factor_, + "float: Upper scale factor value.") .def_readwrite( "lower_scale_factor", ®istration::GlobalOptimizationConvergenceCriteria:: - lower_scale_factor_) + lower_scale_factor_, + "float: Lower scale factor value.") .def("__repr__", [](const registration::GlobalOptimizationConvergenceCriteria &cr) { @@ -248,16 +289,31 @@ void pybind_global_optimization(py::module &m) { option); option.def_readwrite("max_correspondence_distance", ®istration::GlobalOptimizationOption:: - max_correspondence_distance_) + max_correspondence_distance_, + "float: Identifies which distance value is used for " + "finding neighboring points when making information " + "matrix. According to [Choi et al 2015], this " + "distance is used for determining $mu, a line process " + "weight.") .def_readwrite("edge_prune_threshold", ®istration::GlobalOptimizationOption:: - edge_prune_threshold_) + edge_prune_threshold_, + "float: According to [Choi et al 2015], " + "line_process weight < edge_prune_threshold (0.25) " + "is pruned.") .def_readwrite("preference_loop_closure", ®istration::GlobalOptimizationOption:: - preference_loop_closure_) + preference_loop_closure_, + "float: Balancing parameter to decide which one is " + "more reliable: odometry vs loop-closure. [0,1] -> " + "try to unchange odometry edges, [1) -> try to " + "utilize loop-closure. Recommendation: 0.1 for RGBD " + "Odometry, 2.0 for fragment registration.") .def_readwrite( "reference_node", - ®istration::GlobalOptimizationOption::reference_node_) + ®istration::GlobalOptimizationOption::reference_node_, + "int: The pose of this node is unchanged after " + "optimization.") .def(py::init([](double max_correspondence_distance, double edge_prune_threshold, double preference_loop_closure, diff --git a/src/Python/registration/registration.cpp b/src/Python/registration/registration.cpp index 6f68f3d9197..699d6eb04cb 100644 --- a/src/Python/registration/registration.cpp +++ b/src/Python/registration/registration.cpp @@ -78,8 +78,13 @@ class PyCorrespondenceChecker : public CorrespondenceCheckerBase { }; void pybind_registration_classes(py::module &m) { + // ope3dn.registration.ICPConvergenceCriteria py::class_ convergence_criteria( - m, "ICPConvergenceCriteria", "ICPConvergenceCriteria"); + m, "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::detail::bind_copy_functions( convergence_criteria); convergence_criteria @@ -91,13 +96,18 @@ void pybind_registration_classes(py::module &m) { "max_iteration"_a = 30) .def_readwrite( "relative_fitness", - ®istration::ICPConvergenceCriteria::relative_fitness_) + ®istration::ICPConvergenceCriteria::relative_fitness_, + "If relative change (difference) of fitness score is lower " + "than ``relative_fitness``, the iteration stops.") .def_readwrite( "relative_rmse", - ®istration::ICPConvergenceCriteria::relative_rmse_) + ®istration::ICPConvergenceCriteria::relative_rmse_, + "If relative change (difference) of inliner RMSE score is " + "lower than ``relative_rmse``, the iteration stops.") .def_readwrite( "max_iteration", - ®istration::ICPConvergenceCriteria::max_iteration_) + ®istration::ICPConvergenceCriteria::max_iteration_, + "Maximum iteration before iteration stops.") .def("__repr__", [](const registration::ICPConvergenceCriteria &c) { return std::string( "registration::ICPConvergenceCriteria class " @@ -110,8 +120,16 @@ void pybind_registration_classes(py::module &m) { std::to_string(c.max_iteration_)); }); + // ope3dn.registration.RANSACConvergenceCriteria py::class_ ransac_criteria( - m, "RANSACConvergenceCriteria", "RANSACConvergenceCriteria"); + m, "RANSACConvergenceCriteria", + "Class that defines the convergence criteria of RANSAC. RANSAC " + "algorithm stops if the iteration number hits ``max_iteration``, " + "or the validation has been run for ``max_validation`` times. Note " + "that the validation is the most computational expensive operator " + "in an iteration. Most iterations do not do full validation. It is " + "crucial to control ``max_validation`` so that the computation " + "time is acceptable."); py::detail::bind_copy_functions( ransac_criteria); ransac_criteria @@ -122,10 +140,13 @@ void pybind_registration_classes(py::module &m) { "max_iteration"_a = 1000, "max_validation"_a = 1000) .def_readwrite( "max_iteration", - ®istration::RANSACConvergenceCriteria::max_iteration_) + ®istration::RANSACConvergenceCriteria::max_iteration_, + "Maximum iteration before iteration stops.") .def_readwrite( "max_validation", - ®istration::RANSACConvergenceCriteria::max_validation_) + ®istration::RANSACConvergenceCriteria::max_validation_, + "Maximum times the validation has been run before the " + "iteration stops.") .def("__repr__", [](const registration::RANSACConvergenceCriteria &c) { return std::string( @@ -137,21 +158,45 @@ void pybind_registration_classes(py::module &m) { std::to_string(c.max_validation_)); }); + // ope3dn.registration.TransformationEstimation py::class_< registration::TransformationEstimation, PyTransformationEstimation> - te(m, "TransformationEstimation", "TransformationEstimation"); - te.def("compute_rmse", ®istration::TransformationEstimation::ComputeRMSE) - .def("compute_transformation", - ®istration::TransformationEstimation:: - ComputeTransformation); + te(m, "TransformationEstimation", + "Base class that estimates a transformation between two point " + "clouds. The virtual function ComputeTransformation() must be " + "implemented in subclasses."); + te.def("compute_rmse", ®istration::TransformationEstimation::ComputeRMSE, + "source"_a, "target"_a, "corres"_a, + "Compute RMSE between source and target points cloud given " + "correspondences."); + te.def("compute_transformation", + ®istration::TransformationEstimation::ComputeTransformation, + "source"_a, "target"_a, "corres"_a, + "Compute transformation from source to target point cloud given " + "correspondences."); + docstring::ClassMethodDocInject( + m, "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", + {{"source", "Source point cloud."}, + {"target", "Target point cloud."}, + {"corres", + "Correspondence set between source and target point cloud."}}); + // ope3dn.registration.TransformationEstimationPointToPoint: + // TransformationEstimation py::class_, registration::TransformationEstimation> te_p2p(m, "TransformationEstimationPointToPoint", - "TransformationEstimationPointToPoint"); + "Class to estimate a transformation for point to point " + "distance."); py::detail::bind_copy_functions< registration::TransformationEstimationPointToPoint>(te_p2p); te_p2p.def(py::init([](bool with_scaling) { @@ -169,16 +214,29 @@ void pybind_registration_classes(py::module &m) { ? std::string("with scaling.") : std::string("without scaling.")); }) - .def_readwrite("with_scaling", - ®istration::TransformationEstimationPointToPoint:: - with_scaling_); + .def_readwrite( + "with_scaling", + ®istration::TransformationEstimationPointToPoint:: + with_scaling_, + R"(Set to ``True`` to estimate scaling, ``False`` to force +scaling to be ``1``. + +The homogeneous transformation is given by + +:math:`T = \begin{bmatrix} c\mathbf{R} & \mathbf{t} \\ \mathbf{0} & 1 \end{bmatrix}` +Sets :math:`c = 1` if ``with_scaling`` is ``False``. +)"); + + // ope3dn.registration.TransformationEstimationPointToPlane: + // TransformationEstimation py::class_, registration::TransformationEstimation> te_p2l(m, "TransformationEstimationPointToPlane", - "TransformationEstimationPointToPlane"); + "Class to estimate a transformation for point to plane " + "distance."); py::detail::bind_default_constructor< registration::TransformationEstimationPointToPlane>(te_p2l); py::detail::bind_copy_functions< @@ -189,17 +247,41 @@ void pybind_registration_classes(py::module &m) { return std::string("TransformationEstimationPointToPlane"); }); + // ope3dn.registration.CorrespondenceChecker py::class_> - cc(m, "CorrespondenceChecker", "CorrespondenceChecker"); - cc.def("Check", ®istration::CorrespondenceChecker::Check); + 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."); + cc.def("Check", ®istration::CorrespondenceChecker::Check, "source"_a, + "target"_a, "corres"_a, "transformation"_a, + "Function to check if two points can be aligned. The two input " + "point clouds must have exact the same number of points."); + docstring::ClassMethodDocInject( + m, "CorrespondenceChecker", "Check", + {{"source", "Source point cloud."}, + {"target", "Target point cloud."}, + {"corres", + "Correspondence set between source and target point cloud."}, + {"transformation", "The estimated transformation (inplace)."}}); + // ope3dn.registration.CorrespondenceCheckerBasedOnEdgeLength: + // CorrespondenceChecker py::class_, registration::CorrespondenceChecker> cc_el(m, "CorrespondenceCheckerBasedOnEdgeLength", - "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 withinin 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::detail::bind_copy_functions< registration::CorrespondenceCheckerBasedOnEdgeLength>(cc_el); cc_el.def(py::init([](double similarity_threshold) { @@ -220,14 +302,25 @@ void pybind_registration_classes(py::module &m) { .def_readwrite( "similarity_threshold", ®istration::CorrespondenceCheckerBasedOnEdgeLength:: - similarity_threshold_); + similarity_threshold_, + R"(float value between 0 (loose) and 1 (strict): For the +check to be true, + +:math:`||\text{edge}_{\text{source}}|| > \text{similarity_threshold} \times ||\text{edge}_{\text{target}}||` and + +:math:`||\text{edge}_{\text{target}}|| > \text{similarity_threshold} \times ||\text{edge}_{\text{source}}||` +must hold true for all edges.)"); + + // ope3dn.registration.CorrespondenceCheckerBasedOnDistance: + // CorrespondenceChecker py::class_, registration::CorrespondenceChecker> cc_d(m, "CorrespondenceCheckerBasedOnDistance", - "CorrespondenceCheckerBasedOnDistance"); + "Class to check if aligned point clouds are close (less than " + "specified threshold)."); py::detail::bind_copy_functions< registration::CorrespondenceCheckerBasedOnDistance>(cc_d); cc_d.def(py::init([](double distance_threshold) { @@ -246,14 +339,20 @@ void pybind_registration_classes(py::module &m) { }) .def_readwrite("distance_threshold", ®istration::CorrespondenceCheckerBasedOnDistance:: - distance_threshold_); + distance_threshold_, + "Distance threashold for the check."); + // ope3dn.registration.CorrespondenceCheckerBasedOnNormal: + // CorrespondenceChecker py::class_, registration::CorrespondenceChecker> cc_n(m, "CorrespondenceCheckerBasedOnNormal", - "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::detail::bind_copy_functions< registration::CorrespondenceCheckerBasedOnNormal>(cc_n); cc_n.def(py::init([](double normal_angle_threshold) { @@ -271,10 +370,13 @@ void pybind_registration_classes(py::module &m) { }) .def_readwrite("normal_angle_threshold", ®istration::CorrespondenceCheckerBasedOnNormal:: - normal_angle_threshold_); + normal_angle_threshold_, + "Radian value for angle threshold."); + // ope3dn.registration.FastGlobalRegistrationOption: py::class_ fgr_option( - m, "FastGlobalRegistrationOption", "FastGlobalRegistrationOption"); + m, "FastGlobalRegistrationOption", + "Options for FastGlobalRegistration."); py::detail::bind_copy_functions( fgr_option); fgr_option @@ -293,27 +395,39 @@ void pybind_registration_classes(py::module &m) { "maximum_correspondence_distance"_a = 0.025, "iteration_number"_a = 64, "tuple_scale"_a = 0.95, "maximum_tuple_count"_a = 1000) - .def_readwrite("division_factor", - ®istration::FastGlobalRegistrationOption:: - division_factor_) - .def_readwrite("use_absolute_scale", - ®istration::FastGlobalRegistrationOption:: - use_absolute_scale_) + .def_readwrite( + "division_factor", + ®istration::FastGlobalRegistrationOption:: + division_factor_, + "float: Division factor used for graduated non-convexity.") + .def_readwrite( + "use_absolute_scale", + ®istration::FastGlobalRegistrationOption:: + use_absolute_scale_, + "bool: Measure distance in absolute scale (1) or in scale " + "relative to the diameter of the model (0).") .def_readwrite( "decrease_mu", - ®istration::FastGlobalRegistrationOption::decrease_mu_) + ®istration::FastGlobalRegistrationOption::decrease_mu_, + "bool: Set to ``True`` to decrease scale mu by " + "``division_factor`` for graduated non-convexity.") .def_readwrite("maximum_correspondence_distance", ®istration::FastGlobalRegistrationOption:: - maximum_correspondence_distance_) + maximum_correspondence_distance_, + "float: Maximum correspondence distance.") .def_readwrite("iteration_number", ®istration::FastGlobalRegistrationOption:: - iteration_number_) + iteration_number_, + "int: Maximum number of iterations.") .def_readwrite( "tuple_scale", - ®istration::FastGlobalRegistrationOption::tuple_scale_) + ®istration::FastGlobalRegistrationOption::tuple_scale_, + "float: Similarity measure used for tuples of feature " + "points.") .def_readwrite("maximum_tuple_count", ®istration::FastGlobalRegistrationOption:: - maximum_tuple_count_) + maximum_tuple_count_, + "float: Maximum tuple numbers.") .def("__repr__", [](const registration::FastGlobalRegistrationOption &c) { return std::string( @@ -337,22 +451,32 @@ void pybind_registration_classes(py::module &m) { std::to_string(c.maximum_tuple_count_); }); + // ope3dn.registration.RegistrationResult py::class_ registration_result( - m, "RegistrationResult", "RegistrationResult"); + m, "RegistrationResult", + "Class that contains the registration results."); py::detail::bind_default_constructor( registration_result); py::detail::bind_copy_functions( registration_result); registration_result .def_readwrite("transformation", - ®istration::RegistrationResult::transformation_) + ®istration::RegistrationResult::transformation_, + "``4 x 4`` float64 numpy array: The estimated " + "transformation matrix.") .def_readwrite( "correspondence_set", - ®istration::RegistrationResult::correspondence_set_) + ®istration::RegistrationResult::correspondence_set_, + "``n x 2`` int numpy array: Correspondence set between " + "source and target point cloud.") .def_readwrite("inlier_rmse", - ®istration::RegistrationResult::inlier_rmse_) - .def_readwrite("fitness", - ®istration::RegistrationResult::fitness_) + ®istration::RegistrationResult::inlier_rmse_, + "float: RMSE of all inlier correspondences. Lower " + "is better.") + .def_readwrite( + "fitness", ®istration::RegistrationResult::fitness_, + "float: The overlapping area (# of inlier correspondences " + "/ # of points in target). Higher is better.") .def("__repr__", [](const registration::RegistrationResult &rr) { return std::string( "registration::RegistrationResult with fitness " diff --git a/src/Python/utility/console.cpp b/src/Python/utility/console.cpp index dc0040ddd6f..2a9e5698614 100644 --- a/src/Python/utility/console.cpp +++ b/src/Python/utility/console.cpp @@ -32,14 +32,20 @@ using namespace open3d; void pybind_console(py::module &m) { - py::enum_(m, "VerbosityLevel", py::arithmetic(), - "VerbosityLevel") - .value("Error", utility::VerbosityLevel::VerboseError) + py::enum_ vl(m, "VerbosityLevel", py::arithmetic(), + "VerbosityLevel"); + vl.value("Error", utility::VerbosityLevel::VerboseError) .value("Warning", utility::VerbosityLevel::VerboseWarning) .value("Info", utility::VerbosityLevel::VerboseInfo) .value("Debug", utility::VerbosityLevel::VerboseDebug) .value("Always", utility::VerbosityLevel::VerboseAlways) .export_values(); + // Trick to write docs without listing the members in the enum class again. + vl.attr("__doc__") = docstring::static_property( + py::cpp_function([](py::handle arg) -> std::string { + return "Enum class for VerbosityLevel."; + }), + py::none(), py::none(), ""); m.def("set_verbosity_level", &utility::SetVerbosityLevel, "Set global verbosity level of Open3D", py::arg("verbosity_level")); diff --git a/src/Python/utility/eigen.cpp b/src/Python/utility/eigen.cpp index 1d97d3af85b..de7594f8b6c 100644 --- a/src/Python/utility/eigen.cpp +++ b/src/Python/utility/eigen.cpp @@ -156,7 +156,7 @@ void pybind_eigen_vector_of_vector(py::module &m, // Bare bones interface // We choose to disable them because they do not support slice indices - // such as [:,:]. It is recommanded to convert it to numpy.asarray() + // such as [:,:]. It is recommended to convert it to numpy.asarray() // to access raw data. // v.def("__getitem__", [](const std::vector &v, // std::pair i) { diff --git a/src/Python/visualization/renderoption.cpp b/src/Python/visualization/renderoption.cpp index 81da6697be4..da71570912c 100644 --- a/src/Python/visualization/renderoption.cpp +++ b/src/Python/visualization/renderoption.cpp @@ -33,6 +33,7 @@ using namespace open3d; void pybind_renderoption(py::module &m) { + // open3d.visualization.RenderOption py::class_> renderoption(m, "RenderOption"); @@ -49,37 +50,65 @@ void pybind_renderoption(py::module &m) { io::ReadIJsonConvertible(filename, ro); }, "Function to load visualization::RenderOption from a JSON " - "file", + "file.", "filename"_a) .def("save_to_json", [](visualization::RenderOption &ro, const std::string &filename) { io::WriteIJsonConvertible(filename, ro); }, - "Function to save visualization::RenderOption to a JSON file", + "Function to save visualization::RenderOption to a JSON file.", "filename"_a) - .def_readwrite("background_color", - &visualization::RenderOption::background_color_) - .def_readwrite("light_on", &visualization::RenderOption::light_on_) + .def_readwrite( + "background_color", + &visualization::RenderOption::background_color_, + "float numpy array of size ``(3,)``: Background RGB color.") + .def_readwrite("light_on", &visualization::RenderOption::light_on_, + "bool: Whether to turn on Phong lighting.") .def_readwrite("point_size", - &visualization::RenderOption::point_size_) + &visualization::RenderOption::point_size_, + "float: Point size for ``PointCloud``.") .def_readwrite("line_width", - &visualization::RenderOption::line_width_) + &visualization::RenderOption::line_width_, + "float: Line width for ``LineSet``.") .def_readwrite("point_show_normal", - &visualization::RenderOption::point_show_normal_) + &visualization::RenderOption::point_show_normal_, + "bool: Whether to show normal for ``PointCloud``.") .def_readwrite("show_coordinate_frame", - &visualization::RenderOption::show_coordinate_frame_) - .def_readwrite("mesh_show_back_face", - &visualization::RenderOption::mesh_show_back_face_) + &visualization::RenderOption::show_coordinate_frame_, + "bool: Whether to show coordinate frame.") + .def_readwrite( + "mesh_show_back_face", + &visualization::RenderOption::mesh_show_back_face_, + "bool: Whether to show back faces for ``TriangleMesh``.") .def_readwrite("point_color_option", - &visualization::RenderOption::point_color_option_) + &visualization::RenderOption::point_color_option_, + "``PointColorOption``: Point color option for " + "``PointCloud``.") .def_readwrite("mesh_shade_option", - &visualization::RenderOption::mesh_shade_option_) - .def_readwrite("mesh_color_option", - &visualization::RenderOption::mesh_color_option_); + &visualization::RenderOption::mesh_shade_option_, + "``MeshShadeOption``: Mesh shading option for " + "``TriangleMesh``.") + .def_readwrite( + "mesh_color_option", + &visualization::RenderOption::mesh_color_option_, + "``MeshColorOption``: Color option for ``TriangleMesh``."); + docstring::ClassMethodDocInject(m, "RenderOption", "load_from_json", + {{"filename", "Path to file."}}); + docstring::ClassMethodDocInject(m, "RenderOption", "save_to_json", + {{"filename", "Path to file."}}); - py::enum_( - m, "PointColorOption", py::arithmetic(), "PointColorOption") + // 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", visualization::RenderOption::PointColorOption::Default) .value("Color", @@ -93,15 +122,35 @@ void pybind_renderoption(py::module &m) { .value("Normal", visualization::RenderOption::PointColorOption::Normal) .export_values(); - py::enum_( - m, "MeshShadeOption", py::arithmetic(), "MeshShadeOption") + + // 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 msh shading for ``TriangleMesh``."; + }), + py::none(), py::none(), ""); + enum_mesh_shade_option .value("Default", visualization::RenderOption::MeshShadeOption::FlatShade) .value("Color", visualization::RenderOption::MeshShadeOption::SmoothShade) .export_values(); - py::enum_( - m, "MeshColorOption", py::arithmetic(), "MeshColorOption") + + // 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", visualization::RenderOption::MeshColorOption::Default) .value("Color", visualization::RenderOption::MeshColorOption::Color) diff --git a/src/Python/visualization/utility.cpp b/src/Python/visualization/utility.cpp index 054ab393333..d169533eac2 100644 --- a/src/Python/visualization/utility.cpp +++ b/src/Python/visualization/utility.cpp @@ -38,7 +38,8 @@ using namespace open3d; void pybind_visualization_utility(py::module &m) { py::class_ selection_volume( - m, "SelectionPolygonVolume"); + m, "SelectionPolygonVolume", + "Select a polygon volume for cropping."); py::detail::bind_default_constructor( selection_volume); py::detail::bind_copy_functions( @@ -49,13 +50,13 @@ void pybind_visualization_utility(py::module &m) { const geometry::PointCloud &input) { return s.CropPointCloud(input); }, - "input"_a) + "input"_a, "Function to crop point cloud.") .def("crop_triangle_mesh", [](const visualization::SelectionPolygonVolume &s, const geometry::TriangleMesh &input) { return s.CropTriangleMesh(input); }, - "input"_a) + "input"_a, "Function to crop crop triangle mesh.") .def("__repr__", [](const visualization::SelectionPolygonVolume &s) { return std::string( @@ -66,14 +67,25 @@ void pybind_visualization_utility(py::module &m) { }) .def_readwrite( "orthogonal_axis", - &visualization::SelectionPolygonVolume::orthogonal_axis_) + &visualization::SelectionPolygonVolume::orthogonal_axis_, + "string: one of ``{x, y, z}``.") .def_readwrite( "bounding_polygon", - &visualization::SelectionPolygonVolume::bounding_polygon_) + &visualization::SelectionPolygonVolume::bounding_polygon_, + "``(n, 3)`` float64 numpy array: Bounding polygon " + "boundary.") .def_readwrite("axis_min", - &visualization::SelectionPolygonVolume::axis_min_) + &visualization::SelectionPolygonVolume::axis_min_, + "float: Minimum axis value.") .def_readwrite("axis_max", - &visualization::SelectionPolygonVolume::axis_max_); + &visualization::SelectionPolygonVolume::axis_max_, + "float: Maximum axis value."); + docstring::ClassMethodDocInject(m, "SelectionPolygonVolume", + "crop_point_cloud", + {{"input", "The input point cloud."}}); + docstring::ClassMethodDocInject(m, "SelectionPolygonVolume", + "crop_triangle_mesh", + {{"input", "The input triangle mesh."}}); } // Visualization util functions have similar arguments, sharing arg docstrings diff --git a/src/Python/visualization/viewcontrol.cpp b/src/Python/visualization/viewcontrol.cpp index a6ffe85ddbb..5d8831d80a2 100644 --- a/src/Python/visualization/viewcontrol.cpp +++ b/src/Python/visualization/viewcontrol.cpp @@ -32,10 +32,22 @@ #include using namespace open3d; +// Functions have similar arguments, thus the arg docstrings may be shared +static const std::unordered_map + map_view_control_docstrings = { + {"parameter", "The pinhole camera parameter to convert from."}, + {"scale", "Scale ratio."}, + {"x", "Distance the mouse cursor has moved in x-axis."}, + {"y", "Distance the mouse cursor has moved in y-axis."}, + {"xo", "Original point coordinate of the mouse in x-axis."}, + {"yo", "Original point coordinate of the mouse in y-axis."}, + {"step", "The step to change field of view."}, +}; + void pybind_viewcontrol(py::module &m) { py::class_, std::shared_ptr> - viewcontrol(m, "ViewControl"); + viewcontrol(m, "ViewControl", "View controller for visualizer."); py::detail::bind_default_constructor( viewcontrol); viewcontrol @@ -69,6 +81,22 @@ void pybind_viewcontrol(py::module &m) { .def("change_field_of_view", &visualization::ViewControl::ChangeFieldOfView, "Function to change field of view", "step"_a = 0.45); + docstring::ClassMethodDocInject(m, "ViewControl", "change_field_of_view", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", + "convert_from_pinhole_camera_parameters", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", + "convert_to_pinhole_camera_parameters", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", "get_field_of_view", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", "rotate", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", "scale", + map_view_control_docstrings); + docstring::ClassMethodDocInject(m, "ViewControl", "translate", + map_view_control_docstrings); } void pybind_viewcontrol_method(py::module &m) {} diff --git a/src/Python/visualization/visualizer.cpp b/src/Python/visualization/visualizer.cpp index 9cd5a62e010..898d5c1bebd 100644 --- a/src/Python/visualization/visualizer.cpp +++ b/src/Python/visualization/visualizer.cpp @@ -35,6 +35,23 @@ using namespace open3d; +// Functions have similar arguments, thus the arg docstrings may be shared +static const std::unordered_map + map_visualizer_docstrings = { + {"callback_func", "The call back function."}, + {"depth_scale", + "Scale depth value when capturing the depth image."}, + {"do_render", "Set to ``True`` to do render."}, + {"filename", "Path to file."}, + {"geometry", "The ``Geometry`` object."}, + {"height", "Height of window."}, + {"left", "Left margin of the window to the screen."}, + {"top", "Top margin of the window to the screen."}, + {"visible", "Whether the window is visible."}, + {"width", "Width of the window."}, + {"window_name", "Window title name."}, +}; + void pybind_visualizer(py::module &m) { py::class_, std::shared_ptr> @@ -43,8 +60,7 @@ void pybind_visualizer(py::module &m) { visualizer .def("__repr__", [](const visualization::Visualizer &vis) { - return std::string( - "visualization::Visualizer with name ") + + return std::string("Visualizer with name ") + vis.GetWindowName(); }) .def("create_window", @@ -77,13 +93,11 @@ void pybind_visualizer(py::module &m) { "corresponding shaders", "geometry"_a) .def("get_view_control", &visualization::Visualizer::GetViewControl, - "Function to retrieve the associated " - "visualization::ViewControl", + "Function to retrieve the associated ``ViewControl``", py::return_value_policy::reference_internal) .def("get_render_option", &visualization::Visualizer::GetRenderOption, - "Function to retrieve the associated " - "visualization::RenderOption", + "Function to retrieve the associated ``RenderOption``", py::return_value_policy::reference_internal) .def("capture_screen_float_buffer", &visualization::Visualizer::CaptureScreenFloatBuffer, @@ -114,8 +128,7 @@ void pybind_visualizer(py::module &m) { .def("__repr__", [](const visualization::VisualizerWithKeyCallback &vis) { return std::string( - "visualization::VisualizerWithKeyCallback " - "with name ") + + "VisualizerWithKeyCallback with name ") + vis.GetWindowName(); }) .def("register_key_callback", @@ -134,14 +147,49 @@ void pybind_visualizer(py::module &m) { visualizer_edit.def(py::init()) .def("__repr__", [](const visualization::VisualizerWithEditing &vis) { - return std::string( - "visualization::VisualizerWithEditing with " - "name ") + + return std::string("VisualizerWithEditing with name ") + vis.GetWindowName(); }) .def("get_picked_points", &visualization::VisualizerWithEditing::GetPickedPoints, "Function to get picked points"); + docstring::ClassMethodDocInject(m, "Visualizer", "add_geometry", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", + "capture_depth_float_buffer", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "capture_depth_image", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", + "capture_screen_float_buffer", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "capture_screen_image", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "close", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "create_window", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "destroy_window", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "get_render_option", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "get_view_control", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "get_window_name", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "poll_events", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", + "register_animation_callback", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "reset_view_point", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "run", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "update_geometry", + map_visualizer_docstrings); + docstring::ClassMethodDocInject(m, "Visualizer", "update_renderer", + map_visualizer_docstrings); } void pybind_visualizer_method(py::module &m) {}