How does CloudCompare convert normals to Dip/Dip directions

Feel free to ask any question here
Post Reply
david.delaiglesia
Posts: 3
Joined: Mon Mar 21, 2016 11:10 am
Location: Vigo, Galicia

How does CloudCompare convert normals to Dip/Dip directions

Post by david.delaiglesia »

Hi!
First of all, I want to say thanks to Daniel and all the people involved in the develop of this amazing software, really thank you all for doing this. CloudCompare is awesome and I use it every day.

So:
I'm working (more like learn by doing) on a Python's library for managing pointclounds and I'm getting weird results when I use my functions add_inclination and add_orientation and compare with the Dip/Dip directions SF obtained from CC.

Here is the structure of a pointcloud open with my library (in this example a sphere created in CC):

Code: Select all

esfera = PyntCloud.from_ply('Sphere.ply')

esfera.vertex
Out[3]: 
array([ (0.2515081465244293, 0.05602749437093735, 1.9830318689346313, 0.12660565972328186, 0.02801010198891163, 0.9915575981140137, 7.450349807739258, 77.52488708496094),
       (0.09723527729511261, 0.02066999115049839, 1.9934484958648682, 0.048643846064805984, 0.011384730227291584, 0.9987513422966003, 2.863548517227173, 76.82744598388672),
       (0.17640848457813263, 0.028193067759275436, 1.9881943464279175, 0.08916780352592468, 0.01611466333270073, 0.9958862066268921, 5.198856830596924, 79.75591278076172),
       ...,
       (0.17817874252796173, -0.046098098158836365, -1.9879237413406372, 0.08992616087198257, -0.02275240235030651, -0.9956884980201721, 5.322407245635986, 284.19854736328125),
       (0.2002459168434143, -0.002330917865037918, -1.986855149269104, 0.09960971027612686, -0.0010710721835494041, -0.9950260519981384, 5.717002868652344, 270.6160583496094),
       (0.12885123491287231, -0.03245270624756813, -1.9912745952606201, 0.06637085974216461, -0.01580258458852768, -0.9976698756217957, 3.912114381790161, 283.3924865722656)], 
      dtype=[('x', '<f4'), ('y', '<f4'), ('z', '<f4'), ('nx', '<f4'), ('ny', '<f4'), ('nz', '<f4'), ('scalar_Dip_(degrees)', '<f4'), ('scalar_Dip_direction_(degrees)', '<f4')])
      
esfera.vertex['nx']
Out[4]: 
array([ 0.12660566,  0.04864385,  0.0891678 , ...,  0.08992616,
        0.09960971,  0.06637086], dtype=float32)

esfera.vertex[-1]['nx']
Out[5]: 0.06637086
And here are my functions for Inclination:

Code: Select all

def add_inclination(self, degrees=True):
        """ Adds inclination (with respect to z-axis) values to PyntCloud.vertex
        
        This function expects the PyntCloud to have a numpy structured array
        with normals x,y,z values (correctly named) as the corresponding vertex
        atribute.
        
         Args:
            degrees (Optional[bool]): Set the oputput inclination units.
                If True(Default) set units to degrees.
                If False set units to radians.
        """
        #: set copy to False for efficience in large pointclouds
        nx = self.vertex['nx'].astype(np.float64, copy=False)
        nz = self.vertex['nz'].astype(np.float64, copy=False)
        #: get inclination
        angle = np.arctan(np.absolute(nx / nz))
        if degrees == False:
            inclination = np.array(angle, dtype=[('inr', 'f4')])
        else:
            inclination = np.array((180 * angle / np.pi), dtype=[('ind', 'f4')])
        #: merge the structured arrays and replace the old vertex attribute
        self.vertex = join_struct_arrays([self.vertex, inclination])
And orientation:

Code: Select all

def add_orientation(self, degrees=True):
        
        """ Adds orientation (with respect to y-axis) values to PyntCloud.vertex
        
        This function expects the PyntCloud to have a numpy structured array
        with normals x,y,z values (correctly named) as the corresponding vertex
        atribute.
        
         Args:
            degrees (Optional[bool]): Set the oputput orientation units.
                If True(Default) set units to degrees.
                If False set units to radians.
        """  
        
        #: set copy to False for efficience in large pointclouds
        nx = self.vertex['nx'].astype(np.float64, copy=False)
        ny = self.vertex['ny'].astype(np.float64, copy=False)
        
        #: get orientations
        angle = np.arctan(np.absolute(nx / ny))
        
        #: mask for every quadrant
        q2 = np.logical_and((self.vertex['nx']>0),(self.vertex['ny']<0))
        q3 = np.logical_and((self.vertex['nx']<0),(self.vertex['ny']<0))
        q4 = np.logical_and((self.vertex['nx']<0),(self.vertex['ny']>0))
        
        #: apply modification for every quadrant
        angle[q2] = np.pi - angle[q2]
        angle[q3] = np.pi + angle[q3]
        angle[q4] = (2*np.pi) - angle[q4]
        
        if degrees == False:
            orientation = np.array(angle, dtype=[('orir', 'f4')])
        else:
            orientation = np.array((180 * angle / np.pi), dtype=[('orid', 'f4')])
            
        #: merge the structured arrays and replace the old vertex attribute
        self.vertex = join_struct_arrays([self.vertex, orientation])
 
The problem happens when I use this functions over the pointcloud and check the results by comparing with CC's SF:

Code: Select all

esfera.vertex[-1]
Out[8]: (0.12885123491287231, -0.03245270624756813, -1.9912745952606201, 0.06637085974216461, -0.01580258458852768, -0.9976698756217957, 3.912114381790161, 283.3924865722656, 3.8060436248779297, 103.39249420166016)

esfera.vertex[0]
Out[9]: (0.2515081465244293, 0.05602749437093735, 1.9830318689346313, 0.12660565972328186, 0.02801010198891163, 0.9915575981140137, 7.450349807739258, 77.52488708496094, 7.276360511779785, 77.52488708496094)
As you can see, I'm having errors (aprox: 0.1-0.2 decimals) on every inclination results and I honestly don't know what happens with the orientations, as i get the same results in angles < 180 and but I have a 180 degrees difference in some others.

My approach (I've been programming for only 6 months so it may be something wrong with it) is to ignore the z-value of the normal and compute de orientation as follows:
Image

So I would like to know how CloudCompare "extracts" the inclination and orientation values from normals, and if it is possible, discover what is the problem with my code.

Thank you very much.
daniel
Site Admin
Posts: 7396
Joined: Wed Oct 13, 2010 7:34 am
Location: Grenoble, France
Contact:

Re: How does CloudCompare convert normals to Dip/Dip directions

Post by daniel »

Hi,

I may not be the best one to answer as I'm not a geologist and I never fully understood these dip / dip direction conventions ;)

But be sure to use the latest 2.6.3 version to compute the dip / dip direction values (see this thread reporting a bug fix related to the way they were computed before: https://github.com/cloudcompare/trunk/issues/241).
Daniel, CloudCompare admin
david.delaiglesia
Posts: 3
Joined: Mon Mar 21, 2016 11:10 am
Location: Vigo, Galicia

Re: How does CloudCompare convert normals to Dip/Dip directions

Post by david.delaiglesia »

Hi Daniel, thanks for the answer.

Apparently the "errors" about orientations that I was founding were fixed in the issue you pointed (It wasn't errors at all, just the concept about pointing the direction always upwards). My bad for didn't update the software before asking as i was using an old debian pre-compiled version.

And also I fixed the errors about my inclination results (there was nothing related to CC, it was related with the decimal rounding i was using).

I update the software and make those precision fixes and now all results are fine.

Thank you so much.
Post Reply