User guide
The web viewer opens and analyses point clouds in your browser. Files are read locally and never uploaded. Each section links to a demo that loads sample data and runs the analysis.
Opening data
- Drop files anywhere on the page, or use Open…. Several files can be opened at once.
- Point clouds: PLY, PCD, LAS/LAZ and XYZ-style text (
.xyz .txt .csv .pts .asc). Meshes: OBJ, STL and PLY with faces. - RGB, intensity and classification are read when the file has them, and so are normals
(
nx ny nz) in PLY. - Gaussian Splatting output (the PLY of INRIA's trainer or nerfstudio, or a
.splatfile) opens as one point per Gaussian, at its center. The color comes from the SH DC term; opacity (0–1) and size (the standard deviation along the longest axis) are kept, so you can color by opacity, clean up with the filter below and compare the result with LiDAR or a mesh like any cloud. - Open URL downloads a file from a web server. The server must allow cross-origin requests (CORS).
- COPC files (
.copc.laz) are not downloaded whole: the viewer reads every octree level that fits Max points per file with range requests, so large public datasets open in seconds. Try a 10.6M-point tile. - Georeferenced clouds (UTM, ECEF) keep full precision, in any order and mixed with local clouds: each cloud is drawn relative to its own centre. Picked coordinates, measurements and saved files always use the original coordinates; the global shift in the status bar (from the first file) is only the origin of the view.
Moving around
| Mouse | Touch | Action |
|---|---|---|
| Left drag | One finger | Orbit |
| Right drag | Two fingers | Pan |
| Wheel | Pinch | Zoom |
| Click | Tap | Pick a point |
| Double-click | Double tap | Orbit around that point |
Use Fit (F) and the Top, Front, Side and Iso views to reset the camera. Point size, Round (discs instead of squares), EDL (depth shading) and Point budget (points drawn at once) are in the toolbar. Trajectories and pose graph edges are drawn 2 pixels wide. On a phone, Panels opens the side panels as a sheet.
Next to the point size, Adaptive draws each point as wide as the local point spacing (in scene units), so surfaces close up when you come near; EDL is off in that mode. The colour swatch sets the background. Views remembers camera positions under a name, to come back to them; they are part of share links and sessions.
Coloring and classes
Each cloud in the list has a color mode: RGB, intensity, classification, solid color, distances, normals or hillshade. When a cloud has ASPRS classes, the Classes panel shows or hides each class in every cloud.
Scalar fields
Every per-point value is a scalar field: intensity, Z, computed distances and M3C2 results, splat opacity and size, raster heights, and fields you compute. Pick one in a cloud's color menu, or in the Scalar fields panel, which shows its histogram and statistics (percentiles included) and colors the cloud with the colorbar's ramp and range. Keep range / Remove range cut the points by value into a new cloud.
The Calculator makes a new field from an expression such as abs([C2C distance]) * 1000
or intensity > 200 and Z < 5: fields by name (in brackets when the name has spaces),
+ - * / % ^, comparisons, and or not, and abs sqrt exp log log10 sin cos tan
floor ceil round min max atan2 clamp if. New fields are saved with the cloud in PLY and LAS.
Distances (C2C, C2M)
In Distance, pick the compared cloud and the reference, then press Compute:
- Against a cloud (C2C), each point gets the distance to its nearest reference point.
- Against a mesh (C2M), each point gets the distance to the surface. With signed, points behind the surface are negative.
The result is shown with a color ramp, an adjustable range, a colorbar and summary statistics (mean, RMS, percentiles).
Try: two LiDAR scansChange detection (M3C2)
Choose Method: M3C2 in the Distance panel. At each core point, a normal is fitted to the reference within the normal radius. Both clouds are then averaged in a cylinder of the projection radius along that normal, up to the max depth. The result is a new cloud with:
m3c2_distance, the change along the normal;lod95, the 95 % level of detection;significant, which is 1 where the change exceeds the level of detection.
Core points where either cylinder has too few points are grey.
Try: a landslideMap quality against ground truth
Choose Method: Map quality in the Distance panel, with the map to judge as Compared and a ground-truth map as Reference (for a SLAM result, the same scans built with ground-truth poses). The metrics are:
- accuracy, the RMS distance of the map's points within the inlier distance of the truth;
- precision and completeness, the share of the map near the truth and of the truth near the map, and their F1; Chamfer, the mean matched distance both ways;
- AWD, the average Wasserstein distance between the Gaussians of the voxels both maps fill, and SCS, its spread among neighbouring voxels (large where the map is bent rather than shifted).
The map is coloured by its distance to the truth, up to the inlier distance. On KITTI (scans thinned to 0.2 m, the truth built from the ground-truth poses), loop closure and IMU gravity raise F1 at 0.3 m from 0.45 to 0.70 on sequence 07 (completeness 44 → 68 %, AWD 0.33 → 0.25 m) and from 0.16 to 0.42 on 09. KITTI's ground-truth poses are themselves locally noisy, so no map reaches 100 %.
Cut / fill volume
In Volume, pick a before and an after surface. Each can be a cloud, a mesh or a constant height. Both are rasterized on a grid of the chosen cell size, using the mean, lowest or highest point per cell. Fill empty cells interpolates holes.
The result gives the added (fill), removed (cut) and net volume and the areas, with the height difference of each cell shown as a cloud.
Try: a stockpileManual alignment
Pick pairs (P): click a point on the moved cloud, then the same spot on the reference, and repeat for at least three pairs spread over the scene (hide the other cloud if it is in the way; Backspace takes the last pick back). Align moves the cloud so the pairs meet in the least-squares sense and lists the residual of each pair. It is a good start for ICP when the clouds are far apart.
Under Move by hand, Move and Rotate show a gizmo on the moved cloud (rotating about its centre); drag it, then Apply. Apply matrix applies a typed row-major 4×4 rigid transform, for example one from CloudCompare. Every alignment can be undone.
Alignment (ICP)
In Registration (ICP), Align moves one cloud onto a reference, point-to-plane or point-to-point. It can pre-align the centroids and trim non-overlapping points. The panel shows the RMS before and after and the 4×4 transform, and Undo alignment reverts it.
Pose graph (SLAM)
To see it at work, open the SLAM loop closure demo: a drive made in the browser, once and a bit round a city block over a hill, with drifting odometry; it finds the loops, ties the keyframes to the IMU's gravity and colors the map by how far each point moved. The real drive demo does the same from a ROS 2 bag of a real drive (part of the NCLT dataset, 10 MB): odometry first, as the bag holds scans and IMU but no poses. The two seasons demo joins the same block driven in June onto it and compares the maps where the drives meet: the changes between April and June.
Fix a SLAM map by closing loops, by hand or automatically. In Pose graph (SLAM), Open folder… takes a folder holding the poses and one scan per pose; Open files… takes the same files picked one by one.
A ROS bag (ROS 1 .bag, or the .mcap file of a ROS 2 bag) opens with Open
files… too, and so do scans without poses: LiDAR odometry places the scans first, in the browser. It takes the
bag's sensor_msgs/PointCloud2 topic with the most messages, registers each scan onto a local map of
the ones before it (points between the Range limits, 1.5–80 m by default), and keeps a scan every
Keyframe metres of travel as a node. When the bag has a sensor_msgs/Imu topic, its up direction
at each keyframe (from the orientation, else the mean acceleration) then levels the graph as IMU gravity
does, with the IMU's mounting estimated. Deskew scans undoes the sensor's motion during each scan
(a spinning sensor sees the end of its sweep from somewhere else than its start) from the points' time field
(t, time, timestamp…), or, without one, from the order a spinning sensor
takes the points; scans sorted by ring or corrected already (KITTI's) are left as they are. It is off by
default: on the drives tried (a Segway at 1 m/s, an ATV at 3–5 m/s with a 10 Hz sensor) it made no measurable
difference against the ground truth. Compressed bags
(lz4, zstd, bz2) open as they are; ROS 2 bags stored in SQLite (.db3) do not open: record them as
MCAP, or convert them. A
2.4 GB ROS 1 bag of a Velodyne HDL-32E (1,263 scans, 126 s of driving) opens in about five minutes.
- Poses: a g2o graph (
VERTEX_SE3:QUAT,EDGE_SE3:QUAT,FIX), or a TUM / KITTI trajectory, which becomes a chain of odometry edges (σ odom. sets their weight). - Scans: PCD, PLY, LAS/LAZ, XYZ or KITTI Velodyne
.bin, in the pose's frame, named by frame number or vertex id (000042.pcd); without numbers they are matched in name order, one per pose. A Scan → pose frame matrix moves them first; a KITTIcalib.txtin the folder supplies itsTr(Velodyne to camera) when that box is empty. - Scans are thinned to one point per Voxel (0.4 m by default) for registration and the map, and Shown points per scan are drawn, each scan in its own color at its node.
Nodes are white dots, odometry edges blue lines and loop edges orange. Type two node ids in Node A and Node B, or use Pick nodes and click two dots. Add loop registers B's scan onto A's with ICP, starting from their current relative pose, adds the result as a loop edge (weighted by σ loop) and optimises the graph (Levenberg–Marquardt). Robust loops puts a Huber kernel on loop edges, so a wrong loop bends the map less. Undo takes back the last loop, removal or optimisation.
Move A puts a gizmo on Node A: drag its arrows to move it (or turn it, with Rotate); With the nodes after it moves the rest of the path along, to put a broken stretch roughly in place before adding a loop there. Fix A holds a node in place when optimising (it turns red); the first node of a trajectory is fixed from the start. Each drag and each fix can be undone.
Find loops automatically proposes node pairs at least Min travel metres apart along the path, not already joined, at most one every Spacing metres, that the graph puts within Radius metres of each other plus Drift % of the path between them: odometry drifts with distance, so after a long way round a revisit is looked for further away (with 3 %, 60 m after 1.7 km). Each is registered with ICP from the graph's relative pose. When that overlaps too little and the drift allowance is large (5 m or more), it is registered again as if both scans were taken at the same place, from Retry headings headings. A pair is kept when at least Min overlap percent of B's points lie within the Inlier distance of A's and the registration puts B no further from where the graph has it than the drift allows; then the graph is optimised. Turn on Robust loops to soften any wrong loop that slips through, and check the list below. Undo removes all the loops found at once.
Floor constraint removes the tilt and height drift that odometry leaves. Add floor looks for the
floor under every keyframe: the plane with the most scan points within Threshold, at most Max
tilt from the Up axis and below the sensor (Auto tries +Z for LiDAR, −Y for scans moved into
KITTI's camera frame, and +Y). The keyframes that see it are tied to one floor plane (a g2o
VERTEX_PLANE with EDGE_SE3_PLANE edges, weighted by σ tilt and σ
height), and the graph is optimised. Undo removes the floor again. Use it on sites that are flat
overall (a car park, a floor of a building): it asserts that the ground does not bend, and on hills it
flattens the path. Planes per stretch of path were tried and do the same, a little at a time, since each
still asserts a flat stretch; on hills use IMU gravity instead.
Remove dynamic leaves out the points of things that moved while the scans were taken, as in
Removert and dynamic-3d-object-removal: each scan becomes a range image (the nearest return per 3° of azimuth
and elevation), and a point is dynamic when at least Votes of the scans within Window keyframes
on either side saw through it (their beam in its direction reached more than Margin plus 2 % of the
range further) and more of them saw through it than hit it. Dynamic points then gather into objects per scan
(0.7 m apart, 15 points or more): stray ones go back, the rest of an object joins. The result is the map
without them (_static) and them in red (_dynamic), both ordinary clouds. On
SemanticKITTI 07 (its moving classes, KISS-ICP poses, scans thinned to 0.4 m) it scores precision 0.85,
recall 0.55, F1 0.67 and keeps 99.9 % of the static points; the 1,101 scans take about 8 s, parts of the drive judged on the worker pool (35 s on one thread).
On PandaSet scene 019 (a busy San Francisco street, 80 scans, 8 s; moving vehicles from the labelled cuboids)
it scores precision 0.90, recall 0.33 and keeps 99.8 % of the static points: traffic driving along with the
car is never seen through in so short a drive, so it stays.
A beam that passed more than half a degree above a point does not count as seeing through it: on ground the
beams graze, a beam a little higher lands far beyond, and a 32-beam LiDAR's rings leave gaps that wide (on
hdl_graph_slam's hdl_400 drive this took the dynamic share from 7.1 to 4.7 %).
Analyse the map hands the corrected map to the analysis tools. Ground & DEM builds the map,
keeps its ground points (the Cloth Simulation Filter) and rasterizes them into a terrain model of DEM cell
metres, colored between the 2nd and 98th percentile of its heights (save it from the Rasterize panel).
Compare parts (M3C2) builds the map of the path before Split at node and of the path from it, and
runs M3C2 between them: for two joined sessions (the split then defaults to where the second starts) what
changed between them, and for one drive that passes a place twice how well the passes agree. On KITTI 07 split
at node 1000, where the drive comes back to its start, the mean M3C2 distance between the passes went from
0.17 m with the odometry alone to 0.02 m after Find loops and IMU gravity. Only the keyframes of each part
within 50 m of the other part's path are compared, so a long drive fits in memory, and the colors span the
98th percentile of the changes. KITTI 00 (recorded on 3 October 2011) joined with 07 (30 September, three
days earlier) at a street both drove (87 % overlap, then 202 loops between the two) compares at 214,000 core
points in 6 s: the buildings agree (mean 0.002 m) and 12 % of the points changed significantly, mostly the
cars parked along the streets. Changes then lists the changed objects, largest first: core points
that changed significantly by 0.3 m or more, the same way, and within 1 m of each other, with each object's
size and mean change; click one to centre the view on it. The M3C2 result gets a change_object
field (each object's rank) for the scalar field tools. Between the two KITTI drives the largest objects are
rows of parked cars that moved by 0.7 to 0.9 m or went.
IMU gravity is the constraint for hills. Add gravity… takes a KITTI oxts folder (roll and
pitch of each frame, turned into the Velodyne frame by calib_imu_to_velo.txt when it is there, and by
the scan → pose matrix the scans were opened with) or a text file of frame ux uy uz lines (up in scan
coordinates). Each keyframe is held to its measured up direction (σ tilt, raised to the IMU's noise
when that is larger; Estimate the IMU's mounting first finds how a real IMU sits against the LiDAR, turned,
tilted or upside down, from the drive itself, and uses it when that clearly helps); world up is taken from the
first keyframes, where odometry has drifted least. It assumes nothing about the ground, so the path loses its
tilt drift, and the height drift that follows, while hills stay hills. After Find loops, on KITTI with KISS-ICP
odometry, it took the height RMSE from 0.41 to 0.14 m on sequence 07, from 5.9 to 1.8 m on the hills of 09
(ATE RMSE 6.3 → 2.7 m) and from 9.4 to 1.0 m on 00 (ATE RMSE 11.1 → 6.1 m), in a few seconds each.
Gravity ties are not written to g2o.
Join another graph adds a second session of the same place, recorded in its own frame: give a node of this graph (Node here; empty takes Node A) and a node of the other one standing near it (Node there; empty takes its first node), then Join folder… and pick the other session's folder (poses and scans, read with the same loading options). The two scans are registered with ICP from several Headings; the best overlap (at least Min overlap) places the whole second graph, becomes a loop edge between the two, and the joined graph is optimised. Its ids are shifted past this graph's when they clash. Find loops then ties the two sessions together wherever they meet. A join cannot be undone. Join files… takes the other session's files instead, or a ROS bag: odometry places its scans first, and when both bags have an IMU, the up directions of both level the joined graph. Two NCLT drives round the same block, in April and in June 2012, joined this way line up with each other to 0.16 m against the ground truth.
Edges by error colors every edge by how far it disagrees with the optimised graph (√χ², in standard deviations) in the color ramp, and Loops, worst first lists the loop edges by that error: a wrong loop shows up at the top. Click one to select its two nodes, ✕ to remove it and optimise again, or remove every loop with an error above a threshold with Remove loops. Scan colors shows a color per keyframe or colors by height; Show scans hides them to see the graph alone, and Pose axes draws each keyframe's axes (x red, y green, z blue). Play replays the drive: the keyframes' scans appear one after the other at Rate per second, the newest in white, with the camera following the vehicle (Follow), like watching LiDAR odometry run. Loops, optimisations, gravity and undo glide the map into its new place rather than jump (graphs of up to 3,000 keyframes); Go to A centres the view on Node A. This drive opens without corrections, to replay it or close its loop by hand (Node A 0, Node B 128). The scans are drawn without Eye-Dome Lighting, which would darken such thinned points, and the graph on top of them.
Save g2o writes the graph with its edges; KITTI and TUM write the optimised poses.
Build map cloud adds every scan at its optimised pose to the cloud list, thinned to Map voxel,
for measuring, comparing or exporting like any other cloud.
Compare with the start builds the map with a correction field on every point: how far it
moved from its place as loaded, and colors the map by it (a scalar field like any other, with its histogram
and range filter): where the loops, floor, gravity and moves shifted the map, and by how much.
Tried on KITTI odometry sequences with real Velodyne scans and KISS-ICP odometry, against the ground truth,
all with the default settings. Sequence 07 (1,101 scans, 2.1 GB) opens in about 20 s; Find loops brings the
end back from 2.04 m to 0.14 m off (ATE RMSE 2.14 → 1.28 m). Sequence 09 (1,591 scans, 1.7 km over 38 m of
hills) drifts 25 m by the time it returns to its start; Find loops finds that revisit (registered as the same
place) and takes the end error to 4.0 m and the ATE RMSE from 16.4 to 6.5 m (SE(3)-aligned 7.3 → 1.8 m).
Sequence 00 (4,541 scans, 8.6 GB, 3.7 km through a town with many revisits) opens in about 90 s using
1.3 GB; Find loops adds 121 loops in about 20 s and takes the end error from 24.8 to 3.2 m and the
SE(3)-aligned ATE from 8.9 to 2.1 m (1.9 m with Robust loops).
The floor constraint made sequence 09 worse, as said above.
On NCLT session 2012-04-29 (a Segway round the University of Michigan campus: 2,777 keyframes, 3.2 km,
prepared with scripts/prepare_nclt.py), Find loops adds 168 loops in 11 s and takes the SE(3)-aligned
ATE from 4.83 to 1.64 m.
Vector map (Lanelet2)
To draft roads from measurements, open a point cloud and a TUM, KITTI or CSV trajectory, then choose them under Build from a trajectory. Both must use the same coordinate frame in metres. Set lane counts, width, traffic side and speed under Road options. The trajectory must follow the outside forward lane. Elevations come from the point cloud. The builder follows intensity peaks, curb steps and the end of ground coverage; boundaries without a detected feature use the configured width. Inferred lines are anchored to detected outer edges by default; clear the checkbox to keep them relative to the trajectory. They remain inferred after adjustment. The report gives the measured fraction for each boundary and warns about missing data. Scan gaps can look like road edges. Review the draft, especially repeated passes, lane changes and junctions; use Connect to add junction links. A whole build can be undone in one step. Use Frame map to focus the view on the road boundaries, then Edit vertices to drag the yellow boundary points. Each move keeps the vertex's height and updates every lane sharing that boundary, including lanes travelling in the opposite direction. A completed drag is one Undo step; Escape cancels an unfinished drag. Drag empty space to orbit as usual. Moving a section endpoint can leave a gap to the next section: review the validation messages and move its matching endpoint too. Inferred geometry still needs review after editing.
Open a point cloud, choose Top, and expand Vector map (Lanelet2). Use Draw road to click along a road and press Enter or double-click to build it. Backspace removes the last point; Esc cancels. Clicked cloud points supply the road's elevation; clicks without a cloud point use a horizontal plane at the map's height.
Set forward and backward lane counts, lane width, speed limit in km/h and the traffic side in Road options before drawing. Traffic keeps left by default. For a road with both directions, the drawn line separates them; for a one-way road, it is the road centre. A positive piece length divides the road into connected lane sections; zero keeps it whole. Adjacent lanes share boundaries.
- Connect: click the lane to leave, then the lane to enter, to create a turning lane.
- Stop line: click its location on a lane; it spans adjacent lanes going the same way.
- Traffic light: click a lane to add a light and, if needed, a stop line.
- Crosswalk: click a lane to add a 4 m wide crossing and stop lines 1.5 m before it.
- Select: click a lane to inspect its connections, change its speed limit or delete it.
The panel lists Autoware errors and warnings after each edit. Click a lane issue to inspect and
frame that lane. Undo restores up to 100 edits, including Clear.
Open… reads Lanelet2 .osm or vectormap JSON; an .osm file can also
be dropped into the viewer. Opening replaces the current map and clears its undo history.
Import notes lists the chosen coordinate projection and any map elements the reader cannot
represent. Those elements will be absent from the saved map; retain the original file.
Save for Autoware downloads lanelet2_map.osm and
map_projector_info.yaml. Allow multiple downloads if the browser prompts.
New maps use the Local projector; maps opened with a supported georeference retain it.
MGRS maps with mgrs_code node tags retain their UTM grid coordinates and export
with projector_type: MGRS. Only a single 100 km UTM grid is supported;
polar UPS grids and maps spanning multiple squares are rejected.
Check reported issues and the coordinate frame before using the map in Autoware.
Geometry and editing run locally through vectormap-rs.
Trajectories (ATE / RPE)
Open trajectories like clouds: TUM (timestamp tx ty tz qx qy qz qw, or without the quaternion),
KITTI poses (12 numbers per line, a 3×4 matrix) and CSV with a header such as
timestamp,x,y,z,qx,qy,qz,qw. .tum and .kitti files are always
trajectories; a .txt or .csv file is one when its first line looks like it, and a
point cloud otherwise. They are listed in Trajectories and drawn as lines, which you can hide or recolor.
Evaluate compares an estimate with a reference, with the same definitions as the Python CLI
(ca traj-evaluate), so both give the same numbers:
- Every reference pose is matched to the estimate at its timestamp, interpolated between the two estimate poses around it when both are within Max time diff. (0.05 s by default). KITTI poses have no timestamps: pose i gets time i, so compare them with another KITTI file.
- Alignment first moves the estimate onto the reference: by the first pose (Origin), the best rigid motion (SE(3)) or rigid motion and scale (Sim(3), Umeyama; for monocular odometry).
- ATE is the position error of each matched pose (RMSE, mean, median, standard deviation, min, max); with orientations in both files, the rotation error too.
- RPE compares the motion between two poses RPE delta frames apart, or the first pose that many metres further along the reference (KITTI style, also as a percentage of the distance): translation, and rotation in degrees.
The aligned estimate is added as a line colored by its ATE in the current color ramp. Save CSV writes the time, aligned estimate, reference and errors of every matched pose. An Origin or SE(3) alignment can also move a cloud, e.g. the map built from the estimate, into the reference's frame (undo with Ctrl+Z).
Filters and ground extraction
Each filter creates a new cloud and keeps the original:
- Voxel subsampling keeps one point per cube of the given edge.
- Minimum distance keeps points so that no two are closer than the given distance, which gives an even density (CloudCompare's "space" subsampling).
- Octree level keeps the point nearest the centre of each cell of that octree level: level 10 splits the cloud's bounding cube 1,024 times along each axis.
- Random subsampling keeps a given number of points.
- Remove outliers (SOR) drops points whose mean distance to their k neighbours is more than ratio standard deviations above average.
- Splat cleanup (Gaussian splats only) drops nearly transparent Gaussians and huge "floaters": it keeps those with at least the given opacity and at most the given size.
- Extract ground (CSF) drops a simulated cloth onto the upside-down cloud. Points close to the cloth are ground. Output is a copy classified ground / other, or only one of the two.
Rasterize (DEM / DSM)
Rasterize grids a cloud in XY: each cell takes the mean, minimum, maximum or a percentile of the heights in it, and empty cells can be filled from their neighbours. With a classified cloud, pick Ground for a terrain model (DTM) or all classes for a surface model (DSM); both use the same grid. The raster appears as a cloud colored by height. Save GeoTIFF writes a Float32 GeoTIFF placed by its pixel size and corner (no coordinate system is set: assign it in your GIS); Save PNG writes it in the current color ramp, optionally hillshaded.
Meshing (Delaunay 2.5D)
Mesh triangulates a cloud as seen from above (like CloudCompare's Delaunay 2.5D (XY plane)):
the points' XY positions are joined by a Delaunay triangulation and keep their heights, which suits terrain
and other surfaces with one height per position. Triangles with a horizontal edge longer than the
max edge length are dropped; short bridges over gaps can remain. Left empty, it is four times
the median edge, and 0 keeps every triangle up to the convex hull. Clouds of more than 5 million points are
first thinned with a voxel filter, and the status says so. The mesh, named <cloud>_mesh, can
be the reference of a C2M distance or a surface of a volume, and its ⤓ button saves it as binary PLY
or OBJ. The mesh panel also offers wireframe inspection, OBJ/PLY export and a source/settings summary
JSON. Prepare terrain mesh in Map coverage selects a ground cloud; Mesh compared After cells
in Volume selects compared cell centers at After height. Use mesh as After surface prepares
volume calculation with interpolation off. Review Before and grid settings before computing.
See the terrain mesh workflow for scope and limitations.
Normals
Compute normals fits a plane to the k nearest neighbours of each point. The normals face up,
or away from the cloud's centre. They are shown by the Normals color mode (direction as RGB) and
the Hillshade mode (lit from the north-west). They are saved in PLY as nx ny nz.
Merge and split
- Merge visible clouds builds one cloud. Colors and attributes are kept, clouds without RGB take
their display color, and a
sourceattribute records each point's cloud. - Split makes one cloud per classification code, or turns a merged cloud back into its files.
Shapes and clusters
Shapes & clusters finds planes, cylinders or spheres with RANSAC (points within the distance of a shape, at least the minimum number of them), or groups points into clusters: points closer than the link distance belong together, and clusters smaller than the minimum are noise. The result is one cloud colored by segment, or a cloud per segment; the table lists each shape's parameters and fit (RMS). Normals are estimated first if the cloud has none. In a building, find the planes first, then look for cylinders in what is left.
Clipping and cropping
Clipping box limits the view to a box set with per-axis sliders. The X, Y and Z buttons cut a thin section and look straight at it. Crop copies the points inside the box into new clouds.
Lasso segmentation and undo
Segment (S) draws a lasso: click around the points to cut out (Backspace removes the last vertex), then Keep inside (Enter), Remove inside (Delete) or Split into both parts. It cuts every visible cloud through the depth of the view, and skips points hidden by the clipping box or a hidden class.
↶ / ↷ (Ctrl+Z / Ctrl+Shift+Z) undo and redo segmentation, cropping, filters, ground extraction, merge, split, M3C2, volume and ICP. Removing a cloud with ✕ is not undone: it frees the memory right away.
Cross-section profiles
Press Draw line and click points on the view (a top view works best). Finish with a double-click or Enter. Every visible cloud's points within the band width are plotted as distance along the line against height, in the cloud's color.
Hover over the plot to read values. 1:1 uses the same scale on both axes, and Save CSV writes
cloud, distance, x, y, z. Drawing a profile across two surveys compares them directly.
Picking and measuring
Click a point to see its exact coordinates, color and distance value. Measure (M) takes two clicks and shows their distance and ΔX / ΔY / ΔZ. Esc cancels.
Label (L) puts a text label on the point you click. It starts as the point's height, and you edit the text in the Labels panel. Labels are part of share links and sessions. Image saves the view as a PNG, with the labels, measurements, colorbar and profile plot drawn on it.
Saving results
The ⤓ button next to a cloud offers PLY, LAS, LAZ and CSV; the Distance panel saves the cloud whose
distances are shown. A PLY file includes colors and all attributes: intensity, classification, normals,
M3C2 fields and distances as scalar_C2C_distance. CloudCompare reads these as scalar fields.
A mesh's ⤓ offers binary PLY and OBJ.
LAS and LAZ files are LAS 1.4 (point format 6, or 7 with colors) with intensity and classification; distances,
normals and M3C2 fields are stored as float extra bytes, which CloudCompare, PDAL and LAStools
load as scalar fields. Coordinates keep their precision: a cloud read from LAS is written on its original
grid (e.g. 1 cm), and other clouds at the finest power of ten that fits (down to 0.1 µm).
QA report and gates
The QA report panel collects the metrics of the latest results: distances (mean, median, RMS, 95th percentile…), volume, ICP and point-pair alignment, rasters, shapes and trajectories (ATE, RPE). Add gate sets a pass / fail threshold on any of them, with fail or warn severity; each gate shows its status as results change, and gates are kept in sessions and share links.
Save report writes a self-contained HTML page with the verdict, the gates, every result and the view.
Save JSON writes the same metrics with a gate summary in the format of the command line's
ca check (cloudanalyzer.gate_summary.v0.1), so CI can read it.
Sharing and sessions
- Share copies a link to the current view. It includes the camera, display settings, each cloud's visibility, color mode and ICP transform, recomputed distances, hidden classes, the clipping box and the profile line.
- Clouds opened from a URL, and the demos, are downloaded again by whoever opens the link. Local files are not uploaded, so the link asks for them by name.
- Save session writes the same state to a JSON file. Open it together with its files, or first.
- Save workspace snapshot saves the current loaded clouds and meshes, including processed results,
with the edited map, lane notes and display settings in
project.cloudanalyzer.zip. Open that ZIP to resume without rerunning filters. Double coordinates and supported float/byte vertex attributes are retained; current transforms are applied once. The limit is 64 MiB uncompressed and 127 clouds/meshes. Original pose-graph inputs and the verified generated-map review ZIP are included. Download original map package retrieves that archive; its saved audits describe the original pair, not later edits. Unloaded cloud detail stays external; Undo starts fresh. - Opening a workspace snapshot verifies and prepares every saved cloud, graph and map before committing. Failed or cancelled preparation leaves current work and Undo available. Editing current work during staging prevents replacement. Preparation temporarily needs memory for both workspaces; cancellation is handled between parsing and indexing steps.
- Under Filters, Reusable filter recipe saves an ordered sequence of voxel, minimum-distance, octree, SOR or splat-cleanup steps. Select point clouds and run the recipe; all results are prepared before sources are hidden. One Undo restores the batch. Result Recipe buttons download processing records with parameters and input/output/build hashes. Keep results and records together with a workspace snapshot. Recipes are bounded to 8 steps, 16 clouds and 4 million loaded points; preparation needs extra memory. Preview inputs remain previews.
- Browser autosave and Save project keep metadata and source references. Use a workspace snapshot or a separate result export to keep processed point records.
- A link like
…/app/?url=https://example.com/scan.lazopens a remote file directly (the server must allow CORS).?demo=volumestarts a demo.
Large files
- LAS and LAZ are read in chunks of about 50,000 points on a pool of workers, and binary PLY/PCD in 16 MB slices, so a file never has to fit in memory. The progress bar shows how far a load is, and Cancel stops it. A LAS/LAZ from Open URL is read the same way, with range requests.
- Files above Max points per file (50M by default) keep every n-th point. Every analysis uses these points.
- Full detail: zoomed in on a thinned LAS/LAZ file, the viewer draws every point of the file there, decoded from the file chunk by chunk as needed (like COPC, but for any LAS/LAZ, without converting it). The status bar counts the file's points and shows how many chunks are drawn at full density. It works in RGB, intensity, class and solid colors; for distances and other results, which only the loaded points have, the loaded points are drawn alone. Picking and measuring use the loaded points. Moving a cloud (ICP, alignment) turns full detail off for it. Untick Full detail in the toolbar to draw only the loaded points.
- Only the octree nodes and chunks that matter on screen are drawn, up to the point budget.
- Heavy steps run on a pool of workers: indexing, C2C/C2M, SOR and normals.
- The status bar shows the viewer's WebAssembly memory. Reload the page to free it after closing big clouds.
Checked against CloudCompare
C2C, SOR and CSF give the same results as CloudCompare 2.13 on a public LiDAR tile. M3C2 agrees to a thousandth of a foot, and ICP, normals and volume are close. The validation report has the numbers and the differences in conventions.
Python
The same Rust core is available to Python as cloudanalyzer_core, multi-threaded. It covers
reading, C2C/C2M, ICP, filters, CSF, volume, M3C2, profiles and normals. It is not on PyPI yet;
build it from rust/crates/ca-py with maturin, as the
README explains.