Closed xiangtaoxu closed 2 years ago
Hi, I tried to reproduce your problem with test009.py, which is very similar. We can see that the generated scalar field is called default. It contains the right distance information, but an initialization is needed:
sf = cloud.getScalarField(0)
sf.getMin() # returns 0
sf.getMax() # returns 0
sf.computeMinAndMax() # <== not done by default, maybe confusing !
sf.getMax() # returns 9.305278778076172
sf.getMin() # returns 2.082047939300537
Does this answer your problem? If not, can you provide me with a dataset to reproduce it? Anyway, I see that I need to at least complete the documentation and example to make it clearer, and maybe rename the field and compute min and max!
Thanks and regards, Paul
Good to know 'Default' is the SF name.
I should state more clearly. After calculating the distances, I convert the sf to numpy array and check their values. Those values are very different from values I calculated with CC directly.
I am attaching an example cloud and mesh. The cloud (stem_0.bin) has a field called "C2M signed distances", which is calculated from CC directly.
Hi, I reproduce the problem, it is indeed annoying, I do not see where is the error in CloudCompy at first reading, I will make a deeper analysis...
Hello, here is a temporary solution in Python (octree and scalar field before the calculation):
import os
import sys
import math
os.environ["_CCTRACE_"]="ON"
import cloudComPy as cc
mesh = cc.loadMesh("mesh_0.bin")
cloud = cc.loadPointCloud("stem_0.bin")
octree = cloud.computeOctree()
nsf = cloud.addScalarField("Temp. distances")
cloud.setCurrentScalarField(nsf)
params = cc.Cloud2MeshDistancesComputationParams()
params.maxThreadCount=8
params.octreeLevel=6
ret = cc.DistanceComputationTools.computeCloud2MeshDistances(cloud, mesh, params)
if ret == 1:
nsf = cloud.getScalarFieldDic()["Temp. distances"]
sf = cloud.getScalarField(nsf)
sf.computeMinAndMax()
sf.setName("C2M absolute distances")
cc.SaveEntities([cloud, mesh], "cm1.bin")
I will integrate the fixes into CloudComPy with the next release. I hope this answers your problem, Thank you for pointing out this bug!
Regards, Paul
Perfect! It is working for me! Thanks!
Look forward to the new release!
Also, a follow-up question.
I found the speed for C2M calculation in CloudComPy is much SLOWER than CC command line mode. I am testing with a point cloud with ~200k points and a mesh with 13k points.
the CC commandline takes ~ 5-8 seconds while CloudComPy takes 2-3 minutes.
Is this expected?
No. There is no reason why it should take much longer with CloudComPy than CloudCompare, but you have to be sure to use an optimal parameterization, it's quite sensitive, among other things the octree level, the multi-threading... and, as from Python, you don't call the algorithm in the same context as from the GUI, in which there is a big pre-processing that is difficult to wrap in Python without rewriting everything, there is maybe something that I didn't see... I'll do some tests on large enough examples to check, I hadn't done any measurement or perceived any problem on my very simple tests. Paul
I see. I will make some explorations to see whether there is a way to adapt the parameters to match the speed then.
Hello, I did some tests on a good size problem (cloud of 10 millions points, mesh of 60000 faces). The distance calculation time depends very strongly on the distance between the mesh and the cloud (from 1 second when they are close to each other to about two minutes when they are well separated, in my example). It is therefore necessary to compare the same configuration. When the cloud and the mesh are close, the GUI displays a shorter computation time (elapse time) than Python (1.5 s / 3s). When the cloud and the mesh are far away, it is very close: GUI 108s, Python 110s. But it is very sensitive to the octree level: the GUI makes a first approximate calculation to identify a good value for the octree level (it varies with the distance). In my example, if I change the octree level from 7 to 9, I go from 110s to 360s in the Python calculation. in the C++ code there is a function determineBestOctreeLevel which uses the result of computeApproxCloud2CloudDistance. It is not available in CloudComPy but would be useful! I will try to add it. Paul
@prascle
See below for my first attempt to improve C2M calculations in Python
Did some quick benchmark with my data sets.
When using CC command line/GUI, average time is 20s. When using the code below, the average time is 8s. Mean difference of the signed distances is 0.0017 while rmse is 0.0117 (1.17 cm), which looks pretty good to me.
# functions
def ComputeC2MDistance(cloud,mesh,signedDistances=True,resolution=0.01,maxThreadCount=0,
direction=3):
'''
Resolution by default is 1cm
direction is used to refine estimates of octree
'''
############################################################
# Prepare some auxiliary data to approximate best parameters
###########################################################
# Bounding Box and best Octree Level
bb = cloud.getOwnBB()
bb_max, bb_min = bb.maxCorner(), bb.minCorner()
x_dim = bb_max[0] - bb_min[0]
y_dim = bb_max[1] - bb_min[1]
z_dim = bb_max[2] - bb_min[2]
# calculate cc_octree based on direction
# direction has to be 0, 1, 2, 3
# 3 means interested in distances in all directions
# 0, 1, 2 means interested in X, Y, Z directions
octree_levels = [np.floor(np.log2(x_dim / resolution)),
np.floor(np.log2(y_dim / resolution)),
np.floor(np.log2(z_dim / resolution))]
if direction == 3:
best_octree_level = int(np.max(octree_levels))
elif direction in [0,1,2]:
best_octree_level = int(octree_levels[direction])
else:
print("Wrong direction parameter. Must be from [0,1,2,3]")
return -1
# compute octree
octree = cloud.computeOctree()
###########################################################
############################################################
# Approximate MaxSearchDist
###########################################################
# first we need to subsample mesh and approximate distance
mesh_cloud = mesh.samplePoints(densityBased=True,samplingParameter=float((1./resolution)**2))
# add a temporary scalar field
nsf = cloud.addScalarField("Temp. distances")
cloud.setCurrentScalarField(nsf)
ret = cc.DistanceComputationTools.computeApproxCloud2CloudDistance(cloud, mesh_cloud, best_octree_level)
if ret >= 0:
DisApprox = cloud.getScalarField(nsf-1).toNpArray()
DisApprox_min, DisApprox_max = DisApprox.min(), DisApprox.max()
maxSearchDist = np.maximum(np.absolute(DisApprox_max),
np.absolute(DisApprox_min),
) + 0.5 # add 50cm more
else:
# we do not have DisApprox
maxSearchDist = -1 # default
############################################################
############################################################
# Real computation for C2M
############################################################
nsf = cloud.getScalarFieldDic()["Temp. distances"]
# set parameters
params = cc.Cloud2MeshDistancesComputationParams()
params.maxThreadCount=maxThreadCount
params.octreeLevel=best_octree_level
params.signedDistances=signedDistances
params.maxSearchDist=maxSearchDist
ret = cc.DistanceComputationTools.computeCloud2MeshDistances(cloud, mesh, params)
if ret == 1:
sf_idx = cloud.getScalarFieldDic()["Temp. distances"]
sf = cloud.getScalarField(sf_idx)
print(sf.toNpArray().min(),sf.toNpArray().max())
# maintain consistency with CC
if signedDistances:
sf.setName("C2M signed distances")
else:
sf.setName("C2M absolute distances")
# update the field
sf.fromNpArrayCopy(sf.toNpArrayCopy())
return sf_idx # return the index of the new scalar field
else:
# not successful
return -1
@xiangtaoxu Thanks a lot for your contributions! I've just released a new version on Linux and Windows, to fix the bug you detected, and to add new features to help find the right basic settings. What you wrote seems very consistent with the approach I took (reading the GUI C++ code), and I think your script will just be able to be written in fewer lines with the new version. If you find any errors or gaps, please feel free to report them. I haven't covered all the options offered by the GUI dialog! I have corrected the tests to take into account the new functions, but without providing a real example of parameter optimization.
Thanks! I will try the new version out later!
@prascle Paul, where can I find the new release?
Thanks, Paul.
Could I ask you for a favor to build a binary [for linux] with CSF plugin and RANSAC plugin enabled? My current workflow needs to use these two functions with CC command line mode before CloudComPy has them.
I tried to build from scratch following the instructions of both CC and CloudComPy, but the compiled CC has some errors when running with CSF/RANSAC.
Hello, The last Linux binary (with conda packages) is built with both CSF and RANSAC plugins activated in CloudCompare, but the RANSAC plugin does not work (never ends) although it works on Windows. I have made some very simple tests on CSF, I think it is working. Fixing the RANSAC plugin is my first task in the CloudComPy TODO list ! NB: I build also an Ubuntu 20.04 binary with native packages, with the same problems. I do not publish it (I need to fix the list of required packages...). Paul
Thanks for the info! I am now running with the windows version, which is fine. But in the future, I do want to run it on a linux machine. Look forward to the new update!
Hi,
I have been exploring calculating C2M distance using CloudComPy.
Following the test09 example, I am using the following code
Here, pc_ransac is a point cloud, mesh_primitive is a cylinder mesh.
I got a return value of 1 from the C2M Distance and there is no C2M distance field in pc_ransac. Only a scalar field called "default" and the value is totally off.
Could you help me to find out what is wrong? Thanks!