extern crate cgmath;
extern crate city2ba;
extern crate itertools;
extern crate nalgebra as na;
extern crate ply_rs;
extern crate rand;
extern crate structopt;
use cgmath::{Point3, Vector3};
use city2ba::generate::*;
use city2ba::noise::*;
use city2ba::synthetic::*;
use city2ba::*;
use itertools::Itertools;
use ply_rs::ply::{
Addable, DefaultElement, ElementDef, Ply, Property, PropertyDef, PropertyType, ScalarType,
};
use ply_rs::writer::Writer;
use std::fs::File;
use std::io::BufWriter;
use std::str::FromStr;
use structopt::StructOpt;
fn parse_vec3(s: &str) -> Result<Vector3<f64>, std::num::ParseFloatError> {
let mut it = s.split(',').map(|x| f64::from_str(x));
let x = it.next().unwrap()?;
let y = it.next().unwrap()?;
let z = it.next().unwrap()?;
Ok(Vector3::new(x, y, z))
}
#[derive(StructOpt, Debug)]
struct PLYOpt {
#[structopt(name = "FILE", parse(from_os_str))]
input: std::path::PathBuf,
#[structopt(name = "OUT", parse(from_os_str))]
out: std::path::PathBuf,
}
#[derive(StructOpt, Debug)]
struct GenerateOpt {
#[structopt(name = "FILE", parse(from_os_str))]
input: std::path::PathBuf,
#[structopt(long = "cameras", default_value = "100")]
num_cameras: usize,
#[structopt(
long = "intrinsics-start",
default_value = "1,0,0",
parse(try_from_str = parse_vec3)
)]
intrinsics_start: Vector3<f64>,
#[structopt(
long = "intrinsics-end",
default_value = "1,0,0",
parse(try_from_str = parse_vec3)
)]
intrinsics_end: Vector3<f64>,
#[structopt(long = "points", default_value = "1000")]
num_world_points: usize,
#[structopt(long = "max-dist", default_value = "100")]
max_dist: f64,
#[structopt(long = "ground", default_value = "0", allow_hyphen_values = true)]
ground: f64,
#[structopt(long = "height", default_value = "1")]
height: f64,
#[structopt(long = "no-lcc")]
no_lcc: bool,
#[structopt(long = "move-to-origin")]
move_to_origin: bool,
#[structopt(name = "OUT", parse(from_os_str))]
bal_out: std::path::PathBuf,
#[structopt(long = "path", conflicts_with = "ground")]
path: Option<String>,
#[structopt(long = "step-size", default_value = "0")]
step_size: f64,
}
#[derive(StructOpt, Debug)]
struct SyntheticOpt {
#[structopt(long = "cameras-per-block", default_value = "10")]
num_cameras_per_block: usize,
#[structopt(long = "points-per-block", default_value = "10")]
num_points_per_block: usize,
#[structopt(long = "max-dist", default_value = "10")]
max_dist: f64,
#[structopt(long = "camera-height", default_value = "1")]
camera_height: f64,
#[structopt(long = "point-height", default_value = "1")]
point_height: f64,
#[structopt(long = "block-inset", default_value = "1")]
block_inset: f64,
#[structopt(long = "block-length", default_value = "20")]
block_length: f64,
#[structopt(long = "blocks", default_value = "5")]
num_blocks: usize,
#[structopt(name = "OUTPUT", parse(from_os_str))]
output: std::path::PathBuf,
}
#[derive(StructOpt, Debug)]
struct SyntheticLineOpt {
#[structopt(long = "cameras", default_value = "10")]
num_cameras: usize,
#[structopt(long = "points", default_value = "10")]
num_points: usize,
#[structopt(long = "max-dist", default_value = "10")]
max_dist: f64,
#[structopt(long = "camera-height", default_value = "1")]
camera_height: f64,
#[structopt(long = "point-height", default_value = "1")]
point_height: f64,
#[structopt(long = "point-offset", default_value = "1")]
point_offset: f64,
#[structopt(long = "length", default_value = "20")]
length: f64,
#[structopt(name = "OUTPUT", parse(from_os_str))]
output: std::path::PathBuf,
}
#[derive(StructOpt, Debug)]
struct NoiseOpt {
#[structopt(name = "FILE", parse(from_os_str))]
input: std::path::PathBuf,
#[structopt(long = "rotation-std", default_value = "0.0")]
rotation_std: f64,
#[structopt(long = "translation-std", default_value = "0.0")]
translation_std: f64,
#[structopt(long = "point-std", default_value = "0.0")]
point_std: f64,
#[structopt(long = "observation-std", default_value = "0.0")]
observation_std: f64,
#[structopt(long = "drift-std", default_value = "0.0")]
drift_std: f64,
#[structopt(long = "drift-strength", default_value = "0.0")]
drift_strength: f64,
#[structopt(long = "fixed-drift")]
fixed_drift: bool,
#[structopt(long = "drift-angle", default_value = "0.0")]
drift_angle: f64,
#[structopt(long = "mismatch-chance", default_value = "0.0")]
mismatch_chance: f64,
#[structopt(long = "drop-features", default_value = "1.0")]
drop_features: f64,
#[structopt(long = "split-landmarks", default_value = "0.0")]
split_landmarks: f64,
#[structopt(long = "join-landmarks", default_value = "0.0")]
join_landmarks: f64,
#[structopt(long = "sin-strength", default_value = "0.0")]
sin_strength: f64,
#[structopt(long = "sin-frequency", default_value = "1.0")]
sin_frequency: f64,
#[structopt(name = "OUT", parse(from_os_str))]
output: std::path::PathBuf,
}
#[derive(StructOpt, Debug)]
#[structopt(
name = "city2ba",
about = "Tools for generating synthetic bundle adjustment problems."
)]
enum Opt {
PLY(PLYOpt),
Generate(GenerateOpt),
Synthetic(SyntheticOpt),
SyntheticLine(SyntheticLineOpt),
Noise(NoiseOpt),
}
fn run_noise(opt: NoiseOpt) -> Result<(), city2ba::Error> {
let mut bal = BAProblem::from_file(&opt.input)?;
println!(
"Initial error: {:.2e} (L1) {:.2e} (L2)",
bal.total_reprojection_error(1.),
bal.total_reprojection_error(2.)
);
if opt.drop_features < 1.0 {
bal = drop_features(bal, opt.drop_features);
bal = bal.cull();
}
if opt.join_landmarks > 0.0 {
bal = join_landmarks(bal, opt.split_landmarks);
bal = bal.cull();
}
if opt.split_landmarks > 0.0 {
bal = split_landmarks(bal, opt.split_landmarks);
bal = bal.cull();
}
if opt.fixed_drift {
let std_dir = bal.std();
bal = add_drift(
bal,
opt.drift_strength,
opt.drift_angle,
opt.drift_std,
std_dir,
);
} else {
bal = add_drift_normalized(bal, opt.drift_strength, opt.drift_angle, opt.drift_std);
}
if opt.sin_strength > 0. {
bal = add_sin_noise(
bal,
Vector3::new(1., 0., 0.),
Vector3::new(0., 1., 0.),
opt.sin_strength,
opt.sin_frequency,
);
bal = add_sin_noise(
bal,
Vector3::new(0., 0., 1.),
Vector3::new(0., 1., 0.),
opt.sin_strength,
opt.sin_frequency,
);
}
bal = add_noise(
bal,
opt.translation_std,
opt.rotation_std,
opt.point_std,
opt.observation_std,
);
bal = add_incorrect_correspondences(bal, opt.mismatch_chance);
println!(
"BA Problem with {} cameras, {} points, {} correspondences",
bal.num_cameras(),
bal.num_points(),
bal.num_observations()
);
println!(
"Final error: {:.2e} (L1) {:.2e} (L2)",
bal.total_reprojection_error(1.),
bal.total_reprojection_error(2.)
);
bal.write(&opt.output).map_err(Error::from)
}
fn write_cameras<C: Camera>(
path: &std::path::Path,
cameras: &[C],
points: &[Point3<f64>],
observations: &[Vec<(usize, (f64, f64))>],
) -> Result<(), std::io::Error> {
let mut ply = Ply::<DefaultElement>::new();
let mut point_element = ElementDef::new("vertex".to_string());
let p = PropertyDef::new("x".to_string(), PropertyType::Scalar(ScalarType::Float));
point_element.properties.add(p);
let p = PropertyDef::new("y".to_string(), PropertyType::Scalar(ScalarType::Float));
point_element.properties.add(p);
let p = PropertyDef::new("z".to_string(), PropertyType::Scalar(ScalarType::Float));
point_element.properties.add(p);
let p = PropertyDef::new("red".to_string(), PropertyType::Scalar(ScalarType::UChar));
point_element.properties.add(p);
let p = PropertyDef::new("green".to_string(), PropertyType::Scalar(ScalarType::UChar));
point_element.properties.add(p);
let p = PropertyDef::new("blue".to_string(), PropertyType::Scalar(ScalarType::UChar));
point_element.properties.add(p);
ply.header.elements.add(point_element);
let mut edge_element = ElementDef::new("edge".to_string());
edge_element.properties.add(PropertyDef::new(
"vertex1".to_string(),
PropertyType::Scalar(ScalarType::Int),
));
edge_element.properties.add(PropertyDef::new(
"vertex2".to_string(),
PropertyType::Scalar(ScalarType::Int),
));
ply.header.elements.add(edge_element);
let mut cs: Vec<_> = cameras
.iter()
.map(|camera| {
let mut point = DefaultElement::new();
point.insert("x".to_string(), Property::Float(camera.center()[0] as f32));
point.insert("y".to_string(), Property::Float(camera.center()[1] as f32));
point.insert("z".to_string(), Property::Float(camera.center()[2] as f32));
point.insert("red".to_string(), Property::UChar(255));
point.insert("green".to_string(), Property::UChar(0));
point.insert("blue".to_string(), Property::UChar(0));
point
})
.collect();
let pts = points.iter().map(|point| {
let mut p = DefaultElement::new();
p.insert("x".to_string(), Property::Float(point[0] as f32));
p.insert("y".to_string(), Property::Float(point[1] as f32));
p.insert("z".to_string(), Property::Float(point[2] as f32));
p.insert("red".to_string(), Property::UChar(0));
p.insert("green".to_string(), Property::UChar(255));
p.insert("blue".to_string(), Property::UChar(0));
p
});
cs.extend(pts);
ply.payload.insert("vertex".to_string(), cs);
let edges = observations
.iter()
.enumerate()
.flat_map(|(ci, obs)| {
obs.iter().map(move |(pi, _)| {
let mut e = DefaultElement::new();
e.insert("vertex1".to_string(), Property::Int(ci as i32));
e.insert(
"vertex2".to_string(),
Property::Int((*pi + cameras.len()) as i32),
);
e
})
})
.collect();
ply.payload.insert("edge".to_string(), edges);
let mut file = BufWriter::new(File::create(path)?);
let writer = Writer::new();
writer.write_ply(&mut file, &mut ply).map(|_| ())
}
fn run_ply(opt: PLYOpt) -> std::result::Result<(), city2ba::Error> {
let bal = BAProblem::from_file(&opt.input)?;
write_cameras(&opt.out, &bal.cameras, &bal.points, &bal.vis_graph)?;
Ok(())
}
fn run_synthetic(opt: SyntheticOpt) -> Result<(), city2ba::Error> {
let ba = synthetic_grid(
opt.num_cameras_per_block,
opt.num_points_per_block,
opt.num_blocks,
opt.block_length,
opt.block_inset,
opt.camera_height,
opt.point_height,
opt.max_dist,
true,
);
println!("{}", ba);
ba.write(&opt.output)?;
Ok(())
}
fn run_synthetic_line(opt: SyntheticLineOpt) -> Result<(), city2ba::Error> {
let ba = synthetic_line(
opt.num_cameras,
opt.num_points,
opt.length,
opt.point_offset,
opt.camera_height,
opt.point_height,
opt.max_dist,
true,
);
println!("{}", ba);
ba.write(&opt.output)?;
Ok(())
}
fn run_generate(opt: GenerateOpt) -> Result<(), city2ba::Error> {
let city_obj = tobj::load_obj(&opt.input).map_err(|err| match err {
tobj::LoadError::OpenFileFailed => city2ba::Error::IOError(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Could not open file {:?}", opt.input))),
e => city2ba::Error::IOError(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, format!("Load error: {}", e))),
})?;
let (mut models, _) = city_obj;
let model_path = if let Some(path) = opt.path {
let i = models.iter().position(|x| x.name == path);
let model_path = match i {
Some(j) => Some(models[j].clone()),
None => {
let names = models.iter().map(|x| x.name.clone()).join(", ");
panic!(
"Could not find a path named {}. Available model names are {}",
path, names
);
}
};
models.retain(|m| m.name != path);
model_path
} else {
None
};
if opt.move_to_origin {
models = move_to_origin(models);
};
let dev = embree_rs::Device::new();
let mut scene = embree_rs::Scene::new(&dev);
for model in models.iter() {
let mesh = model_to_geometry(model, &dev);
scene.attach_geometry(mesh);
}
let cscene = scene.commit();
let mut cameras = if let Some(m_path) = model_path {
if opt.step_size <= 0.0 {
generate_cameras_path(&cscene, &m_path, opt.num_cameras)
} else {
generate_cameras_path_step(&cscene, &m_path, opt.num_cameras, opt.step_size)
}
} else {
generate_cameras_poisson(&cscene, opt.num_cameras, opt.height, opt.ground)
};
println!("Generated {} cameras", cameras.len());
modify_intrinsics(&mut cameras, opt.intrinsics_start, opt.intrinsics_end);
println!("Modified intrinsics");
let points =
generate_world_points_uniform(&models, &cameras, opt.num_world_points, opt.max_dist);
println!("Generated {} world points", points.len());
let vis_graph = visibility_graph(&cscene, &cameras, &points, opt.max_dist, true);
println!(
"Computed visibility graph with {} edges",
vis_graph.iter().map(|x| x.len()).sum::<usize>()
);
let bal = BAProblem::from_visibility(cameras, points, vis_graph);
let bal_lcc = if !opt.no_lcc { bal.cull() } else { bal };
if bal_lcc.num_cameras() == 0 || bal_lcc.num_points() == 0 {
Err(city2ba::Error::EmptyProblem(
"No cameras remain".to_string(),
))?;
}
println!(
"Computed LCC with {} cameras, {} points, {} edges",
bal_lcc.num_cameras(),
bal_lcc.num_points(),
bal_lcc.num_observations(),
);
println!(
"Total reprojection error: {}",
bal_lcc.total_reprojection_error(1.)
);
bal_lcc.write(&opt.bal_out)?;
Ok(())
}
fn main() -> Result<(), city2ba::Error> {
match Opt::from_args() {
Opt::Generate(opt) => run_generate(opt),
Opt::Noise(opt) => run_noise(opt),
Opt::Synthetic(opt) => run_synthetic(opt),
Opt::SyntheticLine(opt) => run_synthetic_line(opt),
Opt::PLY(opt) => run_ply(opt),
}
}