Tutorial 05
February 26, 2026 · View on GitHub
What you will learn
- Creating SfMData from scratch with pyalicevision (views, intrinsics, poses)
- The AliceVision camera convention (R_c2w, camera Z = look direction)
- Building a look-at camera
- Writing
.abcand.jsonfiles viasfmDataIO - Loading and transforming an existing
.abcfile - Rigid body transformations (rotation, translation, scale)
- Connecting two nodes via file outputs (node chaining)
Prerequisites
- Completed Tutorial 01.
- Meshroom binary release (pyalicevision is bundled, not pip-installable).
Two nodes, one pipeline
This tutorial covers two nodes that work together:
HelloAdvanced_1 ──(.abc)──► HelloTransform_1
(creates the rig) (applies transformation)
HelloAdvanced generates a ring of synthetic cameras and exports them as .abc/.json files via pyalicevision.
HelloTransform reads the .abc file, applies a rigid body transformation (scale, rotation, translation), and writes the transformed cameras.
Why two nodes? Changing the transformation only recomputes HelloTransform — HelloAdvanced's cache is preserved. This demonstrates how Meshroom nodes communicate through files and how its cache invalidation works.
Part 1: HelloAdvanced — Creating the camera rig
AliceVision camera convention
AliceVision stores camera orientation as a cam-to-world rotation matrix (R_c2w). Its columns are the camera's local axes expressed in world coordinates:
| Column | Camera axis | Meaning |
|---|---|---|
| 0 | X | Right in the image |
| 1 | Y | Down in the image |
| 2 | Z | Look direction (towards the scene) |
The system is right-handed: cam_x x cam_y = cam_z.
The center is the camera position in world coordinates. Together, (R_c2w, center) fully describe a camera's extrinsic pose.
SfMData structure
An SfMData object groups cameras into three collections:
| Collection | What it stores |
|---|---|
| Views | Image metadata (path, width, height, links to intrinsic and pose) |
| Intrinsics | Camera optical models (focal length, principal point, sensor size) |
| Poses | Camera positions and orientations (Pose3 = rotation + center) |
HelloAdvanced creates all three from scratch — no input file needed. All cameras share a single intrinsic (same focal length and sensor).
Creating an empty SfMData and adding an intrinsic
from pyalicevision import sfmData as sfmDataModule
from pyalicevision import camera
sfm = sfmDataModule.SfMData()
focal_px = focal_mm * image_width / sensor_width
pinhole = camera.Pinhole(image_width, image_height, focal_px, focal_px,
image_width / 2.0, image_height / 2.0)
sfm.getIntrinsics()[0] = pinhole
Adding a View
view = sfmDataModule.View(
"", # image path (empty for synthetic cameras)
view_id, # unique view identifier
intrinsic_id, # index into getIntrinsics()
pose_id, # index into getPoses()
image_width,
image_height,
)
sfm.getViews()[view_id] = view
Look-at construction and adding a CameraPose
To point a camera at the origin from position center, we build R_c2w — a 3x3 matrix whose columns are the camera's local axes in world coordinates:
cam_z = -center / np.linalg.norm(center) # Z = look direction (towards origin)
cam_x = np.cross(cam_z, up) # X = right in image
cam_x = cam_x / np.linalg.norm(cam_x)
cam_y = np.cross(cam_z, cam_x) # Y = completes right-handed frame
R_c2w = np.column_stack([cam_x, cam_y, cam_z])
Where up = [0, 1, 0] (world Y-up).
To pass this to pyalicevision's Pose3, we transpose to R_w2c. This is necessary because the SWIG Eigen typemap converts between NumPy's row-major layout and Eigen's column-major layout, effectively transposing the matrix:
R_w2c = R_c2w.T
pose3 = geometry.Pose3(
np.ascontiguousarray(R_w2c, dtype=np.float64),
np.ascontiguousarray(center, dtype=np.float64),
)
cam_pose = sfmDataModule.CameraPose(pose3)
sfm.getPoses()[pose_id] = cam_pose
Arrays must be float64 and C-contiguous (the SWIG typemap rejects other formats).
Saving cameras to .abc and .json
from pyalicevision import sfmDataIO
sfmDataIO.save(sfm, "sfm.abc", sfmDataIO.ALL) # Alembic (binary, for 3D viewer)
sfmDataIO.save(sfm, "cameras.json", sfmDataIO.ALL) # JSON (human-readable)
The format is determined by the file extension. Both contain the same data.
Part 2: HelloTransform — Transforming SfMData
Loading an .abc file
HelloTransform receives the .abc path from HelloAdvanced's output. Loading creates an SfMData object populated with the saved data:
sfm = sfmDataModule.SfMData()
sfmDataIO.load(sfm, input_path, sfmDataIO.ALL)
Extracting poses
Poses are accessed through the same API used for creation. Due to the SWIG row/column-major conversion, rotation() returns R_w2c (the transpose of the stored R_c2w):
poses = sfm.getPoses()
for pose_id in poses.keys():
cam_pose = poses[pose_id]
pose3 = cam_pose.getTransform()
R_w2c = np.array(pose3.rotation(), dtype=np.float64).reshape(3, 3)
center = np.array(pose3.center(), dtype=np.float64).flatten()
Building the rotation matrix
Three helper functions (_rot_x, _rot_y, _rot_z) build 3x3 rotation matrices for each axis. They are combined in standard Euler order:
R_rig = Rz @ Ry @ Rx
Applying the transformation
The transformation order is scale -> rotate -> translate.
Camera centers (3D points in world coordinates):
new_center = R_rig @ (scale * center) + translation
Camera rotations (R_w2c). The rig rotation changes the world frame, so R_w2c must compensate:
new_R_w2c = R_w2c @ R_rig.T
Saving
The transformed poses are written back into the SfMData and saved:
new_pose3 = geometry.Pose3(
np.ascontiguousarray(new_R_w2c, dtype=np.float64),
np.ascontiguousarray(new_center, dtype=np.float64),
)
cam_pose.setTransform(new_pose3)
Then sfmDataIO.save() writes .abc and .json.
How to test it
A pre-configured project is available: open meshroom/tuto05-hello-advanced.mg in Meshroom (make sure you have run scripts/setup_projects.sh first). Or set it up manually:
- Add a HelloAdvanced node to your graph.
- Add a HelloTransform node.
- Connect HelloAdvanced's SfMData output to HelloTransform's Input SfMData input.
- Right-click on HelloAdvanced and select Compute to run it alone.
- Double-click on HelloAdvanced to display the generated cameras in the 3D viewer. You should see camera frustums arranged in a circle on the XZ plane, all pointing towards the origin.

- Now right-click on HelloTransform and select Compute. You can change the transformation parameters first (e.g.,
rotateX = 45). - Double-click on HelloTransform to see the transformed cameras in the 3D viewer. Only HelloTransform has recomputed — HelloAdvanced's cache stays valid.
