0
# Data Container Classes
1
2
Primary data container classes for managing LAS file components including headers, point data, and metadata integration. These classes provide the core data structures that synchronize all aspects of LAS files.
3
4
## Capabilities
5
6
### LAS Data Container
7
8
Main container class that synchronizes header, points, and VLRs into a cohesive LAS file representation.
9
10
```python { .api }
11
class LasData:
12
def __init__(self, header: LasHeader, points=None):
13
"""
14
Create LAS data container.
15
16
Parameters:
17
- header: LasHeader - LAS file header
18
- points: PackedPointRecord or ScaleAwarePointRecord - Point data (optional)
19
"""
20
21
@property
22
def point_format(self) -> PointFormat:
23
"""Point format definition."""
24
25
@property
26
def xyz(self) -> np.ndarray:
27
"""XYZ coordinates as Nx3 array."""
28
29
@property
30
def points(self) -> PackedPointRecord:
31
"""Point record data."""
32
33
@property
34
def vlrs(self) -> VLRList:
35
"""Variable Length Records."""
36
37
@property
38
def evlrs(self) -> Optional[VLRList]:
39
"""Extended Variable Length Records."""
40
41
@property
42
def header(self) -> LasHeader:
43
"""LAS file header."""
44
45
def add_extra_dim(self, params: ExtraBytesParams):
46
"""
47
Add single extra dimension to point format.
48
49
Parameters:
50
- params: ExtraBytesParams - Extra dimension parameters
51
"""
52
53
def add_extra_dims(self, params: List[ExtraBytesParams]):
54
"""
55
Add multiple extra dimensions to point format.
56
57
Parameters:
58
- params: List[ExtraBytesParams] - List of extra dimension parameters
59
"""
60
61
def remove_extra_dim(self, name: str):
62
"""
63
Remove extra dimension by name.
64
65
Parameters:
66
- name: str - Name of dimension to remove
67
"""
68
69
def remove_extra_dims(self, names: Iterable[str]):
70
"""
71
Remove multiple extra dimensions.
72
73
Parameters:
74
- names: Iterable[str] - Names of dimensions to remove
75
"""
76
77
def update_header(self):
78
"""Update header statistics from current point data."""
79
80
def write(self, destination, do_compress=None, laz_backend=None):
81
"""
82
Write LAS data to file.
83
84
Parameters:
85
- destination: str, Path, or file-like - Output destination
86
- do_compress: bool - Force compression on/off (optional)
87
- laz_backend: LazBackend - Compression backend (optional)
88
"""
89
90
def change_scaling(self, scales=None, offsets=None):
91
"""
92
Change coordinate scaling factors.
93
94
Parameters:
95
- scales: array-like - New scale factors for X,Y,Z (optional)
96
- offsets: array-like - New offset values for X,Y,Z (optional)
97
"""
98
99
def __getattr__(self, name):
100
"""Access point dimensions as attributes (e.g., las.x, las.classification)."""
101
102
def __setattr__(self, name, value):
103
"""Set point dimension values as attributes."""
104
105
def __getitem__(self, key):
106
"""Access point dimensions by name or index."""
107
108
def __setitem__(self, key, value):
109
"""Set point dimension values by name or index."""
110
111
def __len__(self) -> int:
112
"""Get number of points."""
113
```
114
115
**Usage Examples:**
116
117
```python
118
import laspy
119
import numpy as np
120
121
# Create new LAS data
122
header = laspy.LasHeader(point_format=3, version=(1, 2))
123
las = laspy.LasData(header)
124
125
# Add point data via attributes
126
las.x = np.random.uniform(0, 1000, 5000)
127
las.y = np.random.uniform(0, 1000, 5000)
128
las.z = np.random.uniform(0, 100, 5000)
129
las.classification = np.random.choice([1, 2, 3, 4], 5000)
130
131
# Access XYZ as single array
132
coordinates = las.xyz # Shape: (5000, 3)
133
print(f"Point cloud bounds: {coordinates.min(axis=0)} to {coordinates.max(axis=0)}")
134
135
# Update header with current data statistics
136
las.update_header()
137
print(f"Header bounds: {las.header.mins} to {las.header.maxs}")
138
139
# Add custom extra dimension
140
extra_param = laspy.ExtraBytesParams(
141
name="intensity_normalized",
142
type="f4",
143
description="Normalized intensity values"
144
)
145
las.add_extra_dim(extra_param)
146
las.intensity_normalized = np.random.uniform(0, 1, len(las))
147
148
# Write to file
149
las.write('output.laz', do_compress=True)
150
```
151
152
### LAS Header Management
153
154
Comprehensive header management including metadata, coordinate systems, and format specifications.
155
156
```python { .api }
157
class LasHeader:
158
def __init__(self, *, version=None, point_format=None):
159
"""
160
Create LAS header.
161
162
Parameters:
163
- version: Version or tuple - LAS version (default: (1, 2))
164
- point_format: PointFormat or int - Point format (default: 3)
165
"""
166
167
@property
168
def version(self) -> Version: ...
169
@property
170
def point_format(self) -> PointFormat: ...
171
@property
172
def file_source_id(self) -> int: ...
173
@property
174
def global_encoding(self) -> GlobalEncoding: ...
175
@property
176
def uuid(self) -> UUID: ...
177
@property
178
def system_identifier(self) -> str: ...
179
@property
180
def generating_software(self) -> str: ...
181
@property
182
def creation_date(self) -> date: ...
183
@property
184
def point_count(self) -> int: ...
185
@property
186
def scales(self) -> np.ndarray: ...
187
@property
188
def offsets(self) -> np.ndarray: ...
189
@property
190
def maxs(self) -> np.ndarray: ...
191
@property
192
def mins(self) -> np.ndarray: ...
193
@property
194
def x_scale(self) -> float: ...
195
@property
196
def y_scale(self) -> float: ...
197
@property
198
def z_scale(self) -> float: ...
199
@property
200
def x_offset(self) -> float: ...
201
@property
202
def y_offset(self) -> float: ...
203
@property
204
def z_offset(self) -> float: ...
205
@property
206
def x_max(self) -> float: ...
207
@property
208
def y_max(self) -> float: ...
209
@property
210
def z_max(self) -> float: ...
211
@property
212
def x_min(self) -> float: ...
213
@property
214
def y_min(self) -> float: ...
215
@property
216
def z_min(self) -> float: ...
217
@property
218
def vlrs(self) -> VLRList: ...
219
@property
220
def evlrs(self) -> Optional[VLRList]: ...
221
@property
222
def number_of_points_by_return(self) -> np.ndarray: ...
223
224
def add_extra_dims(self, params: List[ExtraBytesParams]):
225
"""Add multiple extra dimensions to point format."""
226
227
def add_extra_dim(self, params: ExtraBytesParams):
228
"""Add single extra dimension to point format."""
229
230
def add_crs(self, crs, keep_compatibility=True):
231
"""
232
Add coordinate reference system information.
233
234
Parameters:
235
- crs: pyproj.CRS or CRS-like - Coordinate reference system
236
- keep_compatibility: bool - Maintain compatibility with older software
237
"""
238
239
def remove_extra_dim(self, name: str):
240
"""Remove extra dimension by name."""
241
242
def remove_extra_dims(self, names: Iterable[str]):
243
"""Remove multiple extra dimensions."""
244
245
def set_version_and_point_format(self, version: Version, point_format: PointFormat):
246
"""
247
Set version and point format together (ensures compatibility).
248
249
Parameters:
250
- version: Version - Target LAS version
251
- point_format: PointFormat - Target point format
252
"""
253
254
def partial_reset(self):
255
"""Reset header statistics (keeps metadata)."""
256
257
def update(self, points: PackedPointRecord):
258
"""
259
Update header statistics from point data.
260
261
Parameters:
262
- points: PackedPointRecord - Point data to analyze
263
"""
264
265
def grow(self, points: PackedPointRecord):
266
"""
267
Grow header bounds to include new points.
268
269
Parameters:
270
- points: PackedPointRecord - Additional points to include
271
"""
272
273
def set_compressed(self, state: bool):
274
"""
275
Set compression state in header.
276
277
Parameters:
278
- state: bool - True for compressed, False for uncompressed
279
"""
280
281
def max_point_count(self) -> int:
282
"""Get maximum point count for this LAS version."""
283
284
def copy(self) -> LasHeader:
285
"""Create deep copy of header."""
286
287
def parse_crs(self, prefer_wkt=True) -> Optional[pyproj.CRS]:
288
"""
289
Parse coordinate reference system from VLRs.
290
291
Parameters:
292
- prefer_wkt: bool - Prefer WKT over GeoTIFF keys if both present
293
294
Returns:
295
Optional[pyproj.CRS]: Parsed CRS or None if not found
296
"""
297
298
def read_evlrs(self, stream):
299
"""Read Extended VLRs from stream."""
300
301
def write_to(self, stream, ensure_same_size=False, encoding_errors="strict"):
302
"""
303
Write header to stream.
304
305
Parameters:
306
- stream: BinaryIO - Output stream
307
- ensure_same_size: bool - Ensure header stays same size
308
- encoding_errors: str - How to handle encoding errors
309
"""
310
311
@classmethod
312
def read_from(cls, original_stream: BinaryIO, read_evlrs=False) -> LasHeader:
313
"""
314
Read header from stream.
315
316
Parameters:
317
- original_stream: BinaryIO - Input stream
318
- read_evlrs: bool - Whether to read Extended VLRs
319
320
Returns:
321
LasHeader: Parsed header
322
"""
323
```
324
325
**Usage Examples:**
326
327
```python
328
import laspy
329
from datetime import date
330
import numpy as np
331
332
# Create header with specific settings
333
header = laspy.LasHeader(
334
version=(1, 4), # LAS 1.4
335
point_format=6 # Point format 6 (includes GPS time and RGB)
336
)
337
338
# Set metadata
339
header.system_identifier = "My LiDAR System"
340
header.generating_software = "My Processing Software v1.0"
341
header.creation_date = date.today()
342
343
# Set coordinate system scaling
344
header.scales = np.array([0.01, 0.01, 0.001]) # 1cm XY, 1mm Z
345
header.offsets = np.array([500000, 4000000, 0]) # UTM zone offsets
346
347
# Add CRS information (requires pyproj)
348
try:
349
import pyproj
350
crs = pyproj.CRS.from_epsg(32633) # UTM Zone 33N
351
header.add_crs(crs)
352
print("Added CRS information")
353
except ImportError:
354
print("pyproj not available, skipping CRS")
355
356
# Create data and update header statistics
357
las = laspy.LasData(header)
358
# ... add point data ...
359
las.update_header()
360
361
print(f"Header bounds: {header.mins} to {header.maxs}")
362
print(f"Point count: {header.point_count}")
363
```
364
365
### Header Version and Encoding
366
367
Version management and global encoding settings for LAS files.
368
369
```python { .api }
370
class Version:
371
major: int
372
minor: int
373
374
@classmethod
375
def from_str(cls, string: str) -> Version:
376
"""Parse version from string (e.g., '1.4')."""
377
378
def __str__(self) -> str: ...
379
380
class GpsTimeType(IntEnum):
381
WEEK_TIME = 0
382
STANDARD = 1
383
384
class GlobalEncoding:
385
def __init__(self, value=0):
386
"""
387
Create global encoding flags.
388
389
Parameters:
390
- value: int - Raw encoding value
391
"""
392
393
@property
394
def gps_time_type(self) -> GpsTimeType: ...
395
@property
396
def waveform_data_packets_internal(self) -> bool: ...
397
@property
398
def waveform_data_packets_external(self) -> bool: ...
399
@property
400
def synthetic_return_numbers(self) -> bool: ...
401
@property
402
def wkt(self) -> bool: ...
403
404
@classmethod
405
def read_from(cls, stream: BinaryIO) -> GlobalEncoding: ...
406
407
def write_to(self, stream: BinaryIO): ...
408
```
409
410
## Advanced Data Container Usage
411
412
### Working with Large Datasets
413
414
```python
415
import laspy
416
import numpy as np
417
418
def process_large_dataset(input_file, output_file, chunk_size=1000000):
419
"""Process large LAS file in chunks to manage memory."""
420
421
with laspy.open(input_file) as reader:
422
# Copy header for output
423
header = reader.header.copy()
424
425
with laspy.open(output_file, mode='w', header=header) as writer:
426
total_written = 0
427
428
for chunk in reader.chunk_iterator(chunk_size):
429
# Apply processing to chunk
430
processed = process_chunk(chunk)
431
432
# Write processed chunk
433
writer.write_points(processed)
434
total_written += len(processed)
435
436
print(f"Processed {total_written} points")
437
438
print(f"Processing complete: {total_written} total points")
439
440
def process_chunk(points):
441
"""Apply processing to point chunk."""
442
# Example: normalize intensity values
443
if hasattr(points, 'intensity'):
444
max_intensity = points.intensity.max()
445
if max_intensity > 0:
446
points.intensity = (points.intensity / max_intensity * 65535).astype(np.uint16)
447
448
return points
449
```
450
451
### Multi-Scale Coordinate Handling
452
453
```python
454
import laspy
455
import numpy as np
456
457
def handle_multi_scale_data(las_data):
458
"""Handle data with different coordinate scales."""
459
460
# Get current scaling
461
current_scales = las_data.header.scales
462
current_offsets = las_data.header.offsets
463
464
print(f"Current scales: {current_scales}")
465
print(f"Current offsets: {current_offsets}")
466
467
# Check coordinate precision
468
x_precision = 1.0 / current_scales[0]
469
y_precision = 1.0 / current_scales[1]
470
z_precision = 1.0 / current_scales[2]
471
472
print(f"Coordinate precision: X={x_precision}m, Y={y_precision}m, Z={z_precision}m")
473
474
# Increase precision if needed (e.g., for high-accuracy surveys)
475
if x_precision > 0.001: # If precision worse than 1mm
476
new_scales = np.array([0.001, 0.001, 0.0001]) # 1mm XY, 0.1mm Z
477
las_data.change_scaling(scales=new_scales)
478
print(f"Updated to higher precision: {new_scales}")
479
480
return las_data
481
```
482
483
### Header Validation and Repair
484
485
```python
486
import laspy
487
from datetime import date
488
489
def validate_and_repair_header(las_data):
490
"""Validate and repair common header issues."""
491
492
header = las_data.header
493
changes_made = []
494
495
# Check creation date
496
if header.creation_date is None:
497
header.creation_date = date.today()
498
changes_made.append("Set creation date to today")
499
500
# Check software identifier
501
if not header.generating_software.strip():
502
header.generating_software = "laspy"
503
changes_made.append("Set generating software")
504
505
# Validate point count
506
actual_count = len(las_data.points)
507
if header.point_count != actual_count:
508
header.point_count = actual_count
509
changes_made.append(f"Updated point count to {actual_count}")
510
511
# Check bounds consistency
512
if len(las_data.points) > 0:
513
las_data.update_header()
514
changes_made.append("Updated header bounds from point data")
515
516
# Validate scales (prevent zero or negative scales)
517
if np.any(header.scales <= 0):
518
header.scales = np.where(header.scales <= 0, 0.01, header.scales)
519
changes_made.append("Fixed invalid scale factors")
520
521
if changes_made:
522
print(f"Header repairs made: {'; '.join(changes_made)}")
523
else:
524
print("Header validation passed")
525
526
return las_data
527
```