All docs
ViewerPayload
ViewerPayload is what a viewer-delivering @tool.deliver returns — a JSON spec plus Arrow tables. Covers LayerPayload, ChartPayload, and LegendPayload.
ViewerPayload is what a viewer-delivering @tool.deliver step
returns. It is symmetric to how a @tool.load step takes a
Source: a load takes the input, a deliver returns the output artifact, and
the framework does the plumbing on both ends. When a deliver returns a ViewerPayload, the framework
- writes each table in
tablesto Parquet in the step's workspace, - persists the
spec+ table paths + routing as a*_payload.jsonsidecar, - emits the payload's
topicwithentity_id/step_idstamped centrally, and - reconstructs it generically on reopen from the sidecar — no per-step code.
So a package writes return LayerPayload(kind="lines", tables={"path": lines}) instead of
hand-rolling ctx.emit("viewer/layer/set", ...) plus its own parquet-write and reconstruct-on-reopen
dance. It is GTK-free and pyarrow-only (and pyarrow only at write time — import lsd stays cheap).
Usage#
from lsdtools import Tool, Table, LayerPayload, ChartPayload, LegendPayload
tool = Tool("geo")
@tool.deliver
def show_lines(t: Table) -> LayerPayload:
"""Draw the incoming geometry as lines in the 3D viewer."""
return LayerPayload(kind="lines", tables={"path": t}, style={"width": 1.5})
The return annotation is what marks a deliver as viewer-delivering — a ViewerPayload return is
only valid on @tool.deliver (a load/shape returns a Table). Any View that
watches this step's entity then receives the payload.
The three built-ins#
Each folds kind / style / title (and any extra keyword) into spec; tables may be empty.
| Class | Topic | Constructor | Typical use |
|---|---|---|---|
LayerPayload |
viewer/layer/set |
LayerPayload(*, kind="", tables=None, style=None, **extra) |
3D geometry — the geometry Parquet(s) ride in tables. |
ChartPayload |
viewer/chart/set |
ChartPayload(*, kind="", title="", tables=None, **extra) |
A chart — spec-only by default (pass tables= only if table-backed). |
LegendPayload |
viewer/legend/set |
LegendPayload(*, tables=None, **extra) |
A colour legend for a value column — spec-only. |
ChartPayload(kind="bar", title="Grades", series=[...]) # → viewer/chart/set
LegendPayload(title="Au g/t", colormap="grade", vmin=0.0, vmax=5.0) # → viewer/legend/set
The base class#
ViewerPayload(spec=None, tables=None) is the base, exported from lsdtools alongside the three
built-ins. spec is any JSON-serializable mapping (persisted verbatim, carried under the "spec"
key); tables is {name: Table} — each written to Parquet, with the emitted/persisted payload
carrying the paths, not the tables. A topic class attribute ("viewer/...") names the
event-bus topic each subclass delivers on.
Subclassing for a custom viewer surface#
Define a new topic by subclassing ViewerPayload and setting topic. Registering it (so generic
restore on reopen can map the persisted topic back to the class) uses the engine-internal
lsd.flow.payload seam — the same module a custom front-end sink lives beside, so this recipe is
a gui/product-tier task rather than a pure lsdtools package one:
from lsd.flow.payload import ViewerPayload, register_payload
@register_payload
class HeatmapPayload(ViewerPayload):
topic = "viewer/heatmap/set"
def __init__(self, *, tables=None, **spec):
super().__init__(spec=spec, tables=tables)
A viewer surface that renders this topic subscribes to it with a sink that gates each event on
should_handle — the per-view routing rule (a viewer shows a payload iff it watches that payload's
entity_id). The full recipe is on Rendering & payloads.
Where to start#
Learn — Rendering & payloads · Deliver steps
API — Tool.deliver · View · watch · Source