Convert points, transforms, quaternions, and SH between named 3D coordinate systems.
~200 lines of Python (numpy), ~400 lines of JavaScript (zero dependencies).
Interactive demo
![]() |
Python: JavaScript (ES module): <script type="module">
import { convert_points, convert_transforms,
convert_quaternions, convert_sh }
from 'https://cdn.jsdelivr.net/gh/brownvc/yupzup@main/yupzup.js';
</script>Inspired by a meme. Created as a joke. Written with Claude while we made breakfast. Use at your own risk. |
Every 3D framework has its own coordinate convention. OpenGL is Y-up Z-back. Blender is Z-up Y-forward. OpenCV is Y-down Z-forward. Unity is left-handed. And so on.
yupzup converts between any pair by name.
from yupzup import convert_points, convert_transforms, convert_quaternions
# Points — single (3,) or batched (N,3)
pts_opencv = convert_points(pts_opengl, 'opengl', 'opencv')
# 4x4 rigid transforms (camera extrinsics, poses) — single or batched (N,4,4)
T_unity = convert_transforms(T_blender, 'blender', 'unity')
# Quaternions [w, x, y, z] — single or batched (N,4)
q_ros = convert_quaternions(q_colmap, 'colmap', 'ros')| Convention | Systems |
|---|---|
| X=Right, Y=Up, Z=Back (right-handed) | opengl webgl threejs vulkan metal webgpu godot bevy maya houdini gltf arkit arcore nerf nerfstudio instant_ngp llff |
| Y=Right, X=Down, Z=Back (right-handed) | llff_file |
| X=Right, Y=Up, Z=Fwd (left-handed) | unity directx babylonjs |
| X=Right, Y=Down, Z=Fwd (right-handed) | opencv colmap 3dgs ros_camera |
| X=Right, Y=Fwd, Z=Up (right-handed) | blender 3dsmax maya_zup houdini_zup |
| X=Fwd, Y=Left, Z=Up (right-handed) | ros |
| X=Fwd, Y=Right, Z=Up (left-handed) | unreal |
| X=Left, Y=Up, Z=Fwd (left-handed) | pytorch3d |
Convert SH coefficients between framework conventions (ordering, normalization, Condon-Shortley phase) and rotate them between coordinate systems via Wigner-D matrices. Degree-independent.
from yupzup import convert_sh
# Convention only (reorder + rescale + phase toggle)
sh_graphics = convert_sh(sh_3dgs, '3dgs', 'graphics')
# Convention + coordinate rotation
sh_out = convert_sh(sh_in, '3dgs', 'graphics', src_coords='colmap', dst_coords='opengl')
# Coordinate rotation only (same convention)
sh_rotated = convert_sh(sh_in, '3dgs', '3dgs', src_coords='colmap', dst_coords='opengl')
# Multiple channels (e.g., RGB)
sh_rgb = convert_sh(sh_rgb_in, '3dgs', 'shtools') # shape (3, 16)| Name | Ordering | Normalization | CS Phase |
|---|---|---|---|
3dgs |
-+ | orthonormal | yes |
physics |
-+ | orthonormal | yes |
shtools |
-+ | 4pi | no |
graphics |
+- | 4pi | no |
ambi_sn3d |
-+ | Schmidt | no |
ambi_n3d |
-+ | orthonormal | no |
matlab |
-+ | Schmidt | yes |
spaudiopy |
-+ | orthonormal | no |
Each coordinate system is defined by a short string like 'x y -z' (OpenGL: X=Right, Y=Up, Z=Backward). A 5-line parser builds a 3x3 change-of-basis matrix. Converting between any two systems is one matrix multiply.
- Points
(3,)or(N,3)— multiplied by the change-of-basis matrix. - Transforms
(4,4)or(N,4,4)— rigid transforms (rotation + translation) are conjugated:C @ T @ C^T. - Quaternions
(4,)or(N,4)— the rotation matrix is extracted, conjugated by the basis change, and converted back via Shepperd's method. Handedness flips (det=-1) are corrected automatically. - Spherical harmonics — convention conversion handles coefficient ordering, normalization scaling, and Condon-Shortley phase. Coordinate rotation computes Wigner D-matrices via least-squares on a Fibonacci spiral of real SH evaluations. Degree-independent.
Both coordinate systems and SH conventions are defined as one-line strings. To add a new one, add an entry to the appropriate dictionary in yupzup.py (and yupzup.js):
# Coordinate system: 'R U F' — which axis is Right, Up, Forward
'my_system': 'x z -y', # X=Right, Z=Up, -Y=Forward (right-handed)
# SH convention: 'ordering normalization phase'
'my_sh': '-+ 4pi cs', # m=-l..+l, 4pi normalization, Condon-Shortley phasePython — exhaustive pairwise tests across all coordinate systems and SH conventions, plus fuzz and degenerate input testing:
uv run --with numpy --with pytest pytest test_yupzup.py
JavaScript — 125 tests validated against Python reference values:
node test_yupzup.js
MIT
