diff --git a/py_workspace/README.md b/py_workspace/README.md index 0da6520..467fad0 100755 --- a/py_workspace/README.md +++ b/py_workspace/README.md @@ -75,11 +75,12 @@ Use `--birdseye` for a top-down X-Z view (looking down Y axis). uv run visualize_extrinsics.py -i output/extrinsics.json --birdseye --show ``` -**Ground Plane Overlay:** -Render a semi-transparent X-Z ground plane to anchor camera poses. +**Ground Plane & Origin Overlay:** +Render a semi-transparent X-Z ground plane and/or a world origin triad. - `--show-ground/--no-show-ground`: Toggle ground plane (default: show). - `--ground-y FLOAT`: Set the Y height of the plane (default: 0.0). - `--ground-size FLOAT`: Set the side length of the plane in meters (default: 8.0). +- `--show-origin-axes/--no-show-origin-axes`: Toggle world origin triad (X:red, Y:green, Z:blue) (default: show). *Example: Ground plane at Y=-1.5 with 10m size* ```bash diff --git a/py_workspace/visualize_extrinsics.py b/py_workspace/visualize_extrinsics.py index 77eb5e4..78a7a30 100644 --- a/py_workspace/visualize_extrinsics.py +++ b/py_workspace/visualize_extrinsics.py @@ -474,6 +474,11 @@ def run_diagnostics(poses: Dict[str, np.ndarray], convention: str): default=8.0, help="Size of the ground plane (side length in meters).", ) +@click.option( + "--show-origin-axes/--no-show-origin-axes", + default=True, + help="Show a world-origin axis triad (X:red, Y:green, Z:blue).", +) def main( input: str, output: Optional[str], @@ -491,6 +496,7 @@ def main( show_ground: bool, ground_y: float, ground_size: float, + show_origin_axes: bool, ): """Visualize camera extrinsics from JSON using Plotly.""" try: @@ -546,6 +552,52 @@ def main( intrinsics=cam_intrinsics, ) + if show_origin_axes: + origin = np.zeros(3) + axis_len = scale + fig.add_trace( + go.Scatter3d( + x=[origin[0], origin[0] + axis_len], + y=[origin[1], origin[1]], + z=[origin[2], origin[2]], + mode="lines", + line=dict(color="red", width=4), + name="World X", + legendgroup="Origin", + showlegend=True, + hoverinfo="text", + text="World X", + ) + ) + fig.add_trace( + go.Scatter3d( + x=[origin[0], origin[0]], + y=[origin[1], origin[1] + axis_len], + z=[origin[2], origin[2]], + mode="lines", + line=dict(color="green", width=4), + name="World Y", + legendgroup="Origin", + showlegend=True, + hoverinfo="text", + text="World Y", + ) + ) + fig.add_trace( + go.Scatter3d( + x=[origin[0], origin[0]], + y=[origin[1], origin[1]], + z=[origin[2], origin[2] + axis_len], + mode="lines", + line=dict(color="blue", width=4), + name="World Z", + legendgroup="Origin", + showlegend=True, + hoverinfo="text", + text="World Z", + ) + ) + if show_ground: half_size = ground_size / 2.0 x_grid = np.linspace(-half_size, half_size, 2) @@ -583,10 +635,16 @@ def main( eye=dict(x=0, y=2.5, z=0), ) + render_desc = ( + "OpenCV: local +X,+Y,+Z (Y-down)" + if render_space == "opencv" + else "OpenGL: local +X,-Y,-Z (Y-up, Z-back) rel. to OpenCV" + ) + fig.update_layout( - title=f"Camera Extrinsics ({pose_convention}, {render_space})", + title=f"Camera Extrinsics ({pose_convention})
{render_desc}", scene=scene_dict, - margin=dict(l=0, r=0, b=0, t=40), + margin=dict(l=0, r=0, b=0, t=60), legend=dict(x=0, y=1), )