I have found out for this use case that is more efficient to edit the header of the .laz file using binary file I/O to modify the offset values.
Here is my code:
def read_las_header(file):
file.seek(0)
header = file.read(227) # LAS 1.2 header
point_data_offset = struct.unpack('<I', header[96:100])[0]
point_record_length = struct.unpack('<H', header[105:107])[0]
num_point_records = struct.unpack('<I', header[107:111])[0]
x_scale = struct.unpack('<d', header[131:139])[0]
y_scale = struct.unpack('<d', header[139:147])[0]
z_scale = struct.unpack('<d', header[147:155])[0]
x_offset = struct.unpack('<d', header[155:163])[0]
y_offset = struct.unpack('<d', header[163:171])[0]
z_offset = struct.unpack('<d', header[171:179])[0]
return (header, point_data_offset, point_record_length, num_point_records, x_scale, y_scale, z_scale, x_offset, y_offset, z_offset)
def shift_laz_header(input_file_path, output_file_path, tx=0, ty=0, tz=0):
with open(input_file_path, 'rb') as infile:
header_data = read_las_header(infile)
header, point_data_offset, point_record_length, num_point_records, x_scale, y_scale, z_scale, x_offset, y_offset, z_offset = header_data
new_x_offset = x_offset + tx
new_y_offset = y_offset + ty
new_z_offset = z_offset + tz
print(f"Original X Offset: {x_offset}, New X Offset: {new_x_offset}")
print(f"Original Y Offset: {y_offset}, New Y Offset: {new_y_offset}")
print(f"Original Z Offset: {z_offset}, New Z Offset: {new_z_offset}")
updated_header = update_offsets_in_header(header, new_x_offset, new_y_offset, new_z_offset)
with open(output_file_path, 'wb') as outfile:
outfile.write(updated_header)
# Copy the rest of the file
infile.seek(227)
shutil.copyfileobj(infile, outfile)
print(f"Updated offsets in header: X={new_x_offset}, Y={new_y_offset}, Z={new_z_offset}")
Notice: You can modify the scale in similar fashion. Also, beware that this does not alter the bounding box defined from the .laz header, if you want that to be adjusted you have to change the min & max values of each axis.