## Description

The ModifyLidar tool can be used to alter the properties of points within a LiDAR point cloud. The user provides a statement (`statement`) containing one or more expressions, separated by semicolons (;). The expressions are evaluated for each point within the input LiDAR file (`input`). Expressions assign altered values to the properties of points in the output file (`output`), based on any mathematically defined expression that may include the various properties of individual points (e.g. coordinates, intensity, return attributes, etc) or some file-level properties (e.g. min/max coordinates). As a basic example, the following statement:

`x = x + 1000.0` could be used to translate the point cloud 1000 x-units (note, the increment operator could be used as a simpler equivalent, `x += 1000.0`).

Note that if the user does not specify the optional input LiDAR file, the tool will search for all valid LiDAR (.las, .laz, *.zlidar) files contained within the current working directory. This feature can be useful for processing a large number of LiDAR files in batch mode. When this batch mode is applied, the output file names will be the same as the input file names but with a '_modified' suffix added to the end.

Expressions may contain any of the following point-level or file-level variables:

| Variable Name | Description | Type
**Point-level properties**
x | The point x coordinate | float
y | The point y coordinate | float
z | The point z coordinate | float
xy | An x-y coordinate tuple, (x, y) | (float, float)
xyz | An x-y-z coordinate tuple, (x, y, z) | (float, float, float)
intensity | The point intensity value | int
ret | The point return number | int
nret | The point number of returns | int
is_only | True if the point is an only return (i.e. ret == nret == 1), otherwise false | Boolean
is_multiple | True if the point is a multiple return (i.e. nret > 1), otherwise false | Boolean
is_early | True if the point is an early return (i.e. ret == 1), otherwise false | Boolean
is_intermediate | True if the point is an intermediate return (i.e. ret > 1 && ret < nret), otherwise false | Boolean
is_late | True if the point is a late return (i.e. ret == nret), otherwise false | Boolean
is_first | True if the point is a first return (i.e. ret == 1 && nret > 1), otherwise false | Boolean
is_last | True if the point is a last return (i.e. ret == nret && nret > 1), otherwise false | Boolean
class | The class value in numeric form, e.g. 0 = Never classified, 1 = Unclassified, 2 = Ground, etc. | int
is_noise | True if the point is classified noise (i.e. class == 7 | class == 18), otherwise false | Boolean
is_synthetic | True if the point is synthetic, otherwise false | Boolean
is_keypoint | True if the point is a keypoint, otherwise false | Boolean
is_withheld | True if the point is withheld, otherwise false | Boolean
is_overlap | True if the point is an overlap point, otherwise false | Boolean
scan_angle | The point scan angle | int
scan_direction | True if the scanner is moving from the left towards the right, otherwise false | Boolean
is_flightline_edge | True if the point is situated along the filightline edge, otherwise false | Boolean
user_data | The point user data | int
point_source_id | The point source ID | int
scanner_channel | The point scanner channel | int
time | The point GPS time, if it exists, otherwise 0 | float
rgb | A red-green-blue tuple (r, g, b) if it exists, otherwise (0,0,0) | (int, int, int)
nir | The point near infrared value, if it exists, otherwise 0 | int
pt_num | The point number within the input file | int
**File-level properties (invariant)**
n_pts | The number of points within the file | int
min_x | The file minimum x value | float
mid_x | The file mid-point x value | float
max_x | The file maximum x value | float
min_y | The file minimum y value | float
mid_y | The file mid-point y value | float
max_y | The file maximum y value | float
min_z | The file minimum z value | float
mid_z | The file mid-point z value | float
max_z | The file maximum z value | float
x_scale_factor | The file x scale factor | float
y_scale_factor | The file y scale factor | float
z_scale_factor | The file z scale factor | float
x_offset | The file x offset | float
y_offset | The file y offset | float
z_offset | The file z offset | float

Most of the point-level properties above are modifiable, however some are not. The complete list of modifiable point attributes include, x, y, z, xy, xyz, intensity, ret, nret, class, user_data, point_source_id, scanner_channel, scan_angle, time, rgb, nir, is_synthetic, is_keypoint, is_withheld, and is_overlap. The immutable properties include is_only, is_multiple, is_early, is_intermediate, is_late, is_first, is_last, is_noise, and pt_num. Of the file-level properties, the modifiable properties include the x_scale_factor, y_scale_factor, z_scale_factor, x_offset, y_offset, and z_offset.

In addition to the point properties defined above, if the user applies the lidar_eigenvalue_features tool on the input LiDAR file, the modify_lidar tool will automatically read in the additional *.eigen file, which include the eigenvalue-based point neighbourhood measures, such as `lambda1`, `lambda2`, `lambda3`, `linearity`, `planarity`, `sphericity`, `omnivariance`, `eigentropy`, slope, and `residual`. See the lidar_eigenvalue_features documentation for details on each of these metrics describing the structure and distribution of points within the neighbourhood surrounding each point in the LiDAR file.

Expressions may use any of the standard mathematical operators, +, -, *, /, % (modulo), ^ (exponentiation), comparison operators, <, >, <=, >=, == (equality), != (inequality), and logical operators, && (Boolean AND),

| (Boolean OR). Expressions must evaluate to an assignment operation, where the variable that is assigned to must be a modifiable point-level property (see table above). That is, expressions should take the form

`pt_variable = ...`. Other assignment operators are also possible (at least for numeric non-tuple properties), such as the increment (=+) operator (e.g. `x += 1000.0`) and the decrement (-=) operator (e.g. `y -= 1000.0`). Expressions may use a number of built-in mathematical functions, including:

| Function Name | Description | Example
if | Performs an if(CONDITION, TRUE, FALSE) operation, return either the value of TRUE or FALSE depending on CONDITION | `ret = if(ret==0, 1, ret)`
abs | Returns the absolute value of the argument | `value = abs(x - mid_x)`
min | Returns the minimum of the arguments | `value = min(x, y, z)`
max | Returns the maximum of the arguments | `value = max(x, y, z)`
floor | Returns the largest integer less than or equal to a number | `x = floor(x)`
round | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | `x = round(x)`
ceil | Returns the smallest integer greater than or equal to a number | `x = ceil(x)`
clamp | Forces a value to fall within a specified range, defined by a minimum and maximum | `z = clamp(min_z+10.0, z, max_z-20.0)`
int | Returns the integer equivalent of a number | `intensity = int(z)`
float | Returns the float equivalent of a number | `z = float(intensity)`
to_radians | Converts a number in degrees to radians | `val = to_radians(scan_angle)`
to_degrees | Converts a number in radians to degrees | `scan_angle = int(to_degrees(val))`
dist | Returns the distance between two points defined by two n-length tuples | `d = dist(xy, (mid_x, mid_y))` or `d = dist(xyz, (mid_x, mid_y, mid_z))`
rotate_pt | Rotates an x-y point by a certain angle, in degrees | `xy = rotate_pt(xy, 45.0)` or `orig_pt = (1000.0, 1000.0); xy = rotate_pt(xy, 45.0, orig_pt)`
math::ln | Returns the natural logarithm of the number | `z = math::ln(z)`
math::log | Returns the logarithm of the number with respect to an arbitrary base | `z = math::log(z, 10)`
math::log2 | Returns the base 2 logarithm of the number | `z = math::log2(z)`
math::log10 | Returns the base 10 logarithm of the number | `z = math::log10(z)`
math::exp | Returns e^(number), (the exponential function) | `z = math::exp(z)`
math::pow | Raises a number to the power of the other number | `z = math::pow(z, 2.0)`
math::sqrt | Returns the square root of a number. Returns NaN for a negative number | `z = math::sqrt(z, 2.0)`
math::cos | Computes the cosine of a number (in radians) | `z = math::cos(to_radians(z))`
math::sin | Computes the sine of a number (in radians) | `z = math::sin(to_radians(z))`
math::tan | Computes the tangent of a number (in radians) | `z = math::tan(to_radians(z))`
math::acos | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | `z = math::acos(z)`
math::asin | Computes the arcsine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | `z = math::asin(z)`
math::atan | Computes the arctangent of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | `z = math::atan(z)`
rand | Returns a random value between 0 and 1, with an optional seed value | `rgb = (int(255.0 * rand()), int(255.0 * rand()), int(255.0 * rand()))`
helmert_transformation | Performs a Helmert transformation on a point using a 7-parameter transform | `xyz = helmert_transformation(xyz, −446.448, 125.157, −542.06, 20.4894, −0.1502, −0.247, −0.8421 )`

The hyperbolic trigonometric functions are also available for use in expression building, as is `math::atan2` and the mathematical constants `pi` and `e`.

You may use `if` operations within statements to implement a conditional modification of point properties. For example, the following expression demonstrates how you could modify a point's RGB colour based on its classification, assign ground points (class 2) in the output file a green colour:

`rgb = if(class==2, (0,255,0), rgb)` To colour all points within 50 m of the tile mid-point red and all other points blue:

`rgb = if(dist(xy, (mid_x, mid_y))<50.0, (255,0,0), (0,0,255))` `if` operations may also be nested to create more complex compound conditional point modification. For example, in the following statement, we assign first-return points red (255,0,0) and last-return points green (0,255,0) colours and white (255,255,255) to all other points (intermediate-returns and only-returns):

`rgb = if(is_first, (255,0,0), if(is_last, (0,255,0), (255,255,255)))` Here we use an `if` expression to re-classify points above an elevation of 1000.0 as high noise (class 18):

`class = if(z>1000.0, 18, class)` Expressions may be strung together within statements using semicolons (;), with each expression being evaluated individually. When this is the case, at least one of the expressions must assign a value to one of the variant point properties (see table above). The following statement demonstrates multi-expression statements, in this case to swap the x and y coordinates in a LiDAR file:

`new_var = x; x = y; y = new_var` The `rand` function, used with the seeding option, can be useful when assigning colours to points based on common point properties. For example, to assign a point a random RGB colour based on its `point_source_id` (Note, for many point clouds, this operation will assign each flightline a unique colour; if flightline information is not stored in the file's `point_source_id` attribute, one could use the recover_flightline_info tool to calculate this data.):

`rgb=(int(255 * rand(point_source_id)), int(255 * rand(point_source_id+1)), int(255 * rand(point_source_id+2)))` This expression-based approach to modifying point properties provides a great deal of flexibility and power to the processing of LiDAR point cloud data sets.

## See Also

filter_lidar, sort_lidar, lidar_eigenvalue_features

## Function Signature

`def modify_lidar(self, statement: str, input_lidar: Optional[Lidar]) -> Optional[Lidar]: ...`

## Project Links

WbW Homepage User Manual Support WbW