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.

How it works

  1. Drop in a PCD or PLY file. Parsing and transforming both happen on this page.
  2. Paste a matrix, or start from an axis convention preset (Y-up→Z-up, optical→body and so on).
  3. 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

PresetWhen to use itMaths
Y-up → Z-upUnity / OpenGL scenes are Y-up; ROS and RViz are Z-upRotate +90° about X, (x,y,z) → (x,−z,y)
Z-up → Y-upThe reverse: ROS clouds into Unity / three.jsRotate −90° about X
Optical → bodyCamera optical (z forward, x right, y down) to ROS body (x forward, y left, z up)Maps optical z onto body x
Body → opticalThe inverse, used when projecting lidar points into an imageMaps body x onto optical z
mm → mScanners and CAD export millimetres; SLAM works in metresEvery coordinate ×0.001
IdentitySanity-check the import/export path firstLeaves 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

SymptomUsually this
Shape is fine but everything is somewhere elseTranslation 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 mirroredNegative determinant. Some frame changes really do mirror — confirm that is intended
Everything is 1000× too small or largeUnit mismatch: use the mm → m preset
Nothing visible after applyingNaN / Inf in the matrix, or a degenerate rotation block (determinant near 0)
It rotated the wrong wayYou 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.