Created
July 10, 2020 14:05
-
-
Save donno/7e6678826773d5835f0c92741a308cf2 to your computer and use it in GitHub Desktop.
Python module for working with LVX (File format by Livox)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Defines the LVX 1.1.0.0 file format by Livox Tech using the Construct | |
Python package. | |
There is sample data provided at https://www.livoxtech.com/downloads | |
At the time of writing: | |
- Livox Mid-100 samples use version 1.0.0.0. | |
- Livox Horizon samples use version 1.1.0.0. | |
Licence: The MIT License | |
Basically. the author of this work does not find it is fair to claim ownership | |
of this work. This is because if you limit yourself to using Construct and the | |
specification then it leads you down the path that you will end up with what is | |
here. The choices that remain are the naming of fields, the level of commenting | |
and whether to use enums or not. | |
""" | |
from construct import * | |
docs = """ | |
LVX format as used by Livox Tech as their point cloud file format. | |
The file format is based on their LiDAR sensors. | |
See "Lvx Specifications" on https://www.livoxtech.com/downloads | |
""" | |
public_header_block = Struct( | |
"signature" / Const(b"livox_tech\x00\x00\x00\x00\x00\x00"), | |
"version" / Byte[4], | |
"magic_code" / Const(0xAC0EA767, Int32ul), | |
) | |
private_header_block = Struct( | |
"frame_duration" / Int32ul, # Milliseconds. for 1.1.0.0 this is always 50. | |
"device_count" / Int8ul, | |
) | |
device_info = Struct( | |
"lidar_sn_code" / PaddedString(16, "utf8"), | |
"hub_sn_code" / PaddedString(16, "utf8"), # If empty there is no hub connecting this LiDAR. | |
"device_index" / Byte, | |
"device_type" / Enum(Byte, | |
LIVOX_HUB = 0, | |
MID_40_100 = 1, # Mid-40 and MID-100 | |
TEL_15 = 2, | |
HORIZON = 3, | |
), | |
"extrinsic_enable" / Byte, | |
"roll" / Float32l, | |
"pitch" / Float32l, | |
"yaw" / Float32l, | |
"x" / Float32l, | |
"y" / Float32l, | |
"z" / Float32l, | |
) | |
# Data type 0 has 100 points per package | |
data_type_0 = Struct( | |
"x" / Int32sl, # millimetres | |
"y" / Int32sl, # millimetres | |
"z" / Int32sl, # millimetres | |
"reflectivity" / Byte, | |
) | |
# Data type 1 has 100 point per package. | |
data_type_1 = Struct( | |
"depth" / Int32sl, | |
"theta" / Int16ul, # Zenith angle (0.01 degree) 0 to 18000 | |
"phi" / Int16ul, # Azimuth angle (0.01 degree), 0 to 36000 | |
"reflectivity" / Byte, | |
) | |
# Data type 2 has 96 point per package. | |
data_type_2 = Struct( | |
"x" / Int32sl, # millimetres | |
"y" / Int32sl, # millimetres | |
"z" / Int32sl, # millimetres | |
"reflectivity" / Byte, | |
"tag" / Byte, # See Livox SDK Procotcol for details about this. | |
) | |
# Data type 3 has 96 point per package. | |
data_type_3 = Struct( | |
"depth" / Int32sl, | |
"theta" / Int16ul, # Zenith angle (0.01 degree) 0 to 18000 | |
"phi" / Int16ul, # Azimuth angle (0.01 degree), 0 to 36000 | |
"reflectivity" / Byte, | |
"tag" / Byte, # See Livox SDK Procotcol for details about this. | |
) | |
# TODO: Data types 4 and 5 aren't covered. yet. | |
# Data type 6 has 1 point per package. | |
data_type_6 = Struct( | |
"gyro_x" / Float32l, | |
"gyro_y" / Float32l, | |
"gyro_z" / Float32l, | |
"acc_x" / Float32l, | |
"acc_y" / Float32l, | |
"acc_z" / Float32l, | |
) | |
frame_header = Struct( | |
"current_offset" / Int64ul, | |
"next_offset" / Int64ul, | |
"frame_index" / Int64ul, | |
) | |
assert frame_header.sizeof() == 24 | |
frame = Struct( | |
"frame_header" / frame_header, | |
#"packages" / Array(), # Unsure where the number of packages is located. | |
) | |
lvx = docs * Struct( | |
"public_header_block" / public_header_block, | |
"private_header_block" / private_header_block, # This is valid for 1.1.0.0 | |
"device_block" / Array(this.private_header_block.device_count, device_info), | |
#"frames" / frame, # Technically there are N frames but the number of frames is unknown. | |
) | |
if __name__ == '__main__': | |
import sys | |
if len(sys.argv) != 2: | |
print('usage: %s filename.lvx' % sys.argv[0]) | |
exit(1) | |
parsed_file = lvx.parse_file(sys.argv[1]) | |
print(parsed_file) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ python lvx_reader.py HorizonSample.lvx | |
Container: | |
public_header_block = Container: | |
signature = b'livox_tech\x00\x00\x00\x00\x00\x00' (total 16) | |
version = ListContainer: | |
1 | |
1 | |
0 | |
0 | |
magic_code = 2886641511 | |
private_header_block = Container: | |
frame_duration = 50 | |
device_count = 3 | |
device_block = ListContainer: | |
Container: | |
lidar_sn_code = u'1HDDGAU00100101' (total 15) | |
hub_sn_code = u'13UUG1F004001C0' (total 15) | |
device_index = 0 | |
device_type = (enum) HORIZON 3 | |
extrinsic_enable = 1 | |
roll = 0.4399999976158142 | |
pitch = -0.5699999928474426 | |
yaw = -112.73999786376953 | |
x = 0.0 | |
y = -0.05000000074505806 | |
z = 0.0 | |
Container: | |
lidar_sn_code = u'1HDDGAU00100231' (total 15) | |
hub_sn_code = u'13UUG1F004001C0' (total 15) | |
device_index = 1 | |
device_type = (enum) HORIZON 3 | |
extrinsic_enable = 1 | |
roll = 0.0 | |
pitch = 0.0 | |
yaw = 0.0 | |
x = 0.0 | |
y = 0.0 | |
z = 0.0 | |
Container: | |
lidar_sn_code = u'1HDDGAU00100271' (total 15) | |
hub_sn_code = u'13UUG1F004001C0' (total 15) | |
device_index = 2 | |
device_type = (enum) HORIZON 3 | |
extrinsic_enable = 1 | |
roll = -0.949999988079071 | |
pitch = -0.2800000011920929 | |
yaw = -19.90999984741211 | |
x = 0.04600000008940697 | |
y = -0.699999988079071 | |
z = 0.006000000052154064 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment