Understanding LAS Point Data Record Formats
TL;DR: A LAS file’s point data record format (PDRF) fixes which fields each point has and its size in bytes. Formats 0–5 are the legacy layouts (5-bit classification, 1-byte scan angle, GPS time optional); formats 6–10, introduced in LAS 1.4, have an 8-bit classification, a separate flags byte with an overlap bit, a 16-bit scan angle and mandatory GPS time. Deliver new data in PDRF 6 (no colour), 7 (RGB) or 8 (RGB + NIR); check any file with laspy.open(path).header.point_format.id or pdal info --metadata.
# Context and Motivation
This guide is part of LAS/LAZ File Structure. The point format is the single header value that most often explains confusing behaviour: why classification codes above 31 disappear, why there is no RGB when the vendor promised colour, why an overlap flag cannot be set, why a file is 30 percent larger than expected. Every LAS 1.4 specification requirement about classes, flags and GPS time is really a statement about PDRF 6–10, and most modern specifications — including the USGS Lidar Base Specification — require one of them.
# Prerequisites and Assumptions
- A LAS or LAZ file to inspect; laspy 2.x or PDAL.
- LAS 1.4 R15 as the reference specification; earlier versions support only formats 0–5.
- Awareness that LAZ compresses records, so file size on disk does not reveal the format directly.
# Step-by-Step Implementation
# Step 1 — Read the format ID
With laspy: laspy.open(path).header.point_format.id. With PDAL: pdal info --metadata path | jq .metadata.dataformat_id.
# Step 2 — List the dimensions
header.point_format.dimension_names in laspy, or pdal info --schema, shows exactly which fields exist — including any extra-bytes dimensions appended after the standard record.
# Step 3 — Map format to capabilities
Use the table below: colour, NIR, GPS time, waveform, classification range and flags.
# Step 4 — Decide the delivery format
For new airborne deliveries, PDRF 6 without imagery, 7 with RGB, 8 with RGB and NIR. Use 9 or 10 only if full-waveform packets are genuinely delivered.
# Step 5 — Convert when needed
laspy.convert or writers.las with dataformat_id changes the format; see upgrading LAS 1.2 files to LAS 1.4.
# Complete Working Example
"""Report point format details for every LAS/LAZ file in a folder."""
from __future__ import annotations
from pathlib import Path
import laspy
CAPS = {
0: "base", 1: "base + GPS", 2: "base + RGB", 3: "base + GPS + RGB",
4: "1 + waveform", 5: "3 + waveform",
6: "1.4 base (GPS)", 7: "6 + RGB", 8: "7 + NIR", 9: "6 + waveform", 10: "8 + waveform",
}
def describe(path: Path) -> dict:
with laspy.open(path) as f:
h = f.header
pf = h.point_format
return {
"file": path.name,
"las": f"{h.version.major}.{h.version.minor}",
"pdrf": pf.id,
"record_bytes": pf.size,
"standard_bytes": pf.num_standard_bytes,
"extra_dims": list(pf.extra_dimension_names),
"caps": CAPS.get(pf.id, "?"),
"max_class": 31 if pf.id <= 5 else 255,
"has_overlap_flag": pf.id >= 6,
"points": h.point_count,
}
if __name__ == "__main__":
for p in sorted(Path("delivery").glob("*.la[sz]")):
d = describe(p)
warn = " <-- legacy format" if d["pdrf"] <= 5 else ""
print(f"{d['file']:<28} LAS {d['las']} PDRF {d['pdrf']:>2} "
f"{d['record_bytes']:>3} B ({d['caps']}), extra {d['extra_dims']}{warn}")The same information from PDAL for a single file:
pdal info --metadata delivery/t_0431.laz | jq '.metadata | {minor_version, dataformat_id, point_length, count}'# Key Parameter Table
| PDRF | Bytes | GPS time | RGB | NIR | Waveform | Class range | Overlap flag |
|---|---|---|---|---|---|---|---|
| 0 | 20 | — | — | — | — | 0–31 | — |
| 1 | 28 | yes | — | — | — | 0–31 | — |
| 2 | 26 | — | yes | — | — | 0–31 | — |
| 3 | 34 | yes | yes | — | — | 0–31 | — |
| 4 | 57 | yes | — | — | yes | 0–31 | — |
| 5 | 63 | yes | yes | — | yes | 0–31 | — |
| 6 | 30 | yes | — | — | — | 0–255 | yes |
| 7 | 36 | yes | yes | — | — | 0–255 | yes |
| 8 | 38 | yes | yes | yes | — | 0–255 | yes |
| 9 | 59 | yes | — | — | yes | 0–255 | yes |
| 10 | 67 | yes | yes | yes | yes | 0–255 | yes |
Extra-bytes dimensions add to these sizes; point_format.size reports the total record length.
# Verification
- Header and data agree. The header’s point record length equals the format’s standard size plus the extra-bytes size. A mismatch means extra bytes are undeclared, which some readers reject.
- Classes fit the format. In PDRF 0–5 files, the maximum classification is 31. Values above that mean the file was written incorrectly or read with the wrong format.
- Consistent across a delivery. Every tile in a project should share version and PDRF; mixed formats complicate merging and QA.
# Gotchas and Edge Cases
LAS 1.4 with legacy formats. LAS 1.4 files may still use PDRF 0–5 for compatibility. Version 1.4 alone does not guarantee 8-bit classification; check the format.
Scan angle units differ. Formats 0–5 store a signed byte in whole degrees (“scan angle rank”); 6–10 store a 16-bit value in 0.006° increments. Libraries expose both as degrees, but scripts that read raw values must know which.
Waveform formats without waveforms. Some software writes PDRF 9 or 10 with empty waveform packets. It wastes 29 bytes per point; convert to 6 or 8 unless waveforms are delivered.
RGB in PDRF 6. There is no colour field in PDRF 6. Colour added by photogrammetry or colourization must be written to 7 or 8, or it is silently dropped by writers.
# Frequently Asked Questions
Which LAS point format should I use?
For new data, a LAS 1.4 format: 6 without colour, 7 with RGB, 8 with RGB and near-infrared. These support the full classification range, the overlap flag and modern metadata. Use 9 or 10 only when delivering full-waveform data.
Why are my classification codes above 31 lost?
The file uses a legacy format, 0 to 5, whose classification field has only 5 bits. Convert to format 6 or later to store codes up to 255.
How do I find the point format of a LAS file?
Open it with laspy and read header.point_format.id, or run pdal info with the metadata flag and look at dataformat_id. Both read only the header.
Does LAZ compression change the point format?
No. LAZ compresses the records of whatever format the file uses, and decompression restores them exactly. The format ID in the header is the same for the LAS and LAZ versions of a file, although LAS 1.4 formats 6 to 10 compress with a newer, layered LAZ scheme that older decoders cannot read.
What is the difference between format 6 and format 1?
Both have GPS time and no colour, but format 6 has a full-byte classification, a separate flags byte including overlap, a higher-resolution scan angle and a scanner channel field, and it is 30 bytes instead of 28.
# Related
- LAS/LAZ File Structure — header, VLRs and records
- Upgrading LAS 1.2 Files to LAS 1.4 — converting legacy formats
- How to Parse LAS Headers with Python — reading the format ID yourself
- Flagging Overlap and Withheld Points — the flags byte in use
- Estimating PDAL Memory from Point Layout — record size versus in-memory size