use apex_solver::apex_io::{Graph, load_graph};
use apex_solver::init_logger;
use clap::Parser;
use rerun::{
RecordingStreamBuilder, Transform3D,
archetypes::{Pinhole, Points2D},
components::Color,
};
use std::path::PathBuf;
use tracing::info;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short = 'f', long, default_value = "data/odometry/parking-garage.g2o")]
file_path: PathBuf,
#[arg(short, long, default_value_t = 0.1)]
scale: f32,
#[arg(long, default_value_t = 0.0)]
se2_height: f32,
#[arg(long, default_value_t = 0.5)]
frustum_size: f32,
#[arg(long, default_value_t = 30.0)]
fov_degrees: f32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
init_logger();
info!("Loading graph from: {}", args.file_path.display());
let graph = load_graph(&args.file_path)?;
let rec = RecordingStreamBuilder::new("apex-solver-graph-visualization").spawn()?;
info!("Graph loaded successfully:");
info!(" - SE2 vertices: {}", graph.vertices_se2.len());
info!(" - SE3 vertices: {}", graph.vertices_se3.len());
info!(" - Total vertices: {}", graph.vertex_count());
if !graph.vertices_se3.is_empty() {
visualize_se3_poses(
&graph,
&rec,
args.scale,
args.frustum_size,
args.fov_degrees,
)?;
}
if !graph.vertices_se2.is_empty() {
visualize_se2_poses(&graph, &rec, args.scale, args.se2_height, args.frustum_size)?;
}
if graph.vertices_se3.is_empty() && graph.vertices_se2.is_empty() {
info!("No poses found in the graph file.");
Ok(())
} else {
info!("Visualization ready! The Rerun viewer should open automatically.");
info!("Press Ctrl+C to exit.");
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
}
fn visualize_se3_poses(
graph: &Graph,
rec: &rerun::RecordingStream,
scale: f32,
_frustum_size: f32,
fov_degrees: f32,
) -> Result<(), Box<dyn std::error::Error>> {
info!(
"Visualizing {} SE3 poses as camera frustums...",
graph.vertices_se3.len()
);
for (id, vertex) in &graph.vertices_se3 {
let (position, rotation) = vertex.to_rerun_transform(scale);
let transform = Transform3D::from_translation_rotation(position, rotation);
let entity_path = format!("se3_poses/{id}");
rec.log(entity_path.as_str(), &transform)?;
let fov_radians = fov_degrees.to_radians();
let pinhole = Pinhole::from_fov_and_aspect_ratio(fov_radians, 1.0);
rec.log(entity_path.as_str(), &pinhole)?;
}
Ok(())
}
fn visualize_se2_poses(
graph: &Graph,
rec: &rerun::RecordingStream,
scale: f32,
_height: f32,
point_size: f32,
) -> Result<(), Box<dyn std::error::Error>> {
info!(
"Visualizing {} SE2 poses as 2D points...",
graph.vertices_se2.len()
);
let positions: Vec<[f32; 2]> = graph
.vertices_se2
.values()
.map(|vertex| vertex.to_rerun_position_2d(scale))
.collect();
let colors = vec![Color::from_rgb(255, 170, 0); positions.len()];
if !positions.is_empty() {
rec.log(
"se2_poses",
&Points2D::new(positions)
.with_colors(colors)
.with_radii([point_size * scale]),
)?;
}
Ok(())
}