From 654809558dc374fe82b2228534f609b671200cac Mon Sep 17 00:00:00 2001 From: DamienGilliard <127743632+DamienGilliard@users.noreply.github.com> Date: Sat, 21 Sep 2024 09:37:41 +0200 Subject: [PATCH] per-joint segmentation works at joint face level (#117) * FIX-UPDATE: clustering works at face level and not joint level anymore * FIX: diffCheckApp replaced with non-failing one (currently not needed) * FIX-UPDATE: Joint segmentator component adapted to output the joints as well as the joint faces individually * FIX: Component and metadata clean-up * FIX: Clean unassociated cliusters adds the colors as well * FIX-UPDATE: CAD degmentator adapted to reflect changes to segmetation backend for face-level analysis * FIX: return value of RunScript of code.py * FIX: syntax fixes for pre-commit * FIX: cleaning PR * FIX-CAP: minor fixes + convertion to ghtree --------- Co-authored-by: Andrea Settimi --- src/diffCheck/segmentation/DFSegmentation.cc | 41 ++++-- src/diffCheck/segmentation/DFSegmentation.hh | 2 +- src/diffCheckApp.cc | 135 +----------------- src/gh/components/DF_CAD_segmentator/code.py | 13 +- .../components/DF_joint_segmentator/code.py | 71 ++++----- .../DF_joint_segmentator/metadata.json | 30 ++-- src/gh/components/DF_mesh_to_cloud/code.py | 14 +- 7 files changed, 104 insertions(+), 202 deletions(-) diff --git a/src/diffCheck/segmentation/DFSegmentation.cc b/src/diffCheck/segmentation/DFSegmentation.cc index 52c8400f..37bb549a 100644 --- a/src/diffCheck/segmentation/DFSegmentation.cc +++ b/src/diffCheck/segmentation/DFSegmentation.cc @@ -95,23 +95,24 @@ namespace diffCheck::segmentation return segments; } - std::shared_ptr DFSegmentation::AssociateClustersToMeshes( + std::vector> DFSegmentation::AssociateClustersToMeshes( std::vector> referenceMesh, std::vector> &clusters, double angleThreshold, double associationThreshold) { - std::shared_ptr unifiedPointCloud = std::make_shared(); + std::vector> jointFaces = std::vector>(); // iterate through the mesh faces given as function argument if (referenceMesh.size() == 0) { DIFFCHECK_WARN("No mesh faces to associate with the clusters. Returning an empty point cloud."); - return unifiedPointCloud; + return std::vector>(); } for (std::shared_ptr face : referenceMesh) { std::shared_ptr correspondingSegment; + std::shared_ptr facePoints = std::make_shared(); // Getting the center of the mesh face Eigen::Vector3d faceCenter = face->Cvt2O3DTriangleMesh()->GetCenter(); @@ -124,7 +125,7 @@ namespace diffCheck::segmentation if (clusters.size() == 0) { DIFFCHECK_WARN("No clusters to associate with the mesh faces. Returning an empty point cloud."); - return unifiedPointCloud; + return std::vector>(); } for (auto segment : clusters) { @@ -160,28 +161,35 @@ namespace diffCheck::segmentation DIFFCHECK_WARN("No segment found for the face. Skipping the face."); continue; } + bool hasColors = correspondingSegment->GetNumColors() > 0; + for (Eigen::Vector3d point : correspondingSegment->Points) { bool pointInFace = false; if (face->IsPointOnFace(point, associationThreshold)) { - unifiedPointCloud->Points.push_back(point); - unifiedPointCloud->Normals.push_back( + facePoints->Points.push_back(point); + facePoints->Normals.push_back( correspondingSegment->Normals[std::distance( correspondingSegment->Points.begin(), std::find(correspondingSegment->Points.begin(), correspondingSegment->Points.end(), point))] ); + if (hasColors) + { + facePoints->Colors.push_back( + correspondingSegment->Colors[std::distance( + correspondingSegment->Points.begin(), + std::find(correspondingSegment->Points.begin(), + correspondingSegment->Points.end(), + point))] + ); + } } } - // removing points from the segment that are in the face - if (unifiedPointCloud->GetNumPoints() == 0) - { - DIFFCHECK_WARN("No point was associated to this segment. Skipping the segment."); - continue; - } - for(Eigen::Vector3d point : unifiedPointCloud->Points) + + for(Eigen::Vector3d point : facePoints->Points) { correspondingSegment->Points.erase( std::remove( @@ -190,8 +198,9 @@ namespace diffCheck::segmentation point), correspondingSegment->Points.end()); } + jointFaces.push_back(facePoints); } - return unifiedPointCloud; + return jointFaces; } void DFSegmentation::CleanUnassociatedClusters( @@ -307,6 +316,10 @@ namespace diffCheck::segmentation { completed_segment->Points.push_back(point); completed_segment->Normals.push_back(cluster->Normals[std::distance(cluster->Points.begin(), std::find(cluster->Points.begin(), cluster->Points.end(), point))]); + if (cluster->GetNumColors() > 0) + { + completed_segment->Colors.push_back(cluster->Colors[std::distance(cluster->Points.begin(), std::find(cluster->Points.begin(), cluster->Points.end(), point))]); + } } } std::vector indicesToRemove; diff --git a/src/diffCheck/segmentation/DFSegmentation.hh b/src/diffCheck/segmentation/DFSegmentation.hh index 2e325aac..d9a6d913 100644 --- a/src/diffCheck/segmentation/DFSegmentation.hh +++ b/src/diffCheck/segmentation/DFSegmentation.hh @@ -33,7 +33,7 @@ namespace diffCheck::segmentation * @param associationThreshold the threshold to consider the points of a segment and a mesh face as associable. It is the ratio between the surface of the closest mesh triangle and the sum of the areas of the three triangles that form the rest of the pyramid described by the mesh triangle and the point we want to associate or not. The lower the number, the more strict the association will be and some poinnts on the mesh face might be wrongfully excluded. * @return std::shared_ptr The unified segments */ - static std::shared_ptr DFSegmentation::AssociateClustersToMeshes( + static std::vector> DFSegmentation::AssociateClustersToMeshes( std::vector> referenceMesh, std::vector> &clusters, double angleThreshold = 0.1, diff --git a/src/diffCheckApp.cc b/src/diffCheckApp.cc index cdb7c7d7..abcfc23e 100644 --- a/src/diffCheckApp.cc +++ b/src/diffCheckApp.cc @@ -12,139 +12,6 @@ int main() { - auto initTime = std::chrono::high_resolution_clock::now(); - - std::shared_ptr pcdSrc = std::make_shared(); - - std::vector>> meshSrc = std::vector>>(); - std::vector> segments; - std::vector meshPaths; - - std::string meshesFolderPath = R"(C:\Users\localuser\Desktop\meshes_for_diffCheck\joints\)"; - - for (int i = 1; i <= 3; i++) - { - std::vector> fullJoint; - for (int j = 1; j <= 3; j++) - { - std::string meshPath = meshesFolderPath + std::to_string(i) + "/" + std::to_string(j) + ".ply"; - std::shared_ptr mesh = std::make_shared(); - mesh->LoadFromPLY(meshPath); - fullJoint.push_back(mesh); - } - meshSrc.push_back(fullJoint); - } - - std::string pathPcdSrc = R"(C:\Users\localuser\Desktop\meshes_for_diffCheck\joints\full_beam.ply)"; - - pcdSrc->LoadFromPLY(pathPcdSrc); - - pcdSrc->EstimateNormals(false, 100); - pcdSrc->VoxelDownsample(0.007); - auto intermediateTime = std::chrono::high_resolution_clock::now(); - segments = diffCheck::segmentation::DFSegmentation::NormalBasedSegmentation( - pcdSrc, - 15.0f, - 15, - true, - 50, - 0.5f, - false); - std::cout << "number of segments:" << segments.size()<< std::endl; - - std::vector> unifiedSegments; - for (int i = 0; i < meshSrc.size(); i++) - { - std::shared_ptr unifiedSegment = std::make_shared(); - unifiedSegment = diffCheck::segmentation::DFSegmentation::AssociateClustersToMeshes( - meshSrc[i], - segments, - .2, - .05); - unifiedSegments.push_back(unifiedSegment); - } - - diffCheck::segmentation::DFSegmentation::CleanUnassociatedClusters(segments, - unifiedSegments, - meshSrc, - .2, - .05); - - // Perform a registration per joint - for (int i = 0; i < meshSrc.size(); i++) - { - std::shared_ptr referencePointCloud = std::make_shared(); - for (auto jointFace : meshSrc[i]) - { - std::shared_ptr facePointCloud = jointFace->SampleCloudUniform(1000); - referencePointCloud->Points.insert(referencePointCloud->Points.end(), facePointCloud->Points.begin(), facePointCloud->Points.end()); - } - referencePointCloud->EstimateNormals(false, 100); - - diffCheck::transformation::DFTransformation transformation = diffCheck::registrations::DFRefinedRegistration::O3DICP( - unifiedSegments[i], - referencePointCloud); - - std::cout << "Transformation matrix:" << std::endl; - std::cout << transformation.TransformationMatrix << std::endl; - - diffCheck::visualizer::Visualizer deVisu = diffCheck::visualizer::Visualizer("DiffCheckApp", 1000, 800, 50, 50, false, true, false); - for (int i = 0; i < segments.size(); i++) - { - segments[i]->ApplyTransformation(transformation); - deVisu.AddPointCloud(segments[i]); - } - for (auto joint : meshSrc) - { - for (auto face : joint) - { - deVisu.AddMesh(face); - } - } - deVisu.Run(); - } - - diffCheck::visualizer::Visualizer vis(std::string("DiffCheckApp"), 1000, 800, 50, 50, false, true, false); - for (auto segment : segments) - { - // colorize the segments with random colors - double r = static_cast(rand()) / RAND_MAX; - double g = static_cast(rand()) / RAND_MAX; - double b = static_cast(rand()) / RAND_MAX; - - segment->Colors.clear(); - for (int i = 0; i < segment->Points.size(); i++) - { - segment->Colors.push_back(Eigen::Vector3d(0, 0, 0)); - } - vis.AddPointCloud(segment); - } - for(auto joint : meshSrc) - { - for(auto mesh : joint){vis.AddMesh(mesh);} - } - - int numSegments = unifiedSegments.size(); - - for (int i = 0; i < numSegments; i++) - { - for (int j = 0; j < unifiedSegments[i]->Points.size(); j++) - { - unifiedSegments[i]->Colors.push_back(Eigen::Vector3d((double(numSegments) - double(i))/double(numSegments), 1, double(i) / double(numSegments))); - } - } - for (auto seg : unifiedSegments) - { - vis.AddPointCloud(seg); - } - - auto endTime = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(endTime - initTime); - auto segmentationTime = std::chrono::duration_cast(endTime - intermediateTime); - std::cout << "Total computation time:" << duration.count() << std::endl; - std::cout << "Segmentation time:" << segmentationTime.count() << std::endl; - - vis.Run(); - + std::cout << "Hello, World!" << std::endl; return 0; } \ No newline at end of file diff --git a/src/gh/components/DF_CAD_segmentator/code.py b/src/gh/components/DF_CAD_segmentator/code.py index 2e57ca65..f0cc9678 100644 --- a/src/gh/components/DF_CAD_segmentator/code.py +++ b/src/gh/components/DF_CAD_segmentator/code.py @@ -8,6 +8,7 @@ from diffCheck.diffcheck_bindings import dfb_segmentation +from diffCheck.diffcheck_bindings import dfb_geometry from diffCheck import df_cvt_bindings @@ -18,10 +19,10 @@ def RunScript(self, i_clouds: System.Collections.Generic.IList[Rhino.Geometry.PointCloud], i_assembly, i_angle_threshold: float = 0.1, - i_association_threshold: float = 0.1) -> rg.PointCloud: + i_association_threshold: float = 0.1) -> Rhino.Geometry.PointCloud: if i_clouds is None or i_assembly is None: - self.AddRuntimeMessage(RML.Warning, "Please provide a cloud and an assembly to segmentate") + self.AddRuntimeMessage(RML.Warning, "Please provide a cloud and an assembly to segment.") return None if i_angle_threshold is None: i_angle_threshold = 0.1 @@ -43,12 +44,16 @@ def RunScript(self, df_beams_meshes.append(df_b_mesh_faces) rh_beams_meshes.append(rh_b_mesh_faces) - df_asssociated_cluster = dfb_segmentation.DFSegmentation.associate_clusters( + df_asssociated_cluster_faces = dfb_segmentation.DFSegmentation.associate_clusters( reference_mesh=df_b_mesh_faces, unassociated_clusters=df_clouds, angle_threshold=i_angle_threshold, association_threshold=i_association_threshold ) + + df_asssociated_cluster = dfb_geometry.DFPointCloud() + for df_associated_face in df_asssociated_cluster_faces: + df_asssociated_cluster.add_points(df_associated_face) df_clusters.append(df_asssociated_cluster) dfb_segmentation.DFSegmentation.clean_unassociated_clusters( @@ -61,4 +66,4 @@ def RunScript(self, o_clusters = [df_cvt_bindings.cvt_dfcloud_2_rhcloud(cluster) for cluster in df_clusters] - return o_clusters, rh_beams_meshes + return o_clusters diff --git a/src/gh/components/DF_joint_segmentator/code.py b/src/gh/components/DF_joint_segmentator/code.py index c981706b..5446b278 100644 --- a/src/gh/components/DF_joint_segmentator/code.py +++ b/src/gh/components/DF_joint_segmentator/code.py @@ -1,15 +1,15 @@ #! python3 +import System + import Rhino +import ghpythonlib.treehelpers -import diffCheck from diffCheck import diffcheck_bindings from diffCheck import df_cvt_bindings as df_cvt -from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML from ghpythonlib.componentbase import executingcomponent as component -import System ABSTOL = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance @@ -17,16 +17,20 @@ class DFJointSegmentator(component): def __init__(self): super(DFJointSegmentator, self).__init__() def RunScript(self, - i_clusters: System.Collections.Generic.List[Rhino.Geometry.PointCloud], - i_assembly: diffCheck.df_geometries.DFAssembly, - i_angle_threshold: float, - i_distance_threshold: float): - + i_clusters: System.Collections.Generic.List[Rhino.Geometry.PointCloud], + i_assembly, + i_angle_threshold: float, + i_distance_threshold: float, + i_correspondence_distance: float,): + + if i_clusters is None or i_assembly is None: + return None if i_angle_threshold is None: i_angle_threshold = 0.1 if i_distance_threshold is None: i_distance_threshold = 0.1 - + if i_correspondence_distance is None: + i_correspondence_distance = 0.005 if len(i_clusters) == 0: raise ValueError("No clusters given.") if not isinstance(i_clusters[0], Rhino.Geometry.PointCloud): @@ -44,41 +48,38 @@ def RunScript(self, face.Faces.ConvertQuadsToTriangles() df_joints[joint.id].append(df_cvt.cvt_rhmesh_2_dfmesh(face)) - ref_rh_joint_clouds = [] - transforms = [] - rh_joint_segments = [] + o_reference_point_clouds = [] + o_joint_faces_segments = [] df_cloud_clusters = [df_cvt.cvt_rhcloud_2_dfcloud(cluster) for cluster in i_clusters] + df_joint_clouds = [] - # for each joint, find the corresponding clusters and merge them, generate a reference point cloud, and register the merged clusters to the reference point cloud + # for each joint, find the corresponding faces, store them as such but also merge them, generate a reference point cloud, and register the merged clusters to the reference point cloud for df_joint in df_joints: + rh_joint_faces_segments = [] + # create the reference point cloud ref_df_joint_cloud = diffcheck_bindings.dfb_geometry.DFPointCloud() - for face in df_joint: ref_face_cloud = face.sample_points_uniformly(1000) ref_df_joint_cloud.add_points(ref_face_cloud) - - ref_rh_joint_clouds.append(df_cvt.cvt_dfcloud_2_rhcloud(ref_df_joint_cloud)) + o_reference_point_clouds.append(df_cvt.cvt_dfcloud_2_rhcloud(ref_df_joint_cloud)) # find the corresponding clusters and merge them - df_joint_segment = diffcheck_bindings.dfb_segmentation.DFSegmentation.associate_clusters(df_joint, df_cloud_clusters, i_angle_threshold, i_distance_threshold) - diffcheck_bindings.dfb_segmentation.DFSegmentation.clean_unassociated_clusters(df_cloud_clusters, [df_joint_segment], [df_joint], i_angle_threshold, i_distance_threshold) + df_joint_cloud = diffcheck_bindings.dfb_geometry.DFPointCloud() + df_joint_face_segments = diffcheck_bindings.dfb_segmentation.DFSegmentation.associate_clusters(df_joint, df_cloud_clusters, i_angle_threshold, i_distance_threshold) + for df_joint_face_segment in df_joint_face_segments: + df_joint_cloud.add_points(df_joint_face_segment) - # register the merged clusters to the reference point cloud - registration = diffcheck_bindings.dfb_registrations.DFRefinedRegistration.O3DICP(df_joint_segment, ref_df_joint_cloud) - res = registration.transformation_matrix - transforms.append(df_cvt.cvt_ndarray_2_rh_transform(res)) - rh_joint_segments.append(df_cvt.cvt_dfcloud_2_rhcloud(df_joint_segment)) + # register the joint faces to the reference point cloud + transform = diffcheck_bindings.dfb_registrations.DFRefinedRegistration.O3DICP(df_joint_cloud, ref_df_joint_cloud, max_correspondence_distance = i_correspondence_distance) + for df_joint_face_segment in df_joint_face_segments: + df_joint_face_segment.apply_transformation(transform) + rh_joint_faces_segments.append(df_cvt.cvt_dfcloud_2_rhcloud(df_joint_face_segment)) + df_joint_clouds.append(df_joint_cloud) + o_joint_faces_segments.append(rh_joint_faces_segments) - o_joint_segments = [] - o_transforms = [] - o_reference_point_clouds = [] - for joint_segment, transform, _joint_cloud in zip(rh_joint_segments, transforms, ref_rh_joint_clouds): - if joint_segment.IsValid: - o_joint_segments.append(joint_segment) - o_transforms.append(transform) - o_reference_point_clouds.append(_joint_cloud) - else: - ghenv.Component.AddRuntimeMessage(RML.Warning, "Some joints could not be segmented and were ignored.") # noqa: F821 - - return o_joint_segments, o_transforms, o_reference_point_clouds + o_joint_segments = [df_cvt.cvt_dfcloud_2_rhcloud(df_joint_cloud) for df_joint_cloud in df_joint_clouds] + + o_gh_tree_joint_faces_segments = ghpythonlib.treehelpers.list_to_tree(o_joint_faces_segments) + + return o_gh_tree_joint_faces_segments, o_joint_segments, o_reference_point_clouds diff --git a/src/gh/components/DF_joint_segmentator/metadata.json b/src/gh/components/DF_joint_segmentator/metadata.json index 987bcdc6..8cc16a32 100644 --- a/src/gh/components/DF_joint_segmentator/metadata.json +++ b/src/gh/components/DF_joint_segmentator/metadata.json @@ -5,7 +5,7 @@ "subcategory": "Segmentation", "description": "Extracts the joints from a point cloud.", "exposure": 4, - "instanceGuid": "fd804928-bf11-43eb-a29f-8afabefa660d", + "instanceGuid": "bf27b323-6b03-42f2-a2de-638aeb2c08f9", "ghpython": { "hideOutput": true, "hideInput": true, @@ -50,8 +50,8 @@ "typeHintID": "float" }, { - "name": "i_association_threshold", - "nickname": "i_association_threshold", + "name": "i_distance_threshold", + "nickname": "i_distance_threshold", "description": "From 0 to infinite. By default 0.1. The closer to 0 the less permissive your point.", "optional": true, "allowTreeAccess": true, @@ -60,21 +60,33 @@ "wireDisplay": "default", "sourceCount": 0, "typeHintID": "float" + }, + { + "name": "i_correspondence_distance", + "nickname": "i_correspondence_distance", + "description": "The maximum correspondence distance for the local ICP registration that re-aligns the joints for per-face analysis of each joint. The default value is 0.005", + "optional": true, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "float" } ], "outputParameters": [ { - "name": "o_joint_segments", - "nickname": "o_joint_segments", - "description": "The extracted joints.", + "name": "o_joint_face_segments", + "nickname": "o_joint_face_segments", + "description": "The individual faces of the extracted joints.", "optional": false, "sourceCount": 0, "graft": false }, { - "name": "o_transforms", - "nickname": "o_transforms", - "description": "The transformations for each joint.", + "name": "o_joint_segments", + "nickname": "o_joint_segments", + "description": "The extracted joints.", "optional": false, "sourceCount": 0, "graft": false diff --git a/src/gh/components/DF_mesh_to_cloud/code.py b/src/gh/components/DF_mesh_to_cloud/code.py index f3087d84..e7b3f660 100644 --- a/src/gh/components/DF_mesh_to_cloud/code.py +++ b/src/gh/components/DF_mesh_to_cloud/code.py @@ -12,9 +12,15 @@ class DFMeshToCloud(component): - def RunScript(self, i_mesh: Rhino.Geometry.Mesh, i_points: int) -> Rhino.Geometry.PointCloud: - df_mesh = diffCheck.df_cvt_bindings.cvt_rhmesh_2_dfmesh(i_mesh) - df_cloud = df_mesh.sample_points_uniformly(i_points) + def RunScript(self, + i_meshes: System.Collections.Generic.List[Rhino.Geometry.Mesh], + i_points: int) -> Rhino.Geometry.PointCloud: + + if i_meshes is None: + return None + + if i_points is None: + i_points = 1000 rh_mesh = i_meshes[0] for i in range(1, len(i_meshes)): @@ -27,8 +33,6 @@ def RunScript(self, i_mesh: Rhino.Geometry.Mesh, i_points: int) -> Rhino.Geometr df_cloud = df_mesh.sample_points_uniformly(i_points) rgpoints = [Rhino.Geometry.Point3d(p[0], p[1], p[2]) for p in df_cloud.points] # convert the df_cloud to a rhino cloud - - rgpoints = [Rhino.Geometry.Point3d(pt[0], pt[1], pt[2]) for pt in df_cloud.points] rh_cloud = Rhino.Geometry.PointCloud(rgpoints) return [rh_cloud]