or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compression.mdcopc.mdcore-io.mddata-containers.mdindex.mdio-handlers.mdpoint-data.mdvlr.md

compression.mddocs/

0

# Compression Support

1

2

LAZ compression backend management with support for multiple compression libraries and selective field decompression for efficient processing. Laspy provides flexible compression options to optimize file size and processing performance.

3

4

## Capabilities

5

6

### LAZ Backend Management

7

8

Multiple compression backend support with automatic detection and fallback capabilities.

9

10

```python { .api }

11

class LazBackend(Enum):

12

LazrsParallel = 0 # Multi-threaded lazrs backend

13

Lazrs = 1 # Single-threaded lazrs backend

14

Laszip = 2 # LASzip backend

15

16

@classmethod

17

def detect_available(cls) -> Tuple[LazBackend, ...]:

18

"""

19

Detect available compression backends on system.

20

21

Returns:

22

Tuple[LazBackend, ...]: Available backends in priority order

23

"""

24

25

def is_available(self) -> bool:

26

"""

27

Check if this backend is available.

28

29

Returns:

30

bool: True if backend can be used

31

"""

32

33

@property

34

def supports_append(self) -> bool:

35

"""

36

Check if backend supports append operations.

37

38

Returns:

39

bool: True if append is supported

40

"""

41

42

def create_reader(self, source, header, decompression_selection=None):

43

"""Create point reader using this backend."""

44

45

def create_writer(self, dest, header):

46

"""Create point writer using this backend."""

47

48

def create_appender(self, dest, header):

49

"""Create point appender using this backend (if supported)."""

50

```

51

52

**Usage Examples:**

53

54

```python

55

import laspy

56

from laspy import LazBackend

57

58

# Check available backends

59

available = LazBackend.detect_available()

60

print(f"Available backends: {[b.name for b in available]}")

61

62

# Check specific backend availability

63

if LazBackend.LazrsParallel.is_available():

64

print("Multi-threaded lazrs available")

65

backend = LazBackend.LazrsParallel

66

elif LazBackend.Lazrs.is_available():

67

print("Single-threaded lazrs available")

68

backend = LazBackend.Lazrs

69

elif LazBackend.Laszip.is_available():

70

print("LASzip available")

71

backend = LazBackend.Laszip

72

else:

73

print("No LAZ backends available")

74

backend = None

75

76

# Use specific backend for reading

77

if backend:

78

las = laspy.read('compressed.laz', laz_backend=backend)

79

print(f"Read {len(las.points)} points using {backend.name}")

80

81

# Check append support

82

for backend in available:

83

if backend.supports_append:

84

print(f"{backend.name} supports append operations")

85

else:

86

print(f"{backend.name} does not support append operations")

87

```

88

89

### Selective Decompression

90

91

Fine-grained control over which fields to decompress for memory and performance optimization.

92

93

```python { .api }

94

class DecompressionSelection(IntFlag):

95

XY_RETURNS_CHANNEL = ... # X, Y coordinates and return information

96

Z = ... # Z coordinate

97

CLASSIFICATION = ... # Classification field

98

FLAGS = ... # Various flag fields

99

INTENSITY = ... # Intensity values

100

SCAN_ANGLE = ... # Scan angle

101

USER_DATA = ... # User data field

102

POINT_SOURCE_ID = ... # Point source ID

103

GPS_TIME = ... # GPS timestamp (if present)

104

RGB = ... # RGB color information (if present)

105

NIR = ... # Near-infrared (if present)

106

WAVEPACKET = ... # Wavepacket data (if present)

107

ALL_EXTRA_BYTES = ... # All extra byte dimensions

108

109

@classmethod

110

def all(cls) -> DecompressionSelection:

111

"""

112

Select all available fields for decompression.

113

114

Returns:

115

DecompressionSelection: All fields selected

116

"""

117

118

@classmethod

119

def base(cls) -> DecompressionSelection:

120

"""

121

Select base essential fields (XYZ, returns, classification).

122

123

Returns:

124

DecompressionSelection: Essential fields only

125

"""

126

127

@classmethod

128

def xy_returns_channel(cls) -> DecompressionSelection:

129

"""

130

Select only XY coordinates and return information.

131

132

Returns:

133

DecompressionSelection: Minimal coordinate fields

134

"""

135

136

def decompress_xy_returns_channel(self) -> DecompressionSelection:

137

"""Enable XY coordinates and return information decompression."""

138

139

def decompress_z(self) -> DecompressionSelection:

140

"""Enable Z coordinate decompression."""

141

142

def decompress_classification(self) -> DecompressionSelection:

143

"""Enable classification field decompression."""

144

145

def decompress_flags(self) -> DecompressionSelection:

146

"""Enable flag fields decompression."""

147

148

def decompress_intensity(self) -> DecompressionSelection:

149

"""Enable intensity decompression."""

150

151

def decompress_scan_angle(self) -> DecompressionSelection:

152

"""Enable scan angle decompression."""

153

154

def decompress_user_data(self) -> DecompressionSelection:

155

"""Enable user data decompression."""

156

157

def decompress_point_source_id(self) -> DecompressionSelection:

158

"""Enable point source ID decompression."""

159

160

def decompress_gps_time(self) -> DecompressionSelection:

161

"""Enable GPS time decompression."""

162

163

def decompress_rgb(self) -> DecompressionSelection:

164

"""Enable RGB color decompression."""

165

166

def decompress_nir(self) -> DecompressionSelection:

167

"""Enable near-infrared decompression."""

168

169

def decompress_wavepacket(self) -> DecompressionSelection:

170

"""Enable wavepacket data decompression."""

171

172

def decompress_all_extra_bytes(self) -> DecompressionSelection:

173

"""Enable all extra bytes decompression."""

174

175

def skip_xy_returns_channel(self) -> DecompressionSelection:

176

"""Disable XY coordinates and return information decompression."""

177

178

def skip_z(self) -> DecompressionSelection:

179

"""Disable Z coordinate decompression."""

180

181

def skip_classification(self) -> DecompressionSelection:

182

"""Disable classification field decompression."""

183

184

def skip_flags(self) -> DecompressionSelection:

185

"""Disable flag fields decompression."""

186

187

def skip_intensity(self) -> DecompressionSelection:

188

"""Disable intensity decompression."""

189

190

def skip_scan_angle(self) -> DecompressionSelection:

191

"""Disable scan angle decompression."""

192

193

def skip_user_data(self) -> DecompressionSelection:

194

"""Disable user data decompression."""

195

196

def skip_point_source_id(self) -> DecompressionSelection:

197

"""Disable point source ID decompression."""

198

199

def skip_gps_time(self) -> DecompressionSelection:

200

"""Disable GPS time decompression."""

201

202

def skip_rgb(self) -> DecompressionSelection:

203

"""Disable RGB color decompression."""

204

205

def skip_nir(self) -> DecompressionSelection:

206

"""Disable near-infrared decompression."""

207

208

def skip_wavepacket(self) -> DecompressionSelection:

209

"""Disable wavepacket data decompression."""

210

211

def skip_all_extra_bytes(self) -> DecompressionSelection:

212

"""Disable all extra bytes decompression."""

213

214

def is_set_xy_returns_channel(self) -> bool:

215

"""Check if XY coordinates and returns are selected."""

216

217

def is_set_z(self) -> bool:

218

"""Check if Z coordinate is selected."""

219

220

def is_set_classification(self) -> bool:

221

"""Check if classification field is selected."""

222

223

def is_set_flags(self) -> bool:

224

"""Check if flag fields are selected."""

225

226

def is_set_intensity(self) -> bool:

227

"""Check if intensity is selected."""

228

229

def is_set_scan_angle(self) -> bool:

230

"""Check if scan angle is selected."""

231

232

def is_set_user_data(self) -> bool:

233

"""Check if user data is selected."""

234

235

def is_set_point_source_id(self) -> bool:

236

"""Check if point source ID is selected."""

237

238

def is_set_gps_time(self) -> bool:

239

"""Check if GPS time is selected."""

240

241

def is_set_rgb(self) -> bool:

242

"""Check if RGB color is selected."""

243

244

def is_set_nir(self) -> bool:

245

"""Check if near-infrared is selected."""

246

247

def is_set_wavepacket(self) -> bool:

248

"""Check if wavepacket data is selected."""

249

250

def is_set_all_extra_bytes(self) -> bool:

251

"""Check if all extra bytes are selected."""

252

253

def is_set(self, flag) -> bool:

254

"""

255

Check if specific flag is set.

256

257

Parameters:

258

- flag: DecompressionSelection flag to check

259

260

Returns:

261

bool: True if flag is set

262

"""

263

264

def to_lazrs(self):

265

"""Convert to lazrs-specific decompression selection."""

266

267

def to_laszip(self) -> int:

268

"""Convert to LASzip-compatible integer selection."""

269

```

270

271

**Usage Examples:**

272

273

```python

274

import laspy

275

from laspy import DecompressionSelection

276

277

# Read only essential fields to save memory

278

selection = DecompressionSelection.base() # XYZ, returns, classification

279

las = laspy.read('large.laz', decompression_selection=selection)

280

print(f"Loaded {len(las.points)} points with base fields only")

281

282

# Custom field selection for specific analysis

283

selection = (DecompressionSelection.xy_returns_channel()

284

.decompress_z()

285

.decompress_intensity()

286

.decompress_rgb())

287

288

# Skip expensive fields like GPS time and extra bytes

289

las = laspy.read('detailed.laz', decompression_selection=selection)

290

291

# Check what fields are available

292

if selection.is_set_rgb():

293

print("RGB data will be decompressed")

294

# Access RGB data

295

colors = las.points[['red', 'green', 'blue']]

296

297

if not selection.is_set_gps_time():

298

print("GPS time will be skipped (saves memory)")

299

300

# Progressive decompression - start minimal, add fields as needed

301

selection = DecompressionSelection.xy_returns_channel()

302

303

# Read with minimal fields first

304

las = laspy.read('data.laz', decompression_selection=selection)

305

print(f"Initial load: XY coordinates only")

306

307

# If analysis needs Z data, reload with Z

308

if analysis_needs_height:

309

selection = selection.decompress_z()

310

las = laspy.read('data.laz', decompression_selection=selection)

311

print("Reloaded with Z coordinate")

312

313

# For color analysis, add RGB

314

if analysis_needs_color:

315

selection = selection.decompress_rgb()

316

las = laspy.read('data.laz', decompression_selection=selection)

317

print("Reloaded with RGB data")

318

```

319

320

### Point Format Compression Utilities

321

322

Utilities for working with compressed point formats and format conversion.

323

324

```python { .api }

325

def is_point_format_compressed(point_format_id: int) -> bool:

326

"""

327

Check if point format uses compression.

328

329

Parameters:

330

- point_format_id: int - Point format ID to check

331

332

Returns:

333

bool: True if point format is compressed

334

"""

335

336

def compressed_id_to_uncompressed(point_format_id: int) -> int:

337

"""

338

Convert compressed point format ID to uncompressed equivalent.

339

340

Parameters:

341

- point_format_id: int - Compressed point format ID

342

343

Returns:

344

int: Uncompressed point format ID

345

"""

346

347

def uncompressed_id_to_compressed(point_format_id: int) -> int:

348

"""

349

Convert uncompressed point format ID to compressed equivalent.

350

351

Parameters:

352

- point_format_id: int - Uncompressed point format ID

353

354

Returns:

355

int: Compressed point format ID

356

"""

357

```

358

359

**Usage Examples:**

360

361

```python

362

import laspy

363

from laspy.compression import (

364

is_point_format_compressed,

365

compressed_id_to_uncompressed,

366

uncompressed_id_to_compressed

367

)

368

369

# Check point format compression status

370

for fmt_id in range(11): # Point formats 0-10

371

is_compressed = is_point_format_compressed(fmt_id)

372

print(f"Point format {fmt_id}: {'Compressed' if is_compressed else 'Uncompressed'}")

373

374

# Convert between compressed and uncompressed IDs

375

uncompressed_fmt = 3 # Standard point format 3

376

compressed_fmt = uncompressed_id_to_compressed(uncompressed_fmt)

377

print(f"Point format {uncompressed_fmt} compressed equivalent: {compressed_fmt}")

378

379

back_to_uncompressed = compressed_id_to_uncompressed(compressed_fmt)

380

print(f"Point format {compressed_fmt} uncompressed equivalent: {back_to_uncompressed}")

381

382

# Example: Force compression when writing

383

las = laspy.read('input.las')

384

current_fmt = las.header.point_format.id

385

386

if not is_point_format_compressed(current_fmt):

387

# Convert to compressed equivalent for writing

388

compressed_fmt = uncompressed_id_to_compressed(current_fmt)

389

converted = laspy.convert(las, point_format_id=compressed_fmt)

390

converted.write('compressed_output.laz', do_compress=True)

391

print(f"Converted format {current_fmt} to {compressed_fmt} and compressed")

392

```

393

394

## Advanced Compression Usage

395

396

### Backend Performance Comparison

397

398

```python

399

import laspy

400

import time

401

from laspy import LazBackend

402

403

def benchmark_backends(laz_file, chunk_size=100000):

404

"""Compare performance of different LAZ backends."""

405

406

available_backends = LazBackend.detect_available()

407

results = {}

408

409

for backend in available_backends:

410

print(f"Testing {backend.name}...")

411

412

start_time = time.time()

413

total_points = 0

414

415

try:

416

with laspy.open(laz_file, laz_backend=backend) as reader:

417

for chunk in reader.chunk_iterator(chunk_size):

418

total_points += len(chunk)

419

420

elapsed = time.time() - start_time

421

throughput = total_points / elapsed

422

423

results[backend.name] = {

424

'elapsed': elapsed,

425

'points': total_points,

426

'throughput': throughput

427

}

428

429

print(f" {total_points:,} points in {elapsed:.2f}s ({throughput:,.0f} points/sec)")

430

431

except Exception as e:

432

print(f" Failed: {e}")

433

results[backend.name] = {'error': str(e)}

434

435

return results

436

437

# Run benchmark

438

results = benchmark_backends('large_file.laz')

439

440

# Find fastest backend

441

fastest = None

442

best_throughput = 0

443

444

for backend_name, result in results.items():

445

if 'throughput' in result and result['throughput'] > best_throughput:

446

best_throughput = result['throughput']

447

fastest = backend_name

448

449

if fastest:

450

print(f"\nFastest backend: {fastest} ({best_throughput:,.0f} points/sec)")

451

```

452

453

### Memory-Optimized Streaming

454

455

```python

456

import laspy

457

from laspy import DecompressionSelection, LazBackend

458

459

def memory_efficient_processing(laz_file, output_file, processing_func):

460

"""Process large LAZ files with minimal memory usage."""

461

462

# Use most efficient backend

463

backend = LazBackend.detect_available()[0]

464

465

# Minimal field selection

466

selection = DecompressionSelection.base()

467

468

print(f"Processing {laz_file} with {backend.name}")

469

print(f"Decompression selection: base fields only")

470

471

with laspy.open(laz_file, laz_backend=backend, decompression_selection=selection) as reader:

472

header = reader.header.copy()

473

474

with laspy.open(output_file, mode='w', header=header,

475

laz_backend=backend, do_compress=True) as writer:

476

477

total_processed = 0

478

chunk_size = 50000 # Small chunks for low memory usage

479

480

for chunk in reader.chunk_iterator(chunk_size):

481

# Process chunk

482

processed_chunk = processing_func(chunk)

483

484

# Write immediately to minimize memory usage

485

if len(processed_chunk) > 0:

486

writer.write_points(processed_chunk)

487

total_processed += len(processed_chunk)

488

489

# Progress indication

490

if total_processed % 500000 == 0:

491

print(f"Processed {total_processed:,} points")

492

493

print(f"Completed: {total_processed:,} points processed")

494

495

def ground_filter(points):

496

"""Simple ground filtering example."""

497

if len(points) == 0:

498

return points

499

500

# Keep only ground and low vegetation

501

mask = (points.classification == 2) | (points.classification == 3)

502

return points[mask]

503

504

# Use memory-efficient processing

505

memory_efficient_processing('input.laz', 'filtered.laz', ground_filter)

506

```

507

508

### Compression Quality Analysis

509

510

```python

511

import laspy

512

import os

513

from laspy import LazBackend

514

515

def analyze_compression_ratio(input_las, backends=None):

516

"""Analyze compression ratios for different backends."""

517

518

if backends is None:

519

backends = LazBackend.detect_available()

520

521

# Get original file size

522

original_size = os.path.getsize(input_las)

523

print(f"Original LAS file: {original_size:,} bytes")

524

525

las = laspy.read(input_las)

526

527

results = {}

528

529

for backend in backends:

530

try:

531

# Write compressed file with this backend

532

temp_file = f"temp_{backend.name.lower()}.laz"

533

534

with laspy.open(temp_file, mode='w', header=las.header,

535

laz_backend=backend, do_compress=True) as writer:

536

writer.write_points(las.points)

537

538

# Check compressed size

539

compressed_size = os.path.getsize(temp_file)

540

ratio = original_size / compressed_size

541

542

results[backend.name] = {

543

'compressed_size': compressed_size,

544

'compression_ratio': ratio,

545

'space_saved': 1 - (compressed_size / original_size)

546

}

547

548

print(f"{backend.name}:")

549

print(f" Compressed size: {compressed_size:,} bytes")

550

print(f" Compression ratio: {ratio:.1f}:1")

551

print(f" Space saved: {results[backend.name]['space_saved']:.1%}")

552

553

# Clean up temp file

554

os.remove(temp_file)

555

556

except Exception as e:

557

print(f"{backend.name}: Failed - {e}")

558

results[backend.name] = {'error': str(e)}

559

560

return results

561

562

# Analyze different backends

563

compression_results = analyze_compression_ratio('sample.las')

564

565

# Find best compression

566

best_ratio = 0

567

best_backend = None

568

569

for backend_name, result in compression_results.items():

570

if 'compression_ratio' in result and result['compression_ratio'] > best_ratio:

571

best_ratio = result['compression_ratio']

572

best_backend = backend_name

573

574

if best_backend:

575

print(f"\nBest compression: {best_backend} ({best_ratio:.1f}:1)")

576

```

577

578

### Selective Field Processing

579

580

```python

581

import laspy

582

from laspy import DecompressionSelection

583

584

def process_fields_selectively(laz_file):

585

"""Demonstrate selective field processing for different analyses."""

586

587

# Analysis 1: Terrain analysis (needs XYZ + classification)

588

print("Terrain analysis...")

589

selection = (DecompressionSelection.xy_returns_channel()

590

.decompress_z()

591

.decompress_classification())

592

593

with laspy.open(laz_file, decompression_selection=selection) as reader:

594

total_ground = 0

595

for chunk in reader.chunk_iterator(100000):

596

ground_points = chunk[chunk.classification == 2]

597

total_ground += len(ground_points)

598

599

print(f"Found {total_ground:,} ground points")

600

601

# Analysis 2: Intensity analysis (needs XYZ + intensity)

602

print("Intensity analysis...")

603

selection = (DecompressionSelection.base()

604

.decompress_intensity())

605

606

with laspy.open(laz_file, decompression_selection=selection) as reader:

607

intensity_stats = []

608

for chunk in reader.chunk_iterator(100000):

609

if hasattr(chunk, 'intensity'):

610

intensity_stats.extend(chunk.intensity)

611

612

if intensity_stats:

613

import numpy as np

614

mean_intensity = np.mean(intensity_stats)

615

print(f"Mean intensity: {mean_intensity:.1f}")

616

617

# Analysis 3: Color analysis (needs RGB)

618

print("Color analysis...")

619

selection = (DecompressionSelection.xy_returns_channel()

620

.decompress_rgb())

621

622

with laspy.open(laz_file, decompression_selection=selection) as reader:

623

has_color = False

624

for chunk in reader.chunk_iterator(100000):

625

if hasattr(chunk, 'red'):

626

has_color = True

627

break

628

629

if has_color:

630

print("File contains RGB color information")

631

else:

632

print("File does not contain RGB color")

633

634

# Run selective analyses

635

process_fields_selectively('sample.laz')

636

```