Import & export¶
Import¶
Three entry points. All accept the same set of formats — see File formats for the full list.
RIEGL raw projects
A RIEGL .riproject / .PROJ is a directory of scan positions, not a file, so it has its
own path: Import a RIEGL project. Data
that has already been through RiSCAN PRO or RiPROCESS should be exported to
LAS/E57 and imported normally.
Drag any supported file from your file manager anywhere onto the
Phytograph window. Format is auto-detected from the extension and,
for ambiguous formats like .ply and .obj (which can be either
mesh or point cloud), from the file contents. Point clouds open the
import wizard so you can confirm or adjust the
column mapping before loading.
Dropping a Helios scan XML (.xml) loads its scans and grids the
same way the Add Scan popup's Import from XML file does — see
Import scan positions from a real campaign.
Any point-cloud files the XML references are located relative to the
XML and stepped through the import wizard. If a referenced data file
can't be found (moved or renamed), Phytograph warns you by name and
offers to Locate… it — pick the file and the import continues — or
Skip to cancel.
File → Import has an entry per format; each opens a file picker filtered to that type. Pick one or more files and Phytograph imports them as that format:
- Auto-detect (default) — type chosen from extension + contents
(recognizes
.xmlscan files too) - Point Cloud — force, e.g., a
.plyto be read as a cloud - Mesh — force, e.g., a vertex-only
.objto be read as a mesh - Skeleton — for
.jsonskeleton graphs - Scan XML — for Helios
.xmlscan/grid definitions - QSM CSV — for
.csvcylinder tables (see Importing a QSM)
Use a specific format when auto-detection picks the wrong type. As with drag-and-drop, point clouds open the import wizard before loading.
Phytograph registers itself with the operating system as a handler for every importable format, so you can start from your file manager instead of from the app:
- macOS — right-click the file in Finder → Open With → Phytograph (or set Phytograph as the default for that type via Get Info).
- Windows — right-click → Open with → Phytograph (or double-click once Phytograph is the default for the extension).
- Linux — right-click → Open With → Phytograph in file managers that honor the desktop entry.
The file is auto-detected by extension (the same as drag-and-drop), so point clouds open the import wizard and meshes, skeletons, and Helios scan XML each load as their type. Opening several files at once steps through them in the wizard.
If Phytograph is already open, the file imports into the current scene — it does not launch a second copy of the app. On a cold launch the file is imported as soon as the app and its compute backend finish starting up.
The import wizard¶
Every point-cloud import opens an import wizard before the cloud is loaded. It lays the file out like a spreadsheet — one column per column in the file, with the first rows of real data shown underneath — and a dropdown at the top of each column for its role. Auto-detection fills the dropdowns in; you correct anything that's wrong before importing:
-
Column roles — each column's dropdown sets its role: X / Y / Z, Red / Green / Blue, Intensity, Reflectance, Timestamp, Target Index, Target Count, Scan Row Index, Scan Column Index, Miss Flag, Beam Origin X / Y / Z, Scalar, Label, or Skip. X, Y, and Z must be assigned before you can import. Every role except Scalar, Label, and Skip is a singleton — a cloud has exactly one of each — so assigning one to a column removes it from whichever column previously held it (that column drops to Skip).
For ASCII formats (
.xyz,.txt,.csv,.pts,.asc,.ascii) every column is freely assignable, including X / Y / Z.For formats that carry named scalar fields (
.las,.laz, RIEGL projects) the geometry columns are fixed by the file, but the scalar fields can be reassigned. This matters because a scalar's name is whatever the exporting software chose: Phytograph recognizes the common spellings —gps_time,GpsTime,timeandTimestamp[s]all resolve to Timestamp — but it cannot know that a column calledshot_timeis the same thing. Set its dropdown to Timestamp and the tools that need one (Backfill Misses, leaf-area density, multi-return grouping) will find it.Reassigning a role only changes how Phytograph reads the column. Your source file is never modified. - Import (skip a column) — every column except X / Y / Z carries an Import checkbox above its role dropdown. Untick it to leave that field out: its preview values gray out, and the column is never read. A skipped field is not stored in the cloud, doesn't appear in the Display panel's Color by list, and can't be exported — so unticking the columns you don't need keeps a cloud smaller and its field list shorter. X, Y, and Z have no checkbox because a cloud can't be imported without them.
The checkbox works for every format, including the ones whose roles are fixed (
.ply,.pcd,.e57,.ptx). For ASCII files it is the same thing as choosing the Skip role (the two controls stay in sync).Unticking a field that other tools read — Miss Flag, the scan grid indices, or the multi-return trio — shows an inline warning naming what stops working (leaf-area density, gap filling, the Hit/Miss coloring). It's still allowed: an all-zero miss flag on a hits-only export is exactly the kind of dead weight worth dropping. - Scalar vs Label — a Scalar column is a continuous measurement (intensity, height, timestamp) and colors as a smooth gradient; a Label column holds class ids (tree id, segment, classification) and colors as discrete classes with a legend. Phytograph's own class columns —
tree_instance,wood_class,ground_class,noise_class,plant_organ,leaflet_id,manual_class,las_classification— arrive as Label already, so a file this app exported re-imports colored the way it left. For any other column the wizard flags values that look like class labels with a one-click "use Label?" suggestion. That flag reads only the values (small non-negative integers), so it is a hint, not a verdict — ID columns always trip it. A Deviation column is never flagged: RIEGL's pulse-shape deviation is a small integer but a continuous measurement. - Timestamp / Target Index / Target Count — the per-pulse multi-return fields: each return's acquisition time, its index within its laser pulse (1st / 2nd / … return), and the pulse's total return count. Mapping all three lets the gap-filling / miss-reconstruction and leaf-area-density tools group returns back into their originating pulses. LAS/LAZ files fill these in automatically (fromgps_time/return_number/number_of_returns); for ASCII they auto-detect from headers likeTimestamp/Target Index/Target Count(and aliases such asgps_time,return_number,number_of_returns). If a column is unlabeled or named something the auto-detect doesn't recognize, pick the matching role from its dropdown — you no longer have to map it as Scalar and type the exact field name. They carry through under the canonicaltimestamp/target_index/target_countslugs the tools look them up by. - Scan Row / Column Index — integer positions of each point within the scanner's rectangular acquisition grid. Mapping these preserves the scan's raster layout, which the gap-filling / miss-reconstruction tools use to rebuild missing pulses within the scan pattern. They carry through as scalar fields (slugsrow_index/column_index) and auto-detect from common headers likeRow/Column/row_index. With no header, up to two leading all-integer columns sitting before the (fractional) coordinates are recognized as the row/column index pair, so arow col x y z …scanner export lands xyz on the right columns instead of mistaking the indices for coordinates. - Miss Flag — a per-pulse 0/1 indicator of whether the laser returned nothing (0= hit,1= sky/miss). Mapping it preserves the scan's miss rays, which leaf-area-density inversion needs to measure gap fraction. It carries through under the canonicalis_missslug (so the color-by Miss mode and its fixed Hit/Miss legend find it) and auto-detects from headers likeis_miss/miss/sky. By default the viewer colors it with the dedicated Hit/Miss scheme. If you instead pick Scalar for the column, it keeps theis_missslug (LAD still works) but colors as a continuous 0–1 gradient with a numeric legend — useful when you'd rather see the raw flag value than the named classes.Misses are auto-detected even without a Miss Flag column
A Helios synthetic scan exported to plain ASCII often drops the
is_misscolumn but keeps a Target Index column, where misses carry the sentinel value99. On import, Phytograph recovers the miss flag from that sentinel — so the misses appear under the row's sky/miss toggle, stay out of the displayed cloud's bounding box, and feed leaf-area-density — with nothing to configure. If the scan has no target-index column either, a distance fallback tags points farther than the Miss detection distance setting (default 1001 m, the Helios placeholder distance) from the scanner. An explicitis_misscolumn always takes precedence over auto-detection.- Beam Origin X / Y / Z — the per-pulse laser emission point (the scanner's
position for each return), in the same coordinate frame as X/Y/Z. Mapping all
three makes the cloud carry ground-truth origins that leaf-area-density
inversion uses directly — measuring each ray's true path through the canopy
without needing a separate scanner trajectory. This is the ASCII equivalent of
the
ox/oy/ozorigin columns a LAS file can carry; they auto-detect from headers likeox/oy/oz,xorigin/yorigin/zorigin, orbeamoriginx/…y/…z. Origins are kept at full coordinate precision (not the millimeter display quantization), so projected/UTM-scale origins survive exactly. Map all three for them to take effect; a partial pair is ignored. - RGB range — when an RGB role is present, choose whether the values are 0–255 integers or 0–1 floats, so colors import at the right brightness.
- Rename fields — a column set to Scalar or Label shows a name
box under its dropdown; the name you give it is what appears later in the
color-by picker and the Scans panel. For formats that define their own
layout (
.las,.laz,.ply,.pcd,.e57,.ptx) the rename changes only the displayed name. The field keeps the file's own name underneath. - Global shift — scans in a projected coordinate system (UTM, state plane) carry very large coordinates — hundreds of thousands to millions of meters from the meridian/equator. The wizard offers a Global shift: a checkbox plus X / Y / Z fields, pre-filled with a suggested offset when the data's coordinates are large. Leaving it on subtracts the offset at import so the stored cloud sits near the origin; the offset is remembered, so exports recover the original world coordinates automatically. This is the CloudCompare-style "global shift" — convenient when you want small, readable coordinates. Turning it off keeps the original large coordinates — and that's perfectly fine to do: the viewer renders large-coordinate scenes cleanly either way (see the note below). Z defaults to 0/off, matching the common case where only the horizontal coordinates are large.
The suggestion is measured over returns only. Sky/miss points are rays that hit nothing, drawn about a kilometer out along the beam, so a scan's recorded extent can span tens of kilometers while the actual returns sit within a few hundred meters of the origin. Measuring those would suggest a shift that pushes the cloud away from the origin rather than onto it, so they are excluded — a scan that is already near the origin is correctly offered no shift at all, however far its misses reach.
- Beam Origin X / Y / Z — the per-pulse laser emission point (the scanner's
position for each return), in the same coordinate frame as X/Y/Z. Mapping all
three makes the cloud carry ground-truth origins that leaf-area-density
inversion uses directly — measuring each ray's true path through the canopy
without needing a separate scanner trajectory. This is the ASCII equivalent of
the
Large coordinates render cleanly with or without a shift
The 3D viewport stores positions in 32-bit floats, which lose precision at UTM magnitudes (~5 cm at 500,000 m) — historically this made the ground grid kink or vanish and QSM/skeleton meshes flicker. The viewer now applies an automatic, render-only offset that draws every scene near the origin regardless of the stored coordinates' magnitude. It never changes your data, exports, or any measurement — so you can keep large coordinates and still get a clean grid and flicker-free meshes. The global shift above and this automatic rendering offset are independent: the shift changes what's stored; the rendering offset changes only what's drawn.
- Platform trajectory — for mobile-platform data (drone, robot,
backpack, or vehicle MLS), click Import trajectory file… to attach the
platform's trajectory to the scan. The imported cloud then becomes a
moving-platform acquisition: leaf-area density reconstructs a per-beam origin
for every return by joining the return's timestamp to the trajectory, instead
of assuming one fixed scanner position. All trajectory formats Phytograph reads
are accepted — text CSV / TXT / TSV / .traj (
t x y zplus a quaternion or Euler attitude) and binary SBET (.sbet/.out, parsed server-side). The wizard shows the pose count and duration once it's attached; Replace… swaps it and the ✕ removes it. Leave it empty for a static tripod scan. When you import several files at once, attaching a trajectory to one scan fills in the others by default (the common case is one platform pass split across files) — but you can give any scan its own trajectory, and an explicit per-scan choice is never overwritten by the default.
For .ply, .pcd, .las, .laz and .ptx, the column layout is defined
inside the file, so X/Y/Z and color roles can't be reassigned — but you can still
preview the fields, rename scalars, and switch any scalar between Scalar
and Label. For .las, .laz and RIEGL projects you can additionally assign a
scalar its true role (see Column roles above), which is how you tell Phytograph
that a field named something it doesn't recognize is really the timestamp,
reflectance, or a multi-return column. .e57 is the one format with no sample rows: reading values out
of it means decoding its binary point data, so the wizard shows the structure
only.
(A LAS/LAZ or ASCII cloud that already carries per-pulse beam-origin columns —
ox/oy/oz — needs no trajectory: those ground-truth origins are used
directly. The trajectory button is for mobile data whose origins must be
reconstructed from the platform path.)
If a file can't be previewed, the wizard says so and still lets you import with auto-detection.
Source units¶
Phytograph works in meters throughout — leaf area density is reported in m²/m³, QSM radii and tree heights in meters, registration residuals in millimeters. So every scan is converted to meters at import, and the wizard's Source units control is where that conversion is decided.
What you see depends on what the file can tell us:
- The file declares its unit. A LAS/LAZ carrying a coordinate reference
system knows whether it is in meters, US survey feet, or something else, and
the control shows detected from file. An
.e57or a RIEGL project is meters by specification — those formats have no other option. - The file cannot say. An ASCII
.xyz/.csv, a.ply, a.pcdand a.ptxcarry no unit information at all — they are just numbers. The control defaults to meters, which is what Phytograph has always assumed, and you can change it if your data is in something else.
When the chosen unit is not meters, the wizard says it will convert, and the coordinates you see after import are meters rather than the raw numbers in the file. That is deliberate: it is what makes every measurement, density and radius in the app mean what it says.
If your measurements come out wrong by a constant factor
Check the unit. A cloud recorded in millimeters but imported as meters will measure 1000× too large, and one in feet imported as meters about 3.28×. Re-import with the right unit selected.
Two cases are deliberately not auto-detected even from a CRS, because no single factor describes them: a geographic (lat/long) file, whose degrees are not a length at all, and a CRS whose axes disagree (horizontal feet with vertical meters). Both fall back to asking.
Units are chosen per file, not shared across a batch — a set of scans can legitimately mix a meter LAS with a feet one. Apply these settings to all carries your unit choice only to scans that could not detect their own, so a file that declared its unit is never overwritten by a neighbor's.
Importing several files at once¶
Drop multiple files together, or select several at once from the Import menu. The wizard steps through each scan — use Back / Next to move between them, and tick Apply these settings to all scans with the same column layout to copy one scan's settings onto the others: column roles, names, Import ticks, RGB range, global shift, units (where a scan couldn't detect its own) and trajectory. The box stays live while it's ticked. Every change you make afterwards, on any scan, is copied to all the others, including scans whose preview is still loading.
A scan whose column layout differs (a different number or order of columns, different header names, or a different format) is never overwritten. So you don't import any scan without reviewing its settings, the Import button stays disabled until every scan has either been stepped to with Next or is covered by Apply these settings to all scans. If a scan has a different layout, the footer names it, and you step to it to review it. Each file becomes its own entry in the Scene panel with a distinct color; nothing is merged automatically. If you want to merge clouds, use Stitch after import.
While a large import is in progress a modal shows the file currently being read and overall progress, so you know the app is working — reading a multi-GB scan from disk can take 30 seconds or more. The bar tracks the stage the import has reached (reading the source file, loading points into memory, building the octree), so it keeps moving even on a single file.
Cancel stops the import. This genuinely halts the work — the app tells the backend to abandon the run and frees the memory it had allocated, rather than just hiding the dialog and letting the import finish out of sight. Use it if you picked the wrong file or an import is taking longer than you're willing to wait.
When you cancel part-way through a multi-file import, the scans that already finished are kept (they're complete and correct); a notice tells you how many of the selected files made it. Canceling a Helios scan XML import is all-or-nothing, matching how that pathway already treats a failed scan — nothing from the bundle is added.
Importing a Helios scan XML (which can reference several scans at once) runs the same wizard, once the referenced point-cloud files are located.
If the XML also contains one or more top-level <grid> blocks — the voxel grid
Helios uses for leaf-area-density computation — Phytograph creates a matching
voxel grid for each, named Grid 1, Grid 2, … The grid's <center>,
<size>, <Nx>/<Ny>/<Nz>, and optional <rotation> (degrees about z) become
the box's position, size, subdivisions, and rotation, so it's ready to use as
the grid input for leaf-area-density with no
manual setup. A grid-only XML (no <scan>) imports just the grids.
A grid that was snapped to the ground before export carries its per-column
terrain offsets in the XML (<columnOffsets>/<keptColumns> — see the
file-format reference), so it
re-imports already snapped, bending to follow the same terrain, without needing
the DEM in the scene. Likewise, a scan's chosen scanner model round-trips via
a <scannerModel> tag, so a re-imported RIEGL/Leica/FARO/etc. scan keeps its
instrument identity rather than reverting to generic.
A scan's return mode also round-trips, via <returnMode> (plus
<returnSelection> for single-return or <maxReturns> for multi-return) tags.
Helios scan XML has no native field for return mode, so Phytograph writes it
explicitly to preserve the settings a simulated scan was run with.
These tags don't affect how an imported cloud is processed. Leaf-area density and triangulation read the return mode from the per-pulse columns in the point data itself (see Return type is detected, not declared), so an XML without them costs you nothing on a real scan — it only means a later simulated re-run starts from the defaults.
Importing textured meshes¶
A .obj that references a .mtl material library is imported with its
materials applied, as long as the .mtl and any image files it names sit in the
same folder as the .obj. Phytograph reads the diffuse texture (map_Kd) and
diffuse color (Kd) for each material: textured faces get the image, and faces
with only a Kd color get that color (so a multi-material livery with no
textures imports with each part's color, not flat). Kd is read as a linear
color, as Blender and Helios write it — see
File formats. An .obj with no .mtl
(and .stl, which has no materials) imports as plain geometry. See
Meshes: Textures. Meshes imported from a file
default to fully opaque.
A .ply is imported as a mesh when its header declares faces (otherwise it
imports as a point cloud — see
File formats: PLY).
Both ASCII and binary PLY meshes are read, including per-vertex color; PLY
meshes carry no textures.
An .stl is imported as a mesh in either encoding — ASCII or binary, detected
from the file's own contents, so binary files written by Blender, MeshLab, CAD
tools and slicers import directly. Binary STL has an unstandardized per-facet
color field that different tools write incompatibly; Phytograph reads it only
for facets that set the field's "color valid" bit, and leaves the mesh
untinted when no facet does. STL is always exported as ASCII.
Importing ASCII clouds with custom columns¶
For .xyz, .txt, and .csv files, auto-detection maps columns by header
name when one is present. Without a header it falls back to position: the
coordinates are the first three columns (or, if up to two all-integer
columns lead the fractional coordinates, the row/column scan indices come
first and xyz follow); a 0–255 integer triple right after xyz is taken as
RGB; and a lone trailing column as intensity — unless its values look
like class ids (a few repeating non-negative whole numbers, as in an
x y z label file), in which case it is left as an unnamed scalar and the
wizard offers to make it a Label. Anything else is stored
as a scalar field named after the column header (or Column N if there's
no header). The import wizard is where you correct that
mapping when the file uses a non-standard column order, RGB stored as 0–1
floats, or a class column that should be categorical. You can color the cloud
by any scalar field later — see Color modes.
Importing a QSM¶
A QSM cylinder CSV — one Phytograph exported, or one from another SimpleForest/TreeQSM-family tool — can be imported back as a QSM. It appears in the QSM results panel exactly as a freshly built one does, with its shoots and ranks intact, so you can color it by Shoot rank or Shoot id, add leaves, or re-export it.
Export a QSM to CSV and re-import it and you get the same model back — same cylinders, same shoot topology, and the same trunk diameter, height, woody volume, and max rank in the results panel. The whole-tree metrics aren't columns in the file; they're recomputed from the cylinders when you import.
.csv is shared with point clouds, so Phytograph looks at the header row to
tell them apart: a cylinder table has branchID and branchOrder columns,
which no point-cloud CSV does. Drag-and-drop and Open With route on that
automatically. Use File → Import → QSM CSV… to skip the check and force a
file to be read as a QSM — useful for an unusual dialect whose header isn't
recognized.
An imported QSM has no source point cloud, so it's listed under the file's name rather than a scan's, and its coordinates are used exactly as they appear in the file. See File formats: QSM cylinder CSV for the columns and the requirements on a hand-edited file.
Importing scans with sky/miss points¶
.e57, .ptx and structured .ply scans — and a re-imported Helios scan XML
bundle, which carries the scanner <origin> and an is_miss column — bring
sky/miss points — pulses that hit the sky and returned nothing — which the
leaf-area-density inversion relies on.
Phytograph recovers and tags them on import. They're hidden by default (their
true positions are ~20 km away); toggle the Show misses button on a scan row
to draw them in a distinct color, relocated onto the scan's bounding sphere, so
you can confirm a scan actually carries miss information. The relocation needs a
scanner origin — supplied by the E57/PTX/PLY pose or the XML bundle's
<origin>; a bare ASCII cloud with no scanner geometry shows its misses at their
true far-field position instead. See
Sky/miss points.
Scans that bring their own parameters¶
When a point-cloud file records the scanner's geometry in its header, importing
it on its own auto-fills the scan's scan parameters — no need to enter them
by hand. A file holding several scanner setups (a multi-block .ptx, a
multi-scan .e57) imports as one scan per setup, each with its own pose and
grid — see
Files holding several scan positions.
.e57 brings the scanner origin and orientation, plus the angular
sweep and grid resolution when present; .ptx brings the registered scanner
position and the grid resolution from its header, and its angular sweep is
measured back off the scan grid; .pcd brings a sensor origin from its
VIEWPOINT field. Anything the file omits stays at its default. (Loading a
Helios XML still takes precedence — its <scan> definitions win.) See
Scan parameters recovered from the point-cloud file.
Export¶
Choose File → Export… (⌘/Ctrl+S), or run Export from the command palette, to open the Export window.
It opens on an object list holding every point cloud in the scene, each with a checkbox. Whatever you had selected in the Scans panel starts checked — but that is only a starting point, and checking or unchecking a row here never changes the viewport selection. What you check then decides the rest of the window: a single plain cloud shows the format chooser + column picker for that one file, while several objects (or any scan) show the batch controls described under Exporting several objects. A selected mesh or skeleton shows its own formats instead. Once the options are set you pick the destination in a native dialog: a file for the exports that write exactly one (a single plain cloud, a mesh, a skeleton), a folder for the batch export, which writes one file per object.
Point cloud formats¶
Pick a Format in the Export window, then click Export. Most formats show a field picker: check which fields to write (x, y, z, color, intensity, scalars, labels). Everything is checked by default, so a plain export stays lossless and you prune from there. The picker lists every field the cloud actually holds — including scalars that came from a LAS extra dimension or an import-wizard column, and the class labels a segmentation added.
For XYZ / TXT / CSV / ASC / PLY you can also drag the rows to reorder them; the chosen order becomes the file's column order. LAS / LAZ identify their dimensions by name rather than by position, so order doesn't apply there and the drag handle is omitted.
PTS and PCD have no picker: each has a fixed schema a reader decodes by
position, so writing a chosen subset would produce a file that still parses and
is read wrong rather than one that's merely smaller. PTS is always
x y z intensity r g b (after its point-count line); PCD always carries
position plus, if present, color. Use .ply, .las, .csv or .txt when you
need to pick fields or keep other scalars.
What LAS/LAZ cannot leave out
Each scalar becomes a named LAS extra dimension, so any scalar can be unchecked. Two standard dimensions are different:
- X/Y/Z are the point record itself.
- Intensity is present in every LAS point format, so it cannot be removed — unchecking it could only write zeros. Both are shown locked.
Unchecking color is a real omission: it selects LAS point format 6 instead of 7, which has no RGB dimension. Both carry GPS time and a full classification byte (classes 0–255).
The timestamp column is written to the standard 64-bit GPS time field, not to an extra dimension, so pulse times keep their full precision. Unchecking it leaves that field at zero.
Classification byte from chooses which class column fills that byte: Automatic uses the hand labels, then an imported classification, then ground and wood/leaf results; None leaves it at 0; or pick any class column. A column with values above 255 (a plot of more trees than that) cannot go in the byte, and the export says so; it is still written as its own dimension. The file also records the class names and colors, and the LAS flags you painted.
| Format | Carries |
|---|---|
.las / .laz |
x, y, z, intensity, color, plus the scalars you select as named LAS extra dimensions |
.ply |
The columns you select, each declared as a named property |
.xyz |
The columns you select, whitespace-separated, with no header line |
.txt |
The columns you select, whitespace-separated, with a #-prefixed column header |
.csv |
Same columns as .txt but comma-separated with a plain header row |
.asc |
The columns you select, whitespace-separated, no header line (same bytes as .xyz) |
.pts |
Point-count line, then x y z intensity r g b — fixed order, no picker |
.pcd |
Position + color only (PCL ASCII, color packed into one rgb field) — no picker |
The .txt export writes a leading #-prefixed column header (the CloudCompare
convention, e.g. # X Y Z is_miss). Phytograph's own importer reads that header
to auto-map columns on re-import, and most ASCII readers (CloudCompare included)
skip the # line as a comment. Bare .xyz carries no header, so its extra
columns are positional — use .txt or .csv when you want the field names
preserved.
For a full-fidelity round trip, use .las / .laz or .ply. LAS/LAZ writes
each scalar as a named extra dimension, so re-importing the file restores the
same named fields (and is far smaller and faster than text for a large cloud).
Scalar fields are no longer dropped
Before v0.65.0 the text exports wrote only x/y/z (plus color and intensity
for .txt/.csv) and LAS/LAZ wrote only x/y/z and color — every other
field was silently lost, and the column picker offered only x/y/z for a
normally-imported cloud. If you have exports from an earlier version that
are missing their scalars, re-export them.
After you click Export, a save dialog asks where to write the file. Once you confirm the destination the Export window closes and a progress pill appears at the top of the viewer, showing a live percentage as the cloud is written (a 25-million-point text export takes roughly half a minute). The pill has a cancel button — stopping an export leaves no partial file behind. When the write finishes the pill clears and a toast reports the file name and point count, so there's no need to click Export twice. Canceling the save dialog writes nothing and reports nothing.
The pill names the stage it is on. For the text formats that is mostly
Formatting, which is where nearly all their time goes; .las/.laz step
through Computing bounds, Packing coordinates, Packing colors, Packing
intensity, Packing scalar fields and Writing file instead (the packing
stages appear only for the fields the cloud actually has). Binary formats are
several times faster than text for the same cloud, so their pill moves through
those stages quickly.
Exporting several objects¶
Check more than one object in the list — or any single scan (a cloud carrying scanner parameters: origin, field of view, beam optics) — and the window switches to Export objects, which writes one data file per checked object.
Because that is many files from one name, this export does not ask for a file name in a Save dialog. You type a Base name in the window and the button asks only for a destination folder — and above the button the window lists the files it is about to write, so nothing is a surprise:
Base name [ myscan ]
Will write 3 files:
myscan_ScanPos002.laz
myscan_ScanPos001.laz
myscan_ScanPos014.laz
Each file is named for the object it holds, so the exported set maps back to the
Scans panel rather than to the order the scans were added. Characters a file
system won't take (spaces, slashes, : and friends) become underscores, and two
objects sharing a name get a _2, _3 … suffix. Exporting a single object
is the exception — it is written under the base name alone, with nothing
appended. An XML + data bundle also writes <base>.xml next to its per-scan
data files.
With several objects checked, the small Prepend / Append switch beside the
Base name label picks whether the base name leads (myscan_ScanPos002.laz, the
default) or trails (ScanPos002_myscan.laz) each object's name. Clear the base name entirely to
name every file after its object alone (ScanPos002.laz); an XML bundle then
calls its own file scans.xml.
The list holds every cloud in the scene, scans and plain imports alike, with a
Select all checkbox above it and a count of how many of them are checked.
Plain clouds (a .xyz / .las / .ply import with no scanner metadata) can be
written to any of the data formats, so exporting a whole folder's worth of
clouds in one pass is a single check-all and click.
Two outputs do need scan geometry, and the rows they can't write gray out with the reason on hover rather than vanishing:
- the XML + data bundle needs a scanner origin and angular sweep, so it is offered only when something checked is a scan;
- PTX needs a complete raster grid, so it skips plain clouds and non-raster
patterns (a Livox rosette has no
Ntheta × Nphigrid).
Select all only ever checks the rows the current output can actually write, and switching between outputs is non-destructive — a cloud grayed out by XML mode is still checked when you switch back to Data only.
Output mode — the two toggles at the top:
- XML + data (default) — writes a Helios scan bundle: an
.xmlmetadata file plus one.xyzdata file per scan. This bundle is re-loadable as a scan — re-importing the XML restores the scanner parameters and the sky/miss points, so the imported clouds can drive parameter-dependent analyses (leaf area density, Helios triangulation) again. It is the round-trip-faithful path for synthetic and edited scans. - Data only — writes just the per-scan data files, no XML, and reveals a
Format chooser:
LAS,LAZ,PLY,XYZ,CSV,TXT,E57, orPTX. Use this to round-trip a scan into any supported format for another tool.
Exporting to PTX
PTX is a complete raster: it writes one line per grid cell, so the file
always has Ntheta x Nphi data rows and a cell with no return is recorded as
an all-zero row. That completeness is what lets a PTX be re-imported with its
sky/miss points recovered. Two consequences: the scan needs a grid — either
real row/column indices (from an E57/PTX import) or a raster scan's
Ntheta x Nphi resolution — and a non-raster pattern (Risley/Livox) can't be
exported to PTX at all. Include miss points has no effect on a PTX,
because an excluded miss is written as the same empty cell. Points are written
in the scanner's local frame with the registered scanner position in the
header, so the file opens in the right place in Cyclone or CloudCompare.
Exporting to E57
E57 is structured too, but it doesn't require every cell to be present, so it behaves differently from PTX in two ways. The file gets row/column indices whenever the scan has a grid to write — the same two sources PTX uses: real indices from an E57/PTX import, or a raster scan's Ntheta x Nphi sweep to bin against. Where PTX refuses a scan with neither, E57 simply writes an unstructured file. And a cell with more than one return keeps every echo, where PTX has to collapse it to one.
Misses are written as cells flagged with E57's own cartesianInvalidState,
which is what the importer reads — so an exported scan comes back as scan
data rather than as a cloud of points sitting ~20 km out. Unlike PTX,
Include miss points genuinely applies: untick it for a smaller,
hits-only file. Points are written in the scanner's local frame with its
pose in the scan header, as scanners themselves do.
Columns — for the text formats (XYZ / CSV / TXT, and the XML bundle's
.xyz data) plus PLY and LAS / LAZ, a column picker lets you check which fields
to write. x, y, z are required and locked on. Only the text formats and PLY
let you drag to reorder: LAS / LAZ identify their dimensions by name rather
than position, so order doesn't apply. E57 and PTX use their own fixed
schema, so the column picker is hidden for them.
Include miss points — when on (default), the sky/miss points and the
is_miss flag are written, so misses survive the round-trip. The is_miss
column is always included while this is on, even if you unchecked it in the
column picker — without that flag the far-field miss points would re-import as
real returns, breaking the round-trip. (E57 uses the format's own
cartesianInvalidState flag for this instead of a column; PTX needs no flag,
since an empty cell already says it.) Turn it off for a returns-only export.
Available only when at least one checked scan carries misses — and grayed out
for PTX, where it makes no difference to the file.
Export grid — shown only in XML + data mode when the scene holds one or
more voxel grids. Tick it to reveal a checklist of the
scene's grids; each grid you check is written into the bundle's .xml as a
top-level <grid> block (its center, size, Nx/Ny/Nz subdivisions, and
z-rotation). This closes the round-trip for a file like Helios' sphere.xml —
import it (the <grid> becomes a voxel box), then re-export with the grid
ticked and the saved XML carries the grid back out, ready to drive
leaf-area-density or Helios triangulation
again. Leaving the box unticked (or checking it but adding no grids) writes no
<grid> blocks.
The per-object file split is always kept (in XML mode the metadata references each data file by scan). Edits (crop, translation, filtering) are baked into the exported coordinates — what you see is what gets written.
After you choose a save location the export dialog closes and a progress pill appears at the top of the viewer, showing a live percentage as each object is written — it names the object it is on (Writing plot_A (2/5)), and the objects are weighted by point count, so a batch holding one big cloud and three small ones doesn't jump to 75% and stall. The pill has a cancel button; stopping a batch removes the files it had already written, so you never find half a bundle in the destination folder. In XML + data mode the bar advances per scan while the scans are loaded, then parks on Writing Helios scan bundle for the single write that produces the files. The pill clears and a toast confirms the file count when the write finishes — there's no need to click Export twice.
Mesh formats¶
Click a format and pick the destination in the save dialog; the file is written when you confirm, and a toast reports what was saved. The suggested filename is the mesh's name in the Meshes panel (spaces become underscores), so renaming a mesh there also names its export.
| Format | Carries |
|---|---|
.obj |
Vertices, faces, normals, UVs and materials (see below) |
.ply |
Vertices, faces and per-vertex color |
.stl |
Triangles only (no color or topology metadata) |
A mesh is written where it is drawn: its position, rotation and scale — set by the Transform tool, or by an ICP alignment — are applied to the exported vertices, so two aligned meshes stay aligned when you open the files elsewhere or import them back.
A textured mesh — a generated plant, or an OBJ you imported with its materials —
exports to .obj as a bundle: the .obj, a .mtl material library, and one
image per textured material, all written together in the folder you chose. That
is what lets the model round-trip: re-importing the .obj picks the .mtl and
its images back up and the plant comes back textured. Move the three together;
an .obj on its own re-imports as plain gray geometry.
Organs with no texture (petioles, internodes, stems) keep their color too — they
are grouped by color into materials in the same .mtl, since OBJ has no portable
per-vertex color. A mesh with only vertex colors and no textures therefore still
exports an .obj + .mtl pair; one with no color information at all exports as
a single .obj.
Generated plants also write their Helios structure XML (<name>_helios.xml)
beside the mesh.
Skeleton formats¶
| Format | Carries |
|---|---|
.json |
Full graph: nodes, edges, branch orders, per-node attributes |
.obj |
Line segments only — suitable for visualization in other tools |
Use .json if you want to do further analysis programmatically. Use
.obj if you want a quick visualization in Blender or MeshLab.
As with the other object types, clicking a format opens a save dialog; the file is written where you choose and a toast confirms it.
Projects¶
A project (.phyto) saves the whole scene into one self-contained file
and opens it again exactly as you left it. It contains:
- every point cloud with all its points and scalar fields, including edits you have not baked. Undo still works on those edits after reopening;
- every mesh, skeleton, QSM and LAD result, with their positions;
- hand labels you have painted but not yet baked into the display;
- color modes, color ranges, display filters, point size, mesh opacity and coloring, measurements and picked points, the scene origin and the camera;
- the tree inventory, with your species, status and label entries for every cloud you inventoried.
The file does not refer back to the files you imported. It holds the data itself, so you can move it to another computer or send it to someone. Its size depends on the clouds in it. Point data is compressed losslessly (about 27 bytes per point for a RIEGL scan with its GPS time, reflectance and amplitude). The display cache is left out whenever it can be rebuilt. So on a computer that has never shown these clouds, opening the project takes about as long as the first import did, while Phytograph rebuilds each cloud's display.
| Command | Shortcut | |
|---|---|---|
| File → Save Project | Cmd/Ctrl+Shift+S | Save to the current project file, or ask for a name the first time. |
| File → Save Project As… | Save under a new name. | |
| File → Open Project… | Cmd/Ctrl+O | Open a project. It replaces the current scene, so you are asked first when the scene has unsaved changes. |
You can also open a project by double-clicking the .phyto file, or by
dropping it onto the Phytograph window. Dropping a project together with
other files opens the project and imports nothing else, since the project
replaces the scene.
While a project saves or opens, the window is locked behind a progress bar you can cancel, so no edit can slip in half-way through. A canceled or failed save leaves any existing project file untouched.
Closing Phytograph with unsaved changes asks first. Save Project… in that prompt cancels the close and saves; once the save finishes, closing no longer asks.
What a project does not keep
Undo history from before the save, app-wide settings (theme, the class palette library), and the live growth state of a generated plant. The plant's mesh, parameters and seed are kept, so it can be regenerated, but it cannot be grown on from its saved age.
What's next¶
- Viewer navigation — get comfortable moving the camera.
- Clean a point cloud — once your scan is loaded.