pylas: Python library for lidar LAS/LAZ IO.¶
A popular format for lidar pointcloud and full waveform data is LAS, and it’s compressed counterpart LAZ. 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¶
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:
- Header
- VLRs
- Point Records
Header¶
The header contains information about the data such as its version, the point format (which tells the different dimensions stored for each points).
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:
- header
- payload
The payload is limited to 65,535 bytes (Because in the header, the length of the payload is stored on a uint16).
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’
>>> import pylas
>>> las = pylas.read('pylastests/simple.las')
>>> las.X.dtype
dtype('int32')
>>> las.x.dtype
dtype('float64')
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)
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 3 supported options are:
When encountering LAZ data, pylas will try this options in the order described above. (Example: if lazrs is not installed or if it fails during, the process, pylas will try lazperf, and so on)
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.
laz-perf is an alternative laszip implementation in C++. However it can compress and decompress only point formats 0, 1, 2, 3
laszip.exe is the laszip executable from the original LAZ implementation found in LAStools The advandage of laszip.exe is that its the official implementation, however due to the way it is used (pylas uses a python Popen and talks to the laszip exe via its stdin, stdout) there is some overhead giving slower compression / decompression times and a bit higher memory usage.
lazrs and lazperf are available on pypi and can be installed via pip, for LAStools’s laszip you have to either compile it yourself, download it from lastools website.
You can pip install lazperf and/or lazrs by yourself.
Or use pip extra_requires (since pylas 0.4.0)
pip install pylas[lazperf]
# OR
pip install pylas[lazrs]
# OR
pip install pylas[lazperf,lazrs]
Basic Manipulation¶
Opening & Reading¶
You have two ways to read LAS files with pylas.
The easiest one is 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.
las = pylas.read('somefile.las')
print(np.unique(las.classification))
import s3fs
fs = s3fs.S3FileSystem()
with fs.open('my-bucket/some_file.las', 'rb') as f:
las = pylas.read(f)
The other way to read a las file is to use the pylas.open()
.
As the name suggest, this function does not read the whole file, but opens it and only read the header.
This is useful if you only need to read the header without loading the whole file in memory.
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()
Creating¶
Creating a new Las from scratch is simple.
Use pylas.create()
.
Writing¶
To be able to write a las file you will need a pylas.lasdatas.base.LasBase
(or one if its subclasses).
You obtain this type of object by using one of the function above,
use its method pylas.lasdatas.base.LasBase.write()
to write to a file or a stream.
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)>
>>> 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 pylas.headers.rawheader.RawHeader1_1
and its sub-classes.
Accessing Points Records¶
To access point records using the dimension name, you have 2 options:
- regular attribute access using the las.dimension_name syntax
- 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
However if you wish to retrieve the x, y, z coordinates with scale and offset applied your only option is the first method.
>>> las.x.max().dtype
dtype('float64')
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 points_data member:
>>> point_format = las.points_data.point_format
>>> point_format
<PointFormat(3)>
>>> point_format.id
3
If you don’t want to rember the dimensions for each point format, you can access the list of available dimensions in the file you read just like that:
>>> 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:
>>> point_format.extra_dimension_names
[]
>>> las = pylas.read('pylastests/extra.laz')
>>> las.points_data.point_format.extra_dimension_names
['Colors', 'Reserved', 'Flags', 'Intensity', 'Time']
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:
... vlr_list = f.read_vlrs()
>>> vlr_list
[<ExtraBytesVlr(extra bytes structs: 5)>]
To retrieve a particular vlr from the list there are 2 ways: pylas.vlrs.vlrlist.VLRList.get()
and
pylas.vlrs.vlrlist.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_id=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')
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 pylas.lasdatas.base.LasBase.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 float | 32 | floating |
f8 or double | 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(name="codification", type="uint64", description="More classes available")
las.add_extra_dim(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
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 pylas.vlrs.rawvlr.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
pylas.vlrs.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>"
>>> cvlr = CustomVLR()
>>> cvlr.numbers
[]
>>> cvlr.numbers = [1,2, 3]
>>> las = pylas.create()
>>> las.vlrs.append(cvlr)
>>> las.vlrs
[<MyCustomVLR>]
>>> las = pylas.lib.write_then_read_again(las)
>>> las.vlrs
[<MyCustomVLR>]
>>> las.vlrs[0].numbers
[1, 2, 3]
API Documentation¶
pylas package¶
-
pylas.
read
(source, closefd=True)¶ Entry point for reading las data in pylas
Reads the whole file into memory.
>>> las = read_las("pylastests/simple.las") >>> las.classification array([1, 1, 1, ..., 1, 1, 1], dtype=uint8)
Parameters: - source (str or io.BytesIO) – The source to read data from
- 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.
open
(source, closefd=True)¶ Opens and reads the header of the las content in the source
>>> 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)> >>> f.closed False
>>> f = open('pylastests/simple.las', mode='rb') >>> with open_las(f) as flas: ... las = flas.read() >>> f.closed True
Parameters: - source (str or io.BytesIO) – if source is a str it must be a filename a stream if a file object with the methods read, seek, tell
- closefd (bool) – 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
Returns: Return type:
-
pylas.
create
(*, point_format_id=0, file_version=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_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
If you provide only the point_format the file_version will automatically selected for you.
>>> las = create_las(point_format_id=0) >>> las.header.version == '1.2' True
>>> las = create_las(point_format_id=6) >>> las.header.version == '1.4' True
Parameters: Returns: A new las data object
Return type:
-
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 '1.2' >>> las = convert(las, point_format_id=0) >>> las.header.point_format_id 0 >>> 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') >>> las.header.version '1.2' >>> las = convert(las, point_format_id=6) >>> las.header.point_format_id 6 >>> las.header.version '1.4' >>> las = convert(las, point_format_id=0) >>> 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:
Submodules¶
pylas.lib module¶
‘Entry point’ of the library, Contains the various functions meant to be used directly by a user
-
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 '1.2' >>> las = convert(las, point_format_id=0) >>> las.header.point_format_id 0 >>> 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') >>> las.header.version '1.2' >>> las = convert(las, point_format_id=6) >>> las.header.point_format_id 6 >>> las.header.version '1.4' >>> las = convert(las, point_format_id=0) >>> 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.lib.
create_from_header
(header)[source]¶ Creates a File from an existing header, allocating the array of point according to the provided header. The input header is copied.
Parameters: header (existing header to be used to create the file) – Returns: Return type: pylas.lasdatas.base.LasBase
-
pylas.lib.
create_las
(*, point_format_id=0, file_version=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_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
If you provide only the point_format the file_version will automatically selected for you.
>>> las = create_las(point_format_id=0) >>> las.header.version == '1.2' True
>>> las = create_las(point_format_id=6) >>> las.header.version == '1.4' True
Parameters: Returns: A new las data object
Return type:
-
pylas.lib.
merge_las
(*las_files)[source]¶ Merges multiple las files into one
merged = merge_las(las_1, las_2) merged = merge_las([las_1, las_2, las_3])
Parameters: las_files (Iterable of LasData or LasData) – Returns: The result of the merging Return type: pylas.lasdatas.base.LasBase
-
pylas.lib.
mmap_las
(filename)[source]¶ MMap a file, much like laspy did, very experimental not well tested
-
pylas.lib.
open_las
(source, closefd=True)[source]¶ Opens and reads the header of the las content in the source
>>> 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)> >>> f.closed False
>>> f = open('pylastests/simple.las', mode='rb') >>> with open_las(f) as flas: ... las = flas.read() >>> f.closed True
Parameters: - source (str or io.BytesIO) – if source is a str it must be a filename a stream if a file object with the methods read, seek, tell
- closefd (bool) – 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
Returns: Return type:
-
pylas.lib.
read_las
(source, closefd=True)[source]¶ Entry point for reading las data in pylas
Reads the whole file into memory.
>>> las = read_las("pylastests/simple.las") >>> las.classification array([1, 1, 1, ..., 1, 1, 1], dtype=uint8)
Parameters: - source (str or io.BytesIO) – The source to read data from
- 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 module¶
-
class
pylas.lasdatas.base.
LasBase
(*, header, vlrs=None, points=None)[source]¶ Bases:
object
LasBase is the base of all the different LasData classes. These classes are objects that the user will interact with to manipulate las data.
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']
Note
using las[‘dimension_name’] is not possible with the scaled values of x, y, z
-
add_extra_dim
(name, type, description='')[source]¶ Adds a new extra dimension to the point record
Parameters:
-
point_format
¶
-
points
¶ returns the numpy array representing the points
Returns: Return type: the Numpy structured array of points
-
write
(destination, do_compress=None)[source]¶ Writes to a stream or file
When destination is a string, it will be interpreted as the path were the file should be written to, also if do_compress is None, the compression will be guessed from the file extension:
- .laz -> compressed
- .las -> uncompressed
Note
- This means that you could do something like:
# Create .laz but not compressed
las.write(‘out.laz’, do_compress=False)
# Create .las but compressed
las.write(‘out.las’, do_compress=True)
While it should not confuse Las/Laz readers, it will confuse humans so avoid doing it
Parameters:
-
write_to
(out_stream, do_compress=False)[source]¶ writes the data to a stream
Parameters: - out_stream (file object) – the destination stream, implementing the write method
- do_compress (bool, optional, default False) – Flag to indicate if you want the date to be compressed
-
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
-
pylas.lasreader module¶
-
class
pylas.lasreader.
LasReader
(stream, closefd=True)[source]¶ Bases:
object
This class handles the reading of the different parts of a las file.
As the Header is necessary to be able to understand how the data is structured, it will be read during initialisation of the instance
pylas.vlrs.vlrlist module¶
-
class
pylas.vlrs.vlrlist.
RawVLRList
(iterable=None)[source]¶ Bases:
object
A RawVLRList is like a VLR list but it should only hold RawVLRs.
This class is meant to make it easier to write VLRS the the file and know in advance the size in bytes taken by all the VLRs combined
-
classmethod
from_list
(vlrs)[source]¶ Construct a RawVLR list from a list of vlrs
Parameters: vlrs (iterable of VLR) – Returns: Return type: RawVLRList
-
write_to
(out_stream)[source]¶ Writes all the raw vlrs contained in list to the out_stream
Parameters: out_stream (io.RawIOBase) – The stream where vlrs will be written to
-
classmethod
-
class
pylas.vlrs.vlrlist.
VLRList
[source]¶ Bases:
object
Class responsible for managing the vlrs
-
extract
(vlr_type)[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
-
get
(vlr_type)[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
-
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:
-
classmethod
read_from
(data_stream, num_to_read)[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
Returns: List of vlrs
Return type:
-
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
-
class
pylas.point.record.
IPointRecord
[source]¶ Bases:
abc.ABC
Wraps the numpy structured array contained the points data
-
actual_point_size
¶ Shall return the actual size in bytes that ta points take in memory
-
point_size
¶ Shall return the point size as that will be written in the header
-
-
class
pylas.point.record.
PackedPointRecord
(data, point_format=None)[source]¶ Bases:
pylas.point.record.PointRecord
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 However some operations on sub-fields require extra steps:
>>> #return number is a sub-field >>> from pylas import PointFormat >>> packed_point_record = PackedPointRecord.zeros(PointFormat(0), 10) >>> packed_point_record['return_number'][:] = 1 >>> np.alltrue(packed_point_record == 1) False
>>> packed_point_record = PackedPointRecord.zeros(PointFormat(0), 10) >>> rn = packed_point_record['return_number'] >>> rn[:] = 1 >>> packed_point_record['return_number'] = rn >>> np.alltrue(packed_point_record['return_number'] == 1) True
-
all_dimensions_names
¶ Returns all the dimensions names, including the names of sub_fields and their corresponding packed fields
-
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_stream
(stream, point_format, count)[source]¶ Construct the point record by reading the points from the stream
-
point_size
¶ Returns the point size in bytes taken by each points of the record
Returns: The point size in byte Return type: int
-
-
class
pylas.point.record.
PointRecord
(data, point_format: pylas.point.format.PointFormat)[source]¶ Bases:
pylas.point.record.IPointRecord
-
actual_point_size
¶ Returns the point size in bytes taken by each points of the record
Returns: The point size in byte Return type: int
-
copy_fields_from
(other_record)[source]¶ Tries to copy the values of the current dimensions from other_record
-
dimensions_names
¶
-
extra_dimensions_names
¶ Returns the names of extra-dimensions contained in the PointRecord
-
-
class
pylas.point.record.
UnpackedPointRecord
(data, point_fmt_id=None)[source]¶ Bases:
pylas.point.record.PointRecord
In the Unpacked Point Record, all the sub-fields are un-packed meaning that they are in their own array. Because the minimum size for the elements of an array is 8 bits, and sub-fields are only a few bits (less than 8) the resulting unpacked array uses more memory, especially if the point format has lots of sub-fields
-
point_size
¶ Shall return the point size as that will be written in the header
-
pylas.evlr module¶
-
class
pylas.evlrs.
EVLRHeader
[source]¶ Bases:
_ctypes.Structure
-
description
¶ Structure/Union member
-
record_id
¶ Structure/Union member
-
record_length_after_header
¶ Structure/Union member
-
user_id
¶ Structure/Union member
-
-
class
pylas.evlrs.
EVLRList
[source]¶ Bases:
pylas.vlrs.vlrlist.VLRList
-
classmethod
read_from
(data_stream, num_to_read)[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
Returns: List of vlrs
Return type:
-
classmethod
-
class
pylas.evlrs.
RawEVLRList
(iterable=None)[source]¶ Bases:
pylas.vlrs.vlrlist.RawVLRList
-
classmethod
from_list
(vlrs)[source]¶ Construct a RawVLR list from a list of vlrs
Parameters: vlrs (iterable of VLR) – Returns: Return type: RawVLRList
-
classmethod
pylas.errors module¶
All the custom exceptions types
-
exception
pylas.errors.
FileVersionNotSupported
[source]¶ Bases:
pylas.errors.PylasError
-
exception
pylas.errors.
IncompatibleDataFormat
[source]¶ Bases:
pylas.errors.PylasError
-
exception
pylas.errors.
LazError
[source]¶ Bases:
pylas.errors.PylasError
-
exception
pylas.errors.
PointFormatNotSupported
[source]¶ Bases:
pylas.errors.PylasError
-
exception
pylas.errors.
UnknownExtraType
[source]¶ Bases:
pylas.errors.PylasError
pylas.compression module¶
The functions related to the LAZ format (compressed LAS) Lazperf is made optional by catching the ModuleNotFoundError, and raising an exception when compression/decompression is actually needed
There are also functions to use Laszip (meant to be used as a fallback)
-
class
pylas.compression.
LasZipProcess
(action, stdin=-1, stdout=-1)[source]¶ Bases:
object
-
stdin
¶
-
stdout
¶
-
-
pylas.compression.
lazperf_decompress_buffer
(compressed_buffer, point_size, point_count, laszip_vlr)[source]¶
pylas.headers.rawheader module¶
-
class
pylas.headers.rawheader.
GlobalEncoding
[source]¶ Bases:
_ctypes.Structure
-
gps_time_type
¶
-
reserved
¶ Structure/Union member
-
synthetic_return_numbers
¶ Structure/Union member
-
waveform_external
¶ Structure/Union member
-
waveform_internal
¶ Structure/Union member
-
wkt
¶ Structure/Union member
-
-
class
pylas.headers.rawheader.
GpsTimeType
[source]¶ Bases:
enum.IntEnum
An enumeration.
-
STANDARD
= 1¶
-
WEEK_TIME
= 0¶
-
-
class
pylas.headers.rawheader.
HeaderFactory
[source]¶ Bases:
object
Factory to create a new header by specifying the version. This Factory also handles converting headers between different versions.
-
classmethod
convert_header
(old_header, new_version)[source]¶ Converts a header to a another version
Parameters: Returns: Return type: The converted header
>>> old_header = HeaderFactory.new(1.2) >>> HeaderFactory.convert_header(old_header, 1.4) <LasHeader(1.4)>
>>> old_header = HeaderFactory.new('1.4') >>> HeaderFactory.convert_header(old_header, '1.2') <LasHeader(1.2)>
-
classmethod
header_class_for_version
(version)[source]¶ >>> HeaderFactory.header_class_for_version(2.0) Traceback (most recent call last): ... pylas.errors.FileVersionNotSupported: 2.0
>>> HeaderFactory.header_class_for_version(1.2) <class 'pylas.headers.rawheader.RawHeader1_2'>
>>> header_class = HeaderFactory.header_class_for_version(1.4) >>> header_class() <LasHeader(1.4)>
-
classmethod
new
(version)[source]¶ Returns a new instance of a header.
Parameters: version (float or str) – The header version >>> HeaderFactory.new(1.4) <LasHeader(1.4)>
>>> HeaderFactory.new('1.2') <LasHeader(1.2)>
-
classmethod
-
class
pylas.headers.rawheader.
RawHeader1_1
[source]¶ Bases:
_ctypes.Structure
-
are_points_compressed
¶ Returns True if the point_format_id indicates that the points are stored compressed
-
creation_day_of_year
¶ Structure/Union member
-
creation_year
¶ Structure/Union member
-
date
¶ Returns the creation date stored in the las file
Returns: Return type: datetime.date
-
file_signature
¶ Structure/Union member
-
file_source_id
¶ Structure/Union member
-
generating_software
¶ Structure/Union member
-
global_encoding
¶ Structure/Union member
-
legacy_number_of_points_by_return
¶ Structure/Union member
-
legacy_point_count
¶ Structure/Union member
-
maxs
¶ Returns de maximum values of x, y, z as a numpy array
-
mins
¶ Returns de minimum values of x, y, z as a numpy array
-
number_of_points_by_return
¶
-
number_of_vlr
¶ Structure/Union member
-
offset_to_point_data
¶ Structure/Union member
-
offsets
¶ Returns the offsets values of x, y, z as a numpy array
-
point_count
¶ Returns the number of points in the file
-
point_data_record_length
¶ Structure/Union member
-
point_format_id
¶
-
point_size
¶ Returns the number of bits each point takes
-
scales
¶ Returns the scaling values of x, y, z as a numpy array
-
size
¶ Structure/Union member
-
system_identifier
¶ Structure/Union member
-
uuid
¶
-
uuid_bytes
¶ Structure/Union member
-
version
¶ Danger
You should not use this directly if you want to change/convert the header version as this function does not change the underlying size of bytes buffer. See
HeaderFactory.convert_header()
to convert headers
-
version_major
¶ Structure/Union member
-
version_minor
¶ Structure/Union member
-
x_max
¶ Structure/Union member
-
x_min
¶ Structure/Union member
-
x_offset
¶ Structure/Union member
-
x_scale
¶ Structure/Union member
-
y_max
¶ Structure/Union member
-
y_min
¶ Structure/Union member
-
y_offset
¶ Structure/Union member
-
y_scale
¶ Structure/Union member
-
z_max
¶ Structure/Union member
-
z_min
¶ Structure/Union member
-
z_offset
¶ Structure/Union member
-
z_scale
¶ Structure/Union member
-
-
class
pylas.headers.rawheader.
RawHeader1_3
[source]¶ Bases:
pylas.headers.rawheader.RawHeader1_2
-
start_of_waveform_data_packet_record
¶ Structure/Union member
-
pylas.point.format module¶
-
class
pylas.point.format.
PointFormat
(point_format_id, extra_dims=None)[source]¶ Bases:
object
Class that handles all the information about a point format
Most of the methods/properties will throw a pylas.errors.PointFormatNotSupported if the point format id is not supported
-
composed_fields
¶ Returns the dict of composed fields defined for the point format
Returns: maps a composed field name to its sub_fields Return type: Dict[str, List[SubFields]]
-
dimension_names
¶ Returns the names of the dimensions contained in the point format
Returns: the names of the dimensions defined by this point format Return type: list of str
-
dtype
¶ 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
-
extra_dimension_names
¶ Returns the list of extra dimensions attached to this point format
-
has_waveform_packet
¶ Returns True if the point format has waveform packet dimensions
-
num_extra_bytes
¶ Returns the number of extra bytes
-
sub_fields
¶ Returns a dict of the sub fields for this point format
Returns: maps a sub field name to its composed dimension with additional information Return type: Dict[str, Tuple[str, SubField]]
-
unpacked_dtype
¶ Returns the numpy.dtype used to store the point records in a numpy array
Note
The dtype corresponds to the dtype with sub_fields unpacked
-
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.
BaseKnownVLR
(record_id=None, description='')[source]¶ Bases:
pylas.vlrs.rawvlr.BaseVLR
,pylas.vlrs.known.IKnownVLR
Base Class to factorize common code between the different type of Known VLRs
-
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'
-
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
-
static
-
class
pylas.vlrs.known.
ExtraBytesStruct
[source]¶ Bases:
_ctypes.Structure
-
data_type
¶ Structure/Union member
-
description
¶ Structure/Union member
-
max
¶
-
min
¶
-
name
¶ Structure/Union member
-
no_data
¶
-
offset
¶
-
options
¶ Structure/Union member
-
reserved
¶ Structure/Union member
-
scale
¶
-
unused
¶ Structure/Union member
-
-
class
pylas.vlrs.known.
ExtraBytesVlr
[source]¶ Bases:
pylas.vlrs.known.BaseKnownVLR
-
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
-
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
-
static
-
class
pylas.vlrs.known.
GeoAsciiParamsVlr
[source]¶ Bases:
pylas.vlrs.known.BaseKnownVLR
-
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
-
static
-
class
pylas.vlrs.known.
GeoDoubleParamsVlr
[source]¶ Bases:
pylas.vlrs.known.BaseKnownVLR
-
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
-
static
-
class
pylas.vlrs.known.
GeoKeyDirectoryVlr
[source]¶ Bases:
pylas.vlrs.known.BaseKnownVLR
-
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
-
static
-
class
pylas.vlrs.known.
GeoKeyEntryStruct
[source]¶ Bases:
_ctypes.Structure
-
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
-
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.
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_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
-
static
-
class
pylas.vlrs.known.
LasZipVlr
(data)[source]¶ Bases:
pylas.vlrs.known.BaseKnownVLR
Contains the informations needed by laszip & lazperf to compress the point records.
-
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
-
static
-
class
pylas.vlrs.known.
WaveformPacketStruct
[source]¶ Bases:
_ctypes.Structure
-
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
-
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
-
static
-
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”
-
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
-
static
-
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
-
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