From cac37c76c32336348feae700cad9b1bc4c981019 Mon Sep 17 00:00:00 2001 From: Luis Alonso Murillo Rojas Date: Mon, 18 Mar 2024 14:12:01 -0600 Subject: [PATCH 01/22] Upgrade embree to 4.3.1 (#6665) * Replace reference of rtcIntersect1M to rtcIntersect1 * Change RTCRayHit mask default value and enable filter callbacks --- 3rdparty/embree/embree.cmake | 8 +-- cpp/open3d/t/geometry/RaycastingScene.cpp | 66 +++++++++++++---------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/3rdparty/embree/embree.cmake b/3rdparty/embree/embree.cmake index 4e93eadf19e..e0794b55da0 100644 --- a/3rdparty/embree/embree.cmake +++ b/3rdparty/embree/embree.cmake @@ -69,8 +69,8 @@ endif() ExternalProject_Add( ext_embree PREFIX embree - URL https://github.com/embree/embree/archive/refs/tags/v3.13.3.tar.gz - URL_HASH SHA256=74ec785afb8f14d28ea5e0773544572c8df2e899caccdfc88509f1bfff58716f + URL https://github.com/embree/embree/archive/refs/tags/v4.3.1.tar.gz + URL_HASH SHA256=824edcbb7a8cd393c5bdb7a16738487b21ecc4e1d004ac9f761e934f97bb02a4 DOWNLOAD_DIR "${OPEN3D_THIRD_PARTY_DOWNLOAD_DIR}/embree" UPDATE_COMMAND "" CMAKE_ARGS @@ -88,7 +88,7 @@ ExternalProject_Add( -DEMBREE_TASKING_SYSTEM=INTERNAL ${WIN_CMAKE_ARGS} BUILD_BYPRODUCTS - /${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}embree3${CMAKE_STATIC_LIBRARY_SUFFIX} + /${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}embree4${CMAKE_STATIC_LIBRARY_SUFFIX} /${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}simd${CMAKE_STATIC_LIBRARY_SUFFIX} /${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}lexers${CMAKE_STATIC_LIBRARY_SUFFIX} /${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}sys${CMAKE_STATIC_LIBRARY_SUFFIX} @@ -100,4 +100,4 @@ ExternalProject_Add( ExternalProject_Get_Property(ext_embree INSTALL_DIR) set(EMBREE_INCLUDE_DIRS ${INSTALL_DIR}/include/ ${INSTALL_DIR}/src/ext_embree/) # "/" is critical. set(EMBREE_LIB_DIR ${INSTALL_DIR}/${Open3D_INSTALL_LIB_DIR}) -set(EMBREE_LIBRARIES embree3 ${ISA_LIBS} simd lexers sys math tasking) +set(EMBREE_LIBRARIES embree4 ${ISA_LIBS} simd lexers sys math tasking) diff --git a/cpp/open3d/t/geometry/RaycastingScene.cpp b/cpp/open3d/t/geometry/RaycastingScene.cpp index af7dd3f1480..14f9962c26c 100644 --- a/cpp/open3d/t/geometry/RaycastingScene.cpp +++ b/cpp/open3d/t/geometry/RaycastingScene.cpp @@ -12,7 +12,7 @@ #include "open3d/t/geometry/RaycastingScene.h" // This header is in the embree src dir (embree/src/ext_embree/..). -#include +#include #include #include @@ -61,7 +61,7 @@ void AssertTensorDtypeLastDimDeviceMinNDim(const open3d::core::Tensor& tensor, } struct CountIntersectionsContext { - RTCIntersectContext context; + RTCRayQueryContext context; std::vector>* previous_geom_prim_ID_tfar; int* intersections; @@ -111,7 +111,7 @@ void CountIntersectionsFunc(const RTCFilterFunctionNArguments* args) { } struct ListIntersectionsContext { - RTCIntersectContext context; + RTCRayQueryContext context; std::vector>* previous_geom_prim_ID_tfar; unsigned int* ray_ids; @@ -360,9 +360,6 @@ struct RaycastingScene::Impl { const int nthreads) { CommitScene(); - struct RTCIntersectContext context; - rtcInitIntersectContext(&context); - auto LoopFn = [&](const tbb::blocked_range& range) { std::vector rayhits(range.size()); @@ -387,15 +384,14 @@ struct RaycastingScene::Impl { } else { rh.ray.tfar = std::numeric_limits::infinity(); } - rh.ray.mask = 0; + rh.ray.mask = -1; rh.ray.id = i - range.begin(); rh.ray.flags = 0; rh.hit.geomID = RTC_INVALID_GEOMETRY_ID; rh.hit.instID[0] = RTC_INVALID_GEOMETRY_ID; - } - rtcIntersect1M(scene_, &context, &rayhits[0], range.size(), - sizeof(RTCRayHit)); + rtcIntersect1(scene_, &rh); + } for (size_t i = range.begin(); i < range.end(); ++i) { RTCRayHit rh = rayhits[i - range.begin()]; @@ -446,8 +442,12 @@ struct RaycastingScene::Impl { const int nthreads) { CommitScene(); - struct RTCIntersectContext context; - rtcInitIntersectContext(&context); + struct RTCRayQueryContext context; + rtcInitRayQueryContext(&context); + + RTCOccludedArguments args; + rtcInitOccludedArguments(&args); + args.context = &context; auto LoopFn = [&](const tbb::blocked_range& range) { std::vector rayvec(range.size()); @@ -462,13 +462,12 @@ struct RaycastingScene::Impl { ray.dir_z = r[5]; ray.tnear = tnear; ray.tfar = tfar; - ray.mask = 0; + ray.mask = -1; ray.id = i - range.begin(); ray.flags = 0; - } - rtcOccluded1M(scene_, &context, &rayvec[0], range.size(), - sizeof(RTCRay)); + rtcOccluded1(scene_, &ray, &args); + } for (size_t i = range.begin(); i < range.end(); ++i) { RTCRay ray = rayvec[i - range.begin()]; @@ -508,11 +507,15 @@ struct RaycastingScene::Impl { 0.f)); CountIntersectionsContext context; - rtcInitIntersectContext(&context.context); - context.context.filter = CountIntersectionsFunc; + rtcInitRayQueryContext(&context.context); context.previous_geom_prim_ID_tfar = &previous_geom_prim_ID_tfar; context.intersections = intersections; + RTCIntersectArguments args; + rtcInitIntersectArguments(&args); + args.filter = CountIntersectionsFunc; + args.context = &context.context; + auto LoopFn = [&](const tbb::blocked_range& range) { std::vector rayhits(range.size()); @@ -527,14 +530,14 @@ struct RaycastingScene::Impl { rh->ray.dir_z = r[5]; rh->ray.tnear = 0; rh->ray.tfar = std::numeric_limits::infinity(); - rh->ray.mask = 0; + rh->ray.mask = -1; rh->ray.flags = 0; rh->ray.id = i; rh->hit.geomID = RTC_INVALID_GEOMETRY_ID; rh->hit.instID[0] = RTC_INVALID_GEOMETRY_ID; + + rtcIntersect1(scene_, rh, &args); } - rtcIntersect1M(scene_, &context.context, &rayhits[0], range.size(), - sizeof(RTCRayHit)); }; if (nthreads > 0) { @@ -579,8 +582,7 @@ struct RaycastingScene::Impl { 0.f)); ListIntersectionsContext context; - rtcInitIntersectContext(&context.context); - context.context.filter = ListIntersectionsFunc; + rtcInitRayQueryContext(&context.context); context.previous_geom_prim_ID_tfar = &previous_geom_prim_ID_tfar; context.ray_ids = ray_ids; context.geometry_ids = geometry_ids; @@ -590,6 +592,11 @@ struct RaycastingScene::Impl { context.cumsum = cumsum; context.track_intersections = track_intersections; + RTCIntersectArguments args; + rtcInitIntersectArguments(&args); + args.filter = ListIntersectionsFunc; + args.context = &context.context; + auto LoopFn = [&](const tbb::blocked_range& range) { std::vector rayhits(range.size()); @@ -604,14 +611,14 @@ struct RaycastingScene::Impl { rh->ray.dir_z = r[5]; rh->ray.tnear = 0; rh->ray.tfar = std::numeric_limits::infinity(); - rh->ray.mask = 0; + rh->ray.mask = -1; rh->ray.flags = 0; rh->ray.id = i; rh->hit.geomID = RTC_INVALID_GEOMETRY_ID; rh->hit.instID[0] = RTC_INVALID_GEOMETRY_ID; + + rtcIntersect1(scene_, rh, &args); } - rtcIntersect1M(scene_, &context.context, &rayhits[0], range.size(), - sizeof(RTCRayHit)); }; if (nthreads > 0) { @@ -695,9 +702,9 @@ RaycastingScene::RaycastingScene(int64_t nthreads) impl_->scene_ = rtcNewScene(impl_->device_); // set flag for better accuracy - rtcSetSceneFlags( - impl_->scene_, - RTC_SCENE_FLAG_ROBUST | RTC_SCENE_FLAG_CONTEXT_FILTER_FUNCTION); + rtcSetSceneFlags(impl_->scene_, + RTC_SCENE_FLAG_ROBUST | + RTC_SCENE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS); impl_->devprop_join_commit = rtcGetDeviceProperty( impl_->device_, RTC_DEVICE_PROPERTY_JOIN_COMMIT_SUPPORTED); @@ -746,6 +753,7 @@ uint32_t RaycastingScene::AddTriangles(const core::Tensor& vertex_positions, memcpy(index_buffer, data.GetDataPtr(), sizeof(uint32_t) * 3 * num_triangles); } + rtcSetGeometryEnableFilterFunctionFromArguments(geom, true); rtcCommitGeometry(geom); uint32_t geom_id = rtcAttachGeometry(impl_->scene_, geom); From dc7e93ef6349cf68c5bf0906a75079764aadc578 Mon Sep 17 00:00:00 2001 From: ADAWG208 <156703223+ADAWG208@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:16:42 -0400 Subject: [PATCH 02/22] improved t.io.read_triangle_mesh documentation (#6675) Co-authored-by: Benjamin Ummenhofer From fa91f2e6bc65f20be3c8533398f7e6505a2dc2a8 Mon Sep 17 00:00:00 2001 From: bell-one Date: Tue, 19 Mar 2024 06:05:19 +0900 Subject: [PATCH 03/22] Bug fix in RGBD based reconstruction system example (#6660) * Update make_fragments.py: update 'write_point_cloud' variables * Update color_map_optimization_for_reconstruction_system.py: link 'initialize_config' function in color_map_optimization --- .../color_map_optimization_for_reconstruction_system.py | 1 + examples/python/reconstruction_system/make_fragments.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/python/reconstruction_system/color_map_optimization_for_reconstruction_system.py b/examples/python/reconstruction_system/color_map_optimization_for_reconstruction_system.py index 173f6f52c22..088ec51818a 100644 --- a/examples/python/reconstruction_system/color_map_optimization_for_reconstruction_system.py +++ b/examples/python/reconstruction_system/color_map_optimization_for_reconstruction_system.py @@ -14,6 +14,7 @@ pyexample_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(pyexample_path) from open3d_example import * +from initialize_config import initialize_config def parse_keys(filename): diff --git a/examples/python/reconstruction_system/make_fragments.py b/examples/python/reconstruction_system/make_fragments.py index dc4e59d0780..17d875aa86c 100644 --- a/examples/python/reconstruction_system/make_fragments.py +++ b/examples/python/reconstruction_system/make_fragments.py @@ -139,8 +139,11 @@ def make_pointcloud_for_fragment(path_dataset, color_files, depth_files, pcd.colors = mesh.vertex_colors pcd_name = join(path_dataset, config["template_fragment_pointcloud"] % fragment_id) - o3d.io.write_point_cloud(pcd_name, pcd, False, True) - + o3d.io.write_point_cloud(pcd_name, + pcd, + format='auto', + write_ascii=False, + compressed=True) def process_single_fragment(fragment_id, color_files, depth_files, n_files, n_fragments, config): From e8661f749cce8ce34626b4d5840dfebca12aa3aa Mon Sep 17 00:00:00 2001 From: nsaiapova Date: Mon, 18 Mar 2024 22:11:37 +0100 Subject: [PATCH 04/22] Implement t::geometry::TriangleMesh::RemoveUnreferencedVertices (#6640) The algorithm mimics the one in geometry::TriangleMesh::RemoveUnreferencedVertices. We first build a mask of vertices and then update all vertex attributes by that mask. Triangles are left untouched. --- cpp/open3d/t/geometry/TriangleMesh.cpp | 54 ++++++++ cpp/open3d/t/geometry/TriangleMesh.h | 4 + cpp/pybind/t/geometry/trianglemesh.cpp | 4 + cpp/tests/t/geometry/TriangleMesh.cpp | 130 ++++++++++++++++++++ python/test/t/geometry/test_trianglemesh.py | 49 ++++++++ 5 files changed, 241 insertions(+) diff --git a/cpp/open3d/t/geometry/TriangleMesh.cpp b/cpp/open3d/t/geometry/TriangleMesh.cpp index be5442b6428..7316901b473 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.cpp +++ b/cpp/open3d/t/geometry/TriangleMesh.cpp @@ -1254,6 +1254,60 @@ TriangleMesh TriangleMesh::SelectByIndex(const core::Tensor &indices) const { return result; } +TriangleMesh TriangleMesh::RemoveUnreferencedVertices() { + if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) { + utility::LogWarning( + "[RemoveUnreferencedVertices] TriangleMesh has no vertices."); + return *this; + } + GetVertexAttr().AssertSizeSynchronized(); + + core::Dtype tri_dtype = HasTriangleIndices() + ? GetTriangleIndices().GetDtype() + : core::Int64; + + int64_t num_verts_old = GetVertexPositions().GetLength(); + // int mask for vertices as we need to remap indices. + core::Tensor vertex_mask = core::Tensor::Zeros({num_verts_old}, tri_dtype); + + if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) { + utility::LogWarning( + "[RemoveUnreferencedVertices] TriangleMesh has no triangles. " + "Removing all vertices."); + // in this case we need to empty vertices and their attributes + } else { + GetTriangleAttr().AssertSizeSynchronized(); + core::Tensor tris_cpu = + GetTriangleIndices().To(core::Device()).Contiguous(); + DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() { + scalar_tris_t *tris_ptr = tris_cpu.GetDataPtr(); + scalar_tris_t *vertex_mask_ptr = + vertex_mask.GetDataPtr(); + for (int i = 0; i < tris_cpu.GetLength(); i++) { + vertex_mask_ptr[tris_ptr[3 * i]] = 1; + vertex_mask_ptr[tris_ptr[3 * i + 1]] = 1; + vertex_mask_ptr[tris_ptr[3 * i + 2]] = 1; + } + + UpdateTriangleIndicesByVertexMask(tris_cpu, + vertex_mask); + }); + } + + // send the vertex mask to original device and apply to + // vertices + vertex_mask = vertex_mask.To(GetDevice(), core::Bool); + for (auto item : GetVertexAttr()) { + SetVertexAttr(item.first, item.second.IndexGet({vertex_mask})); + } + + utility::LogDebug( + "[RemoveUnreferencedVertices] {:d} vertices have been removed.", + (int)(num_verts_old - GetVertexPositions().GetLength())); + + return *this; +} + } // namespace geometry } // namespace t } // namespace open3d diff --git a/cpp/open3d/t/geometry/TriangleMesh.h b/cpp/open3d/t/geometry/TriangleMesh.h index 7824f193b1c..4a1f9138fd6 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.h +++ b/cpp/open3d/t/geometry/TriangleMesh.h @@ -945,6 +945,10 @@ class TriangleMesh : public Geometry, public DrawableGeometry { /// an empty mesh. TriangleMesh SelectByIndex(const core::Tensor &indices) const; + /// Removes unreferenced vertices from the mesh. + /// \return The reference to itself. + TriangleMesh RemoveUnreferencedVertices(); + protected: core::Device device_ = core::Device("CPU:0"); TensorMap vertex_attr_; diff --git a/cpp/pybind/t/geometry/trianglemesh.cpp b/cpp/pybind/t/geometry/trianglemesh.cpp index 6285b00c6b3..cf245426d58 100644 --- a/cpp/pybind/t/geometry/trianglemesh.cpp +++ b/cpp/pybind/t/geometry/trianglemesh.cpp @@ -962,6 +962,10 @@ or has a negative value, it is ignored. box = o3d.t.geometry.TriangleMesh.create_box() top_face = box.select_by_index([2, 3, 6, 7]) )"); + + triangle_mesh.def("remove_unreferenced_vertices", + &TriangleMesh::RemoveUnreferencedVertices, + "Removes unreferenced vertices from the mesh in-place."); } } // namespace geometry diff --git a/cpp/tests/t/geometry/TriangleMesh.cpp b/cpp/tests/t/geometry/TriangleMesh.cpp index d1e2d819eac..b891d6b6986 100644 --- a/cpp/tests/t/geometry/TriangleMesh.cpp +++ b/cpp/tests/t/geometry/TriangleMesh.cpp @@ -1212,5 +1212,135 @@ TEST_P(TriangleMeshPermuteDevices, SelectByIndex) { box_untouched.GetTriangleIndices())); } +TEST_P(TriangleMeshPermuteDevices, RemoveUnreferencedVertices) { + core::Device device = GetParam(); + t::geometry::TriangleMesh mesh_empty{device}; + + // check completely empty mesh + EXPECT_TRUE(mesh_empty.RemoveUnreferencedVertices().IsEmpty()); + + // check mesh w/o triangles + core::Tensor vertices_no_tris_orig = + core::Tensor::Ones({2, 3}, core::Float32, device); + mesh_empty.SetVertexPositions(vertices_no_tris_orig); + EXPECT_TRUE(mesh_empty.RemoveUnreferencedVertices().IsEmpty()); + + // Torus + core::Tensor verts = core::Tensor::Init( + { + {0, 0, 0}, /* 0 */ + {3.0, 0.0, 0.0}, + {1.5, 0.0, 0.866025}, + {1, 2, 3}, /* 3 */ + {1.5, 0.0, -0.866025}, + {1.5, 2.59808, 0.0}, + {0.75, 1.29904, 0.866025}, + {0.75, 1.29904, -0.866025}, + {-1.5, 2.59808, 0}, + {-0.75, 1.29904, 0.866025}, + {-0.75, 1.29904, -0.866025}, + {-3.0, 0.0, 0.0}, + {-1.5, 0.0, 0.866025}, + {-1.5, 0.0, -0.866025}, + {-1.5, -2.59808, 0.0}, + {-0.75, -1.29904, 0.866025}, + {-0.75, -1.29904, -0.866025}, + {4, 5, 6}, /* 17 */ + {1.5, -2.59808, 0.0}, + {0.75, -1.29904, 0.866025}, + {0.75, -1.29904, -0.866025}, + {7, 8, 9} /* 21 */ + }, + device); + + core::Tensor tris = core::Tensor::Init( + {{5, 6, 1}, {1, 6, 2}, {6, 7, 2}, {2, 7, 4}, + {7, 5, 4}, {4, 5, 1}, {8, 9, 5}, {5, 9, 6}, + {9, 10, 6}, {6, 10, 7}, {10, 8, 7}, {7, 8, 5}, + {11, 12, 8}, {8, 12, 9}, {12, 13, 9}, {9, 13, 10}, + {13, 11, 10}, {10, 11, 8}, {14, 15, 11}, {11, 15, 12}, + {15, 16, 12}, {12, 16, 13}, {16, 14, 13}, {13, 14, 11}, + {18, 19, 14}, {14, 19, 15}, {19, 20, 15}, {15, 20, 16}, + {20, 18, 16}, {16, 18, 14}, {1, 2, 18}, {18, 2, 19}, + {2, 4, 19}, {19, 4, 20}, {4, 1, 20}, {20, 1, 18}}, + device); + t::geometry::TriangleMesh torus{verts, tris}; + core::Tensor vertex_colors = core::Tensor::Init( + {{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, {2.0, 2.0, 2.0}, + {3.0, 3.0, 3.0}, {4.0, 4.0, 4.0}, {5.0, 5.0, 5.0}, + {6.0, 6.0, 6.0}, {7.0, 7.0, 7.0}, {8.0, 8.0, 8.0}, + {9.0, 9.0, 9.0}, {10.0, 10.0, 10.0}, {11.0, 11.0, 11.0}, + {12.0, 12.0, 12.0}, {13.0, 13.0, 13.0}, {14.0, 14.0, 14.0}, + {15.0, 15.0, 15.0}, {16.0, 16.0, 16.0}, {17.0, 17.0, 17.0}, + {18.0, 18.0, 18.0}, {19.0, 19.0, 19.0}, {20.0, 20.0, 20.0}, + {21.0, 21.0, 21.0}}, + device); + core::Tensor vertex_labels = + core::Tensor::Init( + {{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, {2.0, 2.0, 2.0}, + {3.0, 3.0, 3.0}, {4.0, 4.0, 4.0}, {5.0, 5.0, 5.0}, + {6.0, 6.0, 6.0}, {7.0, 7.0, 7.0}, {8.0, 8.0, 8.0}, + {9.0, 9.0, 9.0}, {10.0, 10.0, 10.0}, {11.0, 11.0, 11.0}, + {12.0, 12.0, 12.0}, {13.0, 13.0, 13.0}, {14.0, 14.0, 14.0}, + {15.0, 15.0, 15.0}, {16.0, 16.0, 16.0}, {17.0, 17.0, 17.0}, + {18.0, 18.0, 18.0}, {19.0, 19.0, 19.0}, {20.0, 20.0, 20.0}, + {21.0, 21.0, 21.0}}, + device) * + 10; + + core::Tensor triangle_labels = + core::Tensor::Init({{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, + {2.0, 2.0, 2.0}, {3.0, 3.0, 3.0}, + {4.0, 4.0, 4.0}, {5.0, 5.0, 5.0}, + {6.0, 6.0, 6.0}, {7.0, 7.0, 7.0}, + {8.0, 8.0, 8.0}, {9.0, 9.0, 9.0}, + {10.0, 10.0, 10.0}, {11.0, 11.0, 11.0}, + {12.0, 12.0, 12.0}, {13.0, 13.0, 13.0}, + {14.0, 14.0, 14.0}, {15.0, 15.0, 15.0}, + {16.0, 16.0, 16.0}, {17.0, 17.0, 17.0}, + {18.0, 18.0, 18.0}, {19.0, 19.0, 19.0}, + {20.0, 20.0, 20.0}, {21.0, 21.0, 21.0}, + {22.0, 22.0, 22.0}, {23.0, 23.0, 23.0}, + {24.0, 24.0, 24.0}, {25.0, 25.0, 25.0}, + {26.0, 26.0, 26.0}, {27.0, 27.0, 27.0}, + {28.0, 28.0, 28.0}, {29.0, 29.0, 29.0}, + {30.0, 30.0, 30.0}, {31.0, 31.0, 31.0}, + {32.0, 32.0, 32.0}, {33.0, 33.0, 33.0}, + {34.0, 34.0, 34.0}, {35.0, 35.0, 35.0}}, + device) * + 100; + torus.SetVertexColors(vertex_colors); + torus.SetVertexAttr("labels", vertex_labels); + torus.ComputeVertexNormals(); + torus.ComputeTriangleNormals(); + + // set the expected value + core::Tensor verts_mask = core::Tensor::Init( + {0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0}, + device); + core::Tensor expected_verts = + torus.GetVertexPositions().IndexGet({verts_mask}); + core::Tensor expected_tris = + t::geometry::TriangleMesh::CreateTorus(2, 1, 6, 3, core::Float32, + core::Int32, device) + .GetTriangleIndices(); + core::Tensor expected_vert_normals = + torus.GetVertexNormals().IndexGet({verts_mask}); + core::Tensor expected_tri_normals = torus.GetTriangleNormals().Clone(); + core::Tensor expected_vert_labels = + torus.GetVertexAttr("labels").IndexGet({verts_mask}); + core::Tensor expected_vert_colors = + torus.GetVertexAttr("colors").IndexGet({verts_mask}); + + torus.RemoveUnreferencedVertices(); + + EXPECT_TRUE(torus.GetVertexPositions().AllClose(expected_verts)); + EXPECT_TRUE(torus.GetVertexNormals().AllClose(expected_vert_normals)); + EXPECT_TRUE(torus.GetVertexColors().AllClose(expected_vert_colors)); + EXPECT_TRUE(torus.GetVertexAttr("labels").AllClose(expected_vert_labels)); + EXPECT_TRUE(torus.GetTriangleIndices().AllClose(expected_tris)); + EXPECT_TRUE(torus.GetTriangleNormals().AllClose(expected_tri_normals)); +} + } // namespace tests } // namespace open3d diff --git a/python/test/t/geometry/test_trianglemesh.py b/python/test/t/geometry/test_trianglemesh.py index 4ca8d5e98ab..0eec5b3a4e5 100644 --- a/python/test/t/geometry/test_trianglemesh.py +++ b/python/test/t/geometry/test_trianglemesh.py @@ -660,3 +660,52 @@ def test_select_by_index_64(device): untouched_sphere.vertex.positions) assert sphere_custom.triangle.indices.allclose( untouched_sphere.triangle.indices) + + +def check_no_unreferenced_vertices(device, int_t, float_t): + sphere = o3d.t.geometry.TriangleMesh.create_sphere(1, 3, float_t, int_t, + device) + expected_sphere = o3d.t.geometry.TriangleMesh.create_sphere( + 1, 3, float_t, int_t, device) + + sphere.remove_unreferenced_vertices() + + # nothing should be removed + assert sphere.vertex.positions.allclose(expected_sphere.vertex.positions) + assert sphere.triangle.indices.allclose(expected_sphere.triangle.indices) + + +def check_remove_unreferenced_vertices(device, int_t, float_t): + expected_mobius = o3d.t.geometry.TriangleMesh.create_mobius( + 10, 2, 1, 1, 1, 1, 1, float_t, int_t, device) + + verts = o3c.Tensor( + [[0.5, 0.0, 0.0], [1.5, 0.0, 0.0], [0.424307, 0.308277, -0.154508], + [1.19373, 0.867294, 0.154508], [0.184017, 0.566346, -0.293893], + [0.434017, 1.33577, 0.293893], [-0.218199, 0.671548, -0.404508], + [-0.399835, 1.23057, 0.404508], [-0.684017, 0.496967, -0.475528], + [-0.934017, 0.678603, 0.475528], [-1.0, 0.0, -0.5], [-1.0, 0.0, 0.5], + [-0.934017, -0.678603, -0.475528], [-0.684017, -0.496967, 0.475528], + [-0.399835, -1.23057, -0.404508], [-0.218199, -0.671548, 0.404508], + [0.434017, -1.33577, -0.293893], [0.184017, -0.566346, 0.293893], + [0, 0, 0], [1.19373, -0.867294, -0.154508], [1, 1, 1], + [0.424307, -0.308277, 0.154508]], float_t, device) + + tris = o3c.Tensor( + [[0, 3, 1], [0, 2, 3], [3, 2, 4], [3, 4, 5], [4, 7, 5], [4, 6, 7], + [7, 6, 8], [7, 8, 9], [8, 11, 9], [8, 10, 11], [11, 10, 12], + [11, 12, 13], [12, 15, 13], [12, 14, 15], [15, 14, 16], [15, 16, 17], + [16, 21, 17], [16, 19, 21], [19, 21, 1], [1, 21, 0]], int_t, device) + + mobius = o3d.t.geometry.TriangleMesh(verts, tris) + mobius.remove_unreferenced_vertices() + assert mobius.vertex.positions.allclose(expected_mobius.vertex.positions) + assert mobius.triangle.indices.allclose(expected_mobius.triangle.indices) + + +@pytest.mark.parametrize("device", list_devices()) +@pytest.mark.parametrize("int_t", (o3c.int32, o3c.int64)) +@pytest.mark.parametrize("float_t", (o3c.float32, o3c.float64)) +def test_remove_unreferenced_vertices(device, int_t, float_t): + check_no_unreferenced_vertices(device, int_t, float_t) + check_remove_unreferenced_vertices(device, int_t, float_t) From 74df0a3882565c35ce827c96a052a02fabc1cad7 Mon Sep 17 00:00:00 2001 From: Longyong Wu Date: Tue, 19 Mar 2024 12:21:07 +0800 Subject: [PATCH 05/22] [Fix] Add tolerance threshold to correct planar patch detection for noise-free planes (issue #6550) (#6608) The coplanar patches for noise-free plane are not merged. The reason is normal deviation diff threshold is 1, and the dot product of direction normal (plane patches) would be too. So the robust planar test will fail on this. And also the box size need to be larger than zero, but for ideal points, it maybe calculated to zero. So we need to set a tolerance on this too. --- .../geometry/PointCloudPlanarPatchDetection.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp b/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp index b096ffc5655..16813bad75e 100644 --- a/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp +++ b/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp @@ -27,6 +27,8 @@ namespace geometry { namespace { +double tolerance = 1e-6; + /// \brief Planar patch container struct PlanarPatch { double GetSignedDistanceToPoint(const Eigen::Vector3d& point) const { @@ -400,9 +402,13 @@ class PlaneDetector { (rect.bottom_left(1) + rect.top_right(1)) / 2. * rect.B.col(1); // Scale basis to fit points - const double width = (rect.top_right.x() - rect.bottom_left.x()); - const double height = (rect.top_right.y() - rect.bottom_left.y()); - const double depth = (rect.top_right.z() - rect.bottom_left.z()); + const double _mini = std::min(tolerance, max_point_dist_); + const double width = + std::max(rect.top_right.x() - rect.bottom_left.x(), _mini); + const double height = + std::max(rect.top_right.y() - rect.bottom_left.y(), _mini); + const double depth = + std::max(rect.top_right.z() - rect.bottom_left.z(), _mini); std::shared_ptr obox = std::make_shared(); @@ -590,6 +596,7 @@ class PlaneDetector { // Use lower bound of the spread around the median as an indication // of how similar the point normals associated with the patch are. GetMinMaxRScore(normal_similarities, min_normal_diff_, tmp, 3); + min_normal_diff_ = std::min(min_normal_diff_, 1.0 - tolerance); // Use upper bound of the spread around the median as an indication // of how close the points associated with the patch are to the patch. GetMinMaxRScore(point_distances, tmp, max_point_dist_, 3); @@ -937,7 +944,6 @@ void ExtractPatchesFromPlanes( Eigen::Vector3d(0.4660, 0.6740, 0.1880), Eigen::Vector3d(0.3010, 0.7450, 0.9330), Eigen::Vector3d(0.6350, 0.0780, 0.1840)}; - for (size_t i = 0; i < planes.size(); i++) { if (!planes[i]->IsFalsePositive()) { // create a patch by delimiting the plane using its perimeter points From 80ae047db2cda0390b088cf759c8dc0ce0a8e4bd Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:22:00 -0700 Subject: [PATCH 06/22] Misc. CI updates and fixes (#6716) * Fix RemoveUnreferencedVertices for CUDA * Style fix * Remove docker build cache for CI to avoid SYCL CI failure. * Manual CI run for Ubuntu SYCL (workflow-dispatch) * Update github actions for Node 16 -> Node 20 migration: uploaded artifacts must have unique names * Remove build folders in CI to save space. Keep bin, lib, etc. 9.6G free space available for Ubuntu wheel CI now. --- .github/workflows/clean-gcloud-profiles.yml | 4 +- .github/workflows/documentation.yml | 14 ++--- .github/workflows/macos.yml | 51 +++++++++---------- .github/workflows/style.yml | 4 +- .github/workflows/ubuntu-cuda.yml | 10 ++-- .github/workflows/ubuntu-openblas.yml | 8 +-- .github/workflows/ubuntu-sycl.yml | 8 +-- .github/workflows/ubuntu-wheel.yml | 25 ++++----- .github/workflows/ubuntu.yml | 12 ++--- .github/workflows/vtk_packages.yml | 12 ++--- .github/workflows/webrtc.yml | 16 +++--- .github/workflows/windows.yml | 45 ++++++++-------- cpp/open3d/t/geometry/TriangleMesh.cpp | 1 + .../pipelines/registration/registration.cpp | 2 +- docker/Dockerfile.ci | 5 +- docker/Dockerfile.wheel | 5 +- docker/docker_build.sh | 2 + .../reconstruction_system/make_fragments.py | 1 + 18 files changed, 118 insertions(+), 107 deletions(-) diff --git a/.github/workflows/clean-gcloud-profiles.yml b/.github/workflows/clean-gcloud-profiles.yml index 5b5398156fd..0aec813a48f 100644 --- a/.github/workflows/clean-gcloud-profiles.yml +++ b/.github/workflows/clean-gcloud-profiles.yml @@ -36,12 +36,12 @@ jobs: fail-fast: false steps: - name: GCloud CLI auth - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 85368dd8a45..b0e289e41cc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -29,7 +29,7 @@ jobs: DEVELOPER_BUILD: ${{ github.event.inputs.developer_build || 'ON' }} steps: - name: Checkout Open3D source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | @@ -37,13 +37,13 @@ jobs: maximize_ubuntu_github_actions_build_space - name: Checkout Open3D-ML source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: isl-org/Open3D-ML path: ${{ env.OPEN3D_ML_ROOT }} - name: Setup cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: # Ref: https://github.com/apache/incubator-mxnet/pull/18459/files path: ~/.ccache @@ -56,7 +56,7 @@ jobs: restore-keys: | ${{ runner.os }}-ccache - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' @@ -81,7 +81,7 @@ jobs: ccache -s - name: Upload docs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: open3d_docs path: docs/_out/html @@ -89,13 +89,13 @@ jobs: - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 00bd9636130..16b242d081b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -39,9 +39,9 @@ jobs: LOW_MEM_USAGE: ON steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: # Ref: https://github.com/apache/incubator-mxnet/pull/18459/files path: ~/.ccache @@ -54,7 +54,7 @@ jobs: restore-keys: | ${{ runner.os }}-ccache - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install dependencies @@ -73,7 +73,7 @@ jobs: PATH=/usr/local/var/homebrew/linked/ccache/libexec:$PATH ccache -s ./util/run_ci.sh - DEVEL_PKG_NAME="$(basename package/open3d-devel-*.tar.xz)" + DEVEL_PKG_NAME="$(basename build/package/open3d-devel-*.tar.xz)" echo "DEVEL_PKG_NAME=$DEVEL_PKG_NAME" >> $GITHUB_ENV - name: Build Open3D viewer app if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} @@ -87,21 +87,21 @@ jobs: - name: Upload package if: ${{ env.BUILD_SHARED_LIBS == 'ON' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d-devel-macosx + name: ${{ env.DEVEL_PKG_NAME }} path: build/package/${{ env.DEVEL_PKG_NAME }} if-no-files-found: error - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} @@ -113,7 +113,7 @@ jobs: echo "Download devel package at: https://storage.googleapis.com/open3d-releases/devel/${{ env.DEVEL_PKG_NAME }}" - name: Upload Open3D viewer app - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} with: name: open3d-app-macosx-10_15 @@ -143,16 +143,16 @@ jobs: OPEN3D_ML_ROOT: ${{ github.workspace }}/Open3D-ML steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout Open3D-ML source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: isl-org/Open3D-ML path: ${{ env.OPEN3D_ML_ROOT }} - name: Setup cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: # Ref: https://github.com/apache/incubator-mxnet/pull/18459/files path: ~/.ccache @@ -166,7 +166,7 @@ jobs: ${{ runner.os }}-ccache - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} @@ -199,21 +199,21 @@ jobs: echo "PIP_PKG_NAME=$PIP_PKG_NAME" >> $GITHUB_ENV - name: Upload wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d_macosx_x86_64_wheels + name: ${{ env.PIP_PKG_NAME }} path: build/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} if-no-files-found: error - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} @@ -249,23 +249,22 @@ jobs: OPEN3D_ML_ROOT: ${{ github.workspace }}/Open3D-ML steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout Open3D-ML source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: isl-org/Open3D-ML path: ${{ env.OPEN3D_ML_ROOT }} - name: Download wheels - uses: actions/download-artifact@v3 - # See https://github.com/dawidd6/action-download-artifact for more - # flexible artifact download options + uses: actions/download-artifact@v4 with: - name: open3d_macosx_x86_64_wheels + pattern: open3d-*macosx*.whl + merge-multiple: true - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} @@ -290,12 +289,12 @@ jobs: needs: [build-wheel, MacOS] steps: - name: GCloud CLI auth - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 744b997e2e7..e0de61516be 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Install dependencies diff --git a/.github/workflows/ubuntu-cuda.yml b/.github/workflows/ubuntu-cuda.yml index ce4528a83ca..bc7dcf9f67a 100644 --- a/.github/workflows/ubuntu-cuda.yml +++ b/.github/workflows/ubuntu-cuda.yml @@ -64,7 +64,7 @@ jobs: CCACHE_TAR_NAME : open3d-ci-${{ matrix.CI_CONFIG }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'false' - name: Package code @@ -74,12 +74,12 @@ jobs: tar -czvf Open3D.tar.gz Open3D ls -alh - name: GCloud CLI auth - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} @@ -150,9 +150,9 @@ jobs: - name: Upload package if: ${{ env.BUILD_PACKAGE == 'true' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d-devel-linux-x86_64-cuda + name: open3d-devel-linux-x86_64-cuda-${CI_CONFIG} path: open3d-devel-linux*.tar.xz if-no-files-found: error diff --git a/.github/workflows/ubuntu-openblas.yml b/.github/workflows/ubuntu-openblas.yml index 233c32bd96f..9bbb423f457 100644 --- a/.github/workflows/ubuntu-openblas.yml +++ b/.github/workflows/ubuntu-openblas.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | source util/ci_utils.sh @@ -66,7 +66,7 @@ jobs: GCE_INSTANCE_PREFIX: open3d-ci-openblas-arm64-py310-dev steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Package code run: | # GITHUB_WORKSPACE: /home/runner/work/Open3D/Open3D @@ -74,12 +74,12 @@ jobs: tar -czvf Open3D.tar.gz Open3D ls -alh - name: GCloud CLI auth - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/ubuntu-sycl.yml b/.github/workflows/ubuntu-sycl.yml index 1c1ac21697d..4f4c9f6d9c1 100644 --- a/.github/workflows/ubuntu-sycl.yml +++ b/.github/workflows/ubuntu-sycl.yml @@ -1,6 +1,7 @@ name: Ubuntu SYCL on: + workflow_dispatch: push: branches: - main @@ -24,7 +25,7 @@ jobs: BUILD_SHARED_LIBS: [ON, OFF] steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | source util/ci_utils.sh @@ -38,6 +39,7 @@ jobs: fi - name: Docker test run: | + du -hs $PWD if [ "${{ matrix.BUILD_SHARED_LIBS }}" = "ON" ]; then docker/docker_test.sh sycl-shared else @@ -46,13 +48,13 @@ jobs: - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/ubuntu-wheel.yml b/.github/workflows/ubuntu-wheel.yml index a798762c965..906d07112f7 100644 --- a/.github/workflows/ubuntu-wheel.yml +++ b/.github/workflows/ubuntu-wheel.yml @@ -47,7 +47,7 @@ jobs: OPEN3D_CPU_RENDERING: true steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | source util/ci_utils.sh @@ -78,22 +78,22 @@ jobs: echo "PIP_PKG_NAME=$PIP_PKG_NAME" >> $GITHUB_ENV echo "PIP_CPU_PKG_NAME=$PIP_CPU_PKG_NAME" >> $GITHUB_ENV - name: Upload wheel to GitHub artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d_linux_x86_64_wheels + name: ${{ env.PIP_PKG_NAME }} path: | ${{ env.PIP_PKG_NAME }} ${{ env.PIP_CPU_PKG_NAME }} if-no-files-found: error - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} @@ -131,22 +131,23 @@ jobs: OPEN3D_ML_ROOT: ${{ github.workspace }}/Open3D-ML steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | source util/ci_utils.sh maximize_ubuntu_github_actions_build_space - name: Checkout Open3D-ML source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: isl-org/Open3D-ML path: ${{ env.OPEN3D_ML_ROOT }} - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: open3d_linux_x86_64_wheels + pattern: open3d*-manylinux*.whl + merge-multiple: true - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - name: Test Python package @@ -181,12 +182,12 @@ jobs: needs: [build-wheel] steps: - name: GCloud CLI auth - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1a69ec4da2d..3bbdaadc1d1 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -39,7 +39,7 @@ jobs: OPEN3D_CPU_RENDERING: true steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Maximize build space run: | source util/ci_utils.sh @@ -72,27 +72,27 @@ jobs: fi - name: Upload package to GitHub artifacts if: ${{ env.BUILD_SHARED_LIBS == 'ON' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d-devel-linux-x86_64 + name: open3d-devel-linux-x86_64-ML_${{ matrix.MLOPS }} path: open3d-devel-*.tar.xz if-no-files-found: error - name: Upload viewer to GitHub artifacts if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: open3d-viewer-Linux path: open3d-viewer-*-Linux.deb if-no-files-found: error - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v1' + uses: 'google-github-actions/auth@v2' with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/.github/workflows/vtk_packages.yml b/.github/workflows/vtk_packages.yml index 8ae7d812187..acd20c5260b 100644 --- a/.github/workflows/vtk_packages.yml +++ b/.github/workflows/vtk_packages.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-18.04 steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: CMake configure run: | mkdir build @@ -25,7 +25,7 @@ jobs: make -j$(nproc) cmake -E sha256sum vtk*.tar.gz > checksum_linux.txt - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: vtk_linux path: | @@ -53,7 +53,7 @@ jobs: - name: Disk space used run: Get-PSDrive - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Config # Move build directory to C: https://github.com/actions/virtual-environments/issues/1341 run: | @@ -75,7 +75,7 @@ jobs: ls cmake -E sha256sum (get-item vtk*.tar.gz).Name > checksum_win_${{matrix.configuration}}.txt - name: Upload package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: vtk_windows_${{matrix.configuration}} path: | @@ -90,7 +90,7 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: CMake configure run: | mkdir build @@ -102,7 +102,7 @@ jobs: make -j2 cmake -E sha256sum vtk*.tar.gz > checksum_macos.txt - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: vtk_macos path: | diff --git a/.github/workflows/webrtc.yml b/.github/workflows/webrtc.yml index 4602379d00f..90c45e053e7 100644 --- a/.github/workflows/webrtc.yml +++ b/.github/workflows/webrtc.yml @@ -37,10 +37,10 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 @@ -61,9 +61,9 @@ jobs: build_webrtc - name: Upload WebRTC - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: webrtc_release + name: webrtc_release_${{ matrix.os }} path: | webrtc_*.tar.gz checksum_*.txt @@ -81,10 +81,10 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' @@ -167,9 +167,9 @@ jobs: cmake -E sha256sum webrtc_${env:WEBRTC_COMMIT_SHORT}_win.zip | Tee-Object -FilePath checksum_win.txt - name: Upload WebRTC - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: webrtc_release + name: webrtc_release_windows path: | ${{ env.WORK_DIR }}/webrtc_*.zip ${{ env.WORK_DIR }}/checksum_*.txt diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d687f6b7e33..11223949007 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -54,7 +54,7 @@ jobs: run: Get-PSDrive - name: Setup Windows SDK - uses: GuillaumeFalourd/setup-windows10-sdk-action@v1.11 + uses: GuillaumeFalourd/setup-windows10-sdk-action@v2 with: sdk-version: 19041 @@ -92,10 +92,10 @@ jobs: echo "$CUDA_PATH\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python version - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 @@ -148,9 +148,9 @@ jobs: - name: Upload Package if: ${{ matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d-devel-windows + name: ${{ env.DEVEL_PKG_NAME }} path: ${{ env.BUILD_DIR }}/package/${{ env.DEVEL_PKG_NAME }} if-no-files-found: error @@ -166,7 +166,7 @@ jobs: - name: Upload Viewer if: ${{ matrix.BUILD_SHARED_LIBS == 'OFF' && matrix.STATIC_RUNTIME == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' && matrix.CONFIG == 'Release' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: open3d-app-windows-amd64 path: C:\Program Files\Open3D\bin\Open3D @@ -174,13 +174,13 @@ jobs: - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} - uses: google-github-actions/auth@v1 + uses: google-github-actions/auth@v2 with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_DOCS_PROJECT }} @@ -255,15 +255,15 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Windows SDK - uses: GuillaumeFalourd/setup-windows10-sdk-action@v1.11 + uses: GuillaumeFalourd/setup-windows10-sdk-action@v2 with: sdk-version: 19041 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} @@ -307,21 +307,21 @@ jobs: echo "PIP_PKG_NAME=$PIP_PKG_NAME" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Upload wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: open3d_win_amd64_wheels + name: ${{ env.PIP_PKG_NAME }} path: ${{ env.BUILD_DIR }}/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} if-no-files-found: error - name: GCloud CLI auth if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/auth@v1 + uses: google-github-actions/auth@v2 with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_DOCS_PROJECT }} @@ -360,17 +360,16 @@ jobs: steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download wheels - uses: actions/download-artifact@v3 - # See https://github.com/dawidd6/action-download-artifact for more - # flexible artifact download options + uses: actions/download-artifact@v4 with: - name: open3d_win_amd64_wheels + pattern: open3d-*win*.whl + merge-multiple: true - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} @@ -418,12 +417,12 @@ jobs: needs: [build-wheel] steps: - name: GCloud CLI auth - uses: google-github-actions/auth@v1 + uses: google-github-actions/auth@v2 with: project_id: ${{ secrets.GCE_PROJECT }} credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v1 + uses: google-github-actions/setup-gcloud@v2 with: version: ${{ env.GCE_CLI_GHA_VERSION }} project_id: ${{ secrets.GCE_PROJECT }} diff --git a/cpp/open3d/t/geometry/TriangleMesh.cpp b/cpp/open3d/t/geometry/TriangleMesh.cpp index 7316901b473..2bf329838cc 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.cpp +++ b/cpp/open3d/t/geometry/TriangleMesh.cpp @@ -1292,6 +1292,7 @@ TriangleMesh TriangleMesh::RemoveUnreferencedVertices() { UpdateTriangleIndicesByVertexMask(tris_cpu, vertex_mask); }); + SetTriangleIndices(tris_cpu.To(GetDevice())); } // send the vertex mask to original device and apply to diff --git a/cpp/pybind/pipelines/registration/registration.cpp b/cpp/pybind/pipelines/registration/registration.cpp index f5165537fea..cddcb7e7e80 100644 --- a/cpp/pybind/pipelines/registration/registration.cpp +++ b/cpp/pybind/pipelines/registration/registration.cpp @@ -351,7 +351,7 @@ Sets :math:`c = 1` if ``with_scaling`` is ``False``. "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 " + "drawn within the source point cloud and within the target " "point cloud with correspondences are similar. The only " "parameter similarity_threshold is a number between 0 " "(loose) and 1 (strict)"); diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index e446353744a..73dd3fe1aac 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,6 +17,7 @@ ARG BUILD_TENSORFLOW_OPS ARG BUILD_PYTORCH_OPS ARG PACKAGE ARG BUILD_SYCL_MODULE +ARG CI RUN if [ -z "${DEVELOPER_BUILD}" ]; then echo "Error: ARG DEVELOPER_BUILD not specified."; exit 1; fi \ && if [ -z "${CCACHE_TAR_NAME}" ]; then echo "Error: ARG CCACHE_TAR_NAME not specified."; exit 1; fi \ @@ -210,7 +211,9 @@ RUN \ && make install-pip-package -j$(nproc) \ && make install -j$(nproc) \ && if [ "${PACKAGE}" = "ON" ]; then make package; fi \ - && if [ "${PACKAGE}" = "VIEWER" ]; then make package-Open3DViewer-deb; fi + && if [ "${PACKAGE}" = "VIEWER" ]; then make package-Open3DViewer-deb; fi \ + && if [ "${CI:-}a" != "a" ]; then rm -rf _deps assimp embree ippicv mkl mkl_install webrtc; fi +# If CI is not null or unset, remove all large build folders to save disk space # Compress ccache folder, move to / directory RUN ccache -s \ diff --git a/docker/Dockerfile.wheel b/docker/Dockerfile.wheel index 6a7d7080aed..a719262ece2 100644 --- a/docker/Dockerfile.wheel +++ b/docker/Dockerfile.wheel @@ -10,6 +10,7 @@ ARG CCACHE_VERSION ARG PYTHON_VERSION ARG BUILD_TENSORFLOW_OPS ARG BUILD_PYTORCH_OPS +ARG CI # Forward all ARG to ENV # ci_utils.sh requires these environment variables @@ -130,7 +131,9 @@ WORKDIR /root/Open3D RUN export NPROC=$(nproc) \ && export BUILD_SHARED_LIBS=OFF \ && source /root/Open3D/util/ci_utils.sh \ - && build_pip_package build_azure_kinect build_jupyter + && build_pip_package build_azure_kinect build_jupyter \ + && if [ ${CI:-}a != a ]; then cd /root/Open3D/build/ && ls | grep -Ev '^lib$' | xargs rm -rf ; fi + # remove build folder (except lib) to save CI space on Github # Compress ccache folder, move to / directory RUN ccache -s \ diff --git a/docker/docker_build.sh b/docker/docker_build.sh index 33ab6ad0115..f55276baed5 100755 --- a/docker/docker_build.sh +++ b/docker/docker_build.sh @@ -207,6 +207,7 @@ cuda_wheel_build() { --build-arg PYTHON_VERSION="${PYTHON_VERSION}" \ --build-arg BUILD_TENSORFLOW_OPS="${BUILD_TENSORFLOW_OPS}" \ --build-arg BUILD_PYTORCH_OPS="${BUILD_PYTORCH_OPS}" \ + --build-arg CI="${CI:-}" \ -t open3d-ci:wheel \ -f docker/Dockerfile.wheel . popd @@ -249,6 +250,7 @@ ci_build() { --build-arg BUILD_PYTORCH_OPS="${BUILD_PYTORCH_OPS}" \ --build-arg PACKAGE="${PACKAGE}" \ --build-arg BUILD_SYCL_MODULE="${BUILD_SYCL_MODULE}" \ + --build-arg CI="${CI:-}" \ -t "${DOCKER_TAG}" \ -f docker/Dockerfile.ci . popd diff --git a/examples/python/reconstruction_system/make_fragments.py b/examples/python/reconstruction_system/make_fragments.py index 17d875aa86c..5af9d960f4b 100644 --- a/examples/python/reconstruction_system/make_fragments.py +++ b/examples/python/reconstruction_system/make_fragments.py @@ -145,6 +145,7 @@ def make_pointcloud_for_fragment(path_dataset, color_files, depth_files, write_ascii=False, compressed=True) + def process_single_fragment(fragment_id, color_files, depth_files, n_files, n_fragments, config): if config["path_intrinsic"]: From 84b8e071e23eb65a60df87b785683eea5142b415 Mon Sep 17 00:00:00 2001 From: mone27 Date: Mon, 1 Apr 2024 17:11:57 +0200 Subject: [PATCH 07/22] Fix typos in NearestNeighborSearch.h, NanoFlannIndex and NNSIndex (#6705) --- cpp/open3d/core/nns/NNSIndex.h | 6 +++--- cpp/open3d/core/nns/NanoFlannIndex.h | 6 +++--- cpp/open3d/core/nns/NearestNeighborSearch.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/open3d/core/nns/NNSIndex.h b/cpp/open3d/core/nns/NNSIndex.h index 0e71a6d3a40..8eb8160da5d 100644 --- a/cpp/open3d/core/nns/NNSIndex.h +++ b/cpp/open3d/core/nns/NNSIndex.h @@ -51,7 +51,7 @@ class NNSIndex { /// \param knn Number of nearest neighbor to search. /// \return Pair of Tensors: (indices, distances): /// - indices: Tensor of shape {n, knn}, with dtype Int32. - /// - distainces: Tensor of shape {n, knn}, same dtype with dataset_points. + /// - distances: Tensor of shape {n, knn}, same dtype with dataset_points. virtual std::pair SearchKnn(const Tensor &query_points, int knn) const = 0; @@ -61,7 +61,7 @@ class NNSIndex { /// dtype with dataset_points. /// \param radii list of radius. Must be 1D, with shape {n, }. /// \return Tuple of Tensors: (indices, distances, num_neighbors): - /// - indicecs: Tensor of shape {total_num_neighbors,}, dtype Int32. + /// - indices: Tensor of shape {total_num_neighbors,}, dtype Int32. /// - distances: Tensor of shape {total_num_neighbors,}, same dtype with /// dataset_points. /// - num_neighbors: Tensor of shape {n,}, dtype Int32. @@ -76,7 +76,7 @@ class NNSIndex { /// dtype with dataset_points. /// \param radius Radius. /// \return Tuple of Tensors, (indices, distances, num_neighbors): - /// - indicecs: Tensor of shape {total_num_neighbors,}, dtype Int32. + /// - indices: Tensor of shape {total_num_neighbors,}, dtype Int32. /// - distances: Tensor of shape {total_num_neighbors,}, same dtype with /// dataset_points. /// - num_neighbors: Tensor of shape {n}, dtype Int32. diff --git a/cpp/open3d/core/nns/NanoFlannIndex.h b/cpp/open3d/core/nns/NanoFlannIndex.h index 32f0b48cedd..e092099d46e 100644 --- a/cpp/open3d/core/nns/NanoFlannIndex.h +++ b/cpp/open3d/core/nns/NanoFlannIndex.h @@ -54,7 +54,7 @@ class NanoFlannIndex : public NNSIndex { /// \param knn Number of nearest neighbor to search. /// \return Pair of Tensors: (indices, distances): /// - indices: Tensor of shape {n, knn}, with dtype Int32. - /// - distainces: Tensor of shape {n, knn}, same dtype with dataset_points. + /// - distances: Tensor of shape {n, knn}, same dtype with dataset_points. std::pair SearchKnn(const Tensor &query_points, int knn) const override; @@ -64,7 +64,7 @@ class NanoFlannIndex : public NNSIndex { /// dtype with dataset_points. /// \param radii list of radius. Must be 1D, with shape {n, }. /// \return Tuple of Tensors: (indices, distances, counts): - /// - indicecs: Tensor of shape {total_num_neighbors,}, dtype Int32. + /// - indices: Tensor of shape {total_num_neighbors,}, dtype Int32. /// - distances: Tensor of shape {total_num_neighbors,}, same dtype with /// dataset_points. /// - counts: Tensor of shape {n,}, dtype Int64. @@ -79,7 +79,7 @@ class NanoFlannIndex : public NNSIndex { /// dtype with dataset_points. /// \param radius Radius. /// \return Tuple of Tensors, (indices, distances, counts): - /// - indicecs: Tensor of shape {total_num_neighbors,}, dtype Int32. + /// - indices: Tensor of shape {total_num_neighbors,}, dtype Int32. /// - distances: Tensor of shape {total_num_neighbors,}, same dtype with /// dataset_points. /// - counts: Tensor of shape {n}, dtype Int64. diff --git a/cpp/open3d/core/nns/NearestNeighborSearch.h b/cpp/open3d/core/nns/NearestNeighborSearch.h index 32fa6b87785..7ce92811dd9 100644 --- a/cpp/open3d/core/nns/NearestNeighborSearch.h +++ b/cpp/open3d/core/nns/NearestNeighborSearch.h @@ -76,7 +76,7 @@ class NearestNeighborSearch { /// \param radius Radius. /// \param sort Sort the results by distance. Default is True. /// \return Tuple of Tensors, (indices, distances, num_neighbors): - /// - indicecs: Tensor of shape {total_number_of_neighbors,}, with dtype + /// - indices: Tensor of shape {total_number_of_neighbors,}, with dtype /// same as index_dtype_. /// - distances: Tensor of shape {total_number_of_neighbors,}, same dtype /// with query_points. The distances are squared L2 distances. @@ -91,7 +91,7 @@ class NearestNeighborSearch { /// \param radii Radii of query points. Each query point has one radius. /// Must be 1D, with shape {n,}. /// \return Tuple of Tensors, (indices,distances, num_neighbors): - /// - indicecs: Tensor of shape {total_number_of_neighbors,}, with dtype + /// - indices: Tensor of shape {total_number_of_neighbors,}, with dtype /// same as index_dtype_. /// - distances: Tensor of shape {total_number_of_neighbors,}, same dtype /// with query_points. The distances are squared L2 distances. @@ -108,7 +108,7 @@ class NearestNeighborSearch { /// \param max_knn Maximum number of neighbor to search per query. /// \return Tuple of Tensors, (indices, distances, counts): /// - indices: Tensor of shape {n, knn}, with dtype same as index_dtype_. - /// - distainces: Tensor of shape {n, knn}, with same dtype with + /// - distances: Tensor of shape {n, knn}, with same dtype with /// query_points. The distances are squared L2 distances. /// - counts: Counts of neighbour for each query points. [Tensor /// of shape {n}, with dtype same as index_dtype_]. From 172367ea241e36236af886e3649a09e8401c7503 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 15 Apr 2024 16:40:21 -0700 Subject: [PATCH 08/22] Switched QHull ImGUI and GoogleTest to use FetchContent (#6645) * Switched QHull and GoogleTest to use FetchContent to populate their files at configure time. This means that the open3d_build_3rdparty_library function doesn't need to mark source files as generated which was causing them to be removed when running the clean target. * Switched ImGUI to use FetchContent instead of external project so files are provided at configure time instead of build time. * Removed vestigial ext_imgui target that used to download the imgui sources. Sources are now downloaded at configure time. --- 3rdparty/find_dependencies.cmake | 10 ---------- 3rdparty/googletest/googletest.cmake | 8 ++++---- 3rdparty/imgui/imgui.cmake | 8 ++++---- 3rdparty/qhull/qhull.cmake | 8 ++++---- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/3rdparty/find_dependencies.cmake b/3rdparty/find_dependencies.cmake index 169bfbe1a54..d929b27728a 100644 --- a/3rdparty/find_dependencies.cmake +++ b/3rdparty/find_dependencies.cmake @@ -96,8 +96,6 @@ function(open3d_build_3rdparty_library name) if(arg_SOURCES) foreach(src IN LISTS arg_SOURCES) get_filename_component(abs_src "${src}" ABSOLUTE BASE_DIR "${arg_DIRECTORY}") - # Mark as generated to skip CMake's file existence checks - set_source_files_properties(${abs_src} PROPERTIES GENERATED TRUE) target_sources(${name} PRIVATE ${abs_src}) endforeach() foreach(incl IN LISTS include_dirs) @@ -1030,8 +1028,6 @@ if(NOT USE_SYSTEM_QHULLCPP) src/libqhull_r/rboxlib_r.c INCLUDE_DIRS src/ - DEPENDS - ext_qhull ) open3d_build_3rdparty_library(3rdparty_qhullcpp DIRECTORY ${QHULL_SOURCE_DIR} SOURCES @@ -1057,8 +1053,6 @@ if(NOT USE_SYSTEM_QHULLCPP) src/libqhullcpp/RoadLogEvent.cpp INCLUDE_DIRS src/ - DEPENDS - ext_qhull ) target_link_libraries(3rdparty_qhullcpp PRIVATE 3rdparty_qhull_r) list(APPEND Open3D_3RDPARTY_PRIVATE_TARGETS_FROM_CUSTOM Open3D::3rdparty_qhullcpp) @@ -1158,8 +1152,6 @@ if (BUILD_UNIT_TESTS) googletest/ googlemock/include/ googlemock/ - DEPENDS - ext_googletest ) endif() endif() @@ -1190,8 +1182,6 @@ if(BUILD_GUI) imgui_tables.cpp imgui_widgets.cpp imgui.cpp - DEPENDS - ext_imgui ) list(APPEND Open3D_3RDPARTY_PRIVATE_TARGETS_FROM_CUSTOM Open3D::3rdparty_imgui) else() diff --git a/3rdparty/googletest/googletest.cmake b/3rdparty/googletest/googletest.cmake index 706bf35204a..dd84c07c32c 100644 --- a/3rdparty/googletest/googletest.cmake +++ b/3rdparty/googletest/googletest.cmake @@ -1,6 +1,6 @@ -include(ExternalProject) +include(FetchContent) -ExternalProject_Add( +FetchContent_Declare( ext_googletest PREFIX googletest URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz @@ -12,5 +12,5 @@ ExternalProject_Add( INSTALL_COMMAND "" ) -ExternalProject_Get_Property(ext_googletest SOURCE_DIR) -set(GOOGLETEST_SOURCE_DIR ${SOURCE_DIR}) +FetchContent_Populate(ext_googletest) +FetchContent_GetProperties(ext_googletest SOURCE_DIR GOOGLETEST_SOURCE_DIR) diff --git a/3rdparty/imgui/imgui.cmake b/3rdparty/imgui/imgui.cmake index 752addd86a1..12ea3175736 100644 --- a/3rdparty/imgui/imgui.cmake +++ b/3rdparty/imgui/imgui.cmake @@ -1,6 +1,6 @@ -include(ExternalProject) +include(FetchContent) -ExternalProject_Add( +FetchContent_Declare( ext_imgui PREFIX imgui URL https://github.com/ocornut/imgui/archive/refs/tags/v1.88.tar.gz @@ -12,5 +12,5 @@ ExternalProject_Add( INSTALL_COMMAND "" ) -ExternalProject_Get_Property(ext_imgui SOURCE_DIR) -set(IMGUI_SOURCE_DIR ${SOURCE_DIR}) +FetchContent_Populate(ext_imgui) +FetchContent_GetProperties(ext_imgui SOURCE_DIR IMGUI_SOURCE_DIR) diff --git a/3rdparty/qhull/qhull.cmake b/3rdparty/qhull/qhull.cmake index c20c86c471f..fe15e83ca6f 100644 --- a/3rdparty/qhull/qhull.cmake +++ b/3rdparty/qhull/qhull.cmake @@ -1,6 +1,6 @@ -include(ExternalProject) +include(FetchContent) -ExternalProject_Add( +FetchContent_Declare( ext_qhull PREFIX qhull # v8.0.0+ causes seg fault @@ -14,5 +14,5 @@ ExternalProject_Add( INSTALL_COMMAND "" ) -ExternalProject_Get_Property(ext_qhull SOURCE_DIR) -set(QHULL_SOURCE_DIR ${SOURCE_DIR}) +FetchContent_Populate(ext_qhull) +FetchContent_GetProperties(ext_qhull SOURCE_DIR QHULL_SOURCE_DIR) From 15624686c4fffdde9ad7b6b102a498f593436906 Mon Sep 17 00:00:00 2001 From: Ahmed Elsayed Date: Tue, 16 Apr 2024 18:52:48 +0200 Subject: [PATCH 09/22] Update ispc binary path on Windows, update civetweb to v1.16 (#6577) --- 3rdparty/civetweb/civetweb.cmake | 4 ++-- cmake/Open3DFetchISPCCompiler.cmake | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/3rdparty/civetweb/civetweb.cmake b/3rdparty/civetweb/civetweb.cmake index dc8d556ea64..5d4749fe4e7 100644 --- a/3rdparty/civetweb/civetweb.cmake +++ b/3rdparty/civetweb/civetweb.cmake @@ -3,8 +3,8 @@ include(ExternalProject) ExternalProject_Add( ext_civetweb PREFIX civetweb - URL https://github.com/civetweb/civetweb/archive/refs/tags/v1.15.tar.gz - URL_HASH SHA256=90a533422944ab327a4fbb9969f0845d0dba05354f9cacce3a5005fa59f593b9 + URL https://github.com/civetweb/civetweb/archive/refs/tags/v1.16.tar.gz + URL_HASH SHA256=f0e471c1bf4e7804a6cfb41ea9d13e7d623b2bcc7bc1e2a4dd54951a24d60285 DOWNLOAD_DIR "${OPEN3D_THIRD_PARTY_DOWNLOAD_DIR}/civetweb" UPDATE_COMMAND "" CMAKE_ARGS diff --git a/cmake/Open3DFetchISPCCompiler.cmake b/cmake/Open3DFetchISPCCompiler.cmake index 3f7940c648b..1753b52325e 100644 --- a/cmake/Open3DFetchISPCCompiler.cmake +++ b/cmake/Open3DFetchISPCCompiler.cmake @@ -50,7 +50,11 @@ function(open3d_fetch_ispc_compiler) ) FetchContent_MakeAvailable(ext_ispc) + if (WIN32) + set(CMAKE_ISPC_COMPILER "${ext_ispc_SOURCE_DIR}/bin/ispc.exe" PARENT_SCOPE) + else() # Linux + set(CMAKE_ISPC_COMPILER "${ext_ispc_SOURCE_DIR}/bin/ispc" PARENT_SCOPE) + endif() - set(CMAKE_ISPC_COMPILER "${ext_ispc_SOURCE_DIR}/bin/ispc" PARENT_SCOPE) endif() endfunction() From a4a173e566e5935b89b97de610ae54622a416b55 Mon Sep 17 00:00:00 2001 From: Jan Lebert Date: Tue, 16 Apr 2024 17:40:58 -0700 Subject: [PATCH 10/22] Add macOS arm64 CI runner, fix macOS arm64 builds, fused multi-platform viewer app and python wheels (#6695) * Test macOS arm64 runner * Use matrix to run jobs for both Intel and Apple SIlicon * cleanup workflow, try to fix libomp related issue * Fuse x64 and arm64 wheels, fix wheel naming & unit tests * embree 4.3.1: fix macOS arm64 compile * Add prebuilt arm64 filament binaries * Fuse viewer app * Documentation CI fix by adding lxml[html_clean] to requirements. * Fix for uploading linux CUDA artifacts to github name clash --------- Co-authored-by: Sameer Sheorey --- .github/workflows/macos.yml | 207 +++++++++++++++++--- .github/workflows/ubuntu-cuda.yml | 2 +- 3rdparty/embree/embree.cmake | 10 +- 3rdparty/filament/filament_build.cmake | 1 + 3rdparty/filament/filament_download.cmake | 14 +- 3rdparty/librealsense/fix-macos-arm64.patch | 22 +++ 3rdparty/librealsense/librealsense.cmake | 3 + CHANGELOG.md | 1 + CMakeLists.txt | 17 +- cpp/tests/geometry/TriangleMesh.cpp | 145 +++++++++++--- docs/getting_started.in.rst | 6 +- docs/requirements.txt | 4 +- python/setup.py | 5 + python/test/ml_ops/test_radius_search.py | 4 +- util/ci_utils.sh | 10 +- 15 files changed, 373 insertions(+), 78 deletions(-) create mode 100644 3rdparty/librealsense/fix-macos-arm64.patch diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 16b242d081b..4ac23fd67a8 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -26,10 +26,13 @@ env: jobs: MacOS: - runs-on: macos-12 + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: + # macos-12 is Intel runner, macos-14 is Apple Silicon + # https://github.com/actions/runner-images + os: [macos-12, macos-14] CONFIG: [ON, OFF] env: BUILD_SHARED_LIBS: ${{ matrix.CONFIG }} @@ -47,26 +50,49 @@ jobs: path: ~/.ccache # We include the commit sha in the cache key, as new cache entries are # only created if there is no existing entry for the key yet. - key: ${{ runner.os }}-ccache-${{ github.sha }} + key: ${{ runner.os }}-${{ runner.arch }}-ccache-${{ github.sha }} # Restore any ccache cache entry, if none for - # ${{ runner.os }}-ccache-${{ github.sha }} exists. + # ${{ runner.os }}-${{ runner.arch }}-ccache-${{ github.sha }} exists. # Common prefix will be used so that ccache can be used across commits. restore-keys: | - ${{ runner.os }}-ccache + ${{ runner.os }}-${{ runner.arch }}-ccache + - name: Set up Python version uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.11' + - name: Install dependencies run: | brew install ccache pkg-config - # Install libomp 11.1.0 from old brew bottle for catalina (10.15). - # Directly installing the Ruby formula will install for the current OS + + if [[ ${{ runner.arch}} == "ARM64" ]]; then + # Fix gfortran not found issue + ln -s $(which gfortran-13) /usr/local/bin/gfortran + + # Default macos-14 image Xcode (version 15.0.1) linker causes build issues. + # Newer Xcode versions work, but embree recommends Apple clang <= 14 on + # arm64 to avoid possible "EXEC_BAD_INSTRUCTION" runtime exceptions: + # https://github.com/embree/embree/releases/tag/v4.3.1 + sudo xcode-select -switch /Applications/Xcode_14.3.1.app + fi + + # Install libomp 11.1.0 from old brew bottle for x64 catalina (10.15) + # / arm64 big sur (11.0). Directly installing the Ruby formula will + # install for the current OS. # https://github.com/microsoft/LightGBM/issues/4229 - brew unlink libomp - curl -L -H "Authorization: Bearer QQ==" -o libomp-11.1.0.catalina.bottle.tar.gz \ - https://ghcr.io/v2/homebrew/core/libomp/blobs/sha256:45a5aa653bd45bd5ff5858580b1a4670c4b5a51ea29d68d45a53f72f56010e05 - brew install -f libomp-11.1.0.catalina.bottle.tar.gz + if [[ ${{ runner.arch}} == "X64" ]]; then + brew unlink libomp + # x64 catalina (10.15) bottle + export LIBOMP_BOTTLE_HASH=45a5aa653bd45bd5ff5858580b1a4670c4b5a51ea29d68d45a53f72f56010e05 + else # ARM64 + # arm64 big_sur (11.0) bottle + export LIBOMP_BOTTLE_HASH=f87f7841eb8b72650fa771af39642361aec371ea1a1f94f081ecc0e8168a0e75 + fi + curl -L -H "Authorization: Bearer QQ==" -o libomp-11.1.0.bottle.tar.gz \ + https://ghcr.io/v2/homebrew/core/libomp/blobs/sha256:$LIBOMP_BOTTLE_HASH + brew install -f libomp-11.1.0.bottle.tar.gz + ccache -M 2G # See .github/workflows/readme.md for ccache strategy. - name: Config and build run: | @@ -82,7 +108,7 @@ jobs: pushd build make -j${NPROC} Open3DViewer pushd bin - zip -rv open3d-app-macosx-10_15.zip Open3D.app + zip -rv open3d-app-macosx-10_15-${{ runner.arch}}.zip Open3D.app ccache -s - name: Upload package @@ -116,21 +142,58 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} with: - name: open3d-app-macosx-10_15 - path: build/bin/open3d-app-macosx-10_15.zip + name: open3d-app-macosx-10_15-${{ runner.arch}} + path: build/bin/open3d-app-macosx-10_15-${{ runner.arch}}.zip + if-no-files-found: error + + fuse-viewer: + name: Fuse x64 and ARM64 viewer app + runs-on: [macos-12] + needs: [MacOS] + steps: + - name: Download viewer apps + uses: actions/download-artifact@v4 + with: + pattern: open3d-app-macosx-10_15-* + merge-multiple: true + + - name: Fuse x64 and arm64 viewer apps + run: | + unzip open3d-app-macosx-10_15-X64.zip -d x64 + unzip open3d-app-macosx-10_15-ARM64.zip -d arm64 + for i in arm64/Open3D.app/Contents/MacOS/*; do + filepath=Open3D.app/Contents/MacOS/$(basename $i) + lipo -create arm64/${filepath} x64/${filepath} -output arm64/${filepath} + done + mv arm64/Open3D.app Open3D.app + zip -rv open3d-app-macosx-10_15-universal2.zip Open3D.app + + - name: Upload Open3D viewer app + uses: actions/upload-artifact@v4 + with: + name: open3d-app-macosx-10_15-universal2 + path: open3d-app-macosx-10_15-universal2.zip if-no-files-found: error build-wheel: name: Build wheel - runs-on: macos-12 + runs-on: ${{ matrix.os }} strategy: fail-fast: false # https://github.community/t/how-to-conditionally-include-exclude-items-in-matrix-eg-based-on-branch/16853/6 matrix: + # macos-12 is Intel runner, macos-14 is Apple Silicon + # https://github.com/actions/runner-images + os: [macos-12, macos-14] python_version: ['3.8', '3.9', '3.10', '3.11'] is_main: - ${{ github.ref == 'refs/heads/main' }} exclude: + # TODO: remove macos-14 excludes when https://github.com/actions/setup-python/issues/808 is fixed + - os: macos-14 + python_version: '3.8' + - os: macos-14 + python_version: '3.9' - is_main: false python_version: '3.8' - is_main: false @@ -158,12 +221,12 @@ jobs: path: ~/.ccache # We include the commit sha in the cache key, as new cache entries are # only created if there is no existing entry for the key yet. - key: ${{ runner.os }}-ccache-${{ github.sha }} + key: ${{ runner.os }}-${{ runner.arch }}-ccache-${{ github.sha }} # Restore any ccache cache entry, if none for - # ${{ runner.os }}-ccache-${{ github.sha }} exists. + # ${{ runner.os }}-${{ runner.arch }}-ccache-${{ github.sha }} exists. # Common prefix will be used so that ccache can be used across commits. restore-keys: | - ${{ runner.os }}-ccache + ${{ runner.os }}-${{ runner.arch }}-ccache - name: Set up Python uses: actions/setup-python@v5 @@ -180,12 +243,26 @@ jobs: cmake --version source util/ci_utils.sh install_python_dependencies + + # Fix macos-14 arm64 runner image issues, see comments in MacOS job. + if [[ ${{ runner.arch}} == "ARM64" ]]; then + ln -s $(which gfortran-13) /usr/local/bin/gfortran + sudo xcode-select -switch /Applications/Xcode_14.3.1.app + fi + # Install libomp 11.1.0. See comment above. + if [[ ${{ runner.arch}} == "X64" ]]; then + brew unlink libomp + # x64 catalina (10.15) bottle + export LIBOMP_BOTTLE_HASH=45a5aa653bd45bd5ff5858580b1a4670c4b5a51ea29d68d45a53f72f56010e05 + else # ARM64 + # arm64 big_sur (11.0) bottle + export LIBOMP_BOTTLE_HASH=f87f7841eb8b72650fa771af39642361aec371ea1a1f94f081ecc0e8168a0e75 + fi + curl -L -H "Authorization: Bearer QQ==" -o libomp-11.1.0.bottle.tar.gz \ + https://ghcr.io/v2/homebrew/core/libomp/blobs/sha256:$LIBOMP_BOTTLE_HASH + brew install -f libomp-11.1.0.bottle.tar.gz brew install ccache - brew unlink libomp - curl -L -H "Authorization: Bearer QQ==" -o libomp-11.1.0.catalina.bottle.tar.gz \ - https://ghcr.io/v2/homebrew/core/libomp/blobs/sha256:45a5aa653bd45bd5ff5858580b1a4670c4b5a51ea29d68d45a53f72f56010e05 - brew install -f libomp-11.1.0.catalina.bottle.tar.gz ccache -M 2G # See .github/workflows/readme.md for ccache strategy. - name: Config and build wheel @@ -227,17 +304,97 @@ jobs: gsutil cp build/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} gs://open3d-releases/python-wheels/ echo "Download pip package at: https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }}" + fuse-wheel: + name: Fuse universal2 wheel + runs-on: [macos-12] + needs: [build-wheel] + strategy: + fail-fast: false + # https://github.community/t/how-to-conditionally-include-exclude-items-in-matrix-eg-based-on-branch/16853/6 + matrix: + python_version: ['3.10', '3.11'] + is_main: + - ${{ github.ref == 'refs/heads/main' }} + exclude: + - is_main: false + python_version: '3.10' + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python_version }} + - name: Download X64 wheels + uses: actions/download-artifact@v4 + with: + pattern: open3d-*macosx*_x86_64.whl + path: x64_wheels + merge-multiple: true + - name: Download ARM64 wheels + uses: actions/download-artifact@v4 + with: + pattern: open3d-*macosx*_arm64.whl + path: arm64_wheels + merge-multiple: true + - name: Fuse x64 and ARM64 wheels + env: + python_version: ${{ matrix.python_version }} + run: | + PYTAG="-cp$(echo ${{ env.python_version }} | tr -d '.')" + mkdir universal_wheels + + pip install delocate + delocate-fuse -v x64_wheels/open3d-*${PYTAG}*.whl arm64_wheels/open3d-*${PYTAG}*.whl + + # Normalize file name as delocate-fuse doesn't update it + OLD_WHL_NAME=$(basename x64_wheels/open3d-*${PYTAG}*.whl) + NEW_WHL_NAME=${OLD_WHL_NAME/x86_64/universal2} + mv x64_wheels/${OLD_WHL_NAME} universal_wheels/${NEW_WHL_NAME} + + echo "PIP_PKG_NAME=$NEW_WHL_NAME" >> $GITHUB_ENV + - name: Upload merged wheels + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PIP_PKG_NAME }} + path: universal_wheels/${{ env.PIP_PKG_NAME }} + if-no-files-found: error + + - name: GCloud CLI auth + if: ${{ github.ref == 'refs/heads/main' }} + uses: 'google-github-actions/auth@v2' + with: + project_id: ${{ secrets.GCE_PROJECT }} + credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' + - name: GCloud CLI setup + if: ${{ github.ref == 'refs/heads/main' }} + uses: google-github-actions/setup-gcloud@v2 + with: + version: ${{ env.GCE_CLI_GHA_VERSION }} + project_id: ${{ secrets.GCE_PROJECT }} + + - name: Upload wheel to GCS bucket + if: ${{ github.ref == 'refs/heads/main' }} + env: + python_version: ${{ matrix.python_version }} + run: | + gsutil cp universal_wheels/${{ env.PIP_PKG_NAME }} gs://open3d-releases/python-wheels/ + echo "Download pip package at: https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }}" + test-wheel: name: Test wheel - runs-on: macos-12 + runs-on: ${{ matrix.os }} needs: [build-wheel] strategy: fail-fast: false matrix: + os: [macos-12, macos-14] python_version: ['3.8', '3.9', '3.10', '3.11'] is_main: - ${{ github.ref == 'refs/heads/main' }} exclude: + - os: macos-14 + python_version: '3.8' + - os: macos-14 + python_version: '3.9' - is_main: false python_version: '3.8' - is_main: false @@ -273,7 +430,7 @@ jobs: python -V source util/ci_utils.sh pi_tag=$(python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')") - test_wheel open3d*-"$pi_tag"-*.whl + test_wheel open3d*-"$pi_tag"-*_$(uname -m).whl - name: Run Python unit tests (benchmarks) run: | @@ -286,7 +443,7 @@ jobs: # no need to run on macOS runs-on: ubuntu-latest if: ${{ github.ref == 'refs/heads/main' }} - needs: [build-wheel, MacOS] + needs: [fuse-wheel, MacOS] steps: - name: GCloud CLI auth uses: 'google-github-actions/auth@v2' diff --git a/.github/workflows/ubuntu-cuda.yml b/.github/workflows/ubuntu-cuda.yml index bc7dcf9f67a..250be1597fb 100644 --- a/.github/workflows/ubuntu-cuda.yml +++ b/.github/workflows/ubuntu-cuda.yml @@ -152,7 +152,7 @@ jobs: if: ${{ env.BUILD_PACKAGE == 'true' }} uses: actions/upload-artifact@v4 with: - name: open3d-devel-linux-x86_64-cuda-${CI_CONFIG} + name: open3d-devel-linux-x86_64-cuda-${{ matrix.CI_CONFIG }} path: open3d-devel-linux*.tar.xz if-no-files-found: error diff --git a/3rdparty/embree/embree.cmake b/3rdparty/embree/embree.cmake index e0794b55da0..35d27f11048 100644 --- a/3rdparty/embree/embree.cmake +++ b/3rdparty/embree/embree.cmake @@ -8,13 +8,11 @@ include(ExternalProject) # select ISAs if(APPLE) if(APPLE_AARCH64) - # Turn off ISA optimizations for Apple ARM64 for now. - set(ISA_ARGS -DEMBREE_ISA_AVX=OFF - -DEMBREE_ISA_AVX2=OFF - -DEMBREE_ISA_AVX512=OFF - -DEMBREE_ISA_SSE2=OFF - -DEMBREE_ISA_SSE42=OFF + set(ISA_ARGS -DEMBREE_ISA_NEON=OFF + -DEMBREE_ISA_NEON2X=ON ) + set(ISA_LIBS embree_avx2) + set(ISA_BUILD_BYPRODUCTS "/${Open3D_INSTALL_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}embree_avx2${CMAKE_STATIC_LIBRARY_SUFFIX}") else() # With AppleClang we can select only 1 ISA. set(ISA_ARGS -DEMBREE_ISA_AVX=OFF diff --git a/3rdparty/filament/filament_build.cmake b/3rdparty/filament/filament_build.cmake index cf2d650530e..432f55a28c0 100644 --- a/3rdparty/filament/filament_build.cmake +++ b/3rdparty/filament/filament_build.cmake @@ -72,5 +72,6 @@ ExternalProject_Add( -DFILAMENT_SUPPORTS_VULKAN=OFF -DFILAMENT_SKIP_SAMPLES=ON -DFILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB=20 # to support many small entities + -DSPIRV_WERROR=OFF BUILD_BYPRODUCTS ${lib_byproducts} ) diff --git a/3rdparty/filament/filament_download.cmake b/3rdparty/filament/filament_download.cmake index ef41dd17faf..f21cc91c31b 100644 --- a/3rdparty/filament/filament_download.cmake +++ b/3rdparty/filament/filament_download.cmake @@ -26,9 +26,17 @@ else() string(APPEND lib_dir /x86_64/md) endif() elseif(APPLE) - set(FILAMENT_URL https://github.com/google/filament/releases/download/v1.9.19/filament-v1.9.19-mac.tgz) - set(FILAMENT_SHA256 2765d0ce60647fc17d1880c4618cf7d6b5343d8be4dad87978c3917d9c723b4e) - string(APPEND lib_dir /x86_64) + if (APPLE_AARCH64) + set(FILAMENT_URL https://github.com/isl-org/open3d_downloads/releases/download/filament/filament-v1.9.19-macos_arm64.tgz) + set(FILAMENT_SHA256 3422bdff451d90144fbb69e625d8dcaeaf3222dc2c28879536067937955bc362) + string(APPEND lib_dir /arm64) + # Our arm64 builds use FILAMENT_SUPPORTS_VULKAN=OFF + list(REMOVE_ITEM filament_LIBRARIES bluevk) + else() + set(FILAMENT_URL https://github.com/google/filament/releases/download/v1.9.19/filament-v1.9.19-mac.tgz) + set(FILAMENT_SHA256 2765d0ce60647fc17d1880c4618cf7d6b5343d8be4dad87978c3917d9c723b4e) + string(APPEND lib_dir /x86_64) + endif() else() # Linux: Check glibc version and use open3d filament binary if new (Ubuntu 20.04 and similar) execute_process(COMMAND ldd --version OUTPUT_VARIABLE ldd_version) string(REGEX MATCH "([0-9]+\.)+[0-9]+" glibc_version ${ldd_version}) diff --git a/3rdparty/librealsense/fix-macos-arm64.patch b/3rdparty/librealsense/fix-macos-arm64.patch new file mode 100644 index 00000000000..0de73a574a1 --- /dev/null +++ b/3rdparty/librealsense/fix-macos-arm64.patch @@ -0,0 +1,22 @@ +From beb4c44debc8336de991c983274cad841eb5c323 Mon Sep 17 00:00:00 2001 +From: Pavol Rusnak +Date: Sun, 20 Jun 2021 12:26:58 +0200 +Subject: [PATCH] Fix build on macOS arm64 + +--- + src/proc/color-formats-converter.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/proc/color-formats-converter.cpp b/src/proc/color-formats-converter.cpp +index 564a23d9c4..6c6c8c97d8 100644 +--- a/src/proc/color-formats-converter.cpp ++++ b/src/proc/color-formats-converter.cpp +@@ -18,7 +18,7 @@ + #include // For SSSE3 intrinsics + #endif + +-#if defined (ANDROID) || (defined (__linux__) && !defined (__x86_64__)) ++#if defined (ANDROID) || (defined (__linux__) && !defined (__x86_64__)) || (defined (__APPLE__) && !defined (__x86_64__)) + + bool has_avx() { return false; } + diff --git a/3rdparty/librealsense/librealsense.cmake b/3rdparty/librealsense/librealsense.cmake index c6b4e358e32..e5caa700df7 100644 --- a/3rdparty/librealsense/librealsense.cmake +++ b/3rdparty/librealsense/librealsense.cmake @@ -17,6 +17,9 @@ ExternalProject_Add( COMMAND ${GIT_EXECUTABLE} init COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_LIST_DIR}/fix-cudacrt.patch + # Patch for macOS ARM64 support for versions < 2.50.0 + COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace + ${CMAKE_CURRENT_LIST_DIR}/fix-macos-arm64.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DBUILD_SHARED_LIBS=OFF diff --git a/CHANGELOG.md b/CHANGELOG.md index 3509dda95e6..af429044ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Fix regression in printing cuda tensor from PR #6444 🐛 - Add Python pathlib support for file IO (PR #6619) - Fix log error message for `probability` argument validation in `PointCloud::SegmentPlane` (PR #6622) +- Fix macOS arm64 builds, add CI runner for macOS arm64 (PR #6695) ## 0.13 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d03dad7dc6..7abd4307b91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,6 @@ cmake_minimum_required(VERSION 3.20) # CMake 3.20+ is required to: # - detect IntelLLVM compiler for SYCL -if (APPLE) -set (CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING - "Minimum OS X deployment version" FORCE) -endif() - # CMAKE_HOST_SYSTEM_PROCESSOR is only available after calling project(), # which depends on ${OPEN3D_VERSION}, which depends on ${DEVELOPER_BUILD}. if(UNIX AND NOT APPLE) @@ -35,6 +30,11 @@ if(APPLE) ) if(PROCESSOR_ARCH STREQUAL "arm64") set(APPLE_AARCH64 TRUE) + set (CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING + "Minimum OS X deployment version" FORCE) + else() + set (CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING + "Minimum OS X deployment version" FORCE) endif() endif() @@ -117,9 +117,12 @@ option(USE_SYSTEM_VTK "Use system pre-installed VTK" OFF option(USE_SYSTEM_ZEROMQ "Use system pre-installed ZeroMQ" OFF) if(LINUX_AARCH64 OR APPLE_AARCH64) option(BUILD_VTK_FROM_SOURCE "Build VTK from source" ON ) - option(BUILD_FILAMENT_FROM_SOURCE "Build filament from source" ON ) else() option(BUILD_VTK_FROM_SOURCE "Build VTK from source" OFF) +endif() +if(LINUX_AARCH64) + option(BUILD_FILAMENT_FROM_SOURCE "Build filament from source" ON ) +else() option(BUILD_FILAMENT_FROM_SOURCE "Build filament from source" OFF) endif() @@ -198,7 +201,7 @@ cmake_policy(GET CMP0072 CMP0072_VALUE) if ((LINUX_AARCH64 OR APPLE_AARCH64) AND BUILD_ISPC_MODULE) message(FATAL_ERROR "ISPC module is not yet supported on ARM Linux") endif() -if ((LINUX_AARCH64 OR APPLE_AARCH64) AND NOT BUILD_FILAMENT_FROM_SOURCE) +if (LINUX_AARCH64 AND NOT BUILD_FILAMENT_FROM_SOURCE) message(FATAL_ERROR "ARM CPU detected, you must set BUILD_FILAMENT_FROM_SOURCE=ON.") endif() if ((LINUX_AARCH64 OR APPLE_AARCH64) AND NOT USE_BLAS) diff --git a/cpp/tests/geometry/TriangleMesh.cpp b/cpp/tests/geometry/TriangleMesh.cpp index 559330bb2a0..ff6d75f6c8c 100644 --- a/cpp/tests/geometry/TriangleMesh.cpp +++ b/cpp/tests/geometry/TriangleMesh.cpp @@ -1843,33 +1843,121 @@ TEST(TriangleMesh, CreateFromPointCloudPoisson) { {0.742035, 0.885688, 0.458892}, {0.742035, 0.885688, 0.458892}, {0.383097, 0.761093, 0.173810}, {0.284898, 0.359292, 0.669062}}; mesh_gt.triangles_ = { - {1, 13, 0}, {0, 14, 2}, {13, 14, 0}, {1, 15, 13}, - {15, 16, 13}, {1, 3, 15}, {14, 13, 16}, {16, 17, 14}, - {2, 18, 4}, {14, 18, 2}, {4, 18, 5}, {18, 14, 17}, - {17, 19, 18}, {18, 19, 6}, {6, 5, 18}, {7, 16, 15}, - {7, 8, 16}, {8, 20, 16}, {21, 16, 20}, {17, 16, 21}, - {9, 20, 8}, {10, 20, 9}, {21, 20, 10}, {22, 17, 21}, - {19, 17, 22}, {19, 22, 11}, {11, 6, 19}, {22, 21, 10}, - {10, 12, 22}, {11, 22, 12}, {24, 0, 23}, {1, 0, 24}, - {0, 2, 25}, {25, 23, 0}, {3, 1, 24}, {24, 26, 3}, - {2, 4, 27}, {27, 25, 2}, {27, 5, 28}, {4, 5, 27}, - {28, 6, 29}, {5, 6, 28}, {8, 7, 30}, {30, 31, 8}, - {32, 8, 31}, {9, 8, 32}, {33, 9, 32}, {10, 9, 33}, - {6, 11, 34}, {34, 29, 6}, {12, 10, 33}, {33, 35, 12}, - {34, 12, 35}, {11, 12, 34}, {24, 23, 48}, {36, 23, 25}, - {36, 37, 23}, {37, 48, 23}, {38, 24, 48}, {38, 39, 24}, - {39, 26, 24}, {39, 49, 26}, {38, 48, 37}, {25, 27, 40}, - {40, 36, 25}, {27, 28, 41}, {41, 40, 27}, {28, 29, 42}, - {42, 41, 28}, {39, 30, 49}, {39, 31, 30}, {39, 50, 31}, - {39, 43, 50}, {44, 50, 43}, {32, 31, 50}, {44, 32, 50}, - {44, 45, 32}, {45, 33, 32}, {42, 34, 46}, {29, 34, 42}, - {47, 33, 45}, {35, 33, 47}, {34, 35, 47}, {47, 46, 34}, - {37, 36, 51}, {39, 38, 52}, {53, 52, 51}, {52, 37, 51}, - {52, 38, 37}, {36, 40, 54}, {54, 51, 36}, {54, 40, 41}, - {51, 54, 55}, {55, 53, 51}, {55, 41, 42}, {54, 41, 55}, - {43, 39, 52}, {56, 52, 53}, {56, 44, 52}, {44, 43, 52}, - {45, 44, 56}, {56, 55, 57}, {53, 55, 56}, {55, 42, 46}, - {46, 57, 55}, {45, 56, 57}, {57, 47, 45}, {57, 46, 47}}; + {1, 13, 0}, + {0, 14, 2}, + {13, 14, 0}, + {1, 15, 13}, + {15, 16, 13}, + {1, 3, 15}, + {14, 13, 16}, + {16, 17, 14}, + {2, 18, 4}, + {14, 18, 2}, + {4, 18, 5}, + {18, 14, 17}, + {17, 19, 18}, + {18, 19, 6}, + {6, 5, 18}, + {7, 16, 15}, + {7, 8, 16}, + {8, 20, 16}, + {21, 16, 20}, + {17, 16, 21}, + {9, 20, 8}, + {10, 20, 9}, + {21, 20, 10}, + {22, 17, 21}, + {19, 17, 22}, + {19, 22, 11}, + {11, 6, 19}, + {22, 21, 10}, + {10, 12, 22}, + {11, 22, 12}, + {24, 0, 23}, + {1, 0, 24}, + {0, 2, 25}, + {25, 23, 0}, + {3, 1, 24}, + {24, 26, 3}, + {2, 4, 27}, + {27, 25, 2}, + {27, 5, 28}, + {4, 5, 27}, + {28, 6, 29}, + {5, 6, 28}, + {8, 7, 30}, + {30, 31, 8}, + {32, 8, 31}, + {9, 8, 32}, + {33, 9, 32}, + {10, 9, 33}, + {6, 11, 34}, + {34, 29, 6}, + {12, 10, 33}, + {33, 35, 12}, + {34, 12, 35}, + {11, 12, 34}, + {24, 23, 48}, + {36, 23, 25}, + {36, 37, 23}, + {37, 48, 23}, + {38, 24, 48}, + {38, 39, 24}, + {39, 26, 24}, + {39, 49, 26}, + {38, 48, 37}, + {25, 27, 40}, + {40, 36, 25}, + {27, 28, 41}, + {41, 40, 27}, + {28, 29, 42}, + {42, 41, 28}, + {39, 30, 49}, + {39, 31, 30}, + {39, 50, 31}, + {39, 43, 50}, + {44, 50, 43}, + {32, 31, 50}, +#if defined(__APPLE__) && defined(__arm64__) + // Apple Silicon consistently triangulates the vertices differently + {44, 45, 50}, + {45, 32, 50}, +#else + {44, 32, 50}, + {44, 45, 32}, +#endif + {45, 33, 32}, + {42, 34, 46}, + {29, 34, 42}, + {47, 33, 45}, + {35, 33, 47}, + {34, 35, 47}, + {47, 46, 34}, + {37, 36, 51}, + {39, 38, 52}, + {53, 52, 51}, + {52, 37, 51}, + {52, 38, 37}, + {36, 40, 54}, + {54, 51, 36}, + {54, 40, 41}, + {51, 54, 55}, + {55, 53, 51}, + {55, 41, 42}, + {54, 41, 55}, + {43, 39, 52}, + {56, 52, 53}, + {56, 44, 52}, + {44, 43, 52}, + {45, 44, 56}, + {56, 55, 57}, + {53, 55, 56}, + {55, 42, 46}, + {46, 57, 55}, + {45, 56, 57}, + {57, 47, 45}, + {57, 46, 47} + }; std::vector densities_gt = { 0.39865168929100037, 0.32580316066741943, 0.39778709411621094, 0.2200755625963211, 0.428702175617218, 0.4288075268268585, @@ -2208,7 +2296,8 @@ TEST(TriangleMesh, CreateMeshCoordinateFrame) { indices.push_back(output_tm->triangles_[i](1, 0)); indices.push_back(output_tm->triangles_[i](2, 0)); } - unique(indices.begin(), indices.end()); + auto last = unique(indices.begin(), indices.end()); + indices.erase(last, indices.end()); sort(indices.begin(), indices.end()); auto output = output_tm->SelectByIndex(indices); diff --git a/docs/getting_started.in.rst b/docs/getting_started.in.rst index b9f390ea835..4d3ccc84214 100644 --- a/docs/getting_started.in.rst +++ b/docs/getting_started.in.rst @@ -82,9 +82,9 @@ version (``HEAD`` of ``main`` branch): - `Python 3.11 `__ * - MacOS - - `Python 3.8 `__ - - `Python 3.9 `__ - - `Python 3.10 `__ + - `Python 3.8 `__ + - `Python 3.9 `__ + - `Python 3.10 `__ - `Python 3.11 `__ * - Windows diff --git a/docs/requirements.txt b/docs/requirements.txt index 299c15f5a84..a974fc66a1d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,4 +4,6 @@ jinja2==3.1.3 m2r2==0.3.3.post2 matplotlib==3.7.3 nbsphinx==0.9.3 -sphinx==7.1.2 \ No newline at end of file +sphinx==7.1.2 +nbconvert==6.5.4 +lxml[html_clean]==5.2.1 diff --git a/python/setup.py b/python/setup.py index 029c5717180..e32f69ab615 100644 --- a/python/setup.py +++ b/python/setup.py @@ -70,6 +70,11 @@ def get_tag(self): libc.gnu_get_libc_version.restype = ctypes.c_char_p GLIBC_VER = libc.gnu_get_libc_version().decode("utf8").split(".") plat = f"manylinux_{GLIBC_VER[0]}_{GLIBC_VER[1]}{plat[5:]}" + elif plat[:6] == "macosx": + # If the Python interpreter is an universal2 app the resulting wheel is tagged as + # universal2 instead of the current architecture. This is a workaround to fix it. + plat = plat.replace("universal2", platform.machine()) + return python, abi, plat diff --git a/python/test/ml_ops/test_radius_search.py b/python/test/ml_ops/test_radius_search.py index 85b2af0ad0d..b799b2c7602 100644 --- a/python/test/ml_ops/test_radius_search.py +++ b/python/test/ml_ops/test_radius_search.py @@ -103,7 +103,7 @@ def test_radius_search(dtype, ml, num_points_queries, metric, if normalize_distances: gt_dist /= radii[i] - np.testing.assert_allclose(dist, gt_dist, rtol=1e-7, atol=1e-8) + np.testing.assert_allclose(dist, gt_dist, rtol=1e-7, atol=1e-7) @mltest.parametrize.ml_cpu_only @@ -236,4 +236,4 @@ def test_radius_search_batches(ml, batch_size): if normalize_distances: gt_dist /= radii[i] - np.testing.assert_allclose(dist, gt_dist, rtol=1e-7, atol=1e-8) + np.testing.assert_allclose(dist, gt_dist, rtol=1e-7, atol=1e-7) diff --git a/util/ci_utils.sh b/util/ci_utils.sh index 38ff654880e..aa30d2c12ad 100644 --- a/util/ci_utils.sh +++ b/util/ci_utils.sh @@ -55,8 +55,14 @@ install_python_dependencies() { TF_ARCH_DISABLE_NAME=tensorflow-cpu TORCH_GLNX="torch==$TORCH_CUDA_GLNX_VER" else - TF_ARCH_NAME=tensorflow-cpu - TF_ARCH_DISABLE_NAME=tensorflow + # tensorflow-cpu wheels for macOS arm64 are not available + if [[ "$OSTYPE" == "darwin"* ]]; then + TF_ARCH_NAME=tensorflow + TF_ARCH_DISABLE_NAME=tensorflow + else + TF_ARCH_NAME=tensorflow-cpu + TF_ARCH_DISABLE_NAME=tensorflow + fi TORCH_GLNX="torch==$TORCH_CPU_GLNX_VER" fi From 74dcbe87426b15e921bcc28825a5afc5673c9b0f Mon Sep 17 00:00:00 2001 From: Luis Alonso Murillo Rojas Date: Wed, 17 Apr 2024 14:33:21 -0600 Subject: [PATCH 11/22] Fix check in `ConvertFromPinholeCameraParameters` function from `ViewControl` (#6711) --- cpp/open3d/visualization/visualizer/ViewControl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/open3d/visualization/visualizer/ViewControl.cpp b/cpp/open3d/visualization/visualizer/ViewControl.cpp index a4dff55a64a..a4ba2aae314 100644 --- a/cpp/open3d/visualization/visualizer/ViewControl.cpp +++ b/cpp/open3d/visualization/visualizer/ViewControl.cpp @@ -182,8 +182,8 @@ bool ViewControl::ConvertFromPinholeCameraParameters( window_width_ != intrinsic.width_ || std::abs(intrinsic.intrinsic_matrix_(0, 2) - ((double)window_width_ / 2.0 - 0.5)) > threshold || - std::abs(intrinsic.intrinsic_matrix_(1, 2) = - ((double)window_height_ / 2.0 - 0.5)) > threshold)) { + std::abs(intrinsic.intrinsic_matrix_(1, 2) - + ((double)window_height_ / 2.0 - 0.5)) > threshold)) { utility::LogWarning( "[ViewControl] ConvertFromPinholeCameraParameters() failed " "because window height and width do not match."); From 5c39bc00e4c70c3c468317654d84f707900c6064 Mon Sep 17 00:00:00 2001 From: Daniel Rauch Date: Wed, 17 Apr 2024 22:35:54 +0200 Subject: [PATCH 12/22] Fixes and improvements for point selection in O3DVisualizer (fixes #6725) (#6733) - Make points removable by correctly forwarding the key modifier to the already implemented function - Fix bounding box calculation for picked point size selection (manual bbox extend calculation was wrong) - Fix crash occurring when trying to make selection and non-pickable geometries where in scene - Fix copy&paste error in help text for MACOS --- .../gui/PickPointsInteractor.cpp | 65 ++++++++++--------- .../visualizer/O3DVisualizer.cpp | 22 ++++--- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/cpp/open3d/visualization/gui/PickPointsInteractor.cpp b/cpp/open3d/visualization/gui/PickPointsInteractor.cpp index 45ca7c44c1e..63ecfba7955 100644 --- a/cpp/open3d/visualization/gui/PickPointsInteractor.cpp +++ b/cpp/open3d/visualization/gui/PickPointsInteractor.cpp @@ -155,8 +155,6 @@ void PickPointsInteractor::SetPickableGeometry( // TriangleMesh so that occluded points are not selected. points_.clear(); for (auto &pg : geometry) { - lookup_->Add(pg.name, points_.size()); - auto cloud = dynamic_cast(pg.geometry); auto tcloud = dynamic_cast(pg.tgeometry); @@ -166,6 +164,12 @@ void PickPointsInteractor::SetPickableGeometry( auto lineset = dynamic_cast(pg.geometry); auto tlineset = dynamic_cast(pg.tgeometry); + + // only add geometry with pickable points + if (cloud || tcloud || mesh || tmesh || lineset || tlineset) { + lookup_->Add(pg.name, points_.size()); + } + if (cloud) { points_.insert(points_.end(), cloud->points_.begin(), cloud->points_.end()); @@ -268,35 +272,38 @@ void PickPointsInteractor::SetOnStartedPolygonPicking( } void PickPointsInteractor::Mouse(const MouseEvent &e) { - if (e.type == MouseEvent::BUTTON_UP) { - if (e.modifiers & int(KeyModifier::ALT)) { - if (pending_.empty() || pending_.back().keymods == 0) { - pending_.push({{gui::Point(e.x, e.y)}, int(KeyModifier::ALT)}); - if (on_ui_changed_) { - on_ui_changed_({}); - } - } else { - pending_.back().polygon.push_back(gui::Point(e.x, e.y)); - if (on_started_poly_pick_) { - on_started_poly_pick_(); - } - if (on_ui_changed_) { - std::vector lines; - auto &polygon = pending_.back().polygon; - for (size_t i = 1; i < polygon.size(); ++i) { - auto &p0 = polygon[i - 1]; - auto &p1 = polygon[i]; - lines.push_back({p0.x, p0.y}); - lines.push_back({p1.x, p1.y}); - } - lines.push_back({polygon.back().x, polygon.back().y}); - lines.push_back({polygon[0].x, polygon[0].y}); - on_ui_changed_(lines); - } + if (e.type != MouseEvent::BUTTON_UP) return; + + bool polygon_picking_requested = e.modifiers & int(KeyModifier::ALT); + if (!polygon_picking_requested) { + // standard point picking + pending_.push({{gui::Point(e.x, e.y)}, e.modifiers}); + DoPick(); + } else { + // special polygon picking mode + if (pending_.empty() || pending_.back().keymods == 0) { + pending_.push({{gui::Point(e.x, e.y)}, e.modifiers}); + if (on_ui_changed_) { + on_ui_changed_({}); } } else { - pending_.push({{gui::Point(e.x, e.y)}, 0}); - DoPick(); + pending_.back().polygon.push_back(gui::Point(e.x, e.y)); + if (on_started_poly_pick_) { + on_started_poly_pick_(); + } + if (on_ui_changed_) { + std::vector lines; + auto &polygon = pending_.back().polygon; + for (size_t i = 1; i < polygon.size(); ++i) { + auto &p0 = polygon[i - 1]; + auto &p1 = polygon[i]; + lines.push_back({p0.x, p0.y}); + lines.push_back({p1.x, p1.y}); + } + lines.push_back({polygon.back().x, polygon.back().y}); + lines.push_back({polygon[0].x, polygon[0].y}); + on_ui_changed_(lines); + } } } } diff --git a/cpp/open3d/visualization/visualizer/O3DVisualizer.cpp b/cpp/open3d/visualization/visualizer/O3DVisualizer.cpp index 3ab06cec1b9..554d38b2466 100644 --- a/cpp/open3d/visualization/visualizer/O3DVisualizer.cpp +++ b/cpp/open3d/visualization/visualizer/O3DVisualizer.cpp @@ -384,7 +384,9 @@ struct O3DVisualizer::Impl { std::vector>> &indices, int keymods) { - if ((keymods & int(KeyModifier::SHIFT)) || + bool unselect_mode_requested = + keymods & int(KeyModifier::SHIFT); + if (unselect_mode_requested || polygon_selection_unselects_) { selections_->UnselectIndices(indices); } else { @@ -488,11 +490,13 @@ struct O3DVisualizer::Impl { }); #if __APPLE__ - const char *selection_help = - "Cmd-click to select a point\nCmd-ctrl-click to polygon select"; + const char *selection_help = R"(Cmd-click to select a point +Cmd-shift-click to deselect a point +Cmd-alt-click to polygon select)"; #else - const char *selection_help = - "Ctrl-click to select a point\nCmd-alt-click to polygon select"; + const char *selection_help = R"(Ctrl-click to select a point +Ctrl-shift-click to deselect a point +Ctrl-alt-click to polygon select)"; #endif // __APPLE__ h = new Horiz(); h->AddStretch(); @@ -1545,12 +1549,10 @@ struct O3DVisualizer::Impl { OverrideMaterial(o.name, o.material, ui_state_.scene_shader); } - auto bbox = scene_->GetScene()->GetBoundingBox(); - auto xdim = bbox.max_bound_.x() - bbox.min_bound_.x(); - auto ydim = bbox.max_bound_.y() - bbox.min_bound_.z(); - auto zdim = bbox.max_bound_.z() - bbox.min_bound_.y(); + auto bbox_extend = scene_->GetScene()->GetBoundingBox().GetExtent(); auto psize = double(std::max(5, px)) * 0.000666 * - std::max(xdim, std::max(ydim, zdim)); + std::max(bbox_extend.x(), + std::max(bbox_extend.y(), bbox_extend.z())); selections_->SetPointSize(psize); scene_->SetPickablePointSize(px); From 785878f062aac4e52b791ad726504afddcfc26c2 Mon Sep 17 00:00:00 2001 From: Pier Angelo Vendrame Date: Thu, 18 Apr 2024 01:03:47 +0200 Subject: [PATCH 13/22] Simplify data_ and data_interface_ in KDTreeFlann. (#6734) KDTreeFlann::SetRawData had some confusion around data, data_, and data_interface_. As a result, the data from the parameter was copied to the member std::vector, but the interface (which takes an Eigen::Map) used the data passed as an argument to the method. As a result, the searches risked to be run on a dangling pointer, instead of the intended internal storage. However, rather than adjusting the pointer for Eigen::Map, I preferred simplifying the code, and copy the argument to an Eigen::MatrixXd. --- CHANGELOG.md | 2 ++ cpp/open3d/geometry/KDTreeFlann.cpp | 21 ++++++--------------- cpp/open3d/geometry/KDTreeFlann.h | 14 +++++--------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af429044ee2..b68b0bdbd02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## Main + - Fix TriangleMesh::SamplePointsUniformly not sampling triangle meshes uniformly (PR #6653) - Fix tensor based TSDF integration example. - Use GLIBCXX_USE_CXX11_ABI=ON by default @@ -33,6 +34,7 @@ - Add Python pathlib support for file IO (PR #6619) - Fix log error message for `probability` argument validation in `PointCloud::SegmentPlane` (PR #6622) - Fix macOS arm64 builds, add CI runner for macOS arm64 (PR #6695) +- Fix KDTreeFlann possibly using a dangling pointer instead of internal storage and simplified its members (PR #6734) ## 0.13 diff --git a/cpp/open3d/geometry/KDTreeFlann.cpp b/cpp/open3d/geometry/KDTreeFlann.cpp index d93175a8d43..c81814a724d 100644 --- a/cpp/open3d/geometry/KDTreeFlann.cpp +++ b/cpp/open3d/geometry/KDTreeFlann.cpp @@ -97,8 +97,7 @@ int KDTreeFlann::SearchKNN(const T &query, // This is optimized code for heavily repeated search. // Other flann::Index::knnSearch() implementations lose performance due to // memory allocation/deallocation. - if (data_.empty() || dataset_size_ <= 0 || - size_t(query.rows()) != dimension_ || knn < 0) { + if (data_.size() == 0 || query.rows() != data_.rows() || knn < 0) { return -1; } indices.resize(knn); @@ -121,8 +120,7 @@ int KDTreeFlann::SearchRadius(const T &query, // Since max_nn is not given, we let flann to do its own memory management. // Other flann::Index::radiusSearch() implementations lose performance due // to memory management and CPU caching. - if (data_.empty() || dataset_size_ <= 0 || - size_t(query.rows()) != dimension_) { + if (data_.size() == 0 || query.rows() != data_.rows()) { return -1; } std::vector> indices_dists; @@ -148,8 +146,7 @@ int KDTreeFlann::SearchHybrid(const T &query, // It is also the recommended setting for search. // Other flann::Index::radiusSearch() implementations lose performance due // to memory allocation/deallocation. - if (data_.empty() || dataset_size_ <= 0 || - size_t(query.rows()) != dimension_ || max_nn < 0) { + if (data_.size() == 0 || query.rows() != data_.rows() || max_nn < 0) { return -1; } distance2.resize(max_nn); @@ -166,18 +163,12 @@ int KDTreeFlann::SearchHybrid(const T &query, } bool KDTreeFlann::SetRawData(const Eigen::Map &data) { - dimension_ = data.rows(); - dataset_size_ = data.cols(); - if (dimension_ == 0 || dataset_size_ == 0) { + if (data.size() == 0) { utility::LogWarning("[KDTreeFlann::SetRawData] Failed due to no data."); return false; } - data_.resize(dataset_size_ * dimension_); - memcpy(data_.data(), data.data(), - dataset_size_ * dimension_ * sizeof(double)); - data_interface_.reset(new Eigen::Map(data)); - nanoflann_index_.reset( - new KDTree_t(dimension_, std::cref(*data_interface_), 15)); + data_ = data; + nanoflann_index_ = std::make_unique(data_.rows(), data_, 15); nanoflann_index_->index_->buildIndex(); return true; } diff --git a/cpp/open3d/geometry/KDTreeFlann.h b/cpp/open3d/geometry/KDTreeFlann.h index 3d3ab2c5bea..cb41fccafb7 100644 --- a/cpp/open3d/geometry/KDTreeFlann.h +++ b/cpp/open3d/geometry/KDTreeFlann.h @@ -97,17 +97,13 @@ class KDTreeFlann { bool SetRawData(const Eigen::Map &data); protected: - using KDTree_t = nanoflann::KDTreeEigenMatrixAdaptor< - Eigen::Map, - -1, - nanoflann::metric_L2, - false>; + using KDTree_t = nanoflann::KDTreeEigenMatrixAdaptor; - std::vector data_; - std::unique_ptr> data_interface_; + Eigen::MatrixXd data_; std::unique_ptr nanoflann_index_; - size_t dimension_ = 0; - size_t dataset_size_ = 0; }; } // namespace geometry From 5c982c7b5edc76f899860e2594a950c5c23ec88f Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:15:13 -0700 Subject: [PATCH 14/22] Convert triangle mesh model to tmesh map (#6758) - ToString() for Material and string representation in Python. - MaterialRecord to Material conversion - allows converting triangle mesh models to a map of tmeshes, including materials. Limitation: Only one material per tmesh (same as TriangleMeshModel) - Support for emissive color reading in FileASSIMP (model) and writing for tmesh (tio FIleASSIMP) --- cpp/open3d/io/file_format/FileASSIMP.cpp | 3 + cpp/open3d/t/geometry/TriangleMesh.cpp | 25 +++++- cpp/open3d/t/geometry/TriangleMesh.h | 27 ++++++- cpp/open3d/t/io/file_format/FileASSIMP.cpp | 10 ++- .../visualization/rendering/Material.cpp | 78 +++++++++++++++++++ cpp/open3d/visualization/rendering/Material.h | 16 ++++ cpp/pybind/t/geometry/trianglemesh.cpp | 34 ++++++-- .../visualization/rendering/material.cpp | 4 + cpp/tests/t/geometry/TriangleMesh.cpp | 1 + docs/getting_started.in.rst | 9 ++- 10 files changed, 190 insertions(+), 17 deletions(-) diff --git a/cpp/open3d/io/file_format/FileASSIMP.cpp b/cpp/open3d/io/file_format/FileASSIMP.cpp index e78d8be7718..92a2fda8077 100644 --- a/cpp/open3d/io/file_format/FileASSIMP.cpp +++ b/cpp/open3d/io/file_format/FileASSIMP.cpp @@ -422,6 +422,9 @@ bool ReadModelUsingAssimp(const std::string& filename, mat->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, o3d_mat.base_clearcoat_roughness); mat->Get(AI_MATKEY_ANISOTROPY, o3d_mat.base_anisotropy); + mat->Get(AI_MATKEY_COLOR_EMISSIVE, color); + o3d_mat.emissive_color = + Eigen::Vector4f(color.r, color.g, color.b, 1.f); aiString alpha_mode; mat->Get(AI_MATKEY_GLTF_ALPHAMODE, alpha_mode); std::string alpha_mode_str(alpha_mode.C_Str()); diff --git a/cpp/open3d/t/geometry/TriangleMesh.cpp b/cpp/open3d/t/geometry/TriangleMesh.cpp index 2bf329838cc..43899aaf58f 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.cpp +++ b/cpp/open3d/t/geometry/TriangleMesh.cpp @@ -377,6 +377,7 @@ geometry::TriangleMesh TriangleMesh::FromLegacy( tmat.SetAnisotropy(mat.baseAnisotropy); tmat.SetBaseClearcoat(mat.baseClearCoat); tmat.SetBaseClearcoatRoughness(mat.baseClearCoatRoughness); + // no emissive_color in legacy mesh material if (mat.albedo) tmat.SetAlbedoMap(Image::FromLegacy(*mat.albedo)); if (mat.normalMap) tmat.SetNormalMap(Image::FromLegacy(*mat.normalMap)); if (mat.roughness) @@ -453,10 +454,6 @@ open3d::geometry::TriangleMesh TriangleMesh::ToLegacy() const { legacy_mat.baseColor.f4[1] = tmat.GetBaseColor().y(); legacy_mat.baseColor.f4[2] = tmat.GetBaseColor().z(); legacy_mat.baseColor.f4[3] = tmat.GetBaseColor().w(); - utility::LogWarning("{},{},{},{}", legacy_mat.baseColor.f4[0], - legacy_mat.baseColor.f4[1], - legacy_mat.baseColor.f4[2], - legacy_mat.baseColor.f4[3]); } if (tmat.HasBaseRoughness()) { legacy_mat.baseRoughness = tmat.GetBaseRoughness(); @@ -523,6 +520,26 @@ open3d::geometry::TriangleMesh TriangleMesh::ToLegacy() const { return mesh_legacy; } +std::unordered_map +TriangleMesh::FromTriangleMeshModel( + const open3d::visualization::rendering::TriangleMeshModel &model, + core::Dtype float_dtype, + core::Dtype int_dtype, + const core::Device &device) { + std::unordered_map tmeshes; + for (const auto &mobj : model.meshes_) { + auto tmesh = TriangleMesh::FromLegacy(*mobj.mesh, float_dtype, + int_dtype, device); + // material textures will be on the CPU. GPU resident texture images is + // not yet supported. See comment in Material.cpp + tmesh.SetMaterial( + visualization::rendering::Material::FromMaterialRecord( + model.materials_[mobj.material_idx])); + tmeshes.emplace(mobj.mesh_name, tmesh); + } + return tmeshes; +} + TriangleMesh TriangleMesh::To(const core::Device &device, bool copy) const { if (!copy && GetDevice() == device) { return *this; diff --git a/cpp/open3d/t/geometry/TriangleMesh.h b/cpp/open3d/t/geometry/TriangleMesh.h index 4a1f9138fd6..fc592b0a9f9 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.h +++ b/cpp/open3d/t/geometry/TriangleMesh.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include "open3d/core/Tensor.h" #include "open3d/core/TensorCheck.h" @@ -16,6 +17,7 @@ #include "open3d/t/geometry/DrawableGeometry.h" #include "open3d/t/geometry/Geometry.h" #include "open3d/t/geometry/TensorMap.h" +#include "open3d/visualization/rendering/Model.h" namespace open3d { namespace t { @@ -701,7 +703,8 @@ class TriangleMesh : public Geometry, public DrawableGeometry { /// values, e.g. vertices, normals, colors. /// \param int_dtype Int32 or Int64, used to store index values, e.g. /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. + /// \param device The device where the resulting TriangleMesh resides in + /// (default CPU:0). static geometry::TriangleMesh FromLegacy( const open3d::geometry::TriangleMesh &mesh_legacy, core::Dtype float_dtype = core::Float32, @@ -711,6 +714,28 @@ class TriangleMesh : public Geometry, public DrawableGeometry { /// Convert to a legacy Open3D TriangleMesh. open3d::geometry::TriangleMesh ToLegacy() const; + /// Convert a TriangleMeshModel (e.g. as read from a file with + /// open3d::io::ReadTriangleMeshModel) to an unordered map of mesh names to + /// TriangleMeshes. Only one material is supported per mesh. Materials + /// common to multiple meshes will be dupicated. Textures (as + /// t::geometry::Image) will use shared storage. + /// \param model TriangleMeshModel to convert. + /// \param float_dtype Float32 or Float64, used to store floating point + /// values, e.g. vertices, normals, colors. + /// \param int_dtype Int32 or Int64, used to store index values, e.g. + /// triangles. + /// \param device The device where the resulting TriangleMesh resides in + /// (default CPU:0). Material textures use CPU storage - GPU resident + /// texture images are not yet supported. + /// \return unordered map of constituent mesh names to TriangleMeshes, with + /// materials. + static std::unordered_map + FromTriangleMeshModel( + const open3d::visualization::rendering::TriangleMeshModel &model, + core::Dtype float_dtype = core::Float32, + core::Dtype int_dtype = core::Int64, + const core::Device &device = core::Device("CPU:0")); + /// Compute the convex hull of the triangle mesh using qhull. /// /// This runs on the CPU. diff --git a/cpp/open3d/t/io/file_format/FileASSIMP.cpp b/cpp/open3d/t/io/file_format/FileASSIMP.cpp index 5eae7a9ffdc..514d59fca1b 100644 --- a/cpp/open3d/t/io/file_format/FileASSIMP.cpp +++ b/cpp/open3d/t/io/file_format/FileASSIMP.cpp @@ -6,6 +6,7 @@ // ---------------------------------------------------------------------------- #include +#include #include #include @@ -357,12 +358,17 @@ bool WriteTriangleMeshUsingASSIMP(const std::string& filename, auto r = mesh.GetMaterial().GetBaseClearcoatRoughness(); ai_mat->AddProperty(&r, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); } + if (mesh.GetMaterial().HasEmissiveColor()) { + auto c = mesh.GetMaterial().GetEmissiveColor(); + auto ac = aiColor4D(c.x(), c.y(), c.z(), c.w()); + ai_mat->AddProperty(&ac, 1, AI_MATKEY_COLOR_EMISSIVE); + } // Count texture maps... // NOTE: GLTF2 expects a single combined roughness/metal map. If the // model has one we just export it, otherwise if both roughness and - // metal maps are avaialbe we combine them, otherwise if only one or the - // other is available we just export the one map. + // metal maps are available we combine them, otherwise if only one or + // the other is available we just export the one map. int n_textures = 0; if (mesh.GetMaterial().HasAlbedoMap()) ++n_textures; if (mesh.GetMaterial().HasNormalMap()) ++n_textures; diff --git a/cpp/open3d/visualization/rendering/Material.cpp b/cpp/open3d/visualization/rendering/Material.cpp index fad82623382..d264711f987 100644 --- a/cpp/open3d/visualization/rendering/Material.cpp +++ b/cpp/open3d/visualization/rendering/Material.cpp @@ -26,6 +26,7 @@ void Material::SetDefaultProperties() { SetTransmission(1.f); SetAbsorptionColor(Eigen::Vector4f(1.f, 1.f, 1.f, 1.f)); SetAbsorptionDistance(1.f); + SetEmissiveColor(Eigen::Vector4f(1.f, 1.f, 1.f, 1.f)); SetPointSize(3.f); SetLineWidth(1.f); } @@ -39,6 +40,24 @@ void Material::SetTextureMap(const std::string &key, texture_maps_[key] = image.To(core::Device("CPU:0"), true); } +std::string Material::ToString() const { + if (!IsValid()) { + return "Invalid Material\n"; + } + std::ostringstream os; + os << "Material " << material_name_ << '\n'; + for (const auto &kv : scalar_properties_) { + os << '\t' << kv.first << ": " << kv.second << '\n'; + } + for (const auto &kv : vector_properties_) { + os << '\t' << kv.first << ": " << kv.second.transpose() << '\n'; + } + for (const auto &kv : texture_maps_) { + os << '\t' << kv.first << ": " << kv.second.ToString() << '\n'; + } + return os.str(); +} + void Material::ToMaterialRecord(MaterialRecord &record) const { record.shader = GetMaterialName(); // Convert base material properties @@ -63,6 +82,9 @@ void Material::ToMaterialRecord(MaterialRecord &record) const { if (HasAnisotropy()) { record.base_anisotropy = GetAnisotropy(); } + if (HasEmissiveColor()) { + record.emissive_color = GetEmissiveColor(); + } if (HasThickness()) { record.thickness = GetThickness(); } @@ -124,6 +146,62 @@ void Material::ToMaterialRecord(MaterialRecord &record) const { } } +Material Material::FromMaterialRecord(const MaterialRecord &record) { + using t::geometry::Image; + Material tmat(record.shader); + // scalar and vector properties + tmat.SetBaseColor(record.base_color); + tmat.SetBaseMetallic(record.base_metallic); + tmat.SetBaseRoughness(record.base_roughness); + tmat.SetBaseReflectance(record.base_reflectance); + tmat.SetBaseClearcoat(record.base_clearcoat); + tmat.SetBaseClearcoatRoughness(record.base_clearcoat_roughness); + tmat.SetAnisotropy(record.base_anisotropy); + tmat.SetEmissiveColor(record.emissive_color); + // refractive materials + tmat.SetThickness(record.thickness); + tmat.SetTransmission(record.transmission); + tmat.SetAbsorptionDistance(record.absorption_distance); + // points and lines + tmat.SetPointSize(record.point_size); + tmat.SetLineWidth(record.line_width); + // maps + if (record.albedo_img) { + tmat.SetAlbedoMap(Image::FromLegacy(*record.albedo_img)); + } + if (record.normal_img) { + tmat.SetNormalMap(Image::FromLegacy(*record.normal_img)); + } + if (record.ao_img) { + tmat.SetAOMap(Image::FromLegacy(*record.ao_img)); + } + if (record.metallic_img) { + tmat.SetMetallicMap(Image::FromLegacy(*record.metallic_img)); + } + if (record.roughness_img) { + tmat.SetRoughnessMap(Image::FromLegacy(*record.roughness_img)); + } + if (record.reflectance_img) { + tmat.SetReflectanceMap(Image::FromLegacy(*record.reflectance_img)); + } + if (record.clearcoat_img) { + tmat.SetClearcoatMap(Image::FromLegacy(*record.clearcoat_img)); + } + if (record.clearcoat_roughness_img) { + tmat.SetClearcoatRoughnessMap( + Image::FromLegacy(*record.clearcoat_roughness_img)); + } + if (record.anisotropy_img) { + tmat.SetAnisotropyMap(Image::FromLegacy(*record.anisotropy_img)); + } + if (record.ao_rough_metal_img) { + tmat.SetAORoughnessMetalMap( + Image::FromLegacy(*record.ao_rough_metal_img)); + } + + return tmat; +} + } // namespace rendering } // namespace visualization } // namespace open3d diff --git a/cpp/open3d/visualization/rendering/Material.h b/cpp/open3d/visualization/rendering/Material.h index 7ee1396b281..27afcd8a69a 100644 --- a/cpp/open3d/visualization/rendering/Material.h +++ b/cpp/open3d/visualization/rendering/Material.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include "open3d/t/geometry/Image.h" @@ -34,6 +35,9 @@ class Material { Material(const Material &mat) = default; + /// Convert from MaterialRecord + static Material FromMaterialRecord(const MaterialRecord &mat); + Material &operator=(const Material &other) = default; /// Create an empty but valid material for the specified material name @@ -51,6 +55,9 @@ class Material { /// Get the name of the material. const std::string &GetMaterialName() const { return material_name_; } + /// String reprentation for printing. + std::string ToString() const; + /// Returns the texture map map const TextureMaps &GetTextureMaps() const { return texture_maps_; } @@ -249,6 +256,9 @@ class Material { float GetAbsorptionDistance() const { return GetScalarProperty("absorption_distance"); } + Eigen::Vector4f GetEmissiveColor() const { + return GetVectorProperty("emissive_color"); + } bool HasBaseColor() const { return HasVectorProperty("base_color"); } bool HasBaseMetallic() const { return HasScalarProperty("metallic"); } @@ -267,6 +277,9 @@ class Material { bool HasAbsorptionDistance() const { return HasScalarProperty("absorption_distance"); } + bool HasEmissiveColor() const { + return HasVectorProperty("emissive_color"); + } void SetBaseColor(const Eigen::Vector4f &value) { SetVectorProperty("base_color", value); @@ -295,6 +308,9 @@ class Material { void SetAbsorptionDistance(float value) { SetScalarProperty("absorption_distance", value); } + void SetEmissiveColor(const Eigen::Vector4f &value) { + SetVectorProperty("emissive_color", value); + } //////////////////////////////////////////////////////////////////////////// /// diff --git a/cpp/pybind/t/geometry/trianglemesh.cpp b/cpp/pybind/t/geometry/trianglemesh.cpp index cf245426d58..96307fddc91 100644 --- a/cpp/pybind/t/geometry/trianglemesh.cpp +++ b/cpp/pybind/t/geometry/trianglemesh.cpp @@ -238,6 +238,28 @@ The attributes of the triangle mesh have different levels:: "vertex_dtype"_a = core::Float32, "triangle_dtype"_a = core::Int64, "device"_a = core::Device("CPU:0"), "Create a TriangleMesh from a legacy Open3D TriangleMesh."); + triangle_mesh.def_static( + "from_triangle_mesh_model", &TriangleMesh::FromTriangleMeshModel, + "model"_a, "vertex_dtype"_a = core::Float32, + "triangle_dtype"_a = core::Int64, + "device"_a = core::Device("CPU:0"), + R"(Convert a TriangleMeshModel (e.g. as read from a file with +`open3d.io.read_triangle_mesh_model()`) to a dictionary of mesh names to +triangle meshes with the specified vertex and triangle dtypes and moved to the +specified device. Only a single material per mesh is supported. Materials common +to multiple meshes will be duplicated. Textures (as t.geometry.Image) will use +shared storage on the CPU (GPU resident images for textures is not yet supported). + +Returns: + Dictionary of names to triangle meshes. + +Example: + flight_helmet = o3d.data.FlightHelmetModel() + model = o3d.io.read_triangle_model(flight_helmet.path) + mesh_dict = o3d.t.geometry.TriangleMesh.from_triangle_mesh_model(model) + o3d.visualization.draw(list({"name": name, "geometry": tmesh} for + (name, tmesh) in mesh_dict.items())) + )"); // conversion triangle_mesh.def("to_legacy", &TriangleMesh::ToLegacy, "Convert to a legacy Open3D TriangleMesh."); @@ -706,7 +728,7 @@ This function always uses the CPU device. Returns: This function creates a face attribute "texture_uvs" and returns a tuple - with (max stretch, num_charts, num_partitions) storing the + with (max stretch, num_charts, num_partitions) storing the actual amount of stretch, the number of created charts, and the number of parallel partitions created. @@ -883,7 +905,7 @@ This function always uses the CPU device. "max_faces"_a, R"(Partition the mesh by recursively doing PCA. -This function creates a new face attribute with the name "partition_ids" storing +This function creates a new face attribute with the name "partition_ids" storing the partition id for each face. Args: @@ -892,7 +914,7 @@ the partition id for each face. Example: - This code partitions a mesh such that each partition contains at most 20k + This code partitions a mesh such that each partition contains at most 20k faces:: import open3d as o3d @@ -911,15 +933,15 @@ the partition id for each face. R"(Returns a new mesh with the faces selected by a boolean mask. Args: - mask (open3d.core.Tensor): A boolean mask with the shape (N) with N as the + mask (open3d.core.Tensor): A boolean mask with the shape (N) with N as the number of faces in the mesh. - + Returns: A new mesh with the selected faces. If the original mesh is empty, return an empty mesh. Example: - This code partitions the mesh using PCA and then visualized the individual + This code partitions the mesh using PCA and then visualized the individual parts:: import open3d as o3d diff --git a/cpp/pybind/visualization/rendering/material.cpp b/cpp/pybind/visualization/rendering/material.cpp index 82225c9c90a..3e109dcdbb3 100644 --- a/cpp/pybind/visualization/rendering/material.cpp +++ b/cpp/pybind/visualization/rendering/material.cpp @@ -12,6 +12,7 @@ #include "open3d/visualization/rendering/Material.h" +#include "open3d/visualization/rendering/MaterialRecord.h" #include "pybind/open3d_pybind.h" PYBIND11_MAKE_OPAQUE( @@ -41,6 +42,9 @@ void pybind_material(py::module& m) { mat.def(py::init<>()) .def(py::init(), "", "mat"_a) .def(py::init(), "", "material_name"_a) + .def(py::init(&Material::FromMaterialRecord), "material_record"_a, + "Convert from MaterialRecord.") + .def("__repr__", &Material::ToString) .def("set_default_properties", &Material::SetDefaultProperties, "Fills material with defaults for common PBR material " "properties used by Open3D") diff --git a/cpp/tests/t/geometry/TriangleMesh.cpp b/cpp/tests/t/geometry/TriangleMesh.cpp index b891d6b6986..aa0c75b8b7c 100644 --- a/cpp/tests/t/geometry/TriangleMesh.cpp +++ b/cpp/tests/t/geometry/TriangleMesh.cpp @@ -10,6 +10,7 @@ #include #include "core/CoreTest.h" +#include "open3d/core/Dtype.h" #include "open3d/core/EigenConverter.h" #include "open3d/core/TensorCheck.h" #include "tests/Tests.h" diff --git a/docs/getting_started.in.rst b/docs/getting_started.in.rst index 4d3ccc84214..4bdd9e87d65 100644 --- a/docs/getting_started.in.rst +++ b/docs/getting_started.in.rst @@ -82,10 +82,10 @@ version (``HEAD`` of ``main`` branch): - `Python 3.11 `__ * - MacOS - - `Python 3.8 `__ - - `Python 3.9 `__ - - `Python 3.10 `__ - - `Python 3.11 `__ + - `Python 3.8 (x86_64) `__ + - `Python 3.9 (x86_64) `__ + - `Python 3.10 (x86_64+arm64) `__ + - `Python 3.11 (x86_64+arm64) `__ * - Windows - `Python 3.8 `__ @@ -172,6 +172,7 @@ available for the main supported platforms. Also, the latest development version :columns: 2 * `x86_64 `__ + * `arm64 `__ :Windows 10+: .. hlist:: From 008cfb7fb9831f84b6026617e9aa60ab814c701a Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Mon, 6 May 2024 17:26:37 -0700 Subject: [PATCH 15/22] Switch hosting of devel packages from Google cloud to Github releases (#6784) New pre-release devel-main will be continuously updated with the devel packages for the main branch. Also publish devel versions of viewer app. This is to reduce Google cloud cost. --- .github/workflows/documentation.yml | 81 +++++----- .github/workflows/macos.yml | 143 +++++------------- .github/workflows/ubuntu-cuda.yml | 11 +- .github/workflows/ubuntu-wheel.yml | 50 +----- .github/workflows/ubuntu.yml | 25 ++- .github/workflows/windows.yml | 107 +++---------- CMakeLists.txt | 3 +- .../Open3DViewer/Debian/CMakeLists.in.txt | 2 +- docs/getting_started.in.rst | 63 +++++--- 9 files changed, 161 insertions(+), 324 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index b0e289e41cc..6e4c21bc61f 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,9 +17,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -env: - GCE_CLI_GHA_VERSION: '416.0.0' # Fixed to avoid dependency on API changes - jobs: headless-docs: # Build headless and docs @@ -58,7 +55,7 @@ jobs: - name: Set up Python version uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.11' - name: Install dependencies env: @@ -87,46 +84,44 @@ jobs: path: docs/_out/html if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - - name: Deploy docs + - name: Deploy docs if all artifacts available if: ${{ github.ref == 'refs/heads/main' }} + env: + GH_TOKEN: ${{ github.token }} run: | - # Compress and upload the docs, only for main branch - docs_out_dir="docs/_out" # Docs in ${docs_out_dir}/html - tar_file="${{ github.sha }}_ready.tar.gz" + tar_file="open3d-${GITHUB_SHA}-docs.tar.gz" rm -rf ${tar_file} - tar -C ${docs_out_dir} -czvf ${tar_file} html - gsutil cp ${tar_file} gs://open3d-docs/${tar_file} - echo "Docs archive uploaded to:" - echo "https://storage.googleapis.com/open3d-docs/${tar_file}" + # Docs in docs/_out/html + tar -C docs/_out -czf ${tar_file} html - - name: Check wheels and ready documentation archive - if: ${{ github.ref == 'refs/heads/main' }} - run: | - if [ $(gsutil ls gs://open3d-docs/${{ github.sha }}_ready* | wc -l)\ - -eq 4 ]; then - echo "All wheels and docs available. Making docs ready." - # Remove all marker files: Note _ at end of pattern. - gsutil rm gs://open3d-docs/${{ github.sha }}_ready_* - # Rename docs archive: - gsutil mv gs://open3d-docs/${{ github.sha }}_ready.tar.gz \ - gs://open3d-docs/${{ github.sha }}.tar.gz - # Set holds on new artifacts, release on old - gsutil retention temp release gs://open3d-releases/* - gsutil retention temp set gs://open3d-releases/python-wheels/*${GITHUB_SHA:0:7}*.whl - gsutil retention temp set gs://open3d-releases/devel/*${GITHUB_SHA:0:7}* - else - echo "All wheels / docs not available yet." - fi + echo "Waiting for other release assets..." + this_sha=$(echo ${GITHUB_SHA} | cut -c 1-6) + n_this_sha_assets=$(gh release view main-devel --json assets --jq ".assets | map(select(.name | contains(\"${this_sha}\"))) | length") + # Total assets from each main branch commmit: + # Python wheels (4x4) + Viewer (3) + C++ libs (4+2+2) = 27, + while ((n_this_sha_assets < 27)); do + sleep 60 + echo -n "." + n_this_sha_assets=$(gh release view main-devel --json assets --jq ".assets | map(select(.name | contains(\"${this_sha}\"))) | length") + done + gh release upload main-devel ${tar_file} --clobber + gh release view main-devel + + echo "\nAll assets ready. Removing release assets except from last 3 commits: ${last_shas[@]}" + release_assets=($(gh release view main-devel --json assets --jq '.assets[] | .name')) + last_shas=($(git log --pretty=format:%h --abbrev-commit -n 3)) + echo "Waiting for all assets to be ready..." + for relass in "${release_assets[@]}"; do + found=false + for last_sha in "${last_shas[@]}"; do + if [[ $relass == *${last_sha}* ]]; then + found=true + fi + done + if [ $found == false ]; then + set -x + gh release delete-asset main-devel $relass + set +x + fi + done + gh release view main-devel diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4ac23fd67a8..20af6fdd767 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -22,7 +22,6 @@ env: # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources NPROC: 4 DEVELOPER_BUILD: ${{ github.event.inputs.developer_build || 'ON' }} - GCE_CLI_GHA_VERSION: '416.0.0' # Fixed to avoid dependency on API changes jobs: MacOS: @@ -104,11 +103,12 @@ jobs: - name: Build Open3D viewer app if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} run: | + OPEN3D_VERSION_FULL="$(grep -F OPEN3D_VERSION_FULL build/CMakeCache.txt | cut -f2 -d'=')" PATH=/usr/local/var/homebrew/linked/ccache/libexec:$PATH pushd build make -j${NPROC} Open3DViewer pushd bin - zip -rv open3d-app-macosx-10_15-${{ runner.arch}}.zip Open3D.app + zip -rv "open3d-${OPEN3D_VERSION_FULL}-app-macosx-10_15-${{ runner.arch }}.zip" Open3D.app ccache -s - name: Upload package @@ -119,31 +119,20 @@ jobs: path: build/package/${{ env.DEVEL_PKG_NAME }} if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - - name: Upload package to GCS bucket + - name: Update package devel release if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gsutil cp build/package/${{ env.DEVEL_PKG_NAME }} gs://open3d-releases/devel/ - echo "Download devel package at: https://storage.googleapis.com/open3d-releases/devel/${{ env.DEVEL_PKG_NAME }}" + gh release upload main-devel build/package/${{ env.DEVEL_PKG_NAME }} --clobber + gh release view main-devel - name: Upload Open3D viewer app uses: actions/upload-artifact@v4 if: ${{ env.BUILD_SHARED_LIBS == 'OFF' }} with: name: open3d-app-macosx-10_15-${{ runner.arch}} - path: build/bin/open3d-app-macosx-10_15-${{ runner.arch}}.zip + path: build/bin/open3d-*-app-macosx-10_15-${{ runner.arch }}.zip if-no-files-found: error fuse-viewer: @@ -151,6 +140,8 @@ jobs: runs-on: [macos-12] needs: [MacOS] steps: + - name: Checkout source code # for gh release upload + uses: actions/checkout@v4 - name: Download viewer apps uses: actions/download-artifact@v4 with: @@ -159,22 +150,35 @@ jobs: - name: Fuse x64 and arm64 viewer apps run: | - unzip open3d-app-macosx-10_15-X64.zip -d x64 - unzip open3d-app-macosx-10_15-ARM64.zip -d arm64 + unzip open3d-*-app-macosx-10_15-X64.zip -d x64 + unzip open3d-*-app-macosx-10_15-ARM64.zip -d arm64 + APP_NAME=$(ls open3d-*-app-macosx-10_15-X64.zip) + APP_NAME=${APP_NAME/-X64/-universal2} # includes version for i in arm64/Open3D.app/Contents/MacOS/*; do filepath=Open3D.app/Contents/MacOS/$(basename $i) lipo -create arm64/${filepath} x64/${filepath} -output arm64/${filepath} done mv arm64/Open3D.app Open3D.app - zip -rv open3d-app-macosx-10_15-universal2.zip Open3D.app + zip -rv "${APP_NAME}" Open3D.app - name: Upload Open3D viewer app uses: actions/upload-artifact@v4 with: name: open3d-app-macosx-10_15-universal2 - path: open3d-app-macosx-10_15-universal2.zip + path: open3d-*-app-macosx-10_15-universal2.zip if-no-files-found: error + - name: Checkout source code + uses: actions/checkout@v4 + - name: Update viewer devel release + if: ${{ github.ref == 'refs/heads/main' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload main-devel open3d-*-app-macosx-10_15-universal2.zip --clobber + gh release view main-devel + + build-wheel: name: Build wheel runs-on: ${{ matrix.os }} @@ -282,28 +286,6 @@ jobs: path: build/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - - name: Upload wheel to GCS bucket - if: ${{ github.ref == 'refs/heads/main' }} - env: - python_version: ${{ matrix.python_version }} - run: | - PYTAG="-cp$(echo ${{ env.python_version }} | tr -d '.')" - gsutil cp build/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} gs://open3d-releases/python-wheels/ - echo "Download pip package at: https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }}" - fuse-wheel: name: Fuse universal2 wheel runs-on: [macos-12] @@ -319,38 +301,41 @@ jobs: - is_main: false python_version: '3.10' steps: + - name: Checkout source code # for gh release upload + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} + - name: Download X64 wheels uses: actions/download-artifact@v4 with: pattern: open3d-*macosx*_x86_64.whl path: x64_wheels merge-multiple: true + - name: Download ARM64 wheels uses: actions/download-artifact@v4 with: pattern: open3d-*macosx*_arm64.whl path: arm64_wheels merge-multiple: true + - name: Fuse x64 and ARM64 wheels env: python_version: ${{ matrix.python_version }} run: | PYTAG="-cp$(echo ${{ env.python_version }} | tr -d '.')" mkdir universal_wheels - pip install delocate delocate-fuse -v x64_wheels/open3d-*${PYTAG}*.whl arm64_wheels/open3d-*${PYTAG}*.whl - # Normalize file name as delocate-fuse doesn't update it OLD_WHL_NAME=$(basename x64_wheels/open3d-*${PYTAG}*.whl) NEW_WHL_NAME=${OLD_WHL_NAME/x86_64/universal2} mv x64_wheels/${OLD_WHL_NAME} universal_wheels/${NEW_WHL_NAME} - echo "PIP_PKG_NAME=$NEW_WHL_NAME" >> $GITHUB_ENV + - name: Upload merged wheels uses: actions/upload-artifact@v4 with: @@ -358,27 +343,14 @@ jobs: path: universal_wheels/${{ env.PIP_PKG_NAME }} if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - - name: Upload wheel to GCS bucket + - name: Update devel release if: ${{ github.ref == 'refs/heads/main' }} env: - python_version: ${{ matrix.python_version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gsutil cp universal_wheels/${{ env.PIP_PKG_NAME }} gs://open3d-releases/python-wheels/ - echo "Download pip package at: https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }}" - + gh release upload main-devel universal_wheels/${{ env.PIP_PKG_NAME }} --clobber + gh release view main-devel + test-wheel: name: Test wheel runs-on: ${{ matrix.os }} @@ -432,45 +404,8 @@ jobs: pi_tag=$(python -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')") test_wheel open3d*-"$pi_tag"-*_$(uname -m).whl - - name: Run Python unit tests (benchmarks) + - name: Run Python unit tests run: | source util/ci_utils.sh echo "Running Open3D python tests..." run_python_tests - - ready-docs: - name: Ready docs archive - # no need to run on macOS - runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/heads/main' }} - needs: [fuse-wheel, MacOS] - steps: - - name: GCloud CLI auth - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - name: Check wheels and ready documentation archive - run: | - touch marker_file - gsutil cp marker_file gs://open3d-docs/${{ github.sha }}_ready_macos - if [ $(gsutil ls gs://open3d-docs/${{ github.sha }}_ready* | wc -l)\ - -eq 4 ]; then - echo "All wheels and docs available. Making docs ready." - # Remove all marker files: Note _ at end of pattern. - gsutil rm gs://open3d-docs/${{ github.sha }}_ready_* - # Rename docs archive: - gsutil mv gs://open3d-docs/${{ github.sha }}_ready.tar.gz \ - gs://open3d-docs/${{ github.sha }}.tar.gz - # Set holds on new artifacts, release on old - gsutil retention temp release gs://open3d-releases/* - gsutil retention temp set gs://open3d-releases/python-wheels/*${GITHUB_SHA:0:7}*.whl - gsutil retention temp set gs://open3d-releases/devel/*${GITHUB_SHA:0:7}* - else - echo "All wheels / docs not available yet. Docs not ready." - fi diff --git a/.github/workflows/ubuntu-cuda.yml b/.github/workflows/ubuntu-cuda.yml index 250be1597fb..1ff1d20c75b 100644 --- a/.github/workflows/ubuntu-cuda.yml +++ b/.github/workflows/ubuntu-cuda.yml @@ -156,14 +156,13 @@ jobs: path: open3d-devel-linux*.tar.xz if-no-files-found: error - - name: Upload package to GCS bucket + - name: Update devel release if: ${{ github.ref == 'refs/heads/main' && env.BUILD_PACKAGE == 'true' }} + env: + GH_TOKEN: ${{ github.token }} run: | - gcloud compute ssh "${INSTANCE_NAME}" \ - --zone="${GCE_ZONE}" \ - --command="ls -alh \ - && gsutil cp open3d-devel-linux-*.tar.xz \ - gs://open3d-releases/devel/" + gh release upload main-devel open3d-devel-linux-*.tar.xz --clobber + gh release view main-devel - name: VM run docker run: | diff --git a/.github/workflows/ubuntu-wheel.yml b/.github/workflows/ubuntu-wheel.yml index 906d07112f7..2ff88c74b6a 100644 --- a/.github/workflows/ubuntu-wheel.yml +++ b/.github/workflows/ubuntu-wheel.yml @@ -101,14 +101,14 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} run: | gsutil cp ${GITHUB_WORKSPACE}/${{ env.CCACHE_TAR_NAME }}.tar.gz gs://open3d-ci-cache/ - - name: Upload wheel to GCS - if: ${{ github.ref == 'refs/heads/main' }} + - name: Update devel release + # if: ${{ github.ref == 'refs/heads/main' }} + env: + GH_TOKEN: ${{ github.token }} run: | - gsutil cp ${GITHUB_WORKSPACE}/${{ env.PIP_PKG_NAME }} \ - ${GITHUB_WORKSPACE}/${{ env.PIP_CPU_PKG_NAME }} gs://open3d-releases/python-wheels/ - echo "Download pip package at: - https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }} - https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_CPU_PKG_NAME }}" + gh release upload main-devel ${GITHUB_WORKSPACE}/${{ env.PIP_PKG_NAME }} \ + ${GITHUB_WORKSPACE}/${{ env.PIP_CPU_PKG_NAME }} --clobber + gh release view main-devel test-wheel-cpu: name: Test wheel CPU @@ -174,39 +174,3 @@ jobs: source util/ci_utils.sh echo "Running Open3D python tests (CPU wheel)..." run_python_tests - - ready-docs: - name: Ready docs archive - runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/heads/main' }} - needs: [build-wheel] - steps: - - name: GCloud CLI auth - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - name: Check wheels and ready documentation archive - run: | - touch marker_file - gsutil cp marker_file gs://open3d-docs/${{ github.sha }}_ready_ubuntu - if [ $(gsutil ls gs://open3d-docs/${{ github.sha }}_ready* | wc -l)\ - -eq 4 ]; then - echo "All wheels and docs available. Making docs ready." - # Remove all marker files: Note _ at end of pattern. - gsutil rm gs://open3d-docs/${{ github.sha }}_ready_* - # Rename docs archive: - gsutil mv gs://open3d-docs/${{ github.sha }}_ready.tar.gz \ - gs://open3d-docs/${{ github.sha }}.tar.gz - # Set holds on new artifacts, release on old - gsutil retention temp release gs://open3d-releases/* - gsutil retention temp set gs://open3d-releases/python-wheels/*${GITHUB_SHA:0:7}*.whl - gsutil retention temp set gs://open3d-releases/devel/*${GITHUB_SHA:0:7}* - else - echo "All wheels / docs not available yet." - fi diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3bbdaadc1d1..ba2765c82c7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -19,7 +19,6 @@ concurrency: env: NPROC: 2 - GCE_CLI_GHA_VERSION: "416.0.0" jobs: ubuntu: @@ -84,20 +83,14 @@ jobs: name: open3d-viewer-Linux path: open3d-viewer-*-Linux.deb if-no-files-found: error - - name: GCloud CLI auth + - name: Update devel release if: ${{ github.ref == 'refs/heads/main' }} - uses: 'google-github-actions/auth@v2' - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - name: Upload package to GCS bucket - if: ${{ github.ref == 'refs/heads/main' && env.BUILD_SHARED_LIBS == 'ON' }} + env: + GH_TOKEN: ${{ github.token }} run: | - gsutil cp open3d-devel-*.tar.xz gs://open3d-releases/devel/ - echo "Download devel package at: https://storage.googleapis.com/open3d-releases/devel/${{ env.DEVEL_PKG_NAME }}" + if [ ${BUILD_SHARED_LIBS} == 'ON' ] ; then + gh release upload main-devel open3d-devel-*.tar.xz --clobber + else + gh release upload main-devel open3d-viewer-*-Linux.deb --clobber + fi + gh release view main-devel diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 11223949007..141f1917a30 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -28,7 +28,6 @@ env: BUILD_DIR: "C:\\Open3D\\build" NPROC: 2 DEVELOPER_BUILD: ${{ github.event.inputs.developer_build || 'ON' }} - GCE_CLI_GHA_VERSION: '416.0.0' # Fixed to avoid dependency on API changes jobs: windows: @@ -154,6 +153,14 @@ jobs: path: ${{ env.BUILD_DIR }}/package/${{ env.DEVEL_PKG_NAME }} if-no-files-found: error + - name: Update devel release with package + if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload main-devel ${{ env.BUILD_DIR }}/package/${{ env.DEVEL_PKG_NAME }} --clobber + gh release view main-devel + - name: Viewer App working-directory: ${{ env.BUILD_DIR }} if: ${{ matrix.BUILD_SHARED_LIBS == 'OFF' && matrix.STATIC_RUNTIME == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' && matrix.CONFIG == 'Release' }} @@ -172,29 +179,16 @@ jobs: path: C:\Program Files\Open3D\bin\Open3D if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} - uses: google-github-actions/auth@v2 - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_DOCS_PROJECT }} - - - name: Upload package to GCS bucket - if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' }} + - name: Update devel release with viewer + if: ${{ github.ref == 'refs/heads/main' && matrix.BUILD_SHARED_LIBS == 'OFF' && matrix.STATIC_RUNTIME == 'ON' && matrix.BUILD_CUDA_MODULE == 'OFF' && matrix.CONFIG == 'Release' }} + env: + GH_TOKEN: ${{ github.token }} run: | - gsutil cp ${{ env.BUILD_DIR }}/package/${{ env.DEVEL_PKG_NAME }} ` - gs://open3d-releases/devel/ - if ($LastExitCode -eq 0) { - echo "Download devel package at: https://storage.googleapis.com/open3d-releases/devel/${{ env.DEVEL_PKG_NAME }}" - } else { - throw "Devel package upload failed" - } + $Env:OPEN3D_VERSION_FULL = (Select-String -Path "C:/Open3D/build/CMakeCache.txt" -Pattern "OPEN3D_VERSION_FULL").Line.Split('=')[1] + Compress-Archive -Path "C:/Program Files/Open3D/bin/Open3D" -DestinationPath ` + "open3d-$Env:OPEN3D_VERSION_FULL-app-windows-amd64.zip" + gh release upload main-devel "open3d-$Env:OPEN3D_VERSION_FULL-app-windows-amd64.zip" --clobber + gh release view main-devel - name: Run C++ unit tests if: ${{ matrix.BUILD_CUDA_MODULE == 'OFF' }} @@ -313,32 +307,13 @@ jobs: path: ${{ env.BUILD_DIR }}/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} if-no-files-found: error - - name: GCloud CLI auth - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/auth@v2 - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - if: ${{ github.ref == 'refs/heads/main' }} - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_DOCS_PROJECT }} - - - name: Upload wheel to GCS bucket + - name: Update devel release with wheel if: ${{ github.ref == 'refs/heads/main' }} env: - python_version: ${{ matrix.python_version }} + GH_TOKEN: ${{ github.token }} run: | - $ErrorActionPreference = 'Stop' - $PYTAG="-cp$(${{ env.python_version }} -replace '\.', '')" - gsutil cp ${{ env.BUILD_DIR }}/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} gs://open3d-releases/python-wheels/ - if ($LastExitCode -eq 0) { - echo "Download pip package at: https://storage.googleapis.com/open3d-releases/python-wheels/${{ env.PIP_PKG_NAME }}" - } else { - throw "Wheel upload failed" - } + gh release upload main-devel ${{ env.BUILD_DIR }}/lib/python_package/pip_package/${{ env.PIP_PKG_NAME }} --clobber + gh release view main-devel test-wheel: name: Test wheel @@ -394,7 +369,6 @@ jobs: python -m pip install "$PIP_PKG_NAME" python -c "import open3d; print('Imported:', open3d)" python -c "import open3d; print('CUDA enabled: ', open3d.core.cuda.is_available())" - deactivate - name: Run Python unit tests @@ -406,42 +380,3 @@ jobs: echo "Testing ML and ML Ops disabled" python -m pytest python/test/ --ignore python/test/ml_ops/ deactivate - - ready-docs: - name: Ready docs archive - # no need to run on Windows - runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/heads/main' }} - # temp workaround for Windows CUDA Debug CI out of space. Still update docs. - # needs: [build-wheel, windows] - needs: [build-wheel] - steps: - - name: GCloud CLI auth - uses: google-github-actions/auth@v2 - with: - project_id: ${{ secrets.GCE_PROJECT }} - credentials_json: '${{ secrets.GCE_SA_KEY_GPU_CI }}' - - name: GCloud CLI setup - uses: google-github-actions/setup-gcloud@v2 - with: - version: ${{ env.GCE_CLI_GHA_VERSION }} - project_id: ${{ secrets.GCE_PROJECT }} - - name: Check wheels and ready documentation archive - run: | - touch marker_file - gsutil cp marker_file gs://open3d-docs/${{ github.sha }}_ready_windows - if [ $(gsutil ls gs://open3d-docs/${{ github.sha }}_ready* | wc -l)\ - -eq 4 ]; then - echo "All wheels and docs available. Making docs ready." - # Remove all marker files: Note _ at end of pattern. - gsutil rm gs://open3d-docs/${{ github.sha }}_ready_* - # Rename docs archive: - gsutil mv gs://open3d-docs/${{ github.sha }}_ready.tar.gz \ - gs://open3d-docs/${{ github.sha }}.tar.gz - # Set holds on new artifacts, release on old - gsutil retention temp release gs://open3d-releases/* - gsutil retention temp set gs://open3d-releases/python-wheels/*${GITHUB_SHA:0:7}*.whl - gsutil retention temp set gs://open3d-releases/devel/*${GITHUB_SHA:0:7}* - else - echo "All wheels / docs not available yet." - fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 7abd4307b91..6e5784bb4aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,8 @@ string(CONCAT OPEN3D_VERSION ".${OPEN3D_VERSION_MINOR}" ".${OPEN3D_VERSION_PATCH}" ) -set(OPEN3D_VERSION_FULL "${OPEN3D_VERSION}${OPEN3D_VERSION_DEVHASH}") +set(OPEN3D_VERSION_FULL "${OPEN3D_VERSION}${OPEN3D_VERSION_DEVHASH}" CACHE + STRING "Open3D full version.") # Set additional info set(PROJECT_EMAIL "open3d@intel.com") set(PROJECT_DOCS "https://www.open3d.org/docs") diff --git a/cpp/apps/Open3DViewer/Debian/CMakeLists.in.txt b/cpp/apps/Open3DViewer/Debian/CMakeLists.in.txt index 9320f265d3e..b7bce43f4c6 100644 --- a/cpp/apps/Open3DViewer/Debian/CMakeLists.in.txt +++ b/cpp/apps/Open3DViewer/Debian/CMakeLists.in.txt @@ -32,7 +32,7 @@ set(CPACK_PACKAGE_NAME "open3d-viewer") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Open3D Viewer for 3D files") set(CPACK_PACKAGE_CONTACT "Open3D team <@PROJECT_EMAIL@>") set(CPACK_DEBIAN_PACKAGE_SECTION "Graphics") -set(CPACK_PACKAGE_VERSION "@OPEN3D_VERSION@") +set(CPACK_PACKAGE_VERSION "@OPEN3D_VERSION_FULL@") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc++1, libgomp1, libpng16-16, libglfw3") set(CPACK_PACKAGE_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@") diff --git a/docs/getting_started.in.rst b/docs/getting_started.in.rst index 4bdd9e87d65..a2aa5a745ee 100644 --- a/docs/getting_started.in.rst +++ b/docs/getting_started.in.rst @@ -5,6 +5,21 @@ Getting started .. _install_open3d_python: +Viewer +====== + +Use the Open3D viewer application to visualize 3D data in various formats and +interact with it. You can download the latest stable release app from `Github +releases `__. The latest development +version (``HEAD`` of ``main`` branch) viewer app is provided here [#]_: + +* `Linux (Ubuntu 18.04+ or glibc 2.27+) `__ [#]_ +* `MacOSX v10.15+ (Intel or Apple Silicon) `__ +* `Windows 10+ (64-bit) `__ + +.. [#] Please use these links from the `latest version of this page `__ only. +.. [#] To check the `glibc` version on your system, run :code:`ldd --version`. + Python ====== @@ -70,28 +85,28 @@ version (``HEAD`` of ``main`` branch): :widths: auto * - Linux - - `Python 3.8 `__ - - `Python 3.9 `__ - - `Python 3.10 `__ - - `Python 3.11 `__ + - `Python 3.8 `__ + - `Python 3.9 `__ + - `Python 3.10 `__ + - `Python 3.11 `__ * - Linux (CPU) - - `Python 3.8 `__ - - `Python 3.9 `__ - - `Python 3.10 `__ - - `Python 3.11 `__ + - `Python 3.8 `__ + - `Python 3.9 `__ + - `Python 3.10 `__ + - `Python 3.11 `__ * - MacOS - - `Python 3.8 (x86_64) `__ - - `Python 3.9 (x86_64) `__ - - `Python 3.10 (x86_64+arm64) `__ - - `Python 3.11 (x86_64+arm64) `__ + - `Python 3.8 (x86_64) `__ + - `Python 3.9 (x86_64) `__ + - `Python 3.10 (x86_64+arm64) `__ + - `Python 3.11 (x86_64+arm64) `__ * - Windows - - `Python 3.8 `__ - - `Python 3.9 `__ - - `Python 3.10 `__ - - `Python 3.11 `__ + - `Python 3.8 `__ + - `Python 3.9 `__ + - `Python 3.10 `__ + - `Python 3.11 `__ Please use these links from the `latest version of this page `__ only. You can also @@ -162,24 +177,24 @@ available for the main supported platforms. Also, the latest development version .. hlist:: :columns: 2 - * `x86_64 (CXX11 ABI) `__ - * `x86_64 (CXX11 ABI) with CUDA 11.x `__ - * `x86_64 (pre CXX11 ABI) `__ - * `x86_64 (pre CXX11 ABI) with CUDA 11.x `__ + * `x86_64 (CXX11 ABI) `__ + * `x86_64 (CXX11 ABI) with CUDA 11.x `__ + * `x86_64 (pre CXX11 ABI) `__ + * `x86_64 (pre CXX11 ABI) with CUDA 11.x `__ :MacOSX v10.15+: .. hlist:: :columns: 2 - * `x86_64 `__ - * `arm64 `__ + * `x86_64 `__ + * `arm64 `__ :Windows 10+: .. hlist:: :columns: 2 - * `x86_64 Release `__ - * `x86_64 Debug `__ + * `x86_64 Release `__ + * `x86_64 Debug `__ .. [#] Please use these links from the `latest version of this page `__ only. From bf48b4540583805b56a6286db13ceb30f2b0635b Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Sat, 11 May 2024 23:45:03 -0700 Subject: [PATCH 16/22] Robust image I/O (#6765) - Use file signatures (beginning bytes) instead of filename extension (png/jpg) to select decoder. jpg files with png extension and vice versa are read correctly. - Catch libjpeg errors and report as normal Open3D exceptions. Change Open3D warnings to errors (exceptions) on jpeg I/O error. - Simplify demo_scene example - Clear image on read error - Fix for github release macOS bug. --- .github/workflows/documentation.yml | 3 +- .github/workflows/macos.yml | 2 - cpp/open3d/io/ImageIO.cpp | 56 +++-- cpp/open3d/io/ImageIO.h | 9 +- cpp/open3d/io/file_format/FileJPG.cpp | 258 ++++++++++++-------- cpp/open3d/io/file_format/FilePNG.cpp | 2 + cpp/open3d/t/io/ImageIO.cpp | 66 ++--- cpp/open3d/t/io/file_format/FileJPG.cpp | 176 +++++++------ cpp/open3d/t/io/file_format/FilePNG.cpp | 4 +- examples/python/visualization/demo_scene.py | 45 +--- 10 files changed, 350 insertions(+), 271 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 6e4c21bc61f..00580af33d9 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -92,7 +92,7 @@ jobs: tar_file="open3d-${GITHUB_SHA}-docs.tar.gz" rm -rf ${tar_file} # Docs in docs/_out/html - tar -C docs/_out -czf ${tar_file} html + tar -C docs/_out -cvzf ${tar_file} html echo "Waiting for other release assets..." this_sha=$(echo ${GITHUB_SHA} | cut -c 1-6) @@ -110,7 +110,6 @@ jobs: echo "\nAll assets ready. Removing release assets except from last 3 commits: ${last_shas[@]}" release_assets=($(gh release view main-devel --json assets --jq '.assets[] | .name')) last_shas=($(git log --pretty=format:%h --abbrev-commit -n 3)) - echo "Waiting for all assets to be ready..." for relass in "${release_assets[@]}"; do found=false for last_sha in "${last_shas[@]}"; do diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 20af6fdd767..f0234f975ee 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -168,8 +168,6 @@ jobs: path: open3d-*-app-macosx-10_15-universal2.zip if-no-files-found: error - - name: Checkout source code - uses: actions/checkout@v4 - name: Update viewer devel release if: ${{ github.ref == 'refs/heads/main' }} env: diff --git a/cpp/open3d/io/ImageIO.cpp b/cpp/open3d/io/ImageIO.cpp index a4126df14e6..c00361a449a 100644 --- a/cpp/open3d/io/ImageIO.cpp +++ b/cpp/open3d/io/ImageIO.cpp @@ -7,24 +7,25 @@ #include "open3d/io/ImageIO.h" +#include +#include #include #include "open3d/utility/FileSystem.h" #include "open3d/utility/Logging.h" namespace open3d { +namespace io { namespace { -using namespace io; -static const std::unordered_map< - std::string, - std::function> - file_extension_to_image_read_function{ - {"png", ReadImageFromPNG}, - {"jpg", ReadImageFromJPG}, - {"jpeg", ReadImageFromJPG}, - }; +using signature_decoder_t = + std::pair>; +static const std::array signature_decoder_list{ + {{"\x89\x50\x4e\x47\xd\xa\x1a\xa", ReadImageFromPNG}, + {"\xFF\xD8\xFF", ReadImageFromJPG}}}; +static constexpr uint8_t MAX_SIGNATURE_LEN = 8; static const std::unordered_map< std::string, @@ -34,11 +35,8 @@ static const std::unordered_map< {"jpg", WriteImageToJPG}, {"jpeg", WriteImageToJPG}, }; - } // unnamed namespace -namespace io { - std::shared_ptr CreateImageFromFile( const std::string &filename) { auto image = std::make_shared(); @@ -47,21 +45,27 @@ std::shared_ptr CreateImageFromFile( } bool ReadImage(const std::string &filename, geometry::Image &image) { - std::string filename_ext = - utility::filesystem::GetFileExtensionInLowerCase(filename); - if (filename_ext.empty()) { - utility::LogWarning( - "Read geometry::Image failed: missing file extension."); - return false; - } - auto map_itr = file_extension_to_image_read_function.find(filename_ext); - if (map_itr == file_extension_to_image_read_function.end()) { - utility::LogWarning( - "Read geometry::Image failed: file extension {} unknown", - filename_ext); - return false; + std::string signature_buffer(MAX_SIGNATURE_LEN, 0); + std::ifstream file(filename, std::ios::binary); + file.read(&signature_buffer[0], MAX_SIGNATURE_LEN); + std::string err_msg; + if (!file) { + err_msg = "Read geometry::Image failed for file {}. I/O error."; + } else { + file.close(); + for (const auto &signature_decoder : signature_decoder_list) { + if (signature_buffer.compare(0, signature_decoder.first.size(), + signature_decoder.first) == 0) { + return signature_decoder.second(filename, image); + } + } + err_msg = + "Read geometry::Image failed for file {}. Unknown file " + "signature, only PNG and JPG are supported."; } - return map_itr->second(filename, image); + image.Clear(); + utility::LogWarning(err_msg.c_str(), filename); + return false; } bool WriteImage(const std::string &filename, diff --git a/cpp/open3d/io/ImageIO.h b/cpp/open3d/io/ImageIO.h index 3c6b62be3d8..45b0a3f71ed 100644 --- a/cpp/open3d/io/ImageIO.h +++ b/cpp/open3d/io/ImageIO.h @@ -69,11 +69,18 @@ bool WriteImageToJPG(const std::string &filename, const geometry::Image &image, int quality = kOpen3DImageIODefaultQuality); -/// The general entrance for reading an Image from memory +/// Read a PNG image from memory. +/// \param image_data_ptr the pointer to image data in memory. +/// \param image_data_size the size of image data in memory. +/// \return return true if the read function is successful, false otherwise. bool ReadPNGFromMemory(const unsigned char *image_data_ptr, size_t image_data_size, geometry::Image &image); +/// Read a JPG image from memory. +/// \param image_data_ptr the pointer to image data in memory. +/// \param image_data_size the size of image data in memory. +/// \return return true if the read function is successful, false otherwise. bool ReadJPGFromMemory(const unsigned char *image_data_ptr, size_t image_data_size, geometry::Image &image); diff --git a/cpp/open3d/io/file_format/FileJPG.cpp b/cpp/open3d/io/file_format/FileJPG.cpp index 7de155575bf..cb9cf6760e9 100644 --- a/cpp/open3d/io/file_format/FileJPG.cpp +++ b/cpp/open3d/io/file_format/FileJPG.cpp @@ -17,6 +17,23 @@ namespace open3d { namespace io { +namespace { + +/// Convert libjpeg error messages to std::runtime_error. This prevents +/// libjpeg from exit() in case of errors. +void jpeg_error_throw(j_common_ptr p_cinfo) { + if (p_cinfo->is_decompressor) + jpeg_destroy_decompress( + reinterpret_cast(p_cinfo)); + else + jpeg_destroy_compress( + reinterpret_cast(p_cinfo)); + char buffer[JMSG_LENGTH_MAX]; + (*p_cinfo->err->format_message)(p_cinfo, buffer); + throw std::runtime_error(buffer); +} + +} // namespace bool ReadImageFromJPG(const std::string &filename, geometry::Image &image) { struct jpeg_decompress_struct cinfo; @@ -27,53 +44,64 @@ bool ReadImageFromJPG(const std::string &filename, geometry::Image &image) { if ((file_in = utility::filesystem::FOpen(filename, "rb")) == NULL) { utility::LogWarning("Read JPG failed: unable to open file: {}", filename); + image.Clear(); return false; } - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&cinfo); - jpeg_stdio_src(&cinfo, file_in); - jpeg_read_header(&cinfo, TRUE); - - // We only support two channel types: gray, and RGB. - int num_of_channels = 3; - int bytes_per_channel = 1; - switch (cinfo.jpeg_color_space) { - case JCS_RGB: - case JCS_YCbCr: - cinfo.out_color_space = JCS_RGB; - cinfo.out_color_components = 3; - num_of_channels = 3; - break; - case JCS_GRAYSCALE: - cinfo.jpeg_color_space = JCS_GRAYSCALE; - cinfo.out_color_components = 1; - num_of_channels = 1; - break; - case JCS_CMYK: - case JCS_YCCK: - default: - utility::LogWarning("Read JPG failed: color space not supported."); - jpeg_destroy_decompress(&cinfo); - fclose(file_in); - return false; - } - jpeg_start_decompress(&cinfo); - image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels, - bytes_per_channel); - int row_stride = cinfo.output_width * cinfo.output_components; - buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, - row_stride, 1); - uint8_t *pdata = image.data_.data(); - while (cinfo.output_scanline < cinfo.output_height) { - jpeg_read_scanlines(&cinfo, buffer, 1); - memcpy(pdata, buffer[0], row_stride); - pdata += row_stride; + try { + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error_throw; + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file_in); + jpeg_read_header(&cinfo, TRUE); + + // We only support two channel types: gray, and RGB. + int num_of_channels = 3; + int bytes_per_channel = 1; + switch (cinfo.jpeg_color_space) { + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + cinfo.out_color_components = 3; + num_of_channels = 3; + break; + case JCS_GRAYSCALE: + cinfo.jpeg_color_space = JCS_GRAYSCALE; + cinfo.out_color_components = 1; + num_of_channels = 1; + break; + case JCS_CMYK: + case JCS_YCCK: + default: + utility::LogWarning( + "Read JPG failed: color space not supported."); + jpeg_destroy_decompress(&cinfo); + fclose(file_in); + image.Clear(); + return false; + } + jpeg_start_decompress(&cinfo); + image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels, + bytes_per_channel); + int row_stride = cinfo.output_width * cinfo.output_components; + buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, + row_stride, 1); + uint8_t *pdata = image.data_.data(); + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + memcpy(pdata, buffer[0], row_stride); + pdata += row_stride; + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(file_in); + return true; + } catch (const std::runtime_error &err) { + fclose(file_in); + image.Clear(); + utility::LogWarning("libjpeg error: {}", err.what()); + return false; } - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - fclose(file_in); - return true; } bool WriteImageToJPG(const std::string &filename, @@ -108,30 +136,37 @@ bool WriteImageToJPG(const std::string &filename, return false; } - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_compress(&cinfo); - jpeg_stdio_dest(&cinfo, file_out); - cinfo.image_width = image.width_; - cinfo.image_height = image.height_; - cinfo.input_components = image.num_of_channels_; - cinfo.in_color_space = - (cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB); - jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); - jpeg_start_compress(&cinfo, TRUE); - int row_stride = image.width_ * image.num_of_channels_; - const uint8_t *pdata = image.data_.data(); - std::vector buffer(row_stride); - while (cinfo.next_scanline < cinfo.image_height) { - memcpy(buffer.data(), pdata, row_stride); - row_pointer[0] = buffer.data(); - jpeg_write_scanlines(&cinfo, row_pointer, 1); - pdata += row_stride; + try { + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error_throw; + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, file_out); + cinfo.image_width = image.width_; + cinfo.image_height = image.height_; + cinfo.input_components = image.num_of_channels_; + cinfo.in_color_space = + (cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB); + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + int row_stride = image.width_ * image.num_of_channels_; + const uint8_t *pdata = image.data_.data(); + std::vector buffer(row_stride); + while (cinfo.next_scanline < cinfo.image_height) { + memcpy(buffer.data(), pdata, row_stride); + row_pointer[0] = buffer.data(); + jpeg_write_scanlines(&cinfo, row_pointer, 1); + pdata += row_stride; + } + jpeg_finish_compress(&cinfo); + fclose(file_out); + jpeg_destroy_compress(&cinfo); + return true; + } catch (const std::runtime_error &err) { + fclose(file_out); + utility::LogWarning(err.what()); + return false; } - jpeg_finish_compress(&cinfo); - fclose(file_out); - jpeg_destroy_compress(&cinfo); - return true; } bool ReadJPGFromMemory(const unsigned char *image_data_ptr, @@ -141,48 +176,57 @@ bool ReadJPGFromMemory(const unsigned char *image_data_ptr, struct jpeg_error_mgr jerr; JSAMPARRAY buffer; - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&cinfo); - jpeg_mem_src(&cinfo, image_data_ptr, image_data_size); - jpeg_read_header(&cinfo, TRUE); - - // We only support two channel types: gray, and RGB. - int num_of_channels = 3; - int bytes_per_channel = 1; - switch (cinfo.jpeg_color_space) { - case JCS_RGB: - case JCS_YCbCr: - cinfo.out_color_space = JCS_RGB; - cinfo.out_color_components = 3; - num_of_channels = 3; - break; - case JCS_GRAYSCALE: - cinfo.jpeg_color_space = JCS_GRAYSCALE; - cinfo.out_color_components = 1; - num_of_channels = 1; - break; - case JCS_CMYK: - case JCS_YCCK: - default: - utility::LogWarning("Read JPG failed: color space not supported."); - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_start_decompress(&cinfo); - image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels, - bytes_per_channel); - int row_stride = cinfo.output_width * cinfo.output_components; - buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, - row_stride, 1); - uint8_t *pdata = image.data_.data(); - while (cinfo.output_scanline < cinfo.output_height) { - jpeg_read_scanlines(&cinfo, buffer, 1); - memcpy(pdata, buffer[0], row_stride); - pdata += row_stride; + try { + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error_throw; + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, image_data_ptr, image_data_size); + jpeg_read_header(&cinfo, TRUE); + + // We only support two channel types: gray, and RGB. + int num_of_channels = 3; + int bytes_per_channel = 1; + switch (cinfo.jpeg_color_space) { + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + cinfo.out_color_components = 3; + num_of_channels = 3; + break; + case JCS_GRAYSCALE: + cinfo.jpeg_color_space = JCS_GRAYSCALE; + cinfo.out_color_components = 1; + num_of_channels = 1; + break; + case JCS_CMYK: + case JCS_YCCK: + default: + utility::LogWarning( + "Read JPG failed: color space not supported."); + jpeg_destroy_decompress(&cinfo); + image.Clear(); + return false; + } + jpeg_start_decompress(&cinfo); + image.Prepare(cinfo.output_width, cinfo.output_height, num_of_channels, + bytes_per_channel); + int row_stride = cinfo.output_width * cinfo.output_components; + buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, + row_stride, 1); + uint8_t *pdata = image.data_.data(); + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + memcpy(pdata, buffer[0], row_stride); + pdata += row_stride; + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return true; + } catch (const std::runtime_error &err) { + image.Clear(); + utility::LogWarning(err.what()); + return false; } - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - return true; } } // namespace io diff --git a/cpp/open3d/io/file_format/FilePNG.cpp b/cpp/open3d/io/file_format/FilePNG.cpp index 70832d8ee1e..55a7e410eda 100644 --- a/cpp/open3d/io/file_format/FilePNG.cpp +++ b/cpp/open3d/io/file_format/FilePNG.cpp @@ -106,6 +106,7 @@ bool ReadPNGFromMemory(const unsigned char *image_data_ptr, if (png_image_begin_read_from_memory(&pngimage, image_data_ptr, image_data_size) == 0) { utility::LogWarning("Read PNG failed: unable to parse header."); + image.Clear(); return false; } @@ -122,6 +123,7 @@ bool ReadPNGFromMemory(const unsigned char *image_data_ptr, if (png_image_finish_read(&pngimage, NULL, image.data_.data(), 0, NULL) == 0) { utility::LogWarning("PNG error: {}", pngimage.message); + image.Clear(); return false; } return true; diff --git a/cpp/open3d/t/io/ImageIO.cpp b/cpp/open3d/t/io/ImageIO.cpp index df1ca3625ce..d172ad16cb7 100644 --- a/cpp/open3d/t/io/ImageIO.cpp +++ b/cpp/open3d/t/io/ImageIO.cpp @@ -8,7 +8,9 @@ #include "open3d/t/io/ImageIO.h" #include +#include #include +#include #include #include @@ -23,14 +25,14 @@ namespace open3d { namespace t { namespace io { -static const std::unordered_map< - std::string, - std::function> - file_extension_to_image_read_function{ - {"png", ReadImageFromPNG}, - {"jpg", ReadImageFromJPG}, - {"jpeg", ReadImageFromJPG}, - }; +namespace { +using signature_decoder_t = + std::pair>; +static const std::array signature_decoder_list{ + {{"\x89\x50\x4e\x47\xd\xa\x1a\xa", ReadImageFromPNG}, + {"\xFF\xD8\xFF", ReadImageFromJPG}}}; +static constexpr uint8_t MAX_SIGNATURE_LEN = 8; static const std::unordered_map< std::string, @@ -40,6 +42,7 @@ static const std::unordered_map< {"jpg", WriteImageToJPG}, {"jpeg", WriteImageToJPG}, }; +} // namespace std::shared_ptr CreateImageFromFile( const std::string &filename) { @@ -49,21 +52,27 @@ std::shared_ptr CreateImageFromFile( } bool ReadImage(const std::string &filename, geometry::Image &image) { - std::string filename_ext = - utility::filesystem::GetFileExtensionInLowerCase(filename); - if (filename_ext.empty()) { - utility::LogWarning( - "Read geometry::Image failed: missing file extension."); - return false; - } - auto map_itr = file_extension_to_image_read_function.find(filename_ext); - if (map_itr == file_extension_to_image_read_function.end()) { - utility::LogWarning( - "Read geometry::Image failed: file extension {} unknown", - filename_ext); - return false; + std::string signature_buffer(MAX_SIGNATURE_LEN, 0); + std::ifstream file(filename, std::ios::binary); + file.read(&signature_buffer[0], MAX_SIGNATURE_LEN); + std::string err_msg; + if (!file) { + err_msg = "Read geometry::Image failed for file {}. I/O error."; + } else { + file.close(); + for (const auto &signature_decoder : signature_decoder_list) { + if (signature_buffer.compare(0, signature_decoder.first.size(), + signature_decoder.first) == 0) { + return signature_decoder.second(filename, image); + } + } + err_msg = + "Read geometry::Image failed for file {}. Unknown file " + "signature, only PNG and JPG are supported."; } - return map_itr->second(filename, image); + image.Clear(); + utility::LogWarning(err_msg.c_str(), filename); + return false; } bool WriteImage(const std::string &filename, @@ -99,7 +108,8 @@ DepthNoiseSimulator::DepthNoiseSimulator(const std::string &noise_model_path) { for (int i = 0; i < skip_first_n_lines; ++i) { if (!(line_buffer = file.ReadLine())) { utility::LogError( - "Read depth model failed: file {} is less than {} lines.", + "Read depth model failed: file {} is less than {} " + "lines.", noise_model_path, skip_first_n_lines); } } @@ -182,11 +192,11 @@ geometry::Image DepthNoiseSimulator::Simulate(const geometry::Image &im_src, geometry::kernel::TArrayIndexer dst_indexer(im_dst_tensor, 2); geometry::kernel::TArrayIndexer model_indexer(model_, 3); - // To match the original implementation, we try to keep the same variable - // names with reference to the original code. Compared to the original - // implementation, parallelization is done in im_dst_tensor per-pixel level, - // instead of per-image level. Check out the original code at: - // http://redwood-data.org/indoor/data/simdepth.py. + // To match the original implementation, we try to keep the same + // variable names with reference to the original code. Compared to the + // original implementation, parallelization is done in im_dst_tensor + // per-pixel level, instead of per-image level. Check out the original + // code at: http://redwood-data.org/indoor/data/simdepth.py. core::ParallelFor( core::Device("CPU:0"), width * height, [&] OPEN3D_DEVICE(int workload_idx) { diff --git a/cpp/open3d/t/io/file_format/FileJPG.cpp b/cpp/open3d/t/io/file_format/FileJPG.cpp index dc83fb3ad2e..e1830f5135f 100644 --- a/cpp/open3d/t/io/file_format/FileJPG.cpp +++ b/cpp/open3d/t/io/file_format/FileJPG.cpp @@ -19,6 +19,24 @@ namespace open3d { namespace t { namespace io { +namespace { + +/// Convert libjpeg error messages to std::runtime_error. This prevents +/// libjpeg from exit() in case of errors. +void jpeg_error_throw(j_common_ptr p_cinfo) { + if (p_cinfo->is_decompressor) + jpeg_destroy_decompress( + reinterpret_cast(p_cinfo)); + else + jpeg_destroy_compress( + reinterpret_cast(p_cinfo)); + char buffer[JMSG_LENGTH_MAX]; + (*p_cinfo->err->format_message)(p_cinfo, buffer); + throw std::runtime_error(buffer); +} + +} // namespace + bool ReadImageFromJPG(const std::string &filename, geometry::Image &image) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; @@ -28,56 +46,67 @@ bool ReadImageFromJPG(const std::string &filename, geometry::Image &image) { if ((file_in = utility::filesystem::FOpen(filename, "rb")) == NULL) { utility::LogWarning("Read JPG failed: unable to open file: {}", filename); + image.Clear(); return false; } - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&cinfo); - jpeg_stdio_src(&cinfo, file_in); - jpeg_read_header(&cinfo, TRUE); - - // We only support two channel types: gray, and RGB. - int num_of_channels = 3; - switch (cinfo.jpeg_color_space) { - case JCS_RGB: - case JCS_YCbCr: - cinfo.out_color_space = JCS_RGB; - cinfo.out_color_components = 3; - num_of_channels = 3; - break; - case JCS_GRAYSCALE: - cinfo.jpeg_color_space = JCS_GRAYSCALE; - cinfo.out_color_components = 1; - num_of_channels = 1; - break; - case JCS_CMYK: - case JCS_YCCK: - default: - utility::LogWarning("Read JPG failed: color space not supported."); - jpeg_destroy_decompress(&cinfo); - fclose(file_in); - return false; - } - jpeg_start_decompress(&cinfo); - image.Clear(); - image.Reset(cinfo.output_height, cinfo.output_width, num_of_channels, - core::UInt8, image.GetDevice()); - - int row_stride = cinfo.output_width * cinfo.output_components; - buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, - row_stride, 1); - uint8_t *pdata = static_cast(image.GetDataPtr()); - - while (cinfo.output_scanline < cinfo.output_height) { - jpeg_read_scanlines(&cinfo, buffer, 1); - core::MemoryManager::MemcpyFromHost(pdata, image.GetDevice(), buffer[0], - row_stride * 1); - pdata += row_stride; + try { + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error_throw; + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file_in); + jpeg_read_header(&cinfo, TRUE); + + // We only support two channel types: gray, and RGB. + int num_of_channels = 3; + switch (cinfo.jpeg_color_space) { + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + cinfo.out_color_components = 3; + num_of_channels = 3; + break; + case JCS_GRAYSCALE: + cinfo.jpeg_color_space = JCS_GRAYSCALE; + cinfo.out_color_components = 1; + num_of_channels = 1; + break; + case JCS_CMYK: + case JCS_YCCK: + default: + utility::LogWarning( + "Read JPG failed: color space not supported."); + jpeg_destroy_decompress(&cinfo); + fclose(file_in); + image.Clear(); + return false; + } + jpeg_start_decompress(&cinfo); + image.Clear(); + image.Reset(cinfo.output_height, cinfo.output_width, num_of_channels, + core::UInt8, image.GetDevice()); + + int row_stride = cinfo.output_width * cinfo.output_components; + buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, + row_stride, 1); + uint8_t *pdata = static_cast(image.GetDataPtr()); + + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + core::MemoryManager::MemcpyFromHost(pdata, image.GetDevice(), + buffer[0], row_stride * 1); + pdata += row_stride; + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(file_in); + return true; + } catch (const std::runtime_error &err) { + fclose(file_in); + image.Clear(); + utility::LogWarning("libjpeg error: {}", err.what()); + return false; } - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - fclose(file_in); - return true; } bool WriteImageToJPG(const std::string &filename, @@ -112,31 +141,38 @@ bool WriteImageToJPG(const std::string &filename, return false; } - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_compress(&cinfo); - jpeg_stdio_dest(&cinfo, file_out); - cinfo.image_width = image.GetCols(); - cinfo.image_height = image.GetRows(); - cinfo.input_components = image.GetChannels(); - cinfo.in_color_space = - (cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB); - jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); - jpeg_start_compress(&cinfo, TRUE); - int row_stride = image.GetCols() * image.GetChannels(); - const uint8_t *pdata = static_cast(image.GetDataPtr()); - std::vector buffer(row_stride); - while (cinfo.next_scanline < cinfo.image_height) { - core::MemoryManager::MemcpyToHost(buffer.data(), pdata, - image.GetDevice(), row_stride * 1); - row_pointer[0] = buffer.data(); - jpeg_write_scanlines(&cinfo, row_pointer, 1); - pdata += row_stride; + try { + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error_throw; + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, file_out); + cinfo.image_width = image.GetCols(); + cinfo.image_height = image.GetRows(); + cinfo.input_components = image.GetChannels(); + cinfo.in_color_space = + (cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB); + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + int row_stride = image.GetCols() * image.GetChannels(); + const uint8_t *pdata = static_cast(image.GetDataPtr()); + std::vector buffer(row_stride); + while (cinfo.next_scanline < cinfo.image_height) { + core::MemoryManager::MemcpyToHost( + buffer.data(), pdata, image.GetDevice(), row_stride * 1); + row_pointer[0] = buffer.data(); + jpeg_write_scanlines(&cinfo, row_pointer, 1); + pdata += row_stride; + } + jpeg_finish_compress(&cinfo); + fclose(file_out); + jpeg_destroy_compress(&cinfo); + return true; + } catch (const std::runtime_error &err) { + fclose(file_out); + utility::LogWarning(err.what()); + return false; } - jpeg_finish_compress(&cinfo); - fclose(file_out); - jpeg_destroy_compress(&cinfo); - return true; } } // namespace io diff --git a/cpp/open3d/t/io/file_format/FilePNG.cpp b/cpp/open3d/t/io/file_format/FilePNG.cpp index 2497e2a71c8..8560bc8b431 100644 --- a/cpp/open3d/t/io/file_format/FilePNG.cpp +++ b/cpp/open3d/t/io/file_format/FilePNG.cpp @@ -41,6 +41,7 @@ bool ReadImageFromPNG(const std::string &filename, geometry::Image &image) { pngimage.version = PNG_IMAGE_VERSION; if (png_image_begin_read_from_file(&pngimage, filename.c_str()) == 0) { utility::LogWarning("Read PNG failed: unable to parse header."); + image.Clear(); return false; } @@ -64,6 +65,7 @@ bool ReadImageFromPNG(const std::string &filename, geometry::Image &image) { utility::LogWarning("Read PNG failed: unable to read file: {}", filename); utility::LogWarning("PNG error: {}", pngimage.message); + image.Clear(); return false; } return true; @@ -141,7 +143,7 @@ bool WriteImageToPNGInMemory(std::vector &buffer, buffer.resize(mem_bytes); if (png_image_write_to_memory(&pngimage, &buffer[0], &mem_bytes, 0, image.GetDataPtr(), 0, nullptr) == 0) { - utility::LogWarning("Unable to encode to encode to PNG in memory."); + utility::LogWarning("Unable to encode to PNG in memory."); return false; } return true; diff --git a/examples/python/visualization/demo_scene.py b/examples/python/visualization/demo_scene.py index cfb5d3d2393..b0662feadc7 100644 --- a/examples/python/visualization/demo_scene.py +++ b/examples/python/visualization/demo_scene.py @@ -6,33 +6,15 @@ # ---------------------------------------------------------------------------- """Demo scene demonstrating models, built-in shapes, and materials""" -import math import numpy as np -import os import open3d as o3d import open3d.visualization as vis -def convert_material_record(mat_record): - mat = vis.Material('defaultLit') - # Convert scalar parameters - mat.vector_properties['base_color'] = mat_record.base_color - mat.scalar_properties['metallic'] = mat_record.base_metallic - mat.scalar_properties['roughness'] = mat_record.base_roughness - mat.scalar_properties['reflectance'] = mat_record.base_reflectance - mat.texture_maps['albedo'] = o3d.t.geometry.Image.from_legacy( - mat_record.albedo_img) - mat.texture_maps['normal'] = o3d.t.geometry.Image.from_legacy( - mat_record.normal_img) - mat.texture_maps['ao_rough_metal'] = o3d.t.geometry.Image.from_legacy( - mat_record.ao_rough_metal_img) - return mat - - def create_scene(): - ''' - Creates the geometry and materials for the demo scene and returns a dictionary suitable for draw call - ''' + """Creates the geometry and materials for the demo scene and returns a + dictionary suitable for draw call + """ # Create some shapes for our scene a_cube = o3d.geometry.TriangleMesh.create_box(2, 4, @@ -47,7 +29,7 @@ def create_scene(): resolution=40, create_uv_map=True) a_sphere.compute_vertex_normals() - rotate_90 = o3d.geometry.get_rotation_matrix_from_xyz((-math.pi / 2, 0, 0)) + rotate_90 = o3d.geometry.get_rotation_matrix_from_xyz((-np.pi / 2, 0, 0)) a_sphere.rotate(rotate_90) a_sphere.translate((5, 2.4, 0)) a_sphere = o3d.t.geometry.TriangleMesh.from_legacy(a_sphere) @@ -68,17 +50,13 @@ def create_scene(): # Load an OBJ model for our scene helmet_data = o3d.data.FlightHelmetModel() helmet = o3d.io.read_triangle_model(helmet_data.path) - helmet_parts = [] - for m in helmet.meshes: - # m.mesh.paint_uniform_color((1.0, 0.75, 0.3)) - m.mesh.scale(10.0, (0.0, 0.0, 0.0)) - helmet_parts.append(m) + helmet_parts = o3d.t.geometry.TriangleMesh.from_triangle_mesh_model(helmet) # Create a ground plane ground_plane = o3d.geometry.TriangleMesh.create_box( 50.0, 0.1, 50.0, create_uv_map=True, map_texture_to_each_face=True) ground_plane.compute_triangle_normals() - rotate_180 = o3d.geometry.get_rotation_matrix_from_xyz((-math.pi, 0, 0)) + rotate_180 = o3d.geometry.get_rotation_matrix_from_xyz((-np.pi, 0, 0)) ground_plane.rotate(rotate_180) ground_plane.translate((-25.0, -0.1, -25.0)) ground_plane.paint_uniform_color((1, 1, 1)) @@ -157,12 +135,11 @@ def create_scene(): "geometry": a_sphere }] # Load the helmet - for part in helmet_parts: - name = part.mesh_name - tgeom = o3d.t.geometry.TriangleMesh.from_legacy(part.mesh) - tgeom.material = convert_material_record( - helmet.materials[part.material_idx]) - geoms.append({"name": name, "geometry": tgeom}) + for name, tmesh in helmet_parts.items(): + geoms.append({ + "name": name, + "geometry": tmesh.scale(10.0, (0.0, 0.0, 0.0)) + }) return geoms From a1fb32c92d11e90ce2620787f70d56e3e5acadbc Mon Sep 17 00:00:00 2001 From: Fastriver <31440714+organic-nailer@users.noreply.github.com> Date: Wed, 15 May 2024 13:15:22 +0900 Subject: [PATCH 17/22] Add Mouse Callback to VisualizerWithKeyCallback (#6760) --------- Co-authored-by: Sameer Sheorey --- .../visualizer/VisualizerWithKeyCallback.cpp | 62 +++++++++++++++++++ .../visualizer/VisualizerWithKeyCallback.h | 44 +++++++++++++ cpp/pybind/visualization/visualizer.cpp | 46 ++++++++++++-- .../customized_visualization_key_action.py | 40 +++++++++++- 4 files changed, 185 insertions(+), 7 deletions(-) diff --git a/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.cpp b/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.cpp index a4845166b8a..6fd167b73e5 100644 --- a/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.cpp +++ b/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.cpp @@ -26,6 +26,13 @@ void VisualizerWithKeyCallback::PrintVisualizerHelp() { utility::LogInfo( " The default functions of these keys will be overridden."); utility::LogInfo(""); + + std::string mouse_callbacks = (mouse_move_callback_ ? "MouseMove, " : ""); + mouse_callbacks += (mouse_scroll_callback_ ? "MouseScroll, " : ""); + mouse_callbacks += (mouse_button_callback_ ? "MouseButton, " : ""); + utility::LogInfo(" Custom mouse callbacks registered for: {}", + mouse_callbacks.substr(0, mouse_callbacks.size() - 2)); + utility::LogInfo(""); } void VisualizerWithKeyCallback::RegisterKeyCallback( @@ -38,6 +45,21 @@ void VisualizerWithKeyCallback::RegisterKeyActionCallback( key_action_to_callback_[key] = callback; } +void VisualizerWithKeyCallback::RegisterMouseMoveCallback( + std::function callback) { + mouse_move_callback_ = callback; +} + +void VisualizerWithKeyCallback::RegisterMouseScrollCallback( + std::function callback) { + mouse_scroll_callback_ = callback; +} + +void VisualizerWithKeyCallback::RegisterMouseButtonCallback( + std::function callback) { + mouse_button_callback_ = callback; +} + void VisualizerWithKeyCallback::KeyPressCallback( GLFWwindow *window, int key, int scancode, int action, int mods) { auto action_callback = key_action_to_callback_.find(key); @@ -63,6 +85,46 @@ void VisualizerWithKeyCallback::KeyPressCallback( } } +void VisualizerWithKeyCallback::MouseMoveCallback(GLFWwindow *window, + double x, + double y) { + if (mouse_move_callback_) { + if (mouse_move_callback_(this, x, y)) { + UpdateGeometry(); + } + UpdateRender(); + } else { + Visualizer::MouseMoveCallback(window, x, y); + } +} + +void VisualizerWithKeyCallback::MouseScrollCallback(GLFWwindow *window, + double x, + double y) { + if (mouse_scroll_callback_) { + if (mouse_scroll_callback_(this, x, y)) { + UpdateGeometry(); + } + UpdateRender(); + } else { + Visualizer::MouseScrollCallback(window, x, y); + } +} + +void VisualizerWithKeyCallback::MouseButtonCallback(GLFWwindow *window, + int button, + int action, + int mods) { + if (mouse_button_callback_) { + if (mouse_button_callback_(this, button, action, mods)) { + UpdateGeometry(); + } + UpdateRender(); + } else { + Visualizer::MouseButtonCallback(window, button, action, mods); + } +} + std::string VisualizerWithKeyCallback::PrintKeyToString(int key) { if (key == GLFW_KEY_SPACE) { // 32 return std::string("Space"); diff --git a/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.h b/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.h index 92274978d81..add4e265edc 100644 --- a/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.h +++ b/cpp/open3d/visualization/visualizer/VisualizerWithKeyCallback.h @@ -33,6 +33,7 @@ class VisualizerWithKeyCallback : public Visualizer { void PrintVisualizerHelp() override; void RegisterKeyCallback(int key, std::function callback); + /// Register callback function with access to GLFW key actions. /// /// \param key GLFW key value, see [GLFW key @@ -48,18 +49,61 @@ class VisualizerWithKeyCallback : public Visualizer { void RegisterKeyActionCallback( int key, std::function callback); + /// Register callback function with access to GLFW mouse actions. + /// + /// \param callback The callback function. The callback function takes + /// `Visualizer *`, and the x and y mouse position inside the window and + /// returns a boolean indicating if `UpdateGeometry()` needs to be run. See + /// [GLFW mouse + /// position](https://www.glfw.org/docs/latest/input_guide.html#input_mouse) + /// for more details. + void RegisterMouseMoveCallback( + std::function callback); + + /// Register callback function with access to GLFW mouse actions. + /// + /// \param callback The callback function. The callback function takes + /// `Visualizer *`, and the x and y scroll values and returns a boolean + /// indicating if `UpdateGeometry()` needs to be run. A normal mouse only + /// provides a y scroll value. See [GLFW mouse + /// scrolling](https://www.glfw.org/docs/latest/input_guide.html#scrolling) + /// for more details. + void RegisterMouseScrollCallback( + std::function callback); + + /// Register callback function with access to GLFW mouse actions. + /// + /// \param callback The callback function. The callback function takes + /// `Visualizer *`, `button`, `action` and `mods` as input and returns a + /// boolean indicating UpdateGeometry() needs to be run. The `action` can be + /// one of GLFW_RELEASE (0), GLFW_PRESS (1) or GLFW_REPEAT (2), see [GLFW + /// input interface](https://www.glfw.org/docs/latest/group__input.html). + /// The `mods` specifies the modifier key, see [GLFW modifier + /// key](https://www.glfw.org/docs/latest/group__mods.html). + void RegisterMouseButtonCallback( + std::function callback); + protected: void KeyPressCallback(GLFWwindow *window, int key, int scancode, int action, int mods) override; + void MouseMoveCallback(GLFWwindow *window, double x, double y) override; + void MouseScrollCallback(GLFWwindow *window, double x, double y) override; + void MouseButtonCallback(GLFWwindow *window, + int button, + int action, + int mods) override; std::string PrintKeyToString(int key); protected: std::map> key_to_callback_; std::map> key_action_to_callback_; + std::function mouse_move_callback_; + std::function mouse_scroll_callback_; + std::function mouse_button_callback_; }; } // namespace visualization diff --git a/cpp/pybind/visualization/visualizer.cpp b/cpp/pybind/visualization/visualizer.cpp index 9370612d06c..990727dd45c 100644 --- a/cpp/pybind/visualization/visualizer.cpp +++ b/cpp/pybind/visualization/visualizer.cpp @@ -169,10 +169,48 @@ void pybind_visualizer(py::module &m) { .def("register_key_action_callback", &VisualizerWithKeyCallback::RegisterKeyActionCallback, "Function to register a callback function for a key action " - "event. The callback function takes Visualizer, action and " - "mods as input and returns a boolean indicating if " - "UpdateGeometry() needs to be run.", - "key"_a, "callback_func"_a); + "event. The callback function takes `Visualizer`, `action` " + "and `mods` as input and returns a boolean indicating if " + "`UpdateGeometry()` needs to be run. The `action` can be one " + "of `GLFW_RELEASE` (0), `GLFW_PRESS` (1) or `GLFW_REPEAT` " + "(2), see `GLFW input interface " + "`__. The " + "`mods` specifies the modifier key, see `GLFW modifier key " + "`__", + "key"_a, "callback_func"_a) + + .def("register_mouse_move_callback", + &VisualizerWithKeyCallback::RegisterMouseMoveCallback, + "Function to register a callback function for a mouse move " + "event. The callback function takes Visualizer, x and y mouse " + "position inside the window as input and returns a boolean " + "indicating if UpdateGeometry() needs to be run. `GLFW mouse " + "position `__ for more details.", + "callback_func"_a) + + .def("register_mouse_scroll_callback", + &VisualizerWithKeyCallback::RegisterMouseScrollCallback, + "Function to register a callback function for a mouse scroll " + "event. The callback function takes Visualizer, x and y mouse " + "scroll offset as input and returns a boolean " + "indicating if UpdateGeometry() needs to be run. `GLFW mouse " + "scrolling `__ for more details.", + "callback_func"_a) + + .def("register_mouse_button_callback", + &VisualizerWithKeyCallback::RegisterMouseButtonCallback, + "Function to register a callback function for a mouse button " + "event. The callback function takes `Visualizer`, `button`, " + "`action` and `mods` as input and returns a boolean " + "indicating `UpdateGeometry()` needs to be run. The `action` " + "can be one of GLFW_RELEASE (0), GLFW_PRESS (1) or " + "GLFW_REPEAT (2), see `GLFW input interface " + "`__. " + "The `mods` specifies the modifier key, see `GLFW modifier " + "key `__.", + "callback_func"_a); py::class_, std::shared_ptr> diff --git a/examples/python/visualization/customized_visualization_key_action.py b/examples/python/visualization/customized_visualization_key_action.py index 9f885615e4e..c68e2f1110f 100644 --- a/examples/python/visualization/customized_visualization_key_action.py +++ b/examples/python/visualization/customized_visualization_key_action.py @@ -41,11 +41,45 @@ def animation_callback(vis): vis.run() +def custom_mouse_action(pcd): + + vis = o3d.visualization.VisualizerWithKeyCallback() + buttons = ['left', 'right', 'middle'] + actions = ['up', 'down'] + mods_name = ['shift', 'ctrl', 'alt', 'cmd'] + + def on_key_action(vis, action, mods): + print("on_key_action", action, mods) + + vis.register_key_action_callback(ord("A"), on_key_action) + + def on_mouse_move(vis, x, y): + print(f"on_mouse_move({x:.2f}, {y:.2f})") + + def on_mouse_scroll(vis, x, y): + print(f"on_mouse_scroll({x:.2f}, {y:.2f})") + + def on_mouse_button(vis, button, action, mods): + pressed_mods = " ".join( + [mods_name[i] for i in range(4) if mods & (1 << i)]) + print(f"on_mouse_button: {buttons[button]}, {actions[action]}, " + + pressed_mods) + + vis.register_mouse_move_callback(on_mouse_move) + vis.register_mouse_scroll_callback(on_mouse_scroll) + vis.register_mouse_button_callback(on_mouse_button) + + vis.create_window() + vis.add_geometry(pcd) + vis.run() + + if __name__ == "__main__": ply_data = o3d.data.PLYPointCloud() pcd = o3d.io.read_point_cloud(ply_data.path) - print( - "Customized visualization with smooth key action (without keyboard repeat delay)" - ) + print("Customized visualization with smooth key action " + "(without keyboard repeat delay). Press the space-bar.") custom_key_action_without_kb_repeat_delay(pcd) + print("Customized visualization with mouse action.") + custom_mouse_action(pcd) From 1b55f11f2895edfb6e3312de96644db30fa2b027 Mon Sep 17 00:00:00 2001 From: Nicola Loi <79461707+nicolaloi@users.noreply.github.com> Date: Fri, 17 May 2024 18:34:19 +0200 Subject: [PATCH 18/22] Fix segfault (infinite recursion) of PointCloud::DetectPlanarPatches if multiple points have same coordinates (#6794) --- CHANGELOG.md | 1 + .../PointCloudPlanarPatchDetection.cpp | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b68b0bdbd02..7d3b964d6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Fix log error message for `probability` argument validation in `PointCloud::SegmentPlane` (PR #6622) - Fix macOS arm64 builds, add CI runner for macOS arm64 (PR #6695) - Fix KDTreeFlann possibly using a dangling pointer instead of internal storage and simplified its members (PR #6734) +- Fix segmentation fault (infinite recursion) of DetectPlanarPatches if multiple points have same coordinates (PR #6794) ## 0.13 diff --git a/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp b/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp index 16813bad75e..b76bc9f3fc4 100644 --- a/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp +++ b/cpp/open3d/geometry/PointCloudPlanarPatchDetection.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -57,12 +58,18 @@ class BoundaryVolumeHierarchy { /// \brief Constructor for the root node of the octree. /// - /// \param point_cloud is the associated set of points being partitioned - BoundaryVolumeHierarchy(const PointCloud* point_cloud, - const Eigen::Vector3d& min_bound, - const Eigen::Vector3d& max_bound, - size_t min_points = 1, - double min_size = 0.0) + /// \param point_cloud is the associated set of points being partitioned. + /// \param min_bound is the minimum coordinate of the bounding volume. + /// \param max_bound is the maximum coordinate of the bounding volume. + /// \param min_points is the threshold number of points in a node to stop + /// partitioning it further. \param min_size is the threshold size of a node + /// to stop partitioning it further. + BoundaryVolumeHierarchy( + const PointCloud* point_cloud, + const Eigen::Vector3d& min_bound, + const Eigen::Vector3d& max_bound, + size_t min_points = 1, + double min_size = std::numeric_limits::epsilon()) : point_cloud_(point_cloud), min_points_(min_points), min_size_(min_size), From 69786b68707379f1113a80b0ed75dee2b1c132f2 Mon Sep 17 00:00:00 2001 From: Luis Alonso Murillo Rojas Date: Sun, 19 May 2024 15:17:21 -0600 Subject: [PATCH 19/22] Add SYCL copy support to tensor (#6764) * Add SYCL support to Tensor To method * Check for non-contiguous sycl tensors (not supported) --------- Co-authored-by: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> --- cpp/open3d/core/CMakeLists.txt | 1 + cpp/open3d/core/Device.h | 4 ++ cpp/open3d/core/Tensor.cpp | 2 +- cpp/open3d/core/kernel/UnaryEW.cpp | 16 ++++++-- cpp/open3d/core/kernel/UnaryEW.h | 4 ++ cpp/open3d/core/kernel/UnaryEWSYCL.cpp | 53 ++++++++++++++++++++++++++ cpp/tests/core/Tensor.cpp | 39 +++++++++++++------ docker/Dockerfile.ci | 2 +- 8 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 cpp/open3d/core/kernel/UnaryEWSYCL.cpp diff --git a/cpp/open3d/core/CMakeLists.txt b/cpp/open3d/core/CMakeLists.txt index d42e645da39..7926d3fbcc6 100644 --- a/cpp/open3d/core/CMakeLists.txt +++ b/cpp/open3d/core/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(core PRIVATE kernel/ReductionCPU.cpp kernel/UnaryEW.cpp kernel/UnaryEWCPU.cpp + kernel/UnaryEWSYCL.cpp linalg/AddMM.cpp linalg/AddMMCPU.cpp linalg/Det.cpp diff --git a/cpp/open3d/core/Device.h b/cpp/open3d/core/Device.h index 2df1366c5b9..5b04875ed20 100644 --- a/cpp/open3d/core/Device.h +++ b/cpp/open3d/core/Device.h @@ -99,6 +99,10 @@ class IsDevice { inline bool IsCUDA() const { return GetDevice().GetType() == Device::DeviceType::CUDA; } + + inline bool IsSYCL() const { + return GetDevice().GetType() == Device::DeviceType::SYCL; + } }; } // namespace core diff --git a/cpp/open3d/core/Tensor.cpp b/cpp/open3d/core/Tensor.cpp index 7e1014bc800..8859be594e8 100644 --- a/cpp/open3d/core/Tensor.cpp +++ b/cpp/open3d/core/Tensor.cpp @@ -748,7 +748,7 @@ Tensor Tensor::Contiguous() const { std::string Tensor::ToString(bool with_suffix, const std::string& indent) const { std::ostringstream rc; - if (IsCUDA() || !IsContiguous()) { + if (IsCUDA() || IsSYCL() || !IsContiguous()) { Tensor host_contiguous_tensor = Contiguous().To(Device("CPU:0")); rc << host_contiguous_tensor.ToString(false, indent); } else { diff --git a/cpp/open3d/core/kernel/UnaryEW.cpp b/cpp/open3d/core/kernel/UnaryEW.cpp index d2cb7c89f4d..87b99a268aa 100644 --- a/cpp/open3d/core/kernel/UnaryEW.cpp +++ b/cpp/open3d/core/kernel/UnaryEW.cpp @@ -50,20 +50,28 @@ void Copy(const Tensor& src, Tensor& dst) { src.GetShape(), dst.GetShape()); } - // Disbatch to device + // Dispatch to device Device src_device = src.GetDevice(); Device dst_device = dst.GetDevice(); - if ((!src_device.IsCPU() && !src_device.IsCUDA()) || - (!dst_device.IsCPU() && !dst_device.IsCUDA())) { + if ((!src_device.IsCPU() && !src_device.IsCUDA() && !src_device.IsSYCL()) || + (!dst_device.IsCPU() && !dst_device.IsCUDA() && !dst_device.IsSYCL())) { utility::LogError("Copy: Unimplemented device"); } if (src_device.IsCPU() && dst_device.IsCPU()) { CopyCPU(src, dst); - } else { + } else if ((src_device.IsCPU() || src_device.IsCUDA()) && + (dst_device.IsCPU() || dst_device.IsCUDA())) { #ifdef BUILD_CUDA_MODULE CopyCUDA(src, dst); #else utility::LogError("Not compiled with CUDA, but CUDA device is used."); +#endif + } else if ((src_device.IsCPU() || src_device.IsSYCL()) && + (dst_device.IsCPU() || dst_device.IsSYCL())) { +#ifdef BUILD_SYCL_MODULE + CopySYCL(src, dst); +#else + utility::LogError("Not compiled with SYCL, but SYCL device is used."); #endif } } diff --git a/cpp/open3d/core/kernel/UnaryEW.h b/cpp/open3d/core/kernel/UnaryEW.h index c28c30bcc51..985eba0232a 100644 --- a/cpp/open3d/core/kernel/UnaryEW.h +++ b/cpp/open3d/core/kernel/UnaryEW.h @@ -49,6 +49,10 @@ void CopyCPU(const Tensor& src, Tensor& dst); void CopyCUDA(const Tensor& src, Tensor& dst); #endif +#ifdef BUILD_SYCL_MODULE +void CopySYCL(const Tensor& src, Tensor& dst); +#endif + } // namespace kernel } // namespace core } // namespace open3d diff --git a/cpp/open3d/core/kernel/UnaryEWSYCL.cpp b/cpp/open3d/core/kernel/UnaryEWSYCL.cpp new file mode 100644 index 00000000000..e32005c4b36 --- /dev/null +++ b/cpp/open3d/core/kernel/UnaryEWSYCL.cpp @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// Copyright (c) 2018-2023 www.open3d.org +// SPDX-License-Identifier: MIT +// ---------------------------------------------------------------------------- + +#include +#include + +#include "open3d/core/Dtype.h" +#include "open3d/core/MemoryManager.h" +#include "open3d/core/SizeVector.h" +#include "open3d/core/Tensor.h" +#include "open3d/core/kernel/UnaryEW.h" +#include "open3d/utility/Logging.h" + +namespace open3d { +namespace core { +namespace kernel { + +void CopySYCL(const Tensor& src, Tensor& dst) { + // It has been checked that + // - at least one of src or dst is SYCL device + SizeVector shape = src.GetShape(); + Dtype src_dtype = src.GetDtype(); + Dtype dst_dtype = dst.GetDtype(); + Device dst_device = dst.GetDevice(); + Device src_device = src.GetDevice(); + + if (src_dtype != dst_dtype) { + utility::LogError( + "CopySYCL: Dtype conversion from src to dst not implemented!"); + } + if ((dst_device.IsSYCL() && !dst.IsContiguous()) || + (src_device.IsSYCL() && !src.IsContiguous())) { + utility::LogError( + "CopySYCL: NonContiguous SYCL tensor Copy not implemented!"); + } + Tensor src_conti = src.Contiguous(); // No op if already contiguous + if (dst.IsContiguous() && src.GetShape() == dst.GetShape() && + src_dtype == dst_dtype) { + MemoryManager::Memcpy(dst.GetDataPtr(), dst_device, + src_conti.GetDataPtr(), src_conti.GetDevice(), + src_dtype.ByteSize() * shape.NumElements()); + } else { + dst.CopyFrom(src_conti.To(dst_device)); + } +} + +} // namespace kernel +} // namespace core +} // namespace open3d diff --git a/cpp/tests/core/Tensor.cpp b/cpp/tests/core/Tensor.cpp index ad377274169..e4a4b14cf9d 100644 --- a/cpp/tests/core/Tensor.cpp +++ b/cpp/tests/core/Tensor.cpp @@ -30,12 +30,24 @@ INSTANTIATE_TEST_SUITE_P(Tensor, TensorPermuteDevices, testing::ValuesIn(PermuteDevices::TestCases())); +class TensorPermuteDevicesWithSYCL : public PermuteDevices {}; +INSTANTIATE_TEST_SUITE_P( + Tensor, + TensorPermuteDevicesWithSYCL, + testing::ValuesIn(PermuteDevicesWithSYCL::TestCases())); + class TensorPermuteDevicePairs : public PermuteDevicePairs {}; INSTANTIATE_TEST_SUITE_P( Tensor, TensorPermuteDevicePairs, testing::ValuesIn(TensorPermuteDevicePairs::TestCases())); +class TensorPermuteDevicePairsWithSYCL : public PermuteDevicePairsWithSYCL {}; +INSTANTIATE_TEST_SUITE_P( + Tensor, + TensorPermuteDevicePairsWithSYCL, + testing::ValuesIn(TensorPermuteDevicePairsWithSYCL::TestCases())); + class TensorPermuteSizesDefaultStridesAndDevices : public testing::TestWithParam< std::tuple, @@ -54,7 +66,7 @@ static constexpr const T &AsConst(T &t) noexcept { return t; } -TEST_P(TensorPermuteDevices, Constructor) { +TEST_P(TensorPermuteDevicesWithSYCL, Constructor) { core::Device device = GetParam(); core::Dtype dtype = core::Float32; @@ -71,7 +83,7 @@ TEST_P(TensorPermuteDevices, Constructor) { EXPECT_ANY_THROW(core::Tensor({-1, -1}, dtype, device)); } -TEST_P(TensorPermuteDevices, ConstructorBool) { +TEST_P(TensorPermuteDevicesWithSYCL, ConstructorBool) { core::Device device = GetParam(); core::SizeVector shape{2, 3}; @@ -105,7 +117,7 @@ TEST_P(TensorPermuteDevices, WithInitValue) { EXPECT_EQ(t.ToFlatVector(), vals); } -TEST_P(TensorPermuteDevices, WithInitList) { +TEST_P(TensorPermuteDevicesWithSYCL, WithInitList) { core::Device device = GetParam(); core::Tensor t; @@ -187,7 +199,7 @@ TEST_P(TensorPermuteDevices, WithInitList) { std::exception); } -TEST_P(TensorPermuteDevices, WithInitValueBool) { +TEST_P(TensorPermuteDevicesWithSYCL, WithInitValueBool) { core::Device device = GetParam(); std::vector vals{true, false, true, true, false, false}; @@ -195,7 +207,7 @@ TEST_P(TensorPermuteDevices, WithInitValueBool) { EXPECT_EQ(t.ToFlatVector(), vals); } -TEST_P(TensorPermuteDevices, WithInitValueTypeMismatch) { +TEST_P(TensorPermuteDevicesWithSYCL, WithInitValueTypeMismatch) { core::Device device = GetParam(); std::vector vals{0, 1, 2, 3, 4, 5}; @@ -203,7 +215,7 @@ TEST_P(TensorPermuteDevices, WithInitValueTypeMismatch) { std::runtime_error); } -TEST_P(TensorPermuteDevices, WithInitValueSizeMismatch) { +TEST_P(TensorPermuteDevicesWithSYCL, WithInitValueSizeMismatch) { core::Device device = GetParam(); std::vector vals{0, 1, 2, 3, 4}; @@ -298,7 +310,7 @@ TEST_P(TensorPermuteDevicePairs, IndexSetFillFancy) { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0})); } -TEST_P(TensorPermuteDevicePairs, Copy) { +TEST_P(TensorPermuteDevicePairsWithSYCL, Copy) { core::Device dst_device; core::Device src_device; std::tie(dst_device, src_device) = GetParam(); @@ -317,7 +329,7 @@ TEST_P(TensorPermuteDevicePairs, Copy) { EXPECT_EQ(dst_t.ToFlatVector(), vals); } -TEST_P(TensorPermuteDevicePairs, CopyBool) { +TEST_P(TensorPermuteDevicePairsWithSYCL, CopyBool) { core::Device dst_device; core::Device src_device; std::tie(dst_device, src_device) = GetParam(); @@ -357,12 +369,15 @@ TEST_P(TensorPermuteDevicePairs, ToDevice) { core::Device src_device; std::tie(dst_device, src_device) = GetParam(); - core::Tensor src_t = core::Tensor::Init({0, 1, 2, 3}, src_device); + core::Tensor src_t = + core::Tensor::Init({0.f, 1.f, 2.f, 3.f}, src_device); core::Tensor dst_t = src_t.To(dst_device); EXPECT_TRUE(dst_t.To(src_device).AllClose(src_t)); EXPECT_ANY_THROW(src_t.To(core::Device("CPU:1"))); + EXPECT_ANY_THROW(src_t.To(core::Device("SYCL:100"))); + EXPECT_ANY_THROW(src_t.To(core::Device("CUDA:-1"))); EXPECT_ANY_THROW(src_t.To(core::Device("CUDA:100000"))); } @@ -529,7 +544,7 @@ TEST_P(TensorPermuteDevices, Flatten) { EXPECT_ANY_THROW(src_t.Flatten(2, 1)); } -TEST_P(TensorPermuteDevices, DefaultStrides) { +TEST_P(TensorPermuteDevicesWithSYCL, DefaultStrides) { core::Device device = GetParam(); core::Tensor t0({}, core::Float32, device); @@ -663,7 +678,7 @@ TEST_P(TensorPermuteDevices, ItemAssign) { EXPECT_EQ(t[1][2][3].Item(), 101); } -TEST_P(TensorPermuteDevices, ToString) { +TEST_P(TensorPermuteDevicesWithSYCL, ToString) { using ::testing::AnyOf; core::Device device = GetParam(); core::Tensor t; @@ -738,7 +753,7 @@ TEST_P(TensorPermuteDevices, ToString) { [True False False]])"); } -TEST_P(TensorPermuteDevicePairs, CopyContiguous) { +TEST_P(TensorPermuteDevicePairsWithSYCL, CopyContiguous) { core::Device dst_device; core::Device src_device; std::tie(dst_device, src_device) = GetParam(); diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 73dd3fe1aac..81379352f41 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -94,7 +94,7 @@ RUN conda --version \ # Activate open3d virtualenv # This works during docker build. It becomes the prefix of all RUN commands. # Ref: https://stackoverflow.com/a/60148365/1255535 -SHELL ["conda", "run", "-n", "open3d", "/bin/bash", "-c"] +SHELL ["conda", "run", "-n", "open3d", "/bin/bash", "-o", "pipefail", "-c"] # Dependencies: cmake ENV PATH=${HOME}/${CMAKE_VERSION}/bin:${PATH} From b68eae1616de77529654d038482a34bb8ecb7190 Mon Sep 17 00:00:00 2001 From: Nicola Loi <79461707+nicolaloi@users.noreply.github.com> Date: Thu, 23 May 2024 16:27:10 +0200 Subject: [PATCH 20/22] Fix ransac update of the iteration number (#6789) The update of the iteration number is forced to be non-negative (was negative if corres_inlier_ratio = 0.0) --- CHANGELOG.md | 1 + cpp/open3d/pipelines/registration/Registration.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3b964d6d0..3d9d4bbb575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Fix log error message for `probability` argument validation in `PointCloud::SegmentPlane` (PR #6622) - Fix macOS arm64 builds, add CI runner for macOS arm64 (PR #6695) - Fix KDTreeFlann possibly using a dangling pointer instead of internal storage and simplified its members (PR #6734) +- Fix RANSAC early stop if no inliers in a specific iteration (PR #6789) - Fix segmentation fault (infinite recursion) of DetectPlanarPatches if multiple points have same coordinates (PR #6794) ## 0.13 diff --git a/cpp/open3d/pipelines/registration/Registration.cpp b/cpp/open3d/pipelines/registration/Registration.cpp index 8527d620bf0..6820d2f7c33 100644 --- a/cpp/open3d/pipelines/registration/Registration.cpp +++ b/cpp/open3d/pipelines/registration/Registration.cpp @@ -235,13 +235,15 @@ RegistrationResult RegistrationRANSACBasedOnCorrespondence( max_correspondence_distance, transformation); - // Update exit condition if necessary. - // If confidence is 1.0, then it is safely inf, we always - // consume all the iterations. + // Update exit condition if necessary double est_k_local_d = std::log(1.0 - criteria.confidence_) / std::log(1.0 - std::pow(corres_inlier_ratio, ransac_n)); + // This prevents having a negative number of iterations: + // est_k_local_d = -inf if corres_inlier_ratio = 0.0 + est_k_local_d = + est_k_local_d < 0 ? est_k_local : est_k_local_d; est_k_local = est_k_local_d < est_k_global ? static_cast(std::ceil(est_k_local_d)) From f1f275bb03a7fb69e7bb59c4cc4b5dcd93741134 Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:47:27 -0700 Subject: [PATCH 21/22] Camera coordinate frame visualization with LineSet (#6796) --- cpp/open3d/geometry/LineSetFactory.cpp | 19 +++++++ cpp/open3d/t/geometry/LineSet.cpp | 76 ++++++++++++++++++++++++++ cpp/open3d/t/geometry/LineSet.h | 22 ++++++++ cpp/pybind/t/geometry/lineset.cpp | 31 +++++++++++ 4 files changed, 148 insertions(+) diff --git a/cpp/open3d/geometry/LineSetFactory.cpp b/cpp/open3d/geometry/LineSetFactory.cpp index 41e44d1db5f..3a66d86d300 100644 --- a/cpp/open3d/geometry/LineSetFactory.cpp +++ b/cpp/open3d/geometry/LineSetFactory.cpp @@ -170,6 +170,25 @@ std::shared_ptr LineSet::CreateCameraVisualization( lines->lines_.push_back({4, 1}); lines->PaintUniformColor({0.0f, 0.0f, 1.0f}); + // Add XYZ axes + lines->points_.push_back( + mult(m, Eigen::Vector3d{intrinsic(0, 0) * scale, 0.0, 0.0})); + lines->points_.push_back( + mult(m, Eigen::Vector3d{0.0, intrinsic(1, 1) * scale, 0.0})); + lines->points_.push_back( + mult(m, Eigen::Vector3d{intrinsic(0, 2) * scale, + intrinsic(1, 2) * scale, scale})); + + // Add lines for the axes + lines->lines_.push_back({0, 5}); // X axis (red) + lines->lines_.push_back({0, 6}); // Y axis (green) + lines->lines_.push_back({0, 7}); // Z axis (blue) + + // Set colors for the axes + lines->colors_.push_back({1.0f, 0.0f, 0.0f}); // Red + lines->colors_.push_back({0.0f, 1.0f, 0.0f}); // Green + lines->colors_.push_back({0.0f, 0.0f, 1.0f}); // Blue + return lines; } diff --git a/cpp/open3d/t/geometry/LineSet.cpp b/cpp/open3d/t/geometry/LineSet.cpp index c2d95f0ced2..f525694ca45 100644 --- a/cpp/open3d/t/geometry/LineSet.cpp +++ b/cpp/open3d/t/geometry/LineSet.cpp @@ -9,6 +9,7 @@ #include +#include "open3d/core/Dtype.h" #include "open3d/core/EigenConverter.h" #include "open3d/core/ShapeUtil.h" #include "open3d/core/Tensor.h" @@ -211,6 +212,81 @@ OrientedBoundingBox LineSet::GetOrientedBoundingBox() const { return OrientedBoundingBox::CreateFromPoints(GetPointPositions()); } +LineSet &LineSet::PaintUniformColor(const core::Tensor &color) { + core::AssertTensorShape(color, {3}); + core::Tensor clipped_color = color.To(GetDevice()); + if (color.GetDtype() == core::Float32 || + color.GetDtype() == core::Float64) { + clipped_color = clipped_color.Clip(0.0f, 1.0f); + } + core::Tensor ls_colors = + core::Tensor::Empty({GetLineIndices().GetLength(), 3}, + clipped_color.GetDtype(), GetDevice()); + ls_colors.AsRvalue() = clipped_color; + SetLineColors(ls_colors); + + return *this; +} + +LineSet LineSet::CreateCameraVisualization(int view_width_px, + int view_height_px, + const core::Tensor &intrinsic_in, + const core::Tensor &extrinsic_in, + double scale, + const core::Tensor &color) { + core::AssertTensorShape(intrinsic_in, {3, 3}); + core::AssertTensorShape(extrinsic_in, {4, 4}); + core::Tensor intrinsic = intrinsic_in.To(core::Float32, "CPU:0"); + core::Tensor extrinsic = extrinsic_in.To(core::Float32, "CPU:0"); + + // Calculate points for camera visualization + float w(view_width_px), h(view_height_px), s(scale); + float fx = intrinsic[0][0].Item(), + fy = intrinsic[1][1].Item(), + cx = intrinsic[0][2].Item(), + cy = intrinsic[1][2].Item(); + core::Tensor points = core::Tensor::Init({{0.f, 0.f, 0.f}, // origin + {0.f, 0.f, s}, + {w * s, 0.f, s}, + {w * s, h * s, s}, + {0.f, h * s, s}, + // Add XYZ axes + {fx * s, 0.f, 0.f}, + {0.f, fy * s, 0.f}, + {cx * s, cy * s, s}}); + points = (intrinsic.Inverse().Matmul(points.T()) - + extrinsic.Slice(0, 0, 3).Slice(1, 3, 4)) + .T() + .Matmul(extrinsic.Slice(0, 0, 3).Slice(1, 0, 3)); + + // Add lines for camera frame and XYZ axes + core::Tensor lines = core::Tensor::Init({{0, 1}, + {0, 2}, + {0, 3}, + {0, 4}, + {1, 2}, + {2, 3}, + {3, 4}, + {4, 1}, + // Add XYZ axes + {0, 5}, + {0, 6}, + {0, 7}}); + + LineSet lineset(points, lines); + if (color.NumElements() == 3) { + lineset.PaintUniformColor(color); + } else { + lineset.PaintUniformColor(core::Tensor::Init({0.f, 0.f, 1.f})); + } + auto &lscolors = lineset.GetLineColors(); + lscolors[8] = core::Tensor::Init({1.f, 0.f, 0.f}); // Red + lscolors[9] = core::Tensor::Init({0.f, 1.f, 0.f}); // Green + lscolors[10] = core::Tensor::Init({0.f, 0.f, 1.f}); // Blue + + return lineset; +} + } // namespace geometry } // namespace t } // namespace open3d diff --git a/cpp/open3d/t/geometry/LineSet.h b/cpp/open3d/t/geometry/LineSet.h index 6c3ecb66da8..38fb7c61be2 100644 --- a/cpp/open3d/t/geometry/LineSet.h +++ b/cpp/open3d/t/geometry/LineSet.h @@ -337,6 +337,12 @@ class LineSet : public Geometry, public DrawableGeometry { /// \return Rotated line set. LineSet &Rotate(const core::Tensor &R, const core::Tensor ¢er); + /// \brief Assigns uniform color to all lines of the LineSet. + /// + /// \param color RGB color for the LineSet. {3,} shaped Tensor. + /// Floating color values are clipped between 0.0 and 1.0. + LineSet &PaintUniformColor(const core::Tensor &color); + /// \brief Returns the device attribute of this LineSet. core::Device GetDevice() const override { return device_; } @@ -385,6 +391,22 @@ class LineSet : public Geometry, public DrawableGeometry { double scale = 1.0, bool capping = true) const; + /// Factory function to create a LineSet from intrinsic and extrinsic + /// matrices. + /// + /// \param view_width_px The width of the view, in pixels. + /// \param view_height_px The height of the view, in pixels. + /// \param intrinsic The intrinsic matrix {3,3} shape. + /// \param extrinsic The extrinsic matrix {4,4} shape. + /// \param scale camera scale + /// \param color tensor with float32 dtype and shape {3}. Default is blue. + static LineSet CreateCameraVisualization(int view_width_px, + int view_height_px, + const core::Tensor &intrinsic, + const core::Tensor &extrinsic, + double scale, + const core::Tensor &color = {}); + protected: core::Device device_ = core::Device("CPU:0"); TensorMap point_attr_; diff --git a/cpp/pybind/t/geometry/lineset.cpp b/cpp/pybind/t/geometry/lineset.cpp index d9bf8adbe43..299deef36b0 100644 --- a/cpp/pybind/t/geometry/lineset.cpp +++ b/cpp/pybind/t/geometry/lineset.cpp @@ -299,6 +299,37 @@ transformation as :math:`P = R(P) + t`)"); mesh = lines.extrude_linear([0,1,0]) o3d.visualization.draw([{'name': 'L', 'geometry': mesh}]) +)"); + line_set.def("paint_uniform_color", &LineSet::PaintUniformColor, "color"_a, + "Assigns unifom color to all the lines of the LineSet. " + "Floating color values are clipped between 00 and 1.0. Input " + "`color` should be a (3,) shape tensor."); + line_set.def_static( + "create_camera_visualization", &LineSet::CreateCameraVisualization, + "view_width_px"_a, "view_height_px"_a, "intrinsic"_a, "extrinsic"_a, + "scale"_a = 1.f, "color"_a = core::Tensor({}, core::Float32), + R"(Factory function to create a LineSet from intrinsic and extrinsic +matrices. Camera reference frame is shown with XYZ axes in RGB. + +Args: + view_width_px (int): The width of the view, in pixels. + view_height_px (int): The height of the view, in pixels. + intrinsic (open3d.core.Tensor): The intrinsic matrix {3,3} shape. + extrinsic (open3d.core.Tensor): The extrinsic matrix {4,4} shape. + scale (float): camera scale + color (open3d.core.Tensor): color with float32 and shape {3}. Default is blue. + +Example: + + Draw a purple camera frame with XYZ axes in RGB. + + import open3d.core as o3c + from open3d.t.geometry import LineSet + from open3d.visualization import draw + K = o3c.Tensor([[512, 0, 512], [0, 512, 512], [0, 0, 1]], dtype=o3c.float32) + T = o3c.Tensor.eye(4, dtype=o3c.float32) + ls = LineSet.create_camera_visualization(1024, 1024, K, T, 1, [0.8, 0.2, 0.8]) + draw([ls]) )"); } From 525c4e6567f3a885f930571c3cc83233e35573aa Mon Sep 17 00:00:00 2001 From: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:02:33 -0700 Subject: [PATCH 22/22] Add security policy, GITHUB_TOKEN access restrictions. (#6814) * Add security policy * contents:write for artifact upload, github releases * Add actions:write for concurrency cancellation --- .github/workflows/clean-gcloud-profiles.yml | 7 ++----- .github/workflows/documentation.yml | 3 +++ .github/workflows/macos.yml | 3 +++ .github/workflows/style.yml | 3 +++ .github/workflows/ubuntu-cuda.yml | 3 +++ .github/workflows/ubuntu-openblas.yml | 3 +++ .github/workflows/ubuntu-sycl.yml | 3 +++ .github/workflows/ubuntu-wheel.yml | 5 ++++- .github/workflows/ubuntu.yml | 3 +++ .github/workflows/vtk_packages.yml | 4 ++-- .github/workflows/webrtc.yml | 3 +++ .github/workflows/windows.yml | 3 +++ SECURITY.md | 5 +++++ 13 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 SECURITY.md diff --git a/.github/workflows/clean-gcloud-profiles.yml b/.github/workflows/clean-gcloud-profiles.yml index 0aec813a48f..2521b1de160 100644 --- a/.github/workflows/clean-gcloud-profiles.yml +++ b/.github/workflows/clean-gcloud-profiles.yml @@ -16,14 +16,11 @@ # happens, run this workflow manually to clean up the login profiles. name: Clean GCloud Profiles +permissions: + contents: read on: workflow_dispatch: - # push: - # branches: - # - main - # pull_request: - # types: [opened, reopened, synchronize] env: GCE_GPU_CI_SA: ${{ secrets.GCE_GPU_CI_SA }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 00580af33d9..83527c68b56 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,4 +1,7 @@ name: Documentation +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f0234f975ee..362cc2327f0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,4 +1,7 @@ name: MacOS +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index e0de61516be..3c27504fdd9 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -1,4 +1,7 @@ name: Style Check +permissions: + contents: read + actions: write on: workflow_dispatch: diff --git a/.github/workflows/ubuntu-cuda.yml b/.github/workflows/ubuntu-cuda.yml index 1ff1d20c75b..9cf3cd3e749 100644 --- a/.github/workflows/ubuntu-cuda.yml +++ b/.github/workflows/ubuntu-cuda.yml @@ -1,4 +1,7 @@ name: Ubuntu CUDA +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/.github/workflows/ubuntu-openblas.yml b/.github/workflows/ubuntu-openblas.yml index 9bbb423f457..acfe20e4ddf 100644 --- a/.github/workflows/ubuntu-openblas.yml +++ b/.github/workflows/ubuntu-openblas.yml @@ -1,4 +1,7 @@ name: Ubuntu OpenBLAS +permissions: + contents: read + actions: write on: workflow_dispatch: diff --git a/.github/workflows/ubuntu-sycl.yml b/.github/workflows/ubuntu-sycl.yml index 4f4c9f6d9c1..984d0fe9485 100644 --- a/.github/workflows/ubuntu-sycl.yml +++ b/.github/workflows/ubuntu-sycl.yml @@ -1,4 +1,7 @@ name: Ubuntu SYCL +permissions: + contents: read + actions: write on: workflow_dispatch: diff --git a/.github/workflows/ubuntu-wheel.yml b/.github/workflows/ubuntu-wheel.yml index 2ff88c74b6a..733c52218ee 100644 --- a/.github/workflows/ubuntu-wheel.yml +++ b/.github/workflows/ubuntu-wheel.yml @@ -1,4 +1,7 @@ name: Ubuntu Wheel +permissions: + contents: write + actions: write on: workflow_dispatch: @@ -102,7 +105,7 @@ jobs: run: | gsutil cp ${GITHUB_WORKSPACE}/${{ env.CCACHE_TAR_NAME }}.tar.gz gs://open3d-ci-cache/ - name: Update devel release - # if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/main' }} env: GH_TOKEN: ${{ github.token }} run: | diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ba2765c82c7..08ef6721fea 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -1,4 +1,7 @@ name: Ubuntu +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/.github/workflows/vtk_packages.yml b/.github/workflows/vtk_packages.yml index acd20c5260b..a134daeb71a 100644 --- a/.github/workflows/vtk_packages.yml +++ b/.github/workflows/vtk_packages.yml @@ -1,8 +1,8 @@ name: VTK Packages +permissions: + contents: write on: - # pull_request: - # branches: [ main ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/webrtc.yml b/.github/workflows/webrtc.yml index 90c45e053e7..91a9a5b122d 100644 --- a/.github/workflows/webrtc.yml +++ b/.github/workflows/webrtc.yml @@ -1,4 +1,7 @@ name: WebRTC +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 141f1917a30..c3c1d1e8395 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,4 +1,7 @@ name: Windows +permissions: + contents: write + actions: write on: workflow_dispatch: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..38d9c833993 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy +Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. + +## Reporting a Vulnerability +Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). \ No newline at end of file