From 3d0cf908737ef0dd678b6d0a937b1a32050ebd88 Mon Sep 17 00:00:00 2001
From: Sameer Sheorey <sameer.sheorey@intel.com>
Date: Mon, 18 Nov 2024 23:54:22 -0800
Subject: [PATCH] Enable auto conversion from Python list / tuple by using
 deque.

Add __repr__
---
 cpp/open3d/t/geometry/Geometry.h            |  7 +++++
 cpp/pybind/t/geometry/geometry.cpp          | 34 ++++++++++++++++-----
 cpp/pybind/utility/eigen.cpp                |  1 -
 python/test/t/geometry/test_pointcloud.py   |  4 +--
 python/test/t/geometry/test_trianglemesh.py |  5 +--
 5 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/cpp/open3d/t/geometry/Geometry.h b/cpp/open3d/t/geometry/Geometry.h
index 823f670ffcb..83e566ea2fb 100644
--- a/cpp/open3d/t/geometry/Geometry.h
+++ b/cpp/open3d/t/geometry/Geometry.h
@@ -7,6 +7,8 @@
 
 #pragma once
 
+#include <fmt/format.h>
+
 #include <string>
 
 #include "open3d/core/Device.h"
@@ -106,6 +108,11 @@ struct MetricParameters {
     /// distance computation. This specifies the number of points sampled. No
     /// sampling is done for point clouds.
     size_t n_sampled_points = 1000;
+    std::string ToString() const {
+        return fmt::format(
+                "MetricParameters: fscore_radius={}, n_sampled_points={}",
+                fscore_radius, n_sampled_points);
+    }
 };
 
 }  // namespace geometry
diff --git a/cpp/pybind/t/geometry/geometry.cpp b/cpp/pybind/t/geometry/geometry.cpp
index 19140568401..f6c57124c84 100644
--- a/cpp/pybind/t/geometry/geometry.cpp
+++ b/cpp/pybind/t/geometry/geometry.cpp
@@ -7,8 +7,10 @@
 
 #include "open3d/t/geometry/Geometry.h"
 
+#include <pybind11/stl.h>
 #include <pybind11/stl_bind.h>
 
+#include <deque>
 #include <vector>
 
 #include "pybind/docstring.h"
@@ -56,7 +58,6 @@ void pybind_geometry_declarations(py::module& m) {
                    "Hausdorff Distance")
             .value("FScore", Metric::FScore, "F-Score")
             .export_values();
-    py::bind_vector<std::vector<Metric>>(m_geometry, "VectorMetric");
     py::class_<MetricParameters> metric_parameters(
             m_geometry, "MetricParameters",
             "Holder for various parameters required by metrics.");
@@ -75,23 +76,40 @@ void pybind_geometry_declarations(py::module& m) {
 
 void pybind_geometry_definitions(py::module& m) {
     auto m_geometry = static_cast<py::module>(m.attr("geometry"));
+    py::bind_vector<std::vector<Metric>>(m_geometry, "VectorMetric");
 
     auto metric_parameters = static_cast<py::class_<MetricParameters>>(
             m_geometry.attr("MetricParameters"));
+    // Use std::deque instead of std::vector to enable automatic Python list /
+    // tuple conversion. FIXME: Ideally this should work for std::vector.
     metric_parameters
-            .def(py::init<const std::vector<float>&, size_t>(),
-                 "fscore_radius"_a = std::vector<float>{0.01},
+            .def(py::init([](const std::deque<float>& fsr, size_t nsp) {
+                     std::vector<float> fsrvec{fsr.begin(), fsr.end()};
+                     return MetricParameters{fsrvec, nsp};
+                 }),
+                 "fscore_radius"_a = std::deque<float>{0.01f},
                  "n_sampled_points"_a = 1000)
-            .def_readwrite("fscore_radius", &MetricParameters::fscore_radius,
-                           "Radius for computing the F-Score. A match between "
-                           "a point and its nearest neighbor is sucessful if "
-                           "it is within this radius.")
+            .def_property(
+                    "fscore_radius",
+                    [](const MetricParameters& self) {  // getter
+                        return std::deque<float>(self.fscore_radius.begin(),
+                                                 self.fscore_radius.end());
+                    },
+                    [](MetricParameters& self,
+                       const std::deque<float>& fsr) {  // setter
+                        self.fscore_radius =
+                                std::vector<float>(fsr.begin(), fsr.end());
+                    },
+                    "Radius for computing the F-Score. A match between "
+                    "a point and its nearest neighbor is sucessful if "
+                    "it is within this radius.")
             .def_readwrite("n_sampled_points",
                            &MetricParameters::n_sampled_points,
                            "Points are sampled uniformly from the surface of "
                            "triangle meshes before distance computation. This "
                            "specifies the number of points sampled. No "
-                           "sampling is done for point clouds.");
+                           "sampling is done for point clouds.")
+            .def("__repr__", &MetricParameters::ToString);
 
     pybind_geometry_class_definitions(m_geometry);
     pybind_drawable_geometry_class_definitions(m_geometry);
diff --git a/cpp/pybind/utility/eigen.cpp b/cpp/pybind/utility/eigen.cpp
index 232433642a6..1eb621772eb 100644
--- a/cpp/pybind/utility/eigen.cpp
+++ b/cpp/pybind/utility/eigen.cpp
@@ -308,7 +308,6 @@ namespace utility {
 
 void pybind_eigen_declarations(py::module &m) {
     auto intvector = pybind_eigen_vector_of_scalar<int>(m, "IntVector");
-    auto floatvector = pybind_eigen_vector_of_scalar<float>(m, "FloatVector");
     auto doublevector =
             pybind_eigen_vector_of_scalar<double>(m, "DoubleVector");
     auto vector3dvector = pybind_eigen_vector_of_vector<Eigen::Vector3d>(
diff --git a/python/test/t/geometry/test_pointcloud.py b/python/test/t/geometry/test_pointcloud.py
index ac02aa860da..dc98b1db951 100644
--- a/python/test/t/geometry/test_pointcloud.py
+++ b/python/test/t/geometry/test_pointcloud.py
@@ -205,13 +205,11 @@ def test_metrics():
 
     # (1, 3, 3, 1) vertices are shifted by (0, 0.1, 0.1*sqrt(2), 0.1*sqrt(3))
     # respectively
-    metric_params = MetricParameters(
-        fscore_radius=o3d.utility.FloatVector((0.01, 0.11, 0.15, 0.18)))
+    metric_params = MetricParameters(fscore_radius=(0.01, 0.11, 0.15, 0.18))
     metrics = pcd1.compute_metrics(
         pcd2, (Metric.ChamferDistance, Metric.HausdorffDistance, Metric.FScore),
         metric_params)
 
-    print(metrics)
     np.testing.assert_allclose(
         metrics.cpu().numpy(),
         (0.22436734, np.sqrt(3) / 10, 100. / 8, 400. / 8, 700. / 8, 100.),
diff --git a/python/test/t/geometry/test_trianglemesh.py b/python/test/t/geometry/test_trianglemesh.py
index 91b130dfeae..88678e3f877 100644
--- a/python/test/t/geometry/test_trianglemesh.py
+++ b/python/test/t/geometry/test_trianglemesh.py
@@ -1404,14 +1404,11 @@ def test_metrics():
 
     # 3 faces of the cube are the same, and 3 are shifted up by 0.1 - raycast
     # distances should follow this.
-    metric_params = MetricParameters(fscore_radius=o3d.utility.FloatVector(
-        (0.05, 0.15)),
+    metric_params = MetricParameters(fscore_radius=(0.05, 0.15),
                                      n_sampled_points=100000)
-    # n_sampled_points=100000)
     metrics = box1.compute_metrics(
         box2, (Metric.ChamferDistance, Metric.HausdorffDistance, Metric.FScore),
         metric_params)
 
-    print(metrics)
     np.testing.assert_allclose(metrics.cpu().numpy(), (0.1, 0.17, 50, 100),
                                rtol=0.05)