Point cloud coordinate transform
Move a point cloud into another frame: paste a 4×4 transform, apply it point by point in your browser, check the before/after overlay, then export XYZ or CSV. Nothing is uploaded and there is no sign-up.
Drop a point cloud file here
Supports .pcd (ascii / binary) and .ply (ascii / binary, either endianness). Parsing and transforming happen on this page.
Transform matrix
Row-major, four rows of four. Points are column vectors (p′ = M · p). You can also paste 12 numbers from a KITTI extrinsic, or 7 from TUM (tx ty tz qx qy qz qw).
Axis convention presets
How it works
- Drop in a PCD or PLY file. Parsing and transforming both happen on this page.
- Paste a matrix, or start from an axis convention preset (Y-up→Z-up, optical→body and so on).
- Check where the grey (original) and blue (result) clouds sit relative to each other, then export.
How to write the matrix: row-major, p′ = M · p
The page shows four rows of four, matching what you copy out of tf_echo, T.matrix() or print(T). The first three columns are rotation, the fourth is translation:
x' = r00*x + r01*y + r02*z + tx
y' = r10*x + r11*y + r12*z + ty
z' = r20*x + r21*y + r22*z + tz
The last row must be 0 0 0 1. If it is not, the page says so instead of producing plausible-looking but wrong coordinates.
Column-major: the classic trap
Eigen, OpenGL and many SDKs store matrices column-major. Dump the 16 numbers straight out of memory and the translation lands in the last row — you get a matrix ending in something like 4.2 -1.7 0.3 1.
Press Transpose and you are done. Worth remembering: transposing a rotation equals inverting it, so mixing up “camera frame → body frame” with “body → camera” is exactly one transpose — visible in the preview as the cloud rotating the wrong way.
What the presets cover
| Preset | When to use it | Maths |
|---|---|---|
| Y-up → Z-up | Unity / OpenGL scenes are Y-up; ROS and RViz are Z-up | Rotate +90° about X, (x,y,z) → (x,−z,y) |
| Z-up → Y-up | The reverse: ROS clouds into Unity / three.js | Rotate −90° about X |
| Optical → body | Camera optical (z forward, x right, y down) to ROS body (x forward, y left, z up) | Maps optical z onto body x |
| Body → optical | The inverse, used when projecting lidar points into an image | Maps body x onto optical z |
| mm → m | Scanners and CAD export millimetres; SLAM works in metres | Every coordinate ×0.001 |
| Identity | Sanity-check the import/export path first | Leaves coordinates untouched |
Normals rotate, other fields do not
If the file carries nx/ny/nz or normal_x/normal_y/normal_z, those are rotated by the rotation block (no translation) — a normal is a direction, not a position.
Intensity, colour and timestamps are copied as-is. There is no way to tell from the file which column is a vector and which is a scalar, and guessing is worse than leaving it alone: rotate an intensity as if it were a direction and nothing looks broken.
Symptom → cause
| Symptom | Usually this |
|---|---|
| Shape is fine but everything is somewhere else | Translation column is misplaced — the matrix is column-major, press Transpose; or you applied a pose instead of a cloud transform (they are inverses) |
| The cloud is mirrored | Negative determinant. Some frame changes really do mirror — confirm that is intended |
| Everything is 1000× too small or large | Unit mismatch: use the mm → m preset |
| Nothing visible after applying | NaN / Inf in the matrix, or a degenerate rotation block (determinant near 0) |
| It rotated the wrong way | You applied the forward transform; press Invert |
Why local only: point clouds often carry customer site coordinates or unpublished scene data — sometimes enough to infer where equipment is installed. This tool has no backend, so neither the matrix nor the cloud leaves your browser.
Point cloud editing (crop and voxel downsampling) is live too — straighten first, then crop. Registration is next, and its output is exactly a 4×4 transform you can check on this page.