Skip to content

Commit

Permalink
Removed the Clap arg parser.
Browse files Browse the repository at this point in the history
A custom built parser provides a lot more control, and this also makes
the resulting binary smaller.
  • Loading branch information
zlogic committed Aug 21, 2023
1 parent b7a9a49 commit 7a3f8f7
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 101 deletions.
59 changes: 0 additions & 59 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ rust-version = "1.71"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.3", default_features = false, features = ["std", "derive", "help", "usage", "suggestions"] }
image = { version = "0.24", default_features = false, features = ["png", "jpeg", "tiff", "jpeg_rayon"] }
indicatif = { version = "0.17", default_features = false }
rayon = "*"
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ Download a release distribution from [releases](/zlogic/cybervision/releases).
Run cybervision:

```shell
cybervision [--scale=<scale>] [--mode=<cpu|gpu>] [--interpolation=<none|delaunay>] [--projection=<parallel|perspective>] [--mesh=<plain|vertex-colors|texture-coordinates>] [--no-bundle-adjustment] <img1> <img2> <output>
cybervision [--scale=<scale>] [--focal-length=<focal-length>] [--mode=<cpu|gpu>] [--interpolation=<none|delaunay>] [--projection=<parallel|perspective>] [--mesh=<plain|vertex-colors|texture-coordinates>] [--no-bundle-adjustment] <img1> <img2> [<imgn>] <output>
```

`--scale=<scale>` is an optional argument to specify a depth scale, for example `--scale=-10.0`.

`--focal-length=<focal-length>` is an optional argument to specify a custom focal length for images with perspective projection, for example, `--focal-length=26`;
this should be the image's focal length in 35mm equivalent.
If not specified, EXIF metadata will be used.

`--mode=<cpu|gpu|gpu-low-power>` is an optional argument to specify a depth scale, for example `--mode=cpu` or `--mode=gpu`
Results might be slightly different between modes because the implementation is not completely identical.
`gpu-low-power` will prefer a low-power GPU (like an integrated one) and will reduce the batch size to prevent errors (at the cost of reduced performance).
Expand All @@ -47,6 +51,7 @@ cybervision [--scale=<scale>] [--mode=<cpu|gpu>] [--interpolation=<none|delaunay
Adding this flag can significantly reduce processing time, at the cost of producing incorrect data.

`<img1>` and `<img2>` are input filenames for image 1 and 2; supported formats are `jpg`, `tif` and `png`.
Although experimental, it's also possible to specify more than one image when using perspective projection.

`<output>` is the output filename:
* If the filename ends with `.obj`, this will save a 3D [Wavefront OBJ file](https://en.wikipedia.org/wiki/Wavefront_.obj_file).
Expand Down
182 changes: 144 additions & 38 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::process::exit;
use std::{env, fmt::Display, process::exit};

use clap::Parser;
mod correlation;
mod fundamentalmatrix;
mod orb;
Expand All @@ -9,78 +8,185 @@ mod pointmatching;
mod reconstruction;
mod triangulation;

#[derive(clap::ValueEnum, Clone)]
#[derive(Debug)]
pub enum HardwareMode {
Gpu,
GpuLowPower,
Cpu,
}

#[derive(clap::ValueEnum, Clone)]
#[derive(Debug)]
pub enum InterpolationMode {
Delaunay,
None,
}

#[derive(clap::ValueEnum, Clone)]
#[derive(Debug)]
pub enum ProjectionMode {
Parallel,
Perspective,
}

#[derive(clap::ValueEnum, Clone)]
#[derive(Debug)]
pub enum Mesh {
Plain,
VertexColors,
TextureCoordinates,
}

/// Cybervision commandline arguments
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Depth scale
#[arg(long, default_value_t = -1.0)]
#[derive(Debug)]
pub struct Args {
scale: f32,

/// Focal length in 35mm equivalent
#[arg(long)]
focal_length: Option<u32>,

/// Hardware mode
#[arg(long, value_enum, default_value_t = HardwareMode::Gpu)]
mode: HardwareMode,

/// Interpolation mode
#[arg(long, value_enum, default_value_t = InterpolationMode::Delaunay)]
interpolation: InterpolationMode,

/// Bundle adjustment
#[arg(long, default_value_t = false)]
no_bundle_adjustment: bool,

/// Hardware mode
#[arg(long, value_enum, default_value_t = ProjectionMode::Parallel)]
projection: ProjectionMode,

/// Mesh options
#[arg(long, value_enum, default_value_t = Mesh::Plain)]
mesh: Mesh,

/// Source image(s)
#[arg(required = true, index = 1)]
img_src: Vec<String>,

/// Output image
#[arg(required = true, index = 2)]
img_out: String,
}

const USAGE_INSTRUCTIONS: &str = "Usage: cybervision [OPTIONS] <IMG_SRC>... <IMG_OUT>\n\n\
Arguments:\
\n <IMG_SRC>... Source image(s)\
\n <IMG_OUT> Output image\n\n\
Options:\
\n --scale <SCALE> Depth scale [default: -1]\
\n --focal-length <FOCAL_LENGTH> Focal length in 35mm equivalent\
\n --mode <MODE> Hardware mode [default: gpu] [possible values: gpu, gpu-low-power, cpu]\
\n --interpolation <INTERPOLATION> Interpolation mode [default: delaunay] [possible values: delaunay, none]\
\n --no-bundle-adjustment Bundle adjustment\
\n --projection <PROJECTION> Hardware mode [default: parallel] [possible values: parallel, perspective]\
\n --mesh <MESH> Mesh options [default: plain] [possible values: plain, vertex-colors, texture-coordinates]\
\n --help Print help";
impl Args {
fn parse() -> Args {
let mut args = Args {
scale: -1.0,
focal_length: None,
mode: HardwareMode::Gpu,
interpolation: InterpolationMode::Delaunay,
no_bundle_adjustment: false,
projection: ProjectionMode::Perspective,
mesh: Mesh::VertexColors,
img_src: vec![],
img_out: "".to_string(),
};
let fail_with_error = |name: &str, value: &str, err: &dyn Display| {
eprintln!(
"Argument {} has an unsupported value {}: {}",
name, value, err
);
println!("{}", USAGE_INSTRUCTIONS);
exit(2)
};
let mut filenames = vec![];
for arg in env::args().skip(1) {
if arg.starts_with("--") && filenames.is_empty() {
// Option flags.
if arg == "--no-bundle-adjustment" {
args.no_bundle_adjustment = true;
continue;
}
if arg == "--help" {
println!("{}", USAGE_INSTRUCTIONS);
exit(0);
}
let (name, value) = if let Some(arg) = arg.split_once('=') {
arg
} else {
eprintln!("Option flag {} has no value", arg);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
};
if name == "--scale" {
match value.parse() {
Ok(scale) => args.scale = scale,
Err(err) => fail_with_error(name, value, &err),
};
} else if name == "--focal-length" {
match value.parse() {
Ok(focal_length) => args.focal_length = Some(focal_length),
Err(err) => fail_with_error(name, value, &err),
};
} else if name == "--mode" {
match value {
"gpu" => args.mode = HardwareMode::Gpu,
"gpu-low-power" => args.mode = HardwareMode::GpuLowPower,
"cpu" => args.mode = HardwareMode::Cpu,
_ => {
eprintln!("Unsupported hardware mode {}", value);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
}
};
} else if name == "--interpolation" {
match value {
"delaunay" => args.interpolation = InterpolationMode::Delaunay,
"none" => args.interpolation = InterpolationMode::None,
_ => {
eprintln!("Unsupported interpolation {}", value);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
}
};
} else if name == "--projection" {
match value {
"perspective" => args.projection = ProjectionMode::Perspective,
"parallel" => args.projection = ProjectionMode::Parallel,
_ => {
eprintln!("Unsupported projection {}", value);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
}
};
} else if name == "--mesh" {
match value {
"plain" => args.mesh = Mesh::Plain,
"vertex-colors" => args.mesh = Mesh::VertexColors,
"texture-coordinates" => args.mesh = Mesh::TextureCoordinates,
_ => {
eprintln!("Unsupported mesh vertex output mode {}", value);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
}
};
} else {
eprintln!("Unsupported argument {}", arg);
}
} else {
filenames.push(arg);
}
}

args.img_out = if let Some(img_out) = filenames.pop() {
img_out
} else {
eprintln!("No filenames provided");
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
};
if filenames.len() < 2 {
eprintln!("Not enough source images (need at least 2 to create a stereopair), but only {} were specified: {:?}", filenames.len(), filenames);
println!("{}", USAGE_INSTRUCTIONS);
exit(2);
}
args.img_src = filenames;

args
}
}

fn main() {
let args = Cli::parse();
println!(
"Cybervision version {}",
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")
);
let args = Args::parse();

if let Err(err) = reconstruction::reconstruct(&args) {
println!("Reconstruction failed, root cause is {}", err);
exit(-1);
exit(1);
};
}
4 changes: 2 additions & 2 deletions src/reconstruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::orb;
use crate::output;
use crate::pointmatching;
use crate::triangulation;
use crate::Cli;
use crate::Args;

use image::{imageops::FilterType, GenericImageView, GrayImage, RgbImage};
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
Expand Down Expand Up @@ -186,7 +186,7 @@ struct ImageReconstruction {
focal_length: Option<u32>,
}

pub fn reconstruct(args: &Cli) -> Result<(), Box<dyn error::Error>> {
pub fn reconstruct(args: &Args) -> Result<(), Box<dyn error::Error>> {
let start_time = SystemTime::now();

let projection_mode = match args.projection {
Expand Down

0 comments on commit 7a3f8f7

Please sign in to comment.