easyidp.pointcloud.PointCloud#

class easyidp.pointcloud.PointCloud(pcd_path='', offset=[0.0, 0.0, 0.0])#

EasyIDP defined PointCloud class, consists by point coordinates, and optionally point colors and point normals.

__init__(pcd_path='', offset=[0.0, 0.0, 0.0])#

The method to initialize the PointCloud class

Parameters:
  • pcd_path (str, optional) – The point cloud file path for loading/reading, by default “”, means create an empty point cloud class

  • offset (list, optional) –

    This parameter is used to specify your own offsets rather than the automatically calculated one.

    Note

    When the point cloud xyz value is too large, need to deduct duplicate values (minus offsets) to save the memory cost and increase the precision.

    Caution

    For some Pix4D produced pointcloud, the point cloud itself has been offseted, need manually add the offset value back.

Example

Prepare

Cancel the numpy scientific counting method display:

>>> import numpy as np
>>> np.set_printoptions(suppress=True)

Package loading:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

Read large xyz point cloud

Most point cloud use the CRS (GPS) coordianate as xyz values directly.

>>> pcd = idp.PointCloud(test_data.pcd.maize_las)
>>> pcd.points
array([[ 367993.0206, 3955865.095 ,      57.9707],
       [ 367993.146 , 3955865.3131,      57.9703],
       [ 367992.6317, 3955867.2979,      57.9822],
       ...,
       [ 368014.7912, 3955879.4943,      58.0219],
       [ 368014.1528, 3955883.5785,      58.0321],
       [ 368016.7278, 3955874.1188,      57.9668]])

If store these values directly, will cost a lot of memeory with precision loss. But with offsets, the data can be stored more neatly in the EasyIDP:

>>> pcd.offset
array([ 367900., 3955800.,       0.])
>>> pcd._points
array([[ 93.0206,  65.095 ,  57.9707],
       [ 93.146 ,  65.3131,  57.9703],
       [ 92.6317,  67.2979,  57.9822],
       ...,
       [114.7912,  79.4943,  58.0219],
       [114.1528,  83.5785,  58.0321],
       [116.7278,  74.1188,  57.9668]])

Manually specify offset

The previous offset is calculated automatically by EasyIDP, you can also manually specify the offset values:

>>> pcd = idp.PointCloud(test_data.pcd.maize_las, offset=[367800, 3955700, 50])
>>> pcd.offset
array([ 367800., 3955700.,       50.])
>>> pcd._points
array([[193.0206, 165.095 ,   7.9707],
       [193.146 , 165.3131,   7.9703],
       [192.6317, 167.2979,   7.9822],
       ...,
       [214.7912, 179.4943,   8.0219],
       [214.1528, 183.5785,   8.0321],
       [216.7278, 174.1188,   7.9668]])

Though the inner stored values changed, it does not affect the final point valus:

>>> pcd.points
array([[ 367993.0206, 3955865.095 ,      57.9707],
       [ 367993.146 , 3955865.3131,      57.9703],
       [ 367992.6317, 3955867.2979,      57.9822],
       ...,
       [ 368014.7912, 3955879.4943,      58.0219],
       [ 368014.1528, 3955883.5785,      58.0321],
       [ 368016.7278, 3955874.1188,      57.9668]])

Read Pix4D offseted point cloud and add offset back

If you read the Pix4D produced point cloud directly:

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> pcd
             x        y        z  r    g    b        nx      ny      nz
    0  -18.908  -15.778   -0.779  123  103  79   nodata  nodata  nodata
    1  -18.908  -15.777   -0.78   124  104  81   nodata  nodata  nodata
    2  -18.907  -15.775   -0.802  123  103  80   nodata  nodata  nodata
  ...  ...      ...      ...      ...  ...  ...     ...     ...     ...
42451  -15.789  -17.961   -0.847  116  98   80   nodata  nodata  nodata
42452  -15.789  -17.939   -0.84   113  95   76   nodata  nodata  nodata
42453  -15.786  -17.937   -0.833  115  97   78   nodata  nodata  nodata

Here the xyz seems not the correct one, when we check the Pix4D project {name}_offset.xyz file in the param folders, we can find the offset values stored by Pix4D.

>>> with open(test_data.pix4d.lotus_param / "hasu_tanashi_20170525_Ins1RGB_30m_offset.xyz", 'r') as f:
...     f.readlines()
['368043.000 3955495.000 98.000']

This often requires user manually add that offset back to point cloud. But EasyIDP supports dealing with such situation easily:

>>> p4d_offset_np = np.array([368043, 3955495,  98])
>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin, p4d_offset_np)
>>> pcd
                x            y        z  r    g    b        nx      ny      nz
    0  368024.092  3955479.222   97.221  123  103  79   nodata  nodata  nodata
    1  368024.092  3955479.223   97.22   124  104  81   nodata  nodata  nodata
    2  368024.093  3955479.225   97.198  123  103  80   nodata  nodata  nodata
  ...     ...          ...      ...      ...  ...  ...     ...     ...     ...
42451  368027.211  3955477.039   97.153  116  98   80   nodata  nodata  nodata
42452  368027.211  3955477.061   97.16   113  95   76   nodata  nodata  nodata
42453  368027.214  3955477.063   97.167  115  97   78   nodata  nodata  nodata

Note

You can also obtain the p4d_offset_np by easyidp.Pix4D object:

>>> p4d = idp.Pix4D(project_path   = test_data.pix4d.lotus_folder,
...                 raw_img_folder = test_data.pix4d.lotus_photos,
...                 param_folder   = test_data.pix4d.lotus_param))
>>> p4d.offset_np
array([ 368043., 3955495.,      98.])

And feed it to the previous function:

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin, p4d.offset_np)

Methods

__init__([pcd_path, offset])

The method to initialize the PointCloud class

clear()

Delete all points and make an empty point cloud

crop_point_cloud(polygon_xy)

crop the point cloud along z axis

crop_rois(roi[, save_folder])

Crop several ROIs by given <ROI> or dict object with several polygons and polygon names, along z-axis

has_colors()

Returns True if the point cloud contains point colors.

has_normals()

Returns True if the point cloud contains point normals.

has_points()

Returns True if the point cloud contains points.

read_point_cloud(pcd_path)

Open a new point cloud file to overwritting current file, support ply, laz, and las.

save(pcd_path)

Save current point cloud to a file, support ply, las, laz format.

update_offset_value(off_val)

Change the offset value without affecting the xyz point values.

write_point_cloud(pcd_path)

Save current point cloud to a file, support ply, las, laz format.

Attributes

offset

The offset value of point cloud

points

The xyz values of point cloud

file_path

the file path to the current point cloud file

file_ext

the file extension to the current point cloud file

colors

The color (RGB) values of point cloud

normals

The normal vector values of point cloud

shape

The size of point cloud (xyz)

clear()#

Delete all points and make an empty point cloud

colors#

The color (RGB) values of point cloud

crop_point_cloud(polygon_xy)#

crop the point cloud along z axis

Parameters:

polygon_xy (nx2 ndarray) – the polygon xy coords

Return type:

<easyidp.PointCloud> object, The cropped point cloud

Example

Data prepare:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> polygon = np.array([
...     [-18.42576599, -16.10819054],
...     [-18.00066757, -18.05295944],
...     [-16.05021095, -17.63488388],
...     [-16.46848488, -15.66774559],
...     [-18.42576599, -16.10819054]])

Run this function:

>>> cropped = pcd.crop_point_cloud(polygon)
>>> cropped
             x        y        z  r       g       b           nx      ny      nz
    0  -18.417  -16.119   -0.641  nodata  nodata  nodata  nodata  nodata  nodata
    1  -18.409  -16.169   -0.647  nodata  nodata  nodata  nodata  nodata  nodata
    2  -18.404  -16.142   -0.634  nodata  nodata  nodata  nodata  nodata  nodata
  ...  ...      ...      ...      ...     ...     ...        ...     ...     ...
22419  -16.065  -17.607   -0.699  nodata  nodata  nodata  nodata  nodata  nodata
22420  -16.062  -17.578   -0.678  nodata  nodata  nodata  nodata  nodata  nodata
22421  -16.051  -17.632   -0.692  nodata  nodata  nodata  nodata  nodata  nodata
crop_rois(roi, save_folder=None)#

Crop several ROIs by given <ROI> or dict object with several polygons and polygon names, along z-axis

Parameters:
  • roi (easyidp.ROI | dict) –

    the <ROI> object created by easyidp.ROI()
    or dict object with key as roi name and value as coordinates

  • is_geo (bool, optional) – whether the given polygon is pixel coords on imarray or geo coords (default)

  • save_folder (str, optional) – the folder to save cropped images, use ROI indices as file_names, by default None, means not save.

Return type:

dict, The dictionary with key as roi name and value as <idp.PointCloud> object

Example

Data prepare:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> roi = idp.ROI(test_data.shp.lotus_shp, name_field=0)
>>> roi = roi[0:3]
>>> header = idp.geotiff.get_header(test_data.pix4d.lotus_dom)
>>> roi.change_crs(header['crs'])
<easyidp.ROI> with 3 items
[0]     N1W1
array([[ 368017.7565143 , 3955511.08102276],
       [ 368019.70190232, 3955511.49811902],
       [ 368020.11263046, 3955509.54636219],
       [ 368018.15769062, 3955509.13563382],
       [ 368017.7565143 , 3955511.08102276]])
[1]     N1W2
array([[ 368018.20042946, 3955508.96051697],
       [ 368020.14581791, 3955509.37761334],
       [ 368020.55654627, 3955507.42585654],
       [ 368018.601606  , 3955507.01512806],
       [ 368018.20042946, 3955508.96051697]])
[2]     N1W3
array([[ 368018.64801755, 3955506.84956301],
       [ 368020.59340644, 3955507.26665948],
       [ 368021.00413502, 3955505.31490271],
       [ 368019.04919431, 3955504.90417413],
       [ 368018.64801755, 3955506.84956301]])

>>> pcd = idp.PointCloud(test_data.pix4d.lotus_pcd, offset=[368043, 3955495,  98])
                  x            y        z  r    g    b        nx      ny      nz
      0  368014.849  3955511.333   97.215  51   55   33   nodata  nodata  nodata
      1  368014.853  3955511.352   97.239  49   52   33   nodata  nodata  nodata
      2  368014.865  3955511.485   97.402  46   50   30   nodata  nodata  nodata
    ...     ...          ...      ...      ...  ...  ...     ...     ...     ...
6235710  368055.047  3955482.412   96.997  173  168  170  nodata  nodata  nodata
6235711  368055.051  3955482.407   97.051  154  140  129  nodata  nodata  nodata
6235712  368055.058  3955482.373   97.107  114  93   72   nodata  nodata  nodata

Run this function:

>>> out = pcd.crop_rois(roi)
>>> out
{'N1W1': <easyidp.PointCloud class>, 'N1W2': <easyidp.PointCloud class>, 'N1W3': <easyidp.PointCloud class>}

You can also save the output point cloud to given folder by:

>>> out = pcd.crop_rois(roi, save_folder=r'path/to/save/folder/')

See also

crop_point_cloud

file_ext#

the file extension to the current point cloud file

file_path#

the file path to the current point cloud file

has_colors()#

Returns True if the point cloud contains point colors.

Return type:

bool

has_normals()#

Returns True if the point cloud contains point normals.

Return type:

bool

has_points()#

Returns True if the point cloud contains points.

Return type:

bool

normals#

The normal vector values of point cloud

property offset#

The offset value of point cloud

Caution

If change this value directly, the xyz value of point cloud will also be changed, just like moving the whole point cloud.

Example

For example, the point cloud like:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> pts = idp.PointCloud(test_data.pcd.maize_las)
>>> pts
                x            y        z  r    g    b
    0  367993.021  3955865.095   57.971  28   21   17
    1  367993.146  3955865.313   57.97   28   23   19
    2  367992.632  3955867.298   57.982  29   22   18
    ...     ...          ...      ...      ...  ...  ...
49655  368014.791  3955879.494   58.022  33   28   25
49656  368014.153  3955883.578   58.032  30   40   26
49657  368016.728  3955874.119   57.967  25   20   18
>>> pts.offset
array([ 367900., 3955800.,       0.])

Change the offset directly:

>>> pts.offset = [300, 200, 50]
>>> pts
                x        y        z  r    g    b
    0  393.021  265.095  107.971  28   21   17
    1  393.146  265.313  107.97   28   23   19
    2  392.632  267.298  107.982  29   22   18
    ...  ...      ...      ...      ...  ...  ...
49655  414.791  279.494  108.022  33   28   25
49656  414.153  283.578  108.032  30   40   26
49657  416.728  274.119  107.967  25   20   18

Caution

If you want to change the offset without affecting the point xyz values, please use update_offset_value()

property points#

The xyz values of point cloud

read_point_cloud(pcd_path)#

Open a new point cloud file to overwritting current file, support ply, laz, and las.

Caution

This operation will totally clear all data of current point cloud, and reopen a new point cloud.

Parameters:

pcd_path (str | pathlib.Path) – the path to point cloud file want to open

Example

Data prepare:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> aaa = idp.PointCloud(test_data.pcd.maize_las)
>>> aaa
                x            y        z  r    g    b                        nx                     ny                    nz
    0  367993.021  3955865.095   57.971  28   21   17    -0.031496062992125984    0.36220472440944884    0.9291338582677166
    1  367993.146  3955865.313   57.97   28   23   19     0.08661417322834646     0.07086614173228346    0.9921259842519685
    2  367992.632  3955867.298   57.982  29   22   18    -0.007874015748031496    0.26771653543307083    0.9606299212598425
...     ...          ...      ...      ...  ...  ...  ...                     ...                    ...
49655  368014.791  3955879.494   58.022  33   28   25     0.44881889763779526    -0.14960629921259844    0.8740157480314961
49656  368014.153  3955883.578   58.032  30   40   26     0.44881889763779526    -0.29133858267716534    0.8346456692913385
49657  368016.728  3955874.119   57.967  25   20   18     0.3228346456692913      0.26771653543307083    0.8976377952755905

This operation will totally overwrite the previous point cloud

>>> aaa.read_point_cloud(test_data.pcd.lotus_las)
>>> aaa
            x        y        z  r    g    b        nx      ny      nz
    0  -18.908  -15.778   -0.779  123  103  79   nodata  nodata  nodata
    1  -18.908  -15.777   -0.78   124  104  81   nodata  nodata  nodata
    2  -18.907  -15.775   -0.802  123  103  80   nodata  nodata  nodata
...  ...      ...      ...      ...  ...  ...     ...     ...     ...
42451  -15.789  -17.961   -0.847  116  98   80   nodata  nodata  nodata
42452  -15.789  -17.939   -0.84   113  95   76   nodata  nodata  nodata
42453  -15.786  -17.937   -0.833  115  97   78   nodata  nodata  nodata
save(pcd_path)#

Save current point cloud to a file, support ply, las, laz format.

Parameters:

pcd_path (str) – The file path of saved point cloud, if file extention not given, will use parent point cloud file extention.

shape#

The size of point cloud (xyz)

update_offset_value(off_val)#

Change the offset value without affecting the xyz point values.

Parameters:

off_val (list | tuple | ndarray) – The offset values want to set

Example

For example, the point cloud like:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> pts = idp.PointCloud(test_data.pcd.maize_las)
>>> pts
                x            y        z  r    g    b
    0  367993.021  3955865.095   57.971  28   21   17
    1  367993.146  3955865.313   57.97   28   23   19
    2  367992.632  3955867.298   57.982  29   22   18
    ...     ...          ...      ...      ...  ...  ...
49655  368014.791  3955879.494   58.022  33   28   25
49656  368014.153  3955883.578   58.032  30   40   26
49657  368016.728  3955874.119   57.967  25   20   18
>>> pts.offset
array([ 367900., 3955800.,       0.])

Change the offset without affecting the xyz values:

>>> pts.update_offset_value([360000, 3955000, 50])

>>> pts.offset
array([ 360000., 3955000.,      50.])

>>> pts.points
                x            y        z  r    g    b                        nx                     ny                    nz
    0  367993.021  3955865.095   57.971  28   21   17    -0.031496062992125984    0.36220472440944884    0.9291338582677166
    1  367993.146  3955865.313   57.97   28   23   19     0.08661417322834646     0.07086614173228346    0.9921259842519685
    2  367992.632  3955867.298   57.982  29   22   18    -0.007874015748031496    0.26771653543307083    0.9606299212598425
  ...     ...          ...      ...      ...  ...  ...  ...                     ...                    ...
49655  368014.791  3955879.494   58.022  33   28   25     0.44881889763779526    -0.14960629921259844    0.8740157480314961
49656  368014.153  3955883.578   58.032  30   40   26     0.44881889763779526    -0.29133858267716534    0.8346456692913385
49657  368016.728  3955874.119   57.967  25   20   18     0.3228346456692913      0.26771653543307083    0.8976377952755905

Caution

If you want to change the offset like moving point cloud (also change the xyz values), please use offset()

write_point_cloud(pcd_path)#

Save current point cloud to a file, support ply, las, laz format.

Parameters:

pcd_path (str) – The file path of saved point cloud, if file extention not given, will use parent point cloud file extention.

Example

Data prepare:

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> polygon = np.array([
...     [-18.42576599, -16.10819054],
...     [-18.00066757, -18.05295944],
...     [-16.05021095, -17.63488388],
...     [-16.46848488, -15.66774559],
...     [-18.42576599, -16.10819054]])

>>> cropped = pcd.crop_point_cloud(polygon)
>>> cropped
             x        y        z  r       g       b           nx      ny      nz
    0  -18.417  -16.119   -0.641  nodata  nodata  nodata  nodata  nodata  nodata
    1  -18.409  -16.169   -0.647  nodata  nodata  nodata  nodata  nodata  nodata
    2  -18.404  -16.142   -0.634  nodata  nodata  nodata  nodata  nodata  nodata
  ...  ...      ...      ...      ...     ...     ...        ...     ...     ...
22419  -16.065  -17.607   -0.699  nodata  nodata  nodata  nodata  nodata  nodata
22420  -16.062  -17.578   -0.678  nodata  nodata  nodata  nodata  nodata  nodata
22421  -16.051  -17.632   -0.692  nodata  nodata  nodata  nodata  nodata  nodata

Use this function:

>>> cropped.write_point_cloud(r"path/to/save/pointcloud.ply")