forked from Amsterdam-AI-Team/Urban_PointCloud_Processing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlas_utils.py
128 lines (102 loc) · 3.71 KB
/
las_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import numpy as np
import glob
import pathlib
import re
import os
import laspy
from ..utils.labels import Labels
def get_tilecode_from_filename(filename):
"""Extract the tile code from a file name."""
return re.match(r'.*(\d{4}_\d{4}).*', filename)[1]
def get_bbox_from_tile_code(tile_code, padding=0, width=50, height=50):
"""
Get the <X,Y> bounding box for a given tile code. The tile code is assumed
to represent the lower left corner of the tile.
Parameters
----------
tile_code : str
The tile code, e.g. 2386_9702.
padding : float
Optional padding (in m) by which the bounding box will be extended.
width : int (default: 50)
The width of the tile.
height : int (default: 50)
The height of the tile.
Returns
-------
tuple of tuples
Bounding box with inverted y-axis: ((x_min, y_max), (x_max, y_min))
"""
tile_split = tile_code.split('_')
# The tile code of each tile is defined as
# 'X-coordinaat/50'_'Y-coordinaat/50'
x_min = int(tile_split[0]) * 50
y_min = int(tile_split[1]) * 50
return ((x_min - padding, y_min + height + padding),
(x_min + height + padding, y_min - padding))
def get_bbox_from_las_file(laz_file, padding=0):
"""
Get the <X,Y> bounding box for a given CycloMedia laz file, based on the
filename.
Parameters
----------
laz_file : Path or str
the .laz filename, e.g. filtered_2386_9702.laz
padding : float
Optional padding (in m) by which the bounding box will be extended.
Returns
-------
tuple of tuples
Bounding box with inverted y-axis: ((x_min, y_max), (x_max, y_min))
"""
if type(laz_file) == str:
laz_file = pathlib.Path(laz_file)
tile_code = get_tilecode_from_filename(laz_file.name)
return get_bbox_from_tile_code(tile_code, padding=padding)
def get_bbox_from_las_folder(folder_path, padding=0):
"""
Get the <X,Y> bounding box for a given folder of CycloMedia LAS files.
Parameters
----------
folder_path : str or Path
The folder name.
padding : int (default: 0)
Optional padding (in meters).
Returns
-------
tuple of tuples
Bounding box with inverted y-axis: ((x_min, y_max), (x_max, y_min))
"""
x_min = y_min = 1e6
x_max = y_max = 0
file_types = ('.LAS', '.las', '.LAZ', '.laz')
for file in [f for f in glob.glob(os.path.join(folder_path, '*'))
if f.endswith(file_types)]:
bbox = get_bbox_from_las_file(file)
x_min = min(x_min, bbox[0][0])
x_max = max(x_max, bbox[1][0])
y_min = min(y_min, bbox[1][1])
y_max = max(y_max, bbox[0][1])
return ((x_min-padding, y_max+padding), (x_max+padding, y_min-padding))
def get_stats(labels):
"""Returns a string describing statistics based on labels."""
N = len(labels)
labels, counts = np.unique(labels, return_counts=True)
stats = f'Total: {N:25} points\n'
for label, cnt in zip(labels, counts):
name = Labels.get_str(label)
perc = (float(cnt) / N) * 100
stats += f'Class {label:2}, {name:14} ' +\
f'{cnt:7} points ({perc:4.1f} %)\n'
return stats
def read_las(las_file):
"""Read a las file and return the las object."""
return laspy.read(las_file)
def label_and_save_las(las, labels, outfile):
"""Label a las file using the provided class labels and save to outfile."""
assert len(labels) == las.header.point_count
if 'label' not in las.point_format.extra_dimension_names:
las.add_extra_dim(laspy.ExtraBytesParams(name="label", type="uint16",
description="Labels"))
las.label = labels
las.write(outfile)