Point Cloud trimming with Breps & Meshes

Hi folks,

I’m working with some scanned data that I’m trying to build an automated process around, and I’m having a hell of a time with working with the point clouds because the data is pretty noisy—so I’m looking for ways to pre-process the cloud prior to trying to make a mesh.

I have a known volume in the scanner’s space that I’m interested in data from, and I’d like to start by automatically excluding data outside that volume, which I have modeled as a simple brep.

I must be missing something, because I haven’t found an easy way to test containment on the point cloud to discard points that lie outside the bounding volume that won’t bring my system to it’s knees. I can do it in Grasshopper by first exploding the point cloud into a point collection, but with close to ~1 million points, things slow to a crawl almost immediately.

Can anyone suggest tools/commands that allow basic boolean testing on point-clouds for inclusion/exclusion that will work with larger data sets?

I believe CloudCompare (open source) can do this using scalar fields although I have not done it myself.

I’ve been playing with it, and have been making some progress on things, but I was hoping there’d be a Grasshopper/Rhino-native/compatible solution that I could use because of all the other editing that one cannot do in Meshlab and CloudCompare easily. I know there’s a library written by Ervine LIN and Christophe GIROT, because they show stuff they’ve done with it in this cool paper here.

I just haven’t found those tools on Food4Rhino or anywhere else in the wild, and I’d really like to find those or at least something similar that I could use. Worst-case scenario would be trying to figure out how to do it with scripting or something, which I haven’t done much with yet…

So my question stands for those kinds of resources :blush: and if I can’t find any, then maybe I’ll have to try to make some…

Hi Jonathan,

if you are using Rhino on Windows you might try the python script below to clip a pointcloud.

ClipPointCloud.py (1.5 KB)

c.

Clement, I’ve just read through your script and will give it a try when I get back to my session working on that—thank you a ton, the code is clean and human readable!

I’m inspired to start learning to do this myself, andh hopefully I’m not buoyed by a false confidence that you made it look too easy!

Now my next task if this works is to wrap this in along with a bunch of other steps and automate it on a batch process for about 40 scans :wink:

-Jonathan

Well, i´ve just had a simpler version using the available method to remove points from the pointcloud :

Rhino.Geometry.PointCloud.RemoveAt(index)

but somehow this was running in snailspeed. So i´ve decided to create a new pointcloud and replace the old one which seemed to be much faster. After i´ve seen this is not really fast with large clouds i´ve added multiprocessing. It might be simpler to understand without it :wink:

btw. fighting the noise in your cloud might be better done in CloudCompare by smoothing using MLS.

c.

Hi Clement,

I’ve been having success with the script you had written to check Brep inclusion on point clouds, however I’ve noticed that in doing it’s job it drops color and normal data from the point cloud—can you think of any reason it’s losing those attributes?

Hi Jonathan, yes, you did not ask for the pro version :smile:

ClipPointCloudPro.py (3.2 KB)

Pun aside, if the input PointCloud has colors or normals or both, i have to add item by item to a pointcloud object which is much slower compared to adding only points. Using only points, i can build them threaded first and add them in one go. That does not work with colors and/or normals, if i try to, Rhino 5 would crash.

@stevebaer, i´ve encountered that Rhino.Geometry.PointCloud.Add() is not threadsave. If i try to add points with normals and/or colors to a pointcloud object, Rhino 5 crashes. Example:

def MyWorker(w):
    new_cloud.Add(pts[w], normals[w], colors[w])
tasks.Parallel.ForEach(xrange(old_cloud.Count), MyWorker)

It would be helpful if the constructor allows to create the pointcloud with additional normals and/or colors.

thanks,
c.

Fantastic, thanks! I shall have to tip you someday soon with interesting 3d-printed or CNC cut things. Will need to follow up about that directly :wink:

Very interesting—I was looking into the first version of your code earlier—I hadn’t finished putting it together to try it, but I was wondering if a different approach might be faster (if possible):

  1. Get Point Cloud
  2. Get Brep
  3. Do the usual threaded testing for point inclusion, but keep track of the index of the points which are being kept from the clipping, in addition to the actual XYZ locations.
  4. Use the resulting points-list index values to fetch the matching normal values from the original, and include them in the result.

Then the corollary thought was ‘could this be somewhat “manually” threaded by sub-dividing the source cloud into multiple cloud “sections”?’ and then independently run the testing & subsequent normals/colors re-allocation based on those trimmed lists, and lastly merge the resulting sub-lists back together.

That way you’re not running object.IsPointInside() where you can’t keep it threaded, and you’re getting your normals without having to test for them…

But like I said, I haven’t built said script yet (I’m a noob, these things take me a while…) so you’d be better equipped to tell me if that’s foolishness or not.

Hi Jonathan, your approach would be fine, especially step 4 above is what i would like to do to keep it threaded. But currently the constructor:

Rhino.Geometry.PointCloud(Enumerable(Point3d))

only allows to build the pointcloud in one go including an enumerable of points. There is no way to pass normals or colors yet. Therefore pointcloud items have to be added one by one, which is quite slow. :smirk:

c.

You’re saying the process of getting the information from the existing objects isn’t the problem, it’s a problem of adding the points to the new object that’s troublesome?

It is a missing feature in RhinoCommon. I can add a new pointcloud in one step providing only points. But if i would like to provide colors and/or normals, i have to create an empty pointcloud and add items (a point with color and normal) one by one. In the recent script above, both ways to create a pointcloud are used.

c.

Gotcha, interesting. Well I’m learning as I go, and every bit of explanation helps! Thanks!

It would be very useful to be able to add attributes to individual points in an existing cloud going forward—but continuing to explore by speculation here, I have a feeling that this has to do with an underlying issue of the limited mutability of the point-cloud data structure & memory footprint… since the benefits of the cloud are that it’s fast, I’m guessing the speed is because it’s a known entity and can be treated as fairly static/monolithic—leading to the trade-off with it being less editable?

It’d be interesting if the data structure wasn’t Cloud(Points(Attributes(location, color, normal) but instead was Cloud(PointsList(Location[PointX], Color[PointX], Normal[PointX])), where the location, color, and normal data are kept as lists of tuples all tied together by a common index value.

(just pure novice speculation here)

Actually, regarding my point #4 and your comment about passing points, I didn’t realize that Point3d didn’t have the structure to include that additional data and that points could only hold location—sounds like a ‘Point3dPlus’ class might be needed to give individual point objects the capacity to hold that data?

You can see here which properties the PointCloudItem class supports under:

Rhino.Geometry.PointCloudItem()

Depending on the value you would like to store with a Point3d, there is a Point4d class under:

Rhino.Geometry.Point4d(x,y,z,w)

but this cannot be used in a regular pointcloud. A pointcloud item can store location, normal, color and hidden property only. To store additional data, eg. user text, it could be done but this is bound to the whole pointcloud object, not its items.

c.

Noted this in my reply on the other related post :wink:

Very few ordered lists are threadsafe when it comes to adding elements. This goes for most programming languages and libraries in general.

Thank you @stevebaer for your response, i’ve feared about that.

Is there a way to make the pointcloud constructor capable of adding normals and/or colors along with the points? Something like this:

new_cloud = Rhino.Geometry.PointCloud(points, normals, colors)

I guess if it would accept that, i can append to lists threaded like i do with the points and create the pointcloud in a single step.

c.

Hello clement,

First off, huge thank you for providing the PointCloudPro script. I’ve found myself in need of cutting many colored point clouds, and it was a big help.

However, now I need to invert the effect. As in, treat the mesh object like a cutter and DELETE any points that are inside it. (I already tried IsPointOutside in the script and got this. I am not proficient in Python):

2019-08-06_180225

If you have any insights, I would appreciate it. Is it a quick alteration? Thanks.

It appears I spoke too soon. By adding “not” after “if” to lines 28, 53, 58, and 63, I was able to reverse the effect.

ClipPointCloudPro-Invert.py (3.2 KB)