5. operations on coordinates, scalar fields, colors, normals, meshes with Numpy

5.1. Read, modify or create cloud coordinates with Numpy

There are two ways to access the cloud coordinates from Numpy.

  • If you want to modify the data in place, without copy, use toNpArray()

  • if you prefer to copy the scalar field data, use toNpArrayCopy()


cloud = cc.loadPointCloud(getSampleCloud(5.0))

coords = cloud.toNpArrayCopy()
if coords.shape != (cloud.size(), 3):
    raise RuntimeError

The above code snippet is from test004.py.

You can fill the cloud coordinates with coordsFromNPArray_copy().

  • The array should have a shape (numberOfPoints,3).

  • WARNING Be sure to have an array data in C-style contiguous order. For instance, a transpose operation do not reorder the data in memory. Check the boolean array.flags array.flags['C_CONTIGUOUS'] and, if False, reorder with array.copy(order='C').

  • The cloud memory is reserved /resized automatically.

See below An example of point cloud with scalar field created from Numpy arrays.

5.2. Read, modify or create a scalar field with Numpy

There are two ways to access to the scalar field data from Numpy.

  • If you want to modify the data in place, without copy, use toNpArray()


dic = cloud1.getScalarFieldDic()
sf1 = cloud1.getScalarField(dic['Coord. Z'])
max1 = sf1.getMax()
asf1 = sf1.toNpArray()   # access to Numpy array, without copy
asf1[0] = 2*max1         # modification in place
sf1.computeMinAndMax()

The above code snippet is from test002.py.

You can fill a cloud scalar field with fromNpArrayCopy(). The method checks if the scalar field and the array have the same size, before copy.

See below An example of point cloud with scalar field created from Numpy arrays.

5.3. An example of point cloud with scalar field created from Numpy arrays


# --- generate a set of coords and a scalar field

npts = 10000000
phi = 2*np.pi*np.random.random((npts))
theta = 2*np.pi*np.random.random((npts))
r = 5 + 0.3*np.sin(2*2*np.pi*phi + 3*2*np.pi*theta)
x = np.float32(r*np.sin(phi)*np.cos(theta))
y = np.float32(r*np.sin(phi)*np.sin(theta))
z = np.float32(r*np.cos(phi))
coords = np.column_stack((x,y,z))
dr = np.float32(np.sqrt(x*x + y*y + z*z) -5)

# --- create the pointCloud, add the scalar field

cl = cc.ccPointCloud("boule")
cl.coordsFromNPArray_copy(coords)
cl.addScalarField("delta")
sf = cl.getScalarField(0)
sf.fromNpArrayCopy(dr)

# --- save the point cloud

res = cc.SavePointCloud(cl, os.path.join(dataDir, "boule.bin"))

The above code snippet is from test017.py.

5.4. Access to the indexes of triangles nodes in a mesh

In a mesh, the array of triangles indexes has a shape(number of triangles, 3), with indexes corresponding to the indexes of nodes in the associated cloud.

The method IndexesToNpArray() gives access to the array without copy. the corresponding nodes coordinates are given by the method toNpArray() from the associated cloud (getAssociatedCloud(). The method IndexesToNpArray_copy() creates a copy of the array of indexes.


# --- access to the numpy array of node indexes (one row per triangle)
d = mesh1.IndexesToNpArray()
if d.shape != (19602, 3):
    raise RuntimeError
if d.dtype != np.dtype('uint32'):
    raise RuntimeError

d2 = mesh1.IndexesToNpArray_copy()
if d2.shape != (19602, 3):
    raise RuntimeError
if d2.dtype != np.dtype('uint32'):
    raise RuntimeError

The above code snippet is from test011.py.

5.5. Access to the array of colors in cloud

The colors are stored in an array of shape(number of nodes, 4), the four components are R, G, B and alpha, each stored in a 8bits integer.

The method colorsToNpArray() gives access to the array of colors without copy, the method colorsToNpArrayCopy() creates a copy of the array.


cola = cloud.colorsToNpArray()
if not cola.shape == (1000000, 4):
    raise RuntimeError
if not cola.dtype == 'uint8':
    raise RuntimeError

colaCopy = cloud.colorsToNpArrayCopy()
if not colaCopy.shape == (1000000, 4):
    raise RuntimeError
if not colaCopy.dtype == 'uint8':
    raise RuntimeError

The above code snippet is from test029.py.

5.6. Read, modify or create normals with Numpy

Normals can be exported as Numpy arrays with normalsToNpArrayCopy(). As normals are stored compressed in the CloudCompare cloud, the method decompress the values in a new Numpy array (owned by Python). In the example below, we check that this is the same as converting normals into scalar fields and exporting these scalar fields to Numpy arrays.


cloud = cc.loadPointCloud(getSampleCloud(5.0))
cc.computeNormals([cloud])

cloud.exportNormalToSF(True, True, True)
dic = cloud.getScalarFieldDic()
sfx = cloud.getScalarField(dic['Nx'])
sfy = cloud.getScalarField(dic['Ny'])
sfz = cloud.getScalarField(dic['Nz'])
asfx = sfx.toNpArray()
asfy = sfy.toNpArray()
asfz = sfz.toNpArray()

normals=cloud.normalsToNpArrayCopy()

dx = normals[:,0] -asfx
dy = normals[:,1] -asfy
dz = normals[:,2] -asfz

if dx.max() != 0 or dx.min() !=0:
    raise RuntimeError

if dy.max() != 0 or dy.min() !=0:
    raise RuntimeError

if dz.max() != 0 or dz.min() !=0:
    raise RuntimeError

It is also possible to import Numpy arrays as normals with normalsFromNpArrayCopy(). The numpy array must have the right type, shape and size. During import, the normals are normalized and compressed. In the following example, whe invert and denormalize the numpy array of normals before import/


normals *=-0.5 # an example of modification of the normals: invert and denormalize
cloud.normalsFromNpArrayCopy(normals)

The above code snippets are from test054.py.