All docs
Examples
Example: file loader
A @tool.load that reads a file or SQL query and maps the user's columns onto canonical x/y/z with a columnmap parameter and auto-detect.
TL;DRA load step with a Source parameter (files or SQL) plus a column-map parameter. The columnmap widget lets the user say which of their columns are x, y and z; parse_column_map / resolve_column_map / apply_column_map turn that into a renamed Table, and a blank map auto-detects by name.
Real data never arrives with the column names you want. This load step reads a file or a SQL
query and lets the user map their columns onto canonical x, y, z — with a columnmap widget
in the node form and fuzzy auto-detection when they leave it blank.
The code#
Python
# tool.py
from typing import Annotated
from lsdtools import Tool, Source, Table
from lsdtools.advanced import columnmap_param, parse_column_map, resolve_column_map, apply_column_map
tool = Tool("points", label="Points")
@tool.load(source_kinds=("file", "sql"))
def load_points(
src: Source,
columns: Annotated[str, columnmap_param(["x", "y", "z"])] = "",
) -> Table:
"""Load a file/SQL query and map its columns onto x/y/z (blank = auto-detect by name)."""
t = src.read()
wanted = parse_column_map(columns) # "x=easting,y=northing" → {"x": "easting", …}
resolved, missing = resolve_column_map(t.columns, wanted, required=["x", "y", "z"])
if missing:
raise ValueError(f"could not map required column(s): {missing}")
return Table(apply_column_map(t.arrow, resolved))
What each line does#
@tool.load(source_kinds=("file", "sql"))— the picker offers both a file source and a SQL query.src: Sourceinjects a reader;src.read()returns aTable.columns: Annotated[str, columnmap_param(["x", "y", "z"])] = ""— a normalstrparameter, but thecolumnmap_parammetadata tells the inspector to render a column-map editor seeded with the target rowsx,y,z.columnmap_paramlives inlsdtools.advanced— the escape hatch for domain widgets in a signature.parse_column_map(columns)— parses the"target=source,…"grammar into a{target: source}dict (empty when the field is blank).resolve_column_map(t.columns, wanted, required=[...])— resolves the (possibly partial) map against the file's real columns: explicit entries win, and any required target left unmapped is auto-detected (exact → case-insensitive → fuzzy). Returns(resolved, missing).apply_column_map(t.arrow, resolved)— selects the mapped source columns and renames them to the targets, returning a rawpyarrow.Table;Table(...)re-wraps it.t.arrowis the zero-copy escape hatch to pyarrow.
Run it in Python#
Python
from lsdtools import Engine, Source, Table
# a survey file with domain-specific headers → map them explicitly
Table({"easting": [10.0, 11.0], "northing": [20.0, 21.0], "elev": [5.0, 6.0]}).write_csv("survey.csv")
pts = load_points(Source.file("survey.csv"), "x=easting, y=northing, z=elev")
Engine().run(pts).output.print()
# → x y z
# 10 20 5
# 11 21 6
# a file already using X/Y/Z → leave the map blank; it auto-detects (case-insensitive)
Table({"X": [1.0], "Y": [2.0], "Z": [3.0]}).write_csv("xyz.csv")
Engine().run(load_points(Source.file("xyz.csv"))).output.print()
# → x y z
# 1 2 3
The second positional argument to load_points(...) is the columns parameter — a plain string in
the column-map grammar. In the desktop it is the column-map editor instead.
Try changing it#
- Add
aliases={"z": ["rl", "elevation"]}toresolve_column_mapso common survey names auto-map. - Pass
keep_unmapped=Truetoapply_column_mapto carry the file's extra columns through. - Read a database instead:
load_points(Source.sql("warehouse", "select * from collars")).
Related#
Learn — Load steps · Parameters vs ports · UI overview
API — Source · Table · lsdtools.advanced
Examples — Clean a CSV · Multi-output mesh
By LSD Team · Last updated Jul 17, 2026
Ask on Discord
View as Markdown