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.

Eleven formats, two families Horizontal bars of record size in bytes for formats 0 to 10. The legacy family: 0 is 20 bytes, 1 is 28, 2 is 26, 3 is 34, 4 is 57, 5 is 63. The LAS 1.4 family: 6 is 30, 7 is 36, 8 is 38, 9 is 59, 10 is 67. Formats 4, 5, 9 and 10 include waveform packet fields, which accounts for their size. 012345678910 20 B28263457633036385967 legacy 0–5 LAS 1.4: 6–10

# 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

python
"""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:

bash
pdal info --metadata delivery/t_0431.laz | jq '.metadata | {minor_version, dataformat_id, point_length, count}'
Where classification lives Two byte diagrams. In formats 0 to 5, one byte holds a 5-bit classification plus synthetic, key-point and withheld flag bits, so classes are limited to 0 to 31. In formats 6 to 10, a full byte holds classification from 0 to 255, and a separate byte holds four classification flags — synthetic, key-point, withheld and overlap — plus scanner channel and scan direction bits. PDRF 0–5 class, 5 bits (0–31) S K W one byte shared PDRF 6–10 classification, full byte (0–255) S K W O channel, scan dir, edge separate flags byte S synthetic · K key-point · W withheld · O overlap (new in 1.4)

# 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.

Unused fields still cost bytes Uncompressed size for 100 million points in three formats. PDRF 6 needs 3.0 gigabytes. PDRF 8 needs 3.8 gigabytes because of colour and NIR. PDRF 10 with empty waveform fields needs 6.7 gigabytes, more than double PDRF 6 for no additional information. PDRF 6 3.0 GB PDRF 8 3.8 GB PDRF 10, empty waveforms 6.7 GB 100 million points, uncompressed LAS

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.