pylas: Python library for lidar LAS/LAZ IO.

LAS (and it’s compressed counterpart LAZ), is a popular format for lidar pointcloud and full waveform, pylas reads and writes these formats and provides a Python API via Numpy Arrays.

Here is an example of reading in LAZ data and getting some simple summaries of the pointcloud:

import numpy as np
import pylas

with pylas.open('pylastests/simple.laz') as fh:
    print('Points from Header:', fh.header.point_count)
    las = fh.read()
    print(las)
    print('Points from data:', len(las.points))
    ground_pts = las.classification == 2
    bins, counts = np.unique(las.return_number[ground_pts], return_counts=True)
    print('Ground Point Return Number distribution:')
    for r,c in zip(bins,counts):
        print('    {}:{}'.format(r,c))

Would output:

Points from Header: 1065
<LasData(1.2, point fmt: <PointFormat(3)>, 1065 points, 0 vlrs)>
Points from data: 1065
Ground Point Return Number distribution:
    1:239
    2:25
    3:11
    4:1

User Guide

Installation

Installing from PyPi

pip install pylas

Optional dependencies for LAZ support

pylas does not support LAZ (.laz) file by itself but can use one of several optional dependencies to support compressed LAZ files.

The 2 supported options are:

  1. lazrs [lazrs PyPi]
  2. laszip-python (bindings to laszip)

When encountering LAZ data, pylas will try to use one of the backend in the order described above. (Example: if lazrs is not installed or if it fails during, the process, pylas will try laszip)

lazrs is a Rust port of the laszip compression and decompression. Its main advantage is that it is able to compress/decompress using multiple threads which can greatly speed up things.

laszip is the official and original LAZ implementation by Martin Isenburg. The advantage of the laszip backend is that its the official implementation, but does not offer multi-threaded compression/decompression.

Both the laszip bindings and lazrs are available on pip.

To install pylas with one of its supported backend use one of the following commands

# To install with lazrs only
pip install pylas[lazrs]

# To install with laszip only
pip install pylas[laszip]

# To install with both
pip install pylas[lazrs,laszip]

What is a LAS file ?

LAS is a public file format meant to exchange 3D point data, mostly used to exchange lidar point clouds. LAZ is a lossless compression of the LAS format.

The latest LAS specification is the LAS 1.4. pylas supports LAS files from Version 1.2 to 1.4.

LAS files are organized in 3 main parts:

  1. Header
  2. VLRs
  3. Point Records

VLRs

After the header, LAS files may contain VLRs (Variable Length Record). VLRs are meant to store additional information such as the SRS (Spatial Reference System), description on extra dimensions added to the points.

VLRs are divided in two parts:

  1. header
  2. payload

The payload is limited to 65,535 bytes (Because in the header, the length of the payload is stored on a uint16).

See Manipulating VLRs

Point Records

The last chunk of data (and the biggest one) contains the point records. In a LAS file, points are stored sequentially.

The point records holds the point cloud data the LAS Spec specifies 10 point formats. A point format describe the dimensions stored for each point in the record.

Each LAS specification added new point formats, the table below describe the compatibility between point formats and LAS file version.

LAS file version Compatible point formats
1.2 0, 1, 2, 3
1.3 0, 1, 2, 3, 4, 5
1.4 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

The names written in the tables below are the one you will have to use in your code.

Note

The dimensions ‘X’, ‘Y’, ‘Z’ are signed integers without the scale and offset applied. To access the coordinates as doubles simply use ‘x’, ‘y’ , ‘z’

Point Format 0
Dimensions Type Size (bit)
X signed 32
Y signed 32
Z signed 32
intensity unsigned 16
return_number unsigned 3
number_of_returns unsigned 3
scan_direction_flag bool 1
edge_of_flight_line bool 1
classification unsigned 5
synthetic bool 1
key_point bool 1
withheld bool 1
scan_angle_rank signed 8
user_data unsigned 8
point_source_id unsigned 8

The point formats 1, 2, 3, 4, 5 are based on the point format 0, meaning that they have the same dimensions plus some additional dimensions:

Point Format 1
Added dimensions Type Size (bit)
gps_time Floating 64
Point Format 2
Added dimensions Type Size (bit)
red unsigned 16
green unsigned 16
blue unsigned 16
Point Format 3
Added dimensions Type Size (bit)
gps_time Floating 64
red unsigned 16
green unsigned 16
blue unsigned 16
Point Format 4
Added dimensions Type Size (bit)
gps_time Floating 64
wavepacket_index unsigned 8
wavepacket_offset unsigned 64
wavepacket_size unsigned 32
return_point_wave_location unsigned 32
x_t floating 32
y_t floating 32
z_t floating 32
Point Format 5
Added dimensions Type Size (bit)
gps_time Floating 64
red unsigned 16
green unsigned 16
blue unsigned 16
wavepacket_index unsigned 8
wavepacket_offset unsigned 64
wavepacket_size unsigned 32
return_point_wave_location unsigned 32
x_t floating 32
y_t floating 32
z_t floating 32
Point Format 6

The Point Format 6, is the new base point format (6, 7, 8, 9, 10) introduced in the LAS specification 1.4. The main modifications from point format 0 and point format 6 are that now the gps_time is baseline and some fields takes more bits, for example the classification is now stored on 8 bits (previously 5).

Dimensions Type Size (bit)
X signed 32
Y signed 32
Z signed 32
intensity unsigned 16
return_number unsigned 4
number_of_returns unsigned 4
synthetic bool 1
key_point bool 1
withheld bool 1
overlap bool 1
scanner_channel unsigned 2
scan_direction_flag bool 1
edge_of_flight_line bool 1
classification unsigned 8
user_data unsigned 8
scan_angle_rank signed 16
point_source_id unsigned 8
gps_time Floating 64
Point Format 7

Add RGB to point format 6.

Added dimensions Type Size (bit)
red unsigned 16
green unsigned 16
blue unsigned 16
Point Format 8

Adds RGB and Nir (Near Infrared) to point format 6.

Added dimensions Type Size (bit)
red unsigned 16
green unsigned 16
blue unsigned 16
nir unsigned 16
Point Format 9

Add waveform data to points

Added dimensions Type Size (bit)
wavepacket_index unsigned 8
wavepacket_offset unsigned 64
wavepacket_size unsigned 32
return_point_wave_location unsigned 32
x_t floating 32
y_t floating 32
z_t floating 32
Point Format 10

Adds RGB, Nir (near infrared), waveform data to point format 6

Added dimensions Type Size (bit)
red unsigned 16
green unsigned 16
blue unsigned 16
nir unsigned 16
wavepacket_index unsigned 8
wavepacket_offset unsigned 64
wavepacket_size unsigned 32
return_point_wave_location unsigned 32
x_t floating 32
y_t floating 32
z_t floating 32

EVLRs

Version 1.4 of the LAS specification added a last block following the point records: EVLRs (Extended Variable Length Record) which are the same thing as VLRs but they can carry a higher payload (length of the payload is stored on a uint64)

Basic Manipulation

Opening & Reading

Reading

Reading is done using pylas.read() function. This function will read everything in the file (Header, vlrs, point records, …) and return an object that you can use to access to the data.

import pylas

las = pylas.read('somefile.las')
print(np.unique(las.classification))
Opening

pylas can also pylas.open() files reading just the header and vlrs but not the points, this is useful if you are interested in metadata that are contained in the header and do not need to read the points.

import s3fs
import pylas

fs = s3fs.S3FileSystem()
with fs.open('my-bucket/some_file.las', 'rb') as f:
     if f.header.point_count < 100_000_000:
         las = pylas.read(f)
Chunked reading

Sometimes files are big, too big to be read entirely and fit into your RAM. The object returned by the pylas.open() function, LasReader can also be used to read points chunk by chunk by using LasReader.chunk_iterator(), which will allow you to do some processing on large files (splitting, filtering, etc)

import pylas

with pylas.open("some_big_file.laz") as f:
    for points in f.chunk_iterator(1_000_000):
        do_something_with(points)

Writing

To be able to write a las file you will need a LasData. You obtain this type of object by using one of the function described in the section above use its method LasData.write() to write to a file or a stream.

Chunked Writing

Similar to LasReader there exists a way to write a file chunk by chunk.

import pylas

with pylas.open("some_big_file.laz") as f:
    with pylas.open("grounds.laz", mode="w", header=f.header) as writer:
        for points in f.chunk_iterator(1_234_567):
            writer.write_points(points[points.classification == 2])

Creating

Creating a new Las from scratch is simple. Use pylas.create().

Converting

pylas also offers the ability to convert a file between the different version and point format available (as long as they are compatible).

To convert, use the pylas.convert()

Accessing the file header

You can access the header of a las file you read or opened by retrieving the ‘header’ attribute:

>>> import pylas
>>> las = pylas.read('pylastests/simple.las')
>>> las.header
<LasHeader(1.2, <PointFormat(3, 0 bytes of extra dims)>)>
>>> las.header.point_count
1065
>>> with pylas.open('pylastests/simple.las') as f:
...     f.header.point_count
1065

you can see the accessible fields in LasHeader.

Accessing Points Records

To access point records using the dimension name, you have 2 options:

  1. regular attribute access using the las.dimension_name syntax
  2. dict-like attribute access las[dimension_name].
>>> import numpy as np
>>> las = pylas.read('pylastests/simple.las')
>>> np.all(las.user_data == las['user_data'])
True
Point Format

The dimensions available in a file are dictated by the point format id. The tables in the introduction section contains the list of dimensions for each of the point format. To get the point format of a file you have to access it through the las object:

>>> point_format = las.point_format
>>> point_format
<PointFormat(3, 0 bytes of extra dims)>
>>> point_format.id
3

If you don’t want to remember the dimensions for each point format, you can access the list of available dimensions in the file you read just like that:

>>> list(point_format.dimension_names)
['X', 'Y', 'Z', 'intensity', 'return_number', 'number_of_returns', 'scan_direction_flag', 'edge_of_flight_line', 'classification', 'synthetic', 'key_point', 'withheld', 'scan_angle_rank', 'user_data', 'point_source_id', 'gps_time', 'red', 'green', 'blue']

This gives you all the dimension names, including extra dimensions if any. If you wish to get only the extra dimension names the point format can give them to you:

>>> list(point_format.standard_dimension_names)
['X', 'Y', 'Z', 'intensity', 'return_number', 'number_of_returns', 'scan_direction_flag', 'edge_of_flight_line', 'classification', 'synthetic', 'key_point', 'withheld', 'scan_angle_rank', 'user_data', 'point_source_id', 'gps_time', 'red', 'green', 'blue']
>>> list(point_format.extra_dimension_names)
[]
>>> las = pylas.read('pylastests/extrabytes.las')
>>> list(las.point_format.extra_dimension_names)
['Colors', 'Reserved', 'Flags', 'Intensity', 'Time']

You can also have more information:

>>> point_format[3].name
'intensity'
>>> point_format[3].num_bits
16
>>> point_format[3].kind
<DimensionKind.UnsignedInteger: 1>
>>> point_format[3].max
65535

Manipulating VLRs

To access the VLRs stored in a file, simply access the vlr member of the las object.

>>> las = pylas.read('pylastests/extrabytes.las')
>>> las.vlrs
[<ExtraBytesVlr(extra bytes structs: 5)>]
>>> with pylas.open('pylastests/extrabytes.las') as f:
...     f.header.vlrs
[<ExtraBytesVlr(extra bytes structs: 5)>]

To retrieve a particular vlr from the list there are 2 ways: VLRList.get() and VLRList.get_by_id()

Examples

Filtering

This example shows how you can extract points from a file and write them into a new one. We use the classification field to filter points, but this can work with the other fields.

import pylas

las = pylas.read('pylastests/simple.las')

new_file = pylas.create(point_format=las.header.point_format_id, file_version=las.header.version)
new_file.points = las.points[las.classification == 1]

new_file.write('extracted_points.las')

Creating from scratch

This example shows how you can create a new LAS file from scratch.

import pylas
import numpy as np

las = pylas.create()

array = np.linspace(0.0, 15.0, 10000)
las.x = array
las.y = array
las.z = array

las.write('diagonal.las')

Using chunked reading & writing

This example shows how to use the ‘chunked’ reading and writing feature to split potentially large LAS/LAZ file into multiple smaller file.

import argparse
import sys
from pathlib import Path
from typing import List
from typing import Optional

import numpy as np

import pylas


def recursive_split(x_min, y_min, x_max, y_max, max_x_size, max_y_size):
    x_size = x_max - x_min
    y_size = y_max - y_min

    if x_size > max_x_size:
        left = recursive_split(x_min, y_min, x_min + (x_size // 2), y_max, max_x_size, max_y_size)
        right = recursive_split(x_min + (x_size // 2), y_min, x_max, y_max, max_x_size, max_y_size)
        return left + right
    elif y_size > max_y_size:
        up = recursive_split(x_min, y_min, x_max, y_min + (y_size // 2), max_x_size, max_y_size)
        down = recursive_split(x_min, y_min + (y_size // 2), x_max, y_max, max_x_size, max_y_size)
        return up + down
    else:
        return [(x_min, y_min, x_max, y_max)]


def tuple_size(string):
    try:
        return tuple(map(float, string.split("x")))
    except:
        raise ValueError("Size must be in the form of numberxnumber eg: 50.0x65.14")


def main():
    parser = argparse.ArgumentParser("LAS recursive splitter", description="Splits a las file bounds recursively")
    parser.add_argument("input_file")
    parser.add_argument("output_dir")
    parser.add_argument("size", type=tuple_size, help="eg: 50x64.17")
    parser.add_argument("--points-per-iter", default=10**6, type=int)

    args = parser.parse_args()

    with pylas.open(sys.argv[1]) as file:
        sub_bounds = recursive_split(
            file.header.x_min,
            file.header.y_min,
            file.header.x_max,
            file.header.y_max,
            args.size[0],
            args.size[1]
        )

        writers: List[Optional[pylas.LasWriter]] = [None] * len(sub_bounds)
        try:
            count = 0
            for points in file.chunk_iterator(args.points_per_iter):
                print(f"{count / file.header.point_count * 100}%")

                # For performance we need to use copy
                # so that the underlying arrays are contiguous
                x, y = points.x.copy(), points.y.copy()

                point_piped = 0

                for i, (x_min, y_min, x_max, y_max) in enumerate(sub_bounds):
                    mask = (x >= x_min) & (x <= x_max) & (y >= y_min) & (y <= y_max)

                    if np.any(mask):
                        if writers[i] is None:
                            output_path = Path(sys.argv[2]) / f"output_{i}.laz"
                            writers[i] = pylas.open(output_path,
                                                    mode='w',
                                                    header=file.header)
                        sub_points = points[mask]
                        writers[i].write_points(sub_points)

                    point_piped += np.sum(mask)
                    if point_piped == len(points):
                        break
                count += len(points)
            print(f"{count / file.header.point_count * 100}%")
        finally:
            for writer in writers:
                if writer is not None:
                    writer.close()


if __name__ == '__main__':
    main()

Less Basic Things

Extra Dimensions

The LAS Specification version 1.4 defines a standard way to add extra dimensions to a LAS file.

In pylas you can add extra dimensions using the LasData.add_extra_dim() function

The Allowed base types for an extra dimensions are:

pylas name size (bits) type
u1 or uint8 8 unsigned
i1 or int8 8 signed
u2 or uint16 16 unsigned
i2 or int16 16 signed
u4 or uint32 32 unsigned
i4 or int32 32 signed
u8 or uint64 64 unsigned
i8 or int64 64 signed
f4 or float32 32 floating
f8 or float64 64 floating

You can prepend the number ‘2’ or ‘3’ to one of the above base type to define an extra dimension that is array of 2 or 3 elements per points. Example: 3u2 -> each points will have an extra dimension that is an array of 3 * 16 bits

Here we are adding a new dimension called “codification” where each value is stored on a 64 bit unsigned integer and an array field of 3 doubles for each points.

import pylas
las = pylas.read("somefile.las")

las.add_extra_dim(pylas.ExtraBytesParams(
    name="codification",
    type="uint64",
    description="More classes available"
))

las.add_extra_dim(pylas.ExtraBytesParams(name="mysterious", type="3f8"))

Note

Although the specification of the ExtraBytesVlr appeared in the 1.4 LAS Spec, pylas allows to add new dimensions to file with version < 1.4

Note

If you are adding multiple extra dimensions use LasData.add_extra_dims() as it is more efficient (it allows to allocate all the dimensions at once instead of re-allocating each time a new dimension is added.

Custom VLRs

Provided you have a valid user_id and record_id (meaning that they are not taken by a VLR described in the LAS specification) You can add you own VLRs to a file

Fast & Easy way

The fastest and easiest way to add your custom VLR to a file is to create a VLR, set its record_data (which must be bytes) and add it to the VLR list.

>>> import pylas
>>> vlr = pylas.vlrs.VLR(user_id='A UserId', record_id=1, description='Example VLR')
>>> vlr
<VLR(user_id: 'A UserId', record_id: '1', data len: 0)>
>>> vlr.record_data = b'somebytes'
>>> vlr
<VLR(user_id: 'A UserId', record_id: '1', data len: 9)>
>>> las = pylas.create()
>>> las.vlrs
[]
>>> las.vlrs.append(vlr)
>>> las.vlrs
[<VLR(user_id: 'A UserId', record_id: '1', data len: 9)>]
Complete & Harder way

While the way shown above is quick & easy it might not be perfect for complex VLRs. Also when reading a file that has custom VLR, pylas won’t be able to automatically parse its data into a better structure, so you will have to find the VLR in the vlrs list and parse it yourself one pylas is done.

One way to automate this task is to create your own Custom VLR Class that extends BaseKnownVLR by implementing the missing methods pylas will be able to automatically parse the VLR when reading the file & write it when saving the file.

>>> class CustomVLR(pylas.vlrs.BaseKnownVLR):
...     def __init__(self):
...         super().__init__()
...         self.numbers = []
...
...     @staticmethod
...     def official_user_id():
...         return "CustomId"
...
...     @staticmethod
...     def official_record_ids():
...         return 1,
...
...     def record_data_bytes(self):
...         return bytes(self.numbers)
...
...     def parse_record_data(self, record_data):
...         self.numbers = [b for b in record_data]
...
...     def __repr__(self):
...         return "<MyCustomVLR>"
>>> import numpy as np
>>> cvlr = CustomVLR()
>>> cvlr.numbers
[]
>>> cvlr.numbers = [1,2, 3]
>>> las = pylas.create()
>>> las.vlrs.append(cvlr)
>>> las.vlrs
[<MyCustomVLR>]
>>> las.x = np.array([1.0, 2.0])
>>> las = pylas.lib.write_then_read_again(las)
>>> las.vlrs
[<MyCustomVLR>]
>>> las.vlrs[0].numbers
[1, 2, 3]

Migration guides

From 0.4.x to 1.0.0

Changes in LAZ backend

With pylas 1.0.0, the lazperf backend support was dropped, and the laszip backend changed from using the laszip executable to using laszip python bindings.

If you used lazperf or relied on the laszip executable you’ll have to choose between the available backends. (see Installation section).

Changes in bit fields

Some fields in LAS are ‘bit fields’.

with pylas 0.4.x, there was a inconsistency between ‘normal’ fields and ‘bit’ fields, when getting a bit field, pylas returned a copy of the values in a new numpy array whereas when getting a normal field, the array you got acted as a ‘view’ on the real array where the values where stored.

That meant that modifying the values of the array you got from a bit field would no propagate to the real array.

import pylas
import numpy as np

las = pylas.read("pylastests/simple.las")

# return number is a bit field
print(las.return_number)
# array([1, 1, 1, ..., 1, 1, 1], dtype=uint8)

ascending_order = np.argsort(las.return_number)[::-1]
print(las.return_number[ascending_order])
# array([4, 4, 4, ..., 1, 1, 1], dtype=uint8)
las.return_number[:] = las.return_number[ascending_order]
print(las.return_number)
# array([1, 1, 1, ..., 1, 1, 1], dtype=uint8) # bif oof
las.return_number[0] = 7
print(las.return_number)
# array([1, 1, 1, ..., 1, 1, 1], dtype=uint8) # again value not updated


# To actually update you have to do
las.return_number = las.return_number[ascending_order]
print(las.return_number)
# array([4, 4, 4, ..., 1, 1, 1], dtype=uint8)

rn = las.return_number[ascending_order]
rn[0] = 7
las.return_number = rn
print(las.return_number)
# array([7, 4, 4, ..., 1, 1, 1], dtype=uint8)

In order to try to solve this inconsistency, pylas >= 0.5.0 introduced the SubFieldView class that is meant to propagate modifications to the real array, and tries to act like a real numpy array.

import pylas
import numpy as np

las = pylas.read("pylastests/simple.las")

print(las.return_number)
# <SubFieldView([1 1 1 ... 1 1 1])>

ascending_order = np.argsort(las.return_number)[::-1]
las.return_number[:] = las.return_number[ascending_order]
print(las.return_number)
# <SubFieldView([4 4 4 ... 1 1 1])>
las.return_number[0] = 7
print(las.return_number)
# <SubFieldView([7 4 4 ... 1 1 1])>

It may be possible that some operation on SubFieldView fail, in that case it is easy to copy them to numpy arrays:

import pylas
import numpy as np

las = pylas.read("pylastests/simple.las")
print(las.return_number)
# <SubFieldView([1 1 1 ... 1 1 1])>

array = np.array(las.return_number)
# array([1, 1, 1, ..., 1, 1, 1], dtype=uint8)

The logic is also the same for ‘Scaled dimensions’ such as x, y, z and scaled extra bytes, where a ScaledArrayView class has been introduced

import pylas
import numpy as np

las = pylas.read("pylastests/simple.las")
print(las.x)
# <ScaledArrayView([637012.24 636896.33 636784.74 ... 637501.67 637433.27 637342.85])>>

# ScaledArray view should behave as much as possible as a numpy array
# However if something breaks in your code when upgrading, and / or
# you need a true numpy array you can get one by doing

array = np.array(las.x)
# array([637012.24, 636896.33, 636784.74, ..., 637501.67, 637433.27,
#        637342.85])
Changes in extra bytes creation

The API to create extra bytes changed slightly, now the parameters needed (and the optional ones) are coupled into ExtraBytesParams

Other changes

The points attribute of as LasData used to return a numpy array it now returns a PackedPointRecord to get the same array as before, use the array property of the point record.

# pylas <= 0.4.3
las = pylas.read("somefile.las")
array = las.points

# pylas 1.0.0
las = pylas.read("somefile.las")
array = las.points.array

API Documentation

pylas package

Re-exported functions

pylas.read(source, closefd=True, laz_backend=())

Entry point for reading las data in pylas

Reads the whole file into memory.

>>> las = read_las("pylastests/simple.las")
>>> las.classification
<SubFieldView([1 1 1 ... 1 1 1])>
Parameters:
  • source (str or io.BytesIO) – The source to read data from
  • laz_backend (Optional, the backend to use when the file is as LAZ file.) – By default pylas will find the backend to use by himself. Use if you wan a specific backend to be used
  • closefd (bool) – if True and the source is a stream, the function will close it after it is done reading
Returns:

The object you can interact with to get access to the LAS points & VLRs

Return type:

pylas.lasdatas.base.LasBase

pylas.open(source, mode='r', closefd=True, laz_backend=None, header=None, do_compress=None) → Union[pylas.lasreader.LasReader, pylas.laswriter.LasWriter, pylas.lasappender.LasAppender]

The pylas.open opens a LAS/LAZ file in one of the 3 supported mode:

  • “r” => Reading => a pylas.LasReader will be returned
  • “w” => Writing => a pylas.LasWriter will be returned
  • “a” => Appending => a pylas.LasAppender will be returned

When opening a file in ‘w’ mode, a header (pylas.LasHeader) is required

>>> with open_las('pylastests/simple.las') as f:
...     print(f.header.point_format.id)
3
>>> f = open('pylastests/simple.las', mode='rb')
>>> with open_las(f,closefd=False) as flas:
...     print(flas.header)
<LasHeader(1.2, <PointFormat(3, 0 bytes of extra dims)>)>
>>> f.closed
False
>>> f.close()
>>> f.closed
True
>>> f = open('pylastests/simple.las', mode='rb')
>>> with open_las(f) as flas:
...    las = flas.read()
>>> f.closed
True
Parameters:
  • source (str or bytes or io.BytesIO) – if source is a str it must be a filename
  • mode (Optional, the mode to open the file:) –
    • “r” for reading (default)
    • ”w” for writing
    • ”a” for appending
  • laz_backend (Optional, the LAZ backend to use to handle decompression/comression) – By default available backends are detected, see LazBackend to see the preference order when multiple backends are available
  • header (The header to use when opening in write mode.) –
  • do_compress (optional, bool, only meaningful in writing mode:) –
    • None (default) guess if compression is needed using the file extension

    or if a laz_backend was explicitely provided - True compresses the file - False do not compress the file

  • closefd (optional, bool, True by default) – Whether the stream/file object shall be closed, this only work when using open_las in a with statement. An exception is raised if closefd is specified and the source is a filename
pylas.create(*, point_format: Union[pylas.point.format.PointFormat, int, None] = None, file_version: Union[pylas.header.Version, str, None] = None)

Function to create a new empty las data object

Note

If you provide both point_format and file_version an exception will be raised if they are not compatible

>>> las = create_las(point_format=6,file_version="1.2")
Traceback (most recent call last):
 ...
pylas.errors.PylasError: Point format 6 is not compatible with file version 1.2

If you provide only the point_format the file_version will automatically selected for you.

>>> las = create_las(point_format=0)
>>> las.header.version == '1.2'
True
>>> las = create_las(point_format=PointFormat(6))
>>> las.header.version == '1.4'
True
Parameters:
  • point_format – The point format you want the created file to have
  • file_version – The las version you want the created las to have
Returns:

A new las data object

Return type:

pylas.lasdatas.base.LasBase

pylas.convert(source_las, *, point_format_id=None, file_version=None)[source]

Converts a Las from one point format to another Automatically upgrades the file version if source file version is not compatible with the new point_format_id

convert to point format 0

>>> las = read_las('pylastests/simple.las')
>>> las.header.version
Version(major=1, minor=2)
>>> las = convert(las, point_format_id=0)
>>> las.header.point_format.id
0
>>> str(las.header.version)
'1.2'

convert to point format 6, which need version >= 1.4 then convert back to point format 0, version is not downgraded

>>> las = read_las('pylastests/simple.las')
>>> str(las.header.version)
'1.2'
>>> las = convert(las, point_format_id=6)
>>> las.header.point_format.id
6
>>> str(las.header.version)
'1.4'
>>> las = convert(las, point_format_id=0)
>>> str(las.header.version)
'1.4'

an exception is raised if the requested point format is not compatible with the file version

>>> las = read_las('pylastests/simple.las')
>>> convert(las, point_format_id=6, file_version='1.2')
Traceback (most recent call last):
 ...
pylas.errors.PylasError: Point format 6 is not compatible with file version 1.2
Parameters:
  • source_las (pylas.lasdatas.base.LasBase) – The source data to be converted
  • point_format_id (int, optional) – The new point format id (the default is None, which won’t change the source format id)
  • file_version (str, optional,) – The new file version. None by default which means that the file_version may be upgraded for compatibility with the new point_format. The file version will not be downgraded.
Returns:

Return type:

pylas.lasdatas.base.LasBase

Submodules

pylas.lib module

‘Entry point’ of the library, Contains the various functions meant to be used directly by a user

pylas.lib.open_las(source, mode='r', closefd=True, laz_backend=None, header=None, do_compress=None) → Union[pylas.lasreader.LasReader, pylas.laswriter.LasWriter, pylas.lasappender.LasAppender][source]

The pylas.open opens a LAS/LAZ file in one of the 3 supported mode:

  • “r” => Reading => a pylas.LasReader will be returned
  • “w” => Writing => a pylas.LasWriter will be returned
  • “a” => Appending => a pylas.LasAppender will be returned

When opening a file in ‘w’ mode, a header (pylas.LasHeader) is required

>>> with open_las('pylastests/simple.las') as f:
...     print(f.header.point_format.id)
3
>>> f = open('pylastests/simple.las', mode='rb')
>>> with open_las(f,closefd=False) as flas:
...     print(flas.header)
<LasHeader(1.2, <PointFormat(3, 0 bytes of extra dims)>)>
>>> f.closed
False
>>> f.close()
>>> f.closed
True
>>> f = open('pylastests/simple.las', mode='rb')
>>> with open_las(f) as flas:
...    las = flas.read()
>>> f.closed
True
Parameters:
  • source (str or bytes or io.BytesIO) – if source is a str it must be a filename
  • mode (Optional, the mode to open the file:) –
    • “r” for reading (default)
    • ”w” for writing
    • ”a” for appending
  • laz_backend (Optional, the LAZ backend to use to handle decompression/comression) – By default available backends are detected, see LazBackend to see the preference order when multiple backends are available
  • header (The header to use when opening in write mode.) –
  • do_compress (optional, bool, only meaningful in writing mode:) –
    • None (default) guess if compression is needed using the file extension

    or if a laz_backend was explicitely provided - True compresses the file - False do not compress the file

  • closefd (optional, bool, True by default) – Whether the stream/file object shall be closed, this only work when using open_las in a with statement. An exception is raised if closefd is specified and the source is a filename
pylas.lib.read_las(source, closefd=True, laz_backend=())[source]

Entry point for reading las data in pylas

Reads the whole file into memory.

>>> las = read_las("pylastests/simple.las")
>>> las.classification
<SubFieldView([1 1 1 ... 1 1 1])>
Parameters:
  • source (str or io.BytesIO) – The source to read data from
  • laz_backend (Optional, the backend to use when the file is as LAZ file.) – By default pylas will find the backend to use by himself. Use if you wan a specific backend to be used
  • closefd (bool) – if True and the source is a stream, the function will close it after it is done reading
Returns:

The object you can interact with to get access to the LAS points & VLRs

Return type:

pylas.lasdatas.base.LasBase

pylas.lib.mmap_las(filename)[source]

MMap a file, much like laspy did

pylas.lib.create_las(*, point_format: Union[pylas.point.format.PointFormat, int, None] = None, file_version: Union[pylas.header.Version, str, None] = None)[source]

Function to create a new empty las data object

Note

If you provide both point_format and file_version an exception will be raised if they are not compatible

>>> las = create_las(point_format=6,file_version="1.2")
Traceback (most recent call last):
 ...
pylas.errors.PylasError: Point format 6 is not compatible with file version 1.2

If you provide only the point_format the file_version will automatically selected for you.

>>> las = create_las(point_format=0)
>>> las.header.version == '1.2'
True
>>> las = create_las(point_format=PointFormat(6))
>>> las.header.version == '1.4'
True
Parameters:
  • point_format – The point format you want the created file to have
  • file_version – The las version you want the created las to have
Returns:

A new las data object

Return type:

pylas.lasdatas.base.LasBase

pylas.lib.convert(source_las, *, point_format_id=None, file_version=None)[source]

Converts a Las from one point format to another Automatically upgrades the file version if source file version is not compatible with the new point_format_id

convert to point format 0

>>> las = read_las('pylastests/simple.las')
>>> las.header.version
Version(major=1, minor=2)
>>> las = convert(las, point_format_id=0)
>>> las.header.point_format.id
0
>>> str(las.header.version)
'1.2'

convert to point format 6, which need version >= 1.4 then convert back to point format 0, version is not downgraded

>>> las = read_las('pylastests/simple.las')
>>> str(las.header.version)
'1.2'
>>> las = convert(las, point_format_id=6)
>>> las.header.point_format.id
6
>>> str(las.header.version)
'1.4'
>>> las = convert(las, point_format_id=0)
>>> str(las.header.version)
'1.4'

an exception is raised if the requested point format is not compatible with the file version

>>> las = read_las('pylastests/simple.las')
>>> convert(las, point_format_id=6, file_version='1.2')
Traceback (most recent call last):
 ...
pylas.errors.PylasError: Point format 6 is not compatible with file version 1.2
Parameters:
  • source_las (pylas.lasdatas.base.LasBase) – The source data to be converted
  • point_format_id (int, optional) – The new point format id (the default is None, which won’t change the source format id)
  • file_version (str, optional,) – The new file version. None by default which means that the file_version may be upgraded for compatibility with the new point_format. The file version will not be downgraded.
Returns:

Return type:

pylas.lasdatas.base.LasBase

pylas.lib.write_then_read_again(las, do_compress=False, laz_backend=())[source]

writes the given las into memory using BytesIO and reads it again, returning the newly read file.

Mostly used for testing purposes, without having to write to disk

pylas.header module
LasHeader
class pylas.header.LasHeader(*, version: Union[pylas.header.Version, str, None] = None, point_format: Union[pylas.point.format.PointFormat, int, None] = None)[source]

Bases: object

Contains the information from the header of as LAS file with ‘implementation’ field left out and ‘users’ field stored in more ergonomic classes.

This header also contains the VLRs

Examples

Creating a default header:

>>> header = LasHeader()
>>> header
<LasHeader(1.2, <PointFormat(3, 0 bytes of extra dims)>)>

Creating a header with the wanted version and point format:

>>> header = LasHeader(version=Version(1, 4), point_format=PointFormat(6))
>>> header
<LasHeader(1.4, <PointFormat(6, 0 bytes of extra dims)>)>
>>> header = LasHeader(version="1.4", point_format=6)
>>> header
<LasHeader(1.4, <PointFormat(6, 0 bytes of extra dims)>)>
DEFAULT_VERSION = Version(major=1, minor=2)

The default version used when None is given to init

DEFAULT_POINT_FORMAT = <PointFormat(3, 0 bytes of extra dims)>

The default point format Used when None is given to init

file_source_id = None

File source id

uuid = None

Project ID Initialized to null UUID

system_identifier = None

System identifier Initialized to ‘OTHER’

generating_software = None

The software which generated the file Initialized to ‘pylas’

creation_date = None

Day the file was created, initialized to date.today()

point_count = None

The number of points in the file

scales = None

The numbers used to scale the x,y,z coordinates

offsets = None

The numbers used to offset the x,y,z coordinates

number_of_points_by_return = None

Number of points by return for las <= 1.2 only the first 5 elements matters

vlrs = None

The VLRS

extra_header_bytes = None

Extra bytes between end of header and first vlrs

extra_vlr_bytes = None

Extra bytes between end of vlr end first point

start_of_waveform_data_packet_record = None

Las >= 1.3

start_of_first_evlr = None

Las >= 1.4 Offset to the first evlr in the file

number_of_evlrs = None

The number of evlrs in the file

point_format

The point format

version

The version

x_scale
y_scale
z_scale
x_offset
y_offset
z_offset
x_max
y_max
z_max
x_min
y_min
z_min
add_extra_dims(params: List[pylas.point.format.ExtraBytesParams]) → None[source]
add_extra_dim(params: pylas.point.format.ExtraBytesParams)[source]
set_version_and_point_format(version: pylas.header.Version, point_format: pylas.point.format.PointFormat) → None[source]
pylas.lasreader module
LasReader
class pylas.lasreader.LasReader(source: BinaryIO, closefd: bool = True, laz_backend: Union[pylas.compression.LazBackend, Iterable[pylas.compression.LazBackend], None] = None)[source]

Bases: object

The reader class handles LAS and LAZ via one of the supported backend

read_points(n: int) → Optional[pylas.point.record.ScaleAwarePointRecord][source]

Read n points from the file

If there are no points left to read, returns None.

Parameters:n (The number of points to read) – if n is less than 0, this function will read the remaining points
read() → pylas.lasdata.LasData[source]

Reads all the points not read and returns a LasData object

chunk_iterator(points_per_iteration: int) → pylas.lasreader.PointChunkIterator[source]

Returns an iterator, that will read points by chunks of the requested size

Parameters:points_per_iteration – number of points to be read with each iteration
Returns:
close() → None[source]

closes the file object used by the reader

pylas.lasdata module
LasData
class pylas.lasdata.LasData(header: pylas.header.LasHeader, points: Optional[pylas.point.record.PackedPointRecord] = None)[source]

Bases: object

Class synchronizing all the moving parts of LAS files.

It connects the point record, header, vlrs together.

To access points dimensions using this class you have two possibilities

las = pylas.read('some_file.las')
las.classification
# or
las['classification']
x

Returns the scaled x positions of the points as doubles

y

Returns the scaled y positions of the points as doubles

z

Returns the scaled z positions of the points as doubles

point_format

Shortcut to get the point format

points

Returns the point record

vlrs
add_extra_dim(params: pylas.point.format.ExtraBytesParams) → None[source]

Adds a new extra dimension to the point record

Note

If you plan on adding multiple extra dimensions, prefer add_extra_dims() as it will save re-allocations and data copy

Parameters:params (ExtraBytesParams) – parameters of the new extra dimension to add
add_extra_dims(params: List[pylas.point.format.ExtraBytesParams]) → None[source]

Add multiple extra dimensions at once

Parameters:params (list of parameters of the new extra dimensions to add) –
update_header() → None[source]

Update the information stored in the header to be in sync with the actual data.

This method is called automatically when you save a file using pylas.lasdatas.base.LasBase.write()

write(destination, do_compress=None, laz_backend=None)[source]

Writes to a stream or file

Note

When destination is a string, it will be interpreted as the path were the file should be written to, and whether the file will be compressed depends on the extension used (case insensitive):

  • .laz -> compressed
  • .las -> uncompressed

And the do_compress option will be ignored

Parameters:
  • destination (str or file object) – filename or stream to write to
  • do_compress (bool, optional) – Flags to indicate if you want to compress the data
  • laz_backend (optional, the laz backend to use) – By default, pylas detect available backends
change_scaling(scales=None, offsets=None) → None[source]
pylas.vlrs.vlrlist module
class pylas.vlrs.vlrlist.VLRList(*args, **kwargs)[source]

Bases: list

Class responsible for managing the vlrs

index(value, start: int = 0, stop: int = None) → int[source]

Return first index of value.

Raises ValueError if the value is not present.

get_by_id(user_id='', record_ids=(None, ))[source]

Function to get vlrs by user_id and/or record_ids. Always returns a list even if only one vlr matches the user_id and record_id

>>> import pylas
>>> from pylas.vlrs.known import ExtraBytesVlr, WktCoordinateSystemVlr
>>> las = pylas.read("pylastests/extrabytes.las")
>>> las.vlrs
[<ExtraBytesVlr(extra bytes structs: 5)>]
>>> las.vlrs.get(WktCoordinateSystemVlr.official_user_id())
[]
>>> las.vlrs.get(WktCoordinateSystemVlr.official_user_id())[0]
Traceback (most recent call last):
IndexError: list index out of range
>>> las.vlrs.get_by_id(ExtraBytesVlr.official_user_id())
[<ExtraBytesVlr(extra bytes structs: 5)>]
>>> las.vlrs.get_by_id(ExtraBytesVlr.official_user_id())[0]
<ExtraBytesVlr(extra bytes structs: 5)>
Parameters:
  • user_id (str, optional) – the user id
  • record_ids (iterable of int, optional) – THe record ids of the vlr(s) you wish to get
Returns:

a list of vlrs matching the user_id and records_ids

Return type:

list

get(vlr_type: str) → List[pylas.vlrs.known.IKnownVLR][source]

Returns the list of vlrs of the requested type Always returns a list even if there is only one VLR of type vlr_type.

>>> import pylas
>>> las = pylas.read("pylastests/extrabytes.las")
>>> las.vlrs
[<ExtraBytesVlr(extra bytes structs: 5)>]
>>> las.vlrs.get("WktCoordinateSystemVlr")
[]
>>> las.vlrs.get("WktCoordinateSystemVlr")[0]
Traceback (most recent call last):
IndexError: list index out of range
>>> las.vlrs.get('ExtraBytesVlr')
[<ExtraBytesVlr(extra bytes structs: 5)>]
>>> las.vlrs.get('ExtraBytesVlr')[0]
<ExtraBytesVlr(extra bytes structs: 5)>
Parameters:vlr_type (str) – the class name of the vlr
Returns:a List of vlrs matching the user_id and records_ids
Return type:list
extract(vlr_type: str) → List[pylas.vlrs.known.IKnownVLR][source]

Returns the list of vlrs of the requested type The difference with get is that the returned vlrs will be removed from the list

Parameters:vlr_type (str) – the class name of the vlr
Returns:a List of vlrs matching the user_id and records_ids
Return type:list
classmethod read_from(data_stream: BinaryIO, num_to_read: int, extended: bool = False) → pylas.vlrs.vlrlist.VLRList[source]

Reads vlrs and parse them if possible from the stream

Parameters:
  • data_stream (io.BytesIO) – stream to read from
  • num_to_read (int) – number of vlrs to be read
  • extended (bool) – whether the vlrs are regular vlr or extended vlr
Returns:

List of vlrs

Return type:

pylas.vlrs.vlrlist.VLRList

write_to(stream: BinaryIO, as_extended: bool = False) → int[source]
pylas.vlrs.known module

The definition of the VLR Header, VLR, the KnownVLRs are in this module.

A KnownVLR is a VLR for which we know how to parse its record_data

class pylas.vlrs.known.IKnownVLR[source]

Bases: abc.ABC

Interface that any KnownVLR must implement. A KnownVLR is a VLR for which we know how to parse its record_data

Implementing this interfaces allows to automatically call the right parser for the right VLR when reading them.

static official_user_id() → str[source]

Shall return the official user_id as described in the documentation

static official_record_ids() → Tuple[int, ...][source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
record_data_bytes() → bytes[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
parse_record_data(record_data: bytes) → None[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
class pylas.vlrs.known.BaseKnownVLR(record_id=None, description='')[source]

Bases: pylas.vlrs.vlr.BaseVLR, pylas.vlrs.known.IKnownVLR, abc.ABC

Base Class to factorize common code between the different type of Known VLRs

classmethod from_raw(raw: pylas.vlrs.vlr.VLR)[source]
class pylas.vlrs.known.ClassificationLookupVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

This vlr maps class numbers to short descriptions / names

>>> lookup = ClassificationLookupVlr()
>>> lookup[0] = "never_classified"
>>> lookup[2] = "ground"
>>> lookup[0]
'never_classified'
parse_record_data(record_data: bytes) → None[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes() → bytes[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id() → str[source]

Shall return the official user_id as described in the documentation

static official_record_ids() → Tuple[int, ...][source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.LasZipVlr(data: bytes)[source]

Bases: pylas.vlrs.known.BaseKnownVLR

Contains the information needed by laszip (or any other laz backend) to compress the point records.

parse_record_data(record_data: bytes) → None[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes() → bytes[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id() → str[source]

Shall return the official user_id as described in the documentation

static official_record_ids() → Tuple[int, ...][source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
classmethod from_raw(raw_vlr)[source]
class pylas.vlrs.known.ExtraBytesStruct[source]

Bases: _ctypes.Structure

NO_DATA_BIT_MASK = 1
MIN_BIT_MASK = 2
MAX_BIT_MASK = 4
SCALE_BIT_MASK = 8
OFFSET_BIT_MASK = 16
no_data
min
max
offset
scale
set_scale_is_relevant() → None[source]
set_offset_is_relevant() → None[source]
format_name()[source]
type_str()[source]
num_elements() → int[source]
static size()[source]
data_type

Structure/Union member

description

Structure/Union member

name

Structure/Union member

options

Structure/Union member

reserved

Structure/Union member

unused

Structure/Union member

class pylas.vlrs.known.ExtraBytesVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

parse_record_data(data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
type_of_extra_dims() → List[pylas.point.format.ExtraBytesParams][source]
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.WaveformPacketStruct[source]

Bases: _ctypes.Structure

static size()[source]
bits_per_sample

Structure/Union member

digitizer_gain

Structure/Union member

digitizer_offset

Structure/Union member

number_of_samples

Structure/Union member

temporal_sample_spacing

Structure/Union member

waveform_compression_type

Structure/Union member

class pylas.vlrs.known.WaveformPacketVlr(record_id, description='')[source]

Bases: pylas.vlrs.known.BaseKnownVLR

parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
static official_user_id()[source]

Shall return the official user_id as described in the documentation

classmethod from_raw(raw_vlr)[source]
class pylas.vlrs.known.GeoKeyEntryStruct[source]

Bases: _ctypes.Structure

static size()[source]
count

Structure/Union member

id

Structure/Union member

tiff_tag_location

Structure/Union member

value_offset

Structure/Union member

class pylas.vlrs.known.GeoKeysHeaderStructs[source]

Bases: _ctypes.Structure

static size()[source]
key_direction_version

Structure/Union member

key_revision

Structure/Union member

minor_revision

Structure/Union member

number_of_keys

Structure/Union member

class pylas.vlrs.known.GeoKeyDirectoryVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.GeoDoubleParamsVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.GeoAsciiParamsVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.WktMathTransformVlr[source]

Bases: pylas.vlrs.known.BaseKnownVLR

From the Spec:
Note that the math transform WKT record is added for completeness, and a coordinate system WKT may or may not require a math transform WKT record
parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
class pylas.vlrs.known.WktCoordinateSystemVlr(wkt_string='')[source]

Bases: pylas.vlrs.known.BaseKnownVLR

Replaces Coordinates Reference System for new las files (point fmt >= 5) “LAS is not using the “ESRI WKT”

parse_record_data(record_data)[source]

Shall parse the given record_data into a user-friendlier structure

Parameters:record_data (bytes) – The record_data bytes read from the file
record_data_bytes()[source]

Shall return the bytes corresponding to the record_data part of the VLR as they should be written in the file.

Returns:The bytes of the vlr’s record_data
Return type:bytes
static official_user_id()[source]

Shall return the official user_id as described in the documentation

static official_record_ids()[source]

Shall return the official record_id for the VLR

Note

Even if the VLR has one record_id, the return type must be a tuple

Returns:The record_ids this VLR type can have
Return type:tuple of int
pylas.vlrs.known.vlr_factory(vlr: pylas.vlrs.vlr.VLR)[source]

Given a vlr tries to find its corresponding KnownVLR class that can parse its data. If no KnownVLR implementation is found, returns the input vlr unchanged

pylas.vlrs.vlr
class pylas.VLR(user_id, record_id, description='', record_data=b'')[source]
record_data = None

The record_data as bytes

record_data_bytes() → bytes[source]
description
record_id
user_id
pylas.point.record module

Contains the classes that manages Las PointRecords Las PointRecords are represented using Numpy’s structured arrays, The PointRecord classes provide a few extra things to manage these arrays in the context of Las point data

pylas.point.record.scale_dimension(array_dim, scale, offset)[source]
pylas.point.record.unscale_dimension(array_dim, scale, offset)[source]
pylas.point.record.raise_not_enough_bytes_error(expected_bytes_len, missing_bytes_len, point_data_buffer_len, points_dtype) → NoReturn[source]
class pylas.point.record.PackedPointRecord(data: numpy.ndarray, point_format: pylas.point.format.PointFormat)[source]

Bases: object

In the PackedPointRecord, fields that are a combinations of many sub-fields (fields stored on less than a byte) are still packed together and are only de-packed and re-packed when accessed.

This uses of less memory than if the sub-fields were unpacked

>>> #return number is a sub-field
>>> from pylas import PointFormat
>>> packed_point_record = PackedPointRecord.zeros(PointFormat(0), 10)
>>> return_number = packed_point_record['return_number']
>>> return_number
<SubFieldView([0 0 0 0 0 0 0 0 0 0])>
>>> return_number[:] = 1
>>> np.alltrue(packed_point_record['return_number'] == 1)
True
point_size

Returns the point size in bytes taken by each points of the record

Returns:The point size in byte
Return type:int
classmethod zeros(point_format, point_count)[source]

Creates a new point record with all dimensions initialized to zero

Parameters:
  • point_format (PointFormat) – The point format id the point record should have
  • point_count (int) – The number of point the point record should have
Returns:

Return type:

PackedPointRecord

classmethod empty(point_format)[source]

Creates an empty point record.

Parameters:point_format (pylas.PointFormat) – The point format id the point record should have
Returns:
Return type:PackedPointRecord
classmethod from_point_record(other_point_record: pylas.point.record.PackedPointRecord, new_point_format: pylas.point.format.PointFormat) → pylas.point.record.PackedPointRecord[source]

Construct a new PackedPointRecord from an existing one with the ability to change to point format while doing so

classmethod from_buffer(buffer, point_format, count, offset=0)[source]
copy_fields_from(other_record: pylas.point.record.PackedPointRecord) → None[source]

Tries to copy the values of the current dimensions from other_record

memoryview() → memoryview[source]
resize(new_size: int) → None[source]
pylas.point.record.apply_new_scaling(record, scales: numpy.ndarray, offsets: numpy.ndarray) → None[source]
class pylas.point.record.ScaleAwarePointRecord(array, point_format, scales, offsets)[source]

Bases: pylas.point.record.PackedPointRecord

change_scaling(scales=None, offsets=None) → None[source]
pylas.errors module

All the custom exceptions types

exception pylas.errors.PylasError[source]

Bases: Exception

exception pylas.errors.UnknownExtraType[source]

Bases: pylas.errors.PylasError

exception pylas.errors.PointFormatNotSupported[source]

Bases: pylas.errors.PylasError

exception pylas.errors.FileVersionNotSupported[source]

Bases: pylas.errors.PylasError

exception pylas.errors.LazError[source]

Bases: pylas.errors.PylasError

exception pylas.errors.IncompatibleDataFormat[source]

Bases: pylas.errors.PylasError

pylas.compression module
LazBackend
class pylas.compression.LazBackend[source]

Bases: enum.Enum

Supported backends for reading and writing LAS/LAZ

LazrsParallel = 0

lazrs in multi-thread mode

Lazrs = 1

lazrs in single-thread mode

Laszip = 2

laszip backend

is_available() → bool[source]

Returns true if the backend is available

detect_available = <function LazBackend.detect_available>[source]
pylas.point.format module
class pylas.point.format.ExtraBytesParams(name: str, type: str, description: str = '', offsets: Optional[numpy.ndarray] = None, scales: Optional[numpy.ndarray] = None)[source]

Bases: object

All parameters needed to create extra bytes

name = None

The name of the extra dimension

type = None

The type of the extra dimension

description = None

A description of the extra dimension

offsets = None

The offsets to use if its a ‘scaled dimension’, can be none

scales = None

The scales to use if its a ‘scaled dimension’, can be none

class pylas.point.format.PointFormat(point_format_id: int)[source]

Bases: object

Class that contains the informations about the dimensions that forms a PointFormat.

A PointFormat has ‘standard’ dimensions (dimensions defined in the LAS standard, each point format has its set of dimensions), but it can also have extra (non-standard) dimensions defined by the user)

>>> fmt = PointFormat(3)
>>> all(dim.is_standard for dim in fmt.dimensions)
True
>>> dim = fmt.dimension_by_name("classification") # or fmt["classification"]
>>> dim.max
31
>>> dim.min
0
>>> dim.num_bits
5
standard_dimensions

Returns an iterable of the standard dimensions

>>> fmt = PointFormat(0)
>>> standard_dims = list(fmt.standard_dimensions)
>>> len(standard_dims)
15
>>> standard_dims[4].name
'return_number'
extra_dimensions
dimension_names

Returns the names of the dimensions contained in the point format

standard_dimension_names

Returns the names of the extra dimensions in this point format

extra_dimension_names

Returns the names of the extra dimensions in this point format

size

Returns the number of bytes (standard + extra) a point takes

>>> PointFormat(3).size
34
>>> fmt = PointFormat(3)
>>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64"))
>>> fmt.size
42
num_standard_bytes

Returns the number of bytes used by standard dims

>>> fmt = PointFormat(3)
>>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64"))
>>> fmt.num_standard_bytes
34
num_extra_bytes

Returns the number of extra bytes

>>> fmt = PointFormat(3)
>>> fmt.add_extra_dimension(ExtraBytesParams("codification", "uint64"))
>>> fmt.num_extra_bytes
8
has_waveform_packet

Returns True if the point format has waveform packet dimensions

dimension_by_name(name: str) → pylas.point.dims.DimensionInfo[source]

Returns the dimension info for the dimension by name

ValueError is raised if the dimension does not exist un the point format

>>> info = PointFormat(2).dimension_by_name('number_of_returns')
>>> info.name == 'number_of_returns'
True
>>> info.num_bits == 3
True
>>> info = PointFormat(2).dimension_by_name('gps_time')
Traceback (most recent call last):
...
ValueError: Dimension 'gps_time' does not exist
add_extra_dimension(param: pylas.point.format.ExtraBytesParams) → None[source]

Add an extra, user-defined dimension

dtype()[source]

Returns the numpy.dtype used to store the point records in a numpy array

Note

The dtype corresponds to the dtype with sub_fields packed into their composed fields

pylas.point.format.lost_dimensions(point_fmt_in, point_fmt_out)[source]

Returns a list of the names of the dimensions that will be lost when converting from point_fmt_in to point_fmt_out

pylas.lasmmap module
class pylas.lasmmap.LasMMAP(filename: Union[str, pathlib.Path])[source]

Bases: pylas.lasdata.LasData

Memory map a LAS file. It works like a regular LasData however the data is not actually read in memory,

Access to dimensions are made directly from the file itself, changes made to the points are directly reflected in the mmap file.

Vlrs cannot be modified.

This can be useful if you want to be able to process a big LAS file

Note

A LAZ (compressed LAS) cannot be mmapped

close() → None[source]
pylas.lasappender
LasAppender
class pylas.lasappender.LasAppender(dest: BinaryIO, laz_backend: Union[pylas.compression.LazBackend, Iterable[pylas.compression.LazBackend], None] = None, closefd: bool = True)[source]

Allows to append points to and existing LAS/LAZ file.

Appending to LAZ is only supported by the lazrs backend

append_points(points: pylas.point.record.PackedPointRecord) → None[source]

Append the points to the file, the points must have the same point format as the points already contained within the file.

Parameters:points – The points to append
pylas.laswriter module
LasWriter
class pylas.laswriter.LasWriter(dest: BinaryIO, header: pylas.header.LasHeader, do_compress: Optional[bool] = None, laz_backend: Union[pylas.compression.LazBackend, Iterable[pylas.compression.LazBackend], None] = None, closefd: bool = True)[source]

Bases: object

Allows to write a complete LAS/LAZ file to the destination.

write_points(points: pylas.point.record.PackedPointRecord) → None[source]
write_evlrs(evlrs: pylas.vlrs.vlrlist.VLRList) → None[source]
close() → None[source]

Indices and tables