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.

Attributes

colors

The color (RGB) values of point cloud

crs

The Coordinate Reference System (CRS) of point cloud

file_ext

the file extension to the current point cloud file

file_path

the file path to the current point cloud file

normals

The normal vector values of point cloud

offset

The offset value of point cloud

points

The xyz values of point cloud

shape

The size of point cloud (xyz)

tree

The 2D KDTree of point cloud for fast spatial query

Methods

change_crs(target_crs)

Change the point cloud coordinate system in place.

clear()

Delete all points and make an empty point cloud.

crop(geometry)

Crop point cloud by a 2D geometry.

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.

save(pcd_path)

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

select_by_index(indices[, invert])

Select points by index and return a new PointCloud.

to_crs(target_crs)

Convert point cloud to a new CRS, returning a new PointCloud.

update_offset_value(off_val)

Change the offset value without affecting the xyz point values.

__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)
change_crs(target_crs)

Change the point cloud coordinate system in place.

Transforms absolute coordinates and automatically recomputes the offset for the new coordinate space. Colors and normals are preserved. Normals are kept without rotation (orientation is relative to the surface plane and stays valid after rigid-body CRS transform). The spatial tree is cleared.

Parameters:

target_crs (str or pyproj.CRS) – Target coordinate reference system.

Returns:

Modifies the PointCloud in place.

Return type:

None

Raises:

TypeError – If self.crs is None.

Notes

If the source and target CRS are the same, a warning is logged and no transformation is performed.

Examples

>>> pcd = idp.PointCloud(test_data.pcd.maize_las)
>>> pcd.crs = "EPSG:32654"
>>> pcd.change_crs("EPSG:4326")
>>> pcd.crs.to_epsg()
4326
clear()

Delete all points and make an empty point cloud.

Returns:

Clears point, color, normal, CRS, offset, and display state.

Return type:

None

Examples

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> pcd.clear()
>>> pcd.has_points()
False
colors

The color (RGB) values of point cloud

crop(geometry)

Crop point cloud by a 2D geometry.

Parameters:

geometry (shapely.geometry.Polygon, shapely.geometry.MultiPolygon, or easyidp.ROI) – The 2D crop geometry. - Polygon / MultiPolygon: return a single PointCloud. - ROI: return a dict of {label: PointCloud}.

Returns:

PointCloud or dict of {str – Cropped point cloud(s).

Return type:

PointCloud}

Raises:

TypeError – If geometry is an unsupported type, with migration guidance.

Examples

Crop by a Shapely polygon:

>>> from shapely.geometry import Polygon
>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> polygon = Polygon([
...     [-18.5, -18.1], [-15.7, -18.1],
...     [-15.7, -15.6], [-18.5, -15.6],
... ])
>>> cropped = pcd.crop(polygon)
>>> isinstance(cropped, idp.PointCloud)
True

Crop by an ROI and keep ROI labels:

>>> roi = idp.ROI(test_data.shp.lotus_shp, name_field=0)
>>> crops = pcd.crop(roi)
>>> isinstance(crops, dict)
True
crs

The Coordinate Reference System (CRS) of 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

Examples

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> pcd.has_colors()
True
has_normals()

Returns True if the point cloud contains point normals.

Return type:

bool

Examples

>>> pcd = idp.PointCloud()
>>> pcd.has_normals()
False
has_points()

Returns True if the point cloud contains points.

Return type:

bool

Examples

>>> pcd = idp.PointCloud()
>>> pcd.has_points()
False
normals

The normal vector values of point cloud

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()

points

The xyz values of point cloud

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.

Examples

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> pcd.save("lotus_crop.ply")
select_by_index(indices, invert=False)

Select points by index and return a new PointCloud.

Parameters:
  • indices (ndarray of int) – Indices of points to select.

  • invert (bool, optional) – If True, select all points NOT in indices. Default False.

Returns:

A new PointCloud containing only the selected points. Empty selection produces an empty PointCloud with CRS and offset preserved.

Return type:

PointCloud

Examples

>>> pcd = idp.PointCloud(test_data.pcd.lotus_ply_bin)
>>> sub = pcd.select_by_index(np.array([0, 5, 10]))
>>> sub.shape
(3, 3)
shape

The size of point cloud (xyz)

to_crs(target_crs)

Convert point cloud to a new CRS, returning a new PointCloud.

Parameters:

target_crs (str or pyproj.CRS) – Target coordinate reference system.

Returns:

A new PointCloud with coordinates transformed to the target CRS. Colors and normals are preserved. Normals are copied without rotation (orientation is relative to the surface plane and stays valid after rigid-body CRS transform).

Return type:

PointCloud

Raises:

TypeError – If self.crs is None.

Examples

>>> pcd.crs = "EPSG:32654"
>>> pcd_wgs84 = pcd.to_crs("EPSG:4326")
tree

The 2D KDTree of point cloud for fast spatial query

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()