Skip to main content

PyOpenCRG

Unofficial implementation of the OpenCRG file format. Not affiliated with, endorsed by or supported by ASAM e.V. OpenCRG® is a registered trademark of ASAM e.V.

Belgian block road rendered with PyOpenCRG

Belgian block sample road of the ASAM OpenCRG project, drawn with road.view3d(color="grid", cmap="jet", z_scale=2.5) (relief exaggerated 2.5 times).

PyOpenCRG (import pyopencrg) reads, analyses, edits, shows and writes OpenCRG road surface files (.crg) in pure Python. You do not need to compile anything, and it runs on Windows, Linux and macOS.

  • All OpenCRG formats: binary KRBI / KDBI and text LRFI / LDFI, with full header support (comments, options, modifiers, map projection, WGS84).
  • Fast: everything is vectorised with numpy. A file of 10 million values is read in 0.1 s (binary) or about 4 s (text).
  • Accurate: data stays in double precision (float64). The algorithms follow the OpenCRG reference tools.
  • Simple: one object, CrgRoad, with plain-named methods. Every editing method returns a new road.
  • Complete: everything in the OpenCRG MATLAB tools, plus tools to clean laser scans, convert STL meshes and export to OBJ / STL / CSV / images / Simpack.

French guide: docs/guide_fr.md. Equivalence with the MATLAB tools: docs/matlab_equivalence.md.

Installation

pip install "pyopencrg[all]"     # recommended: everything (scipy, matplotlib, PyVista, Pillow)
pip install pyopencrg            # light: only numpy (read, check, evaluate, edit, write)

The light installation is useful in a simulation or server environment (e.g. conda, where mixing pip and conda packages can conflict). The other features need an extra package; if it is missing, the error message gives the command to install it:

Extra Package Needed for
scipy scipy smoothing, filters, re-sampling of curved roads, scan repair, point cloud import
plot matplotlib figures (road.show(), road.plot...) and image export
viewer PyVista interactive 3D viewer road.view3d()
image Pillow engrave_image()

In 10 lines

import pyopencrg as crg

road = crg.read("my_road.crg")          # any OpenCRG file
print(road)                              # summary: length, width, grid, slope...
print(road.check())                      # consistency checks

z = road.uv_to_z(120.0, 0.5)             # height 120 m along the road, 0.5 m left of the reference line
x, y = road.uv_to_xy(120.0, 0.5)         # where this point is in the world

part = road.crop(u=(100, 200))           # keep 100 m of road (a NEW road, `road` is unchanged)
part.save("part.crg", fmt="LDFI")        # save as a text file
road.show()                              # overview figures
road.view3d()                            # interactive 3D view (PyVista)

Files are checked when they are read: a truncated, unreadable or badly defined file raises a CrgCheckError explaining the problem, and minor problems are shown once as a warning. Use crg.read(path, check=False) to open a damaged file anyway (to repair it), or check="strict" to refuse files with warnings too.

Large files: only what you ask for is loaded.

preview = crg.read("scan.crg", step=4)                  # one point out of 4 in both directions (16 x less memory)
detail = crg.read("scan.crg", u=(500, 520))             # 20 m at full resolution
light = crg.read("scan.crg", dtype="float32")           # half the memory for the elevation grid

with crg.open("scan.crg") as f:                         # open once, like a Python file...
    for u0 in range(0, int(f.length), 100):
        part = f.read(u=(u0, u0 + 100))                 # ...then read parts in a few milliseconds

Tyre contact and simulation

The surface normal and the slopes are exact derivatives of the interpolation (grid, slope, banking and curvature of the road included). For a simulation loop, an evaluator prepares everything once:

nx, ny, nz = road.xy_to_normal(x, y)                     # unit normal, (0, 0, 1) on a flat road
dz_du, dz_dv = road.uv_to_gradient(u, v)                 # slopes along and across the road

ev = road.evaluator()                                    # prepared once
for step in range(n_steps):
    z, nx, ny, nz = ev.xy_to_z(x, y, with_normal=True)   # height and normal of 640 contact points: ~0.25 ms

The evaluator remembers the last position of each point and starts the next search from there. Give u_hint (one value, e.g. the u of the wheel) to road.xy_to_uv() / road.xy_to_z() when the road passes several times close to the same place.

Height map of the whole road, and a zoom where every 1 cm grid point is visible (road.plot.heightmap(cmap="jet"); zooming in a figure re-samples the image automatically):

Height map

Reference line, valid data limits, cross sections and long sections (road.plot.show_cuts_and_limits()):

Cuts and limits

Pictures rendered from the sample belgian_block.crg of the ASAM OpenCRG project (data provided by DaimlerChrysler, 2005; Apache License 2.0). The sample file is not distributed with PyOpenCRG; the pictures are made with docs/make_images.py.

Coordinates in one picture

OpenCRG describes a road along a reference line (usually the road centre):

  • u = distance along the reference line (m), v = lateral distance to it (m, positive to the left);
  • x, y, z = position in the world.

The data is a grid road.z of shape (nu, nv): road.z[i, j] is the height at u = road.u[i], v = road.v[j], relative to the reference line. The absolute height of the road is

z_absolute = road.z  +  road.zr (reference line elevation)  +  road.banking * v

road.uv_to_z(u, v) always returns the absolute height.

What you can do

Task Code
Read / write crg.read(path), crg.open(path), road.save(path, fmt="KRBI"), crg.convert(a, b, "LDFI")
Information print(road), road.check(), road.stats()
Elevation, position uv_to_z, uv_to_xy, xy_to_uv, xy_to_z, u_to_phi, u_to_curvature
Normal, slopes uv_to_normal, xy_to_normal, uv_to_gradient, xy_to_z(..., with_normal=True)
Fast repeated calls ev = road.evaluator(), then ev.xy_to_z(...), ev.uv_to_z(...)...
Profiles road.long_section(v=0.9), road.cross_section(u=50)
Cut, join, flip crop(u=..., v=...), trim(1, 1), crg.concatenate([r1, r2]), flip()
Re-sample resample(u_inc=0.05, v_inc=0.05)
Slope / banking extract_slope(), extract_banking(), separate_slope_banking(), set_banking()
Modifiers road.mods, apply_mods(), move_to(x, y, z, phi), transform(...), scale(...)
Filter filter("gauss", 5), clip_z(-0.05, 0.05)
Repair scans remove_artifacts(0.3, 15), fill_holes("linear"), fill_nan(), find_peaks()
Synthetic roads crg.generate.synthetic_road(...), from_function, road_from_points, trapezoid_bumps
Import crg.importers.from_stl(path), from_point_cloud(x, y, z)
Add features add_profile(...), add_random(...), overlay_uv(other), engrave_image("logo.png")
Export to_obj, to_stl, to_csv, to_trm (Simpack), save_heightmap, to_html_map
Geodesy xy_to_wgs84, xyz_to_llh, llh_to_xyz, crg.wgs84.distance(...)
Plots road.show(), road.plot.heightmap() (zoom shows every grid point), road.plot.cross_sections(), ...
3D viewer road.view3d(color="z" / "grid" / "normal", z_scale=5) (millions of points, GPU)

Every function has a detailed docstring: type help(road.crop) in Python.

Command line

pyopencrg info  road.crg
pyopencrg check road.crg
pyopencrg show  road.crg
pyopencrg view  road.crg                          # 3D viewer (PyVista)
pyopencrg convert road.crg road_text.crg --fmt LDFI
pyopencrg export road.crg road.obj --step 2       # .obj .stl .csv .png .html .trm

Examples

See the examples folder: reading and plotting, editing, synthetic roads, cleaning a laser scan, STL to CRG conversion, an obstacle test track.

Modifiers: a word of warning

The $ROAD_CRG_MODS block of a file tells a simulator how to modify the road when it loads it (scale it, fill NaN values, move it). crg.read() returns the road as stored in the file; call road.apply_mods() (or crg.read(path, apply_mods=True)) to get what a simulator sees. If a file has no modifier block, OpenCRG applies default modifiers: NaN values are replaced by the last valid value of the cross section and the road start is moved to x = y = z = 0 with heading 0.

Development

pip install -e ".[test,image]"
pytest

Citing

If you use pyopencrg in a report, a publication or another software, please cite it (see CITATION.cff):

Hardy, T. PyOpenCRG: OpenCRG road surface files in pure Python, version 0.2.0, 2026.

License

Apache License 2.0, see LICENSE and NOTICE. Parts of the algorithms are derived from the ASAM OpenCRG MATLAB tools (Apache 2.0).

Metadata

Release files for PyOpenCRG 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for PyOpenCRG 0.2.0
File Size Uploaded Provenance Actions
pyopencrg-0.2.0.tar.gz 776.2 kB With provenance Details for pyopencrg-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for PyOpenCRG 0.2.0
File Interpreter ABI Platform Actions
pyopencrg-0.2.0-py3-none-any.whl Python 3 none any Details for pyopencrg-0.2.0-py3-none-any.whl

Total release size: 886.8 kB

Release files / pyopencrg-0.2.0.tar.gz

Download URL pyopencrg-0.2.0.tar.gz
Size 776.2 kB
Tags Source
SHA-256 checksum
How to use checksums
93283f6053a09d574bc469540a368f8fb70522f030f9568f7786a52b808380fb
BLAKE2b-256 checksum
How to use checksums
24eb4740d29e47c74b8715094102afc2995e2e912481dc91d7f24ab116958357
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / pyopencrg-0.2.0-py3-none-any.whl

Download URL pyopencrg-0.2.0-py3-none-any.whl
Size 110.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
31b08e68f1ea40af8fa65a354d182b15490c0bb7d8eaa774c803ee8d98d10f0a
BLAKE2b-256 checksum
How to use checksums
576ac2f7845d9c06c974dcdf9bdfa5f15df4a1e5684b1080a741ea4d5c6b1f7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for PyOpenCRG 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pyopencrg-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page