#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum MvsProfile {
#[default]
Fast,
Quality,
}
impl MvsProfile {
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"fast" => Some(Self::Fast),
"quality" => Some(Self::Quality),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Fast => "fast",
Self::Quality => "quality",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LevelSchedule {
pub photometric_iters: u32,
pub restorer_random_init: bool,
pub geometric_rounds: u32,
pub geometric_iters: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PmParams {
pub window_radius: i32,
pub window_step: i32,
pub sigma_spatial: f32,
pub sigma_color: f32,
pub tau0: f32,
pub tau_alpha: f32,
pub tau1: f32,
pub beta: f32,
pub n1: u32,
pub n2: u32,
pub prev_view_weight: f32,
pub min_triangulation_angle_deg: f32,
pub incident_angle_sigma: f32,
pub geom_lambda: f32,
pub geom_max_cost: f32,
pub top_k: u32,
pub eps0_photometric: f32,
pub eps0_geometric: f32,
pub phi0_photometric_deg: f32,
pub phi0_geometric_deg: f32,
pub eps_min: f32,
pub phi_min_deg: f32,
pub weak_texture_var: f32,
}
impl Default for PmParams {
fn default() -> Self {
Self {
window_radius: 5,
window_step: 2,
sigma_spatial: -1.0,
sigma_color: 0.2,
tau0: 0.8,
tau_alpha: 90.0,
tau1: 1.2,
beta: 0.3,
n1: 2,
n2: 3,
prev_view_weight: 0.2,
min_triangulation_angle_deg: 1.0,
incident_angle_sigma: 0.9,
geom_lambda: 0.2,
geom_max_cost: 3.0,
top_k: 3,
eps0_photometric: 0.2,
eps0_geometric: 0.05,
phi0_photometric_deg: 30.0,
phi0_geometric_deg: 10.0,
eps_min: 0.005,
phi_min_deg: 1.0,
weak_texture_var: 0.0,
}
}
}
impl PmParams {
pub fn sigma_s(&self) -> f32 {
if self.sigma_spatial > 0.0 {
self.sigma_spatial
} else {
self.window_radius as f32
}
}
pub fn window_offsets(&self) -> Vec<i32> {
let r = self.window_radius;
let s = self.window_step.max(1);
let mut v = Vec::new();
let mut o = -r;
while o <= r {
v.push(o);
o += s;
}
v
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FilterParams {
pub min_ncc: f32,
pub min_triangulation_angle_deg: f32,
pub min_num_consistent: u32,
pub geom_max_cost: f32,
pub ncc_sigma: f32,
pub median_filter: bool,
}
impl Default for FilterParams {
fn default() -> Self {
Self {
min_ncc: 0.1,
min_triangulation_angle_deg: 3.0,
min_num_consistent: 2,
geom_max_cost: 1.0,
ncc_sigma: 0.6,
median_filter: true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum FusionMode {
Traversal,
#[default]
Consistency,
}
impl FusionMode {
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"traversal" | "median" => Some(Self::Traversal),
"consistency" | "average" => Some(Self::Consistency),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum FusionResidual {
#[default]
None,
Release,
SecondPass,
}
impl FusionResidual {
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().replace('_', "-").as_str() {
"none" => Some(Self::None),
"release" => Some(Self::Release),
"second-pass" | "secondpass" => Some(Self::SecondPass),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::None => "none",
Self::Release => "release",
Self::SecondPass => "second-pass",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ResidualParams {
pub min_views: usize,
pub depth_error: Option<f64>,
pub normal_error_deg: Option<f64>,
pub min_dist_gsd: f64,
}
impl Default for ResidualParams {
fn default() -> Self {
Self { min_views: 2, depth_error: None, normal_error_deg: None, min_dist_gsd: 0.5 }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FusionParams {
pub mode: FusionMode,
pub residual: FusionResidual,
pub residual_params: ResidualParams,
pub min_consistent_views: usize,
pub mark_used: bool,
pub mark_radius: usize,
pub inverse_variance: bool,
pub sigma_px0: f64,
pub sigma_px_slope: f64,
pub consistency_num_images: usize,
pub min_num_pixels: usize,
pub max_num_pixels: usize,
pub max_traversal_depth: usize,
pub max_reproj_error: f64,
pub max_depth_error: f64,
pub max_normal_error_deg: f64,
pub check_num_images: usize,
pub smooth: crate::smooth::SmoothParams,
pub visibility: crate::visibility::VisibilityParams,
}
impl Default for FusionParams {
fn default() -> Self {
Self {
mode: FusionMode::Consistency,
residual: FusionResidual::None,
residual_params: ResidualParams::default(),
min_consistent_views: 5,
mark_used: true,
mark_radius: 0,
inverse_variance: true,
sigma_px0: 0.25,
sigma_px_slope: 1.0,
consistency_num_images: 12,
min_num_pixels: 5,
max_num_pixels: 10000,
max_traversal_depth: 100,
max_reproj_error: 2.0,
max_depth_error: 0.01,
max_normal_error_deg: 10.0,
check_num_images: 50,
smooth: crate::smooth::SmoothParams::default(),
visibility: crate::visibility::VisibilityParams::default(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NeighborParams {
pub num_views: usize,
pub min_triangulation_angle_deg: f64,
pub direction_bins: usize,
pub diversity_decay: f64,
}
impl Default for NeighborParams {
fn default() -> Self {
Self { num_views: 10, min_triangulation_angle_deg: 1.0, direction_bins: 8, diversity_decay: 1.0 }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DensifyOptions {
pub profile: MvsProfile,
pub neighbors: NeighborParams,
pub pm: PmParams,
pub filter: FilterParams,
pub fusion: FusionParams,
pub post: crate::postproc::PostParams,
pub max_levels: usize,
pub min_level_size: usize,
pub jbu_sigma_spatial: f32,
pub jbu_sigma_color: f32,
pub restorer_threshold: f32,
pub seed: u64,
pub geometric_iters_override: Option<u32>,
pub geometric_rounds_override: Option<u32>,
}
impl Default for DensifyOptions {
fn default() -> Self {
Self::with_profile(MvsProfile::Fast)
}
}
impl DensifyOptions {
fn base() -> Self {
Self {
profile: MvsProfile::Fast,
neighbors: NeighborParams::default(),
pm: PmParams::default(),
filter: FilterParams::default(),
fusion: FusionParams::default(),
post: crate::postproc::PostParams::default(),
max_levels: 3,
min_level_size: 64,
jbu_sigma_spatial: 1.0,
jbu_sigma_color: 0.04,
restorer_threshold: 0.1,
seed: 0x005e_ed0f_de75,
geometric_iters_override: None,
geometric_rounds_override: None,
}
}
}
impl DensifyOptions {
pub fn with_profile(profile: MvsProfile) -> Self {
let mut o = Self { profile, ..Self::base() };
o.pm.window_step = match profile {
MvsProfile::Fast => 2,
MvsProfile::Quality => 1,
};
if profile == MvsProfile::Fast {
o.apply_fast_tuning();
}
o
}
fn apply_fast_tuning(&mut self) {
let f = &mut self.filter;
(f.min_ncc, f.geom_max_cost, f.min_num_consistent, f.median_filter) = (0.2, 0.5, 3, false);
self.post.fill_holes = true;
let u = &mut self.fusion;
(u.min_consistent_views, u.max_depth_error, u.max_normal_error_deg, u.max_reproj_error) = (3, 0.005, 23.0, 100.0);
}
pub fn set_fusion_mode(&mut self, mode: FusionMode) {
if mode == FusionMode::Traversal {
let d = FusionParams::default();
let f = &mut self.fusion;
(f.max_depth_error, f.max_normal_error_deg, f.max_reproj_error) =
(d.max_depth_error, d.max_normal_error_deg, d.max_reproj_error);
}
self.fusion.mode = mode;
}
pub fn schedule(&self, level: usize, top: usize) -> LevelSchedule {
let mut s = self.base_schedule(level, top);
if let Some(v) = self.geometric_iters_override {
s.geometric_iters = v;
}
if let Some(v) = self.geometric_rounds_override {
s.geometric_rounds = v;
}
s
}
fn base_schedule(&self, level: usize, top: usize) -> LevelSchedule {
match self.profile {
MvsProfile::Quality => LevelSchedule {
photometric_iters: if level == 0 { 7 } else { 6 },
restorer_random_init: true,
geometric_rounds: 2,
geometric_iters: 6,
},
MvsProfile::Fast => {
let _ = top;
LevelSchedule {
photometric_iters: if level == 0 { 6 } else { 3 },
restorer_random_init: false,
geometric_rounds: 2,
geometric_iters: 2,
}
}
}
}
pub fn fingerprint(&self) -> u64 {
let s = format!(
"{:?}",
(
self.profile,
&self.neighbors,
&self.pm,
&self.filter,
&self.post,
self.max_levels,
self.min_level_size,
self.jbu_sigma_spatial,
self.jbu_sigma_color,
(self.restorer_threshold, self.seed, self.geometric_iters_override, self.geometric_rounds_override)
)
);
crate::math::hash_bytes(s.as_bytes())
}
}