Commit: 693438f
Parent: f8c421e

Replace per-item log lines with indicatif progress bars

Mårten Åsberg committed on 2026-08-03 at 13:43
Both feature_extractor and exhaustive_matcher were printing one line
per image/pair, which is noisy and doesn't convey how much work is
left. Switch to indicatif progress bars (styled after Brush's CLI:
elapsed time, bar, position/total, rate, ETA) covering image
extraction, LightGlue matching, and RANSAC verification. Failures
during matching now carry the offending image pair in their error
context instead, since that's no longer visible from a running log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cargo.lock +95 -0
diff --git a/Cargo.lock b/Cargo.lock
index 8000b8d..5e390ac 100644
@@ -34,6 +34,7 @@ dependencies = [
"bytemuck",
"clap",
"image",
"indicatif",
"nalgebra",
"ndarray",
"ort",
@@ -354,6 +355,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "console"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
dependencies = [
"encode_unicode",
"libc",
"unicode-width",
"windows-sys",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -435,6 +448,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "equator"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -552,6 +571,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "futures-core"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-task"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-util"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -713,6 +756,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7"
[[package]]
name = "indicatif"
version = "0.18.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c"
dependencies = [
"console",
"portable-atomic",
"unicode-width",
"unit-prefix",
"web-time",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -755,6 +811,17 @@ dependencies = [
]
[[package]]
name = "js-sys"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]]
name = "lebe"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1537,6 +1604,12 @@ dependencies = [
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1666,6 +1739,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unit-prefix"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
[[package]]
name = "ureq"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1795,6 +1880,16 @@ dependencies = [
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.toml +1 -0
diff --git a/Cargo.toml b/Cargo.toml
index 74e1703..2487411 100644
@@ -8,6 +8,7 @@ anyhow = "1.0.104"
bytemuck = { version = "1.25.2", features = ["derive"] }
clap = { version = "4.6.5", features = ["derive"] }
image = "0.25.10"
indicatif = "0.18.6"
nalgebra = "0.35.0"
ndarray = "0.17.2"
ort = { version = "2.0.0-rc.13", features = ["webgpu"] }
src/exhaustive_matcher.rs +14 -18
diff --git a/src/exhaustive_matcher.rs b/src/exhaustive_matcher.rs
index 3cc6929..522b90f 100644
@@ -8,6 +8,7 @@ use rayon::prelude::*;
use crate::database::{self, image_pair_to_pair_id};
use crate::models::{self, Device};
use crate::progress;
use crate::ransac::{self, RansacResult};
pub struct ExhaustiveMatcherConfig {
@@ -135,15 +136,16 @@ pub fn run(cfg: ExhaustiveMatcherConfig) -> Result<()> {
}
let n_images = features.len();
println!(
"Matching {n_images} images exhaustively ({} pairs)...",
n_images * (n_images - 1) / 2
);
let total_pairs = n_images * (n_images - 1) / 2;
println!("Matching {n_images} images exhaustively ({total_pairs} pairs)...");
let match_pb = progress::new(total_pairs as u64, "pairs");
let mut pending: Vec<PendingPair> = Vec::new();
for i in 0..n_images {
for j in (i + 1)..n_images {
let matched = run_lightglue(&mut session, &features[i], &features[j], cfg.min_score)?;
let matched = run_lightglue(&mut session, &features[i], &features[j], cfg.min_score)
.with_context(|| format!("matching {} <-> {}", features[i].name, features[j].name))?;
match_pb.inc(1);
if matched.is_empty() {
continue;
}
@@ -175,13 +177,6 @@ pub fn run(cfg: ExhaustiveMatcherConfig) -> Result<()> {
.map(|&(_, b)| (hi.keypoints[b][0], hi.keypoints[b][1]))
.collect();
println!(
" {} <-> {}: {} raw matches",
features[i].name,
features[j].name,
ordered_matched.len()
);
pending.push(PendingPair {
pair_id,
matched: ordered_matched,
@@ -190,23 +185,24 @@ pub fn run(cfg: ExhaustiveMatcherConfig) -> Result<()> {
});
}
}
match_pb.finish();
println!(
"Running RANSAC geometric verification on {} candidate pairs...",
pending.len()
);
let ransac_pb = progress::new(pending.len() as u64, "pairs");
let ransac_results: Vec<Option<RansacResult>> = pending
.par_iter()
.map(|p| {
ransac::ransac_fundamental_matrix(
let result = ransac::ransac_fundamental_matrix(
&p.pts0,
&p.pts1,
ransac::DEFAULT_MAX_ITERATIONS,
ransac::DEFAULT_INLIER_THRESHOLD,
ransac::DEFAULT_CONFIDENCE,
)
);
ransac_pb.inc(1);
result
})
.collect();
ransac_pb.finish();
let mut verified_pairs = 0usize;
let mut total_inliers = 0usize;
src/feature_extractor.rs +4 -1
diff --git a/src/feature_extractor.rs b/src/feature_extractor.rs
index c83388d..9dd08e3 100644
@@ -9,6 +9,7 @@ use walkdir::WalkDir;
use crate::database;
use crate::models::{self, Device};
use crate::progress;
pub struct FeatureExtractorConfig {
pub database_path: PathBuf,
@@ -117,6 +118,7 @@ pub fn run(cfg: FeatureExtractorConfig) -> Result<()> {
anyhow::bail!("no images found under {}", cfg.image_path.display());
}
println!("Found {} images under {}", paths.len(), cfg.image_path.display());
let pb = progress::new(paths.len() as u64, "images");
for path in &paths {
let rel_name = path
@@ -172,8 +174,9 @@ pub fn run(cfg: FeatureExtractorConfig) -> Result<()> {
database::write_keypoints(&conn, image_id, &kpt_rows)?;
database::write_descriptors(&conn, image_id, keep.len(), desc_dim, &desc_rows)?;
println!("{rel_name}: {} keypoints", keep.len());
pb.inc(1);
}
pb.finish();
Ok(())
}
src/main.rs +1 -0
diff --git a/src/main.rs b/src/main.rs
index 50ff664..4f03145 100644
@@ -2,6 +2,7 @@ mod database;
mod exhaustive_matcher;
mod feature_extractor;
mod models;
mod progress;
mod ransac;
use std::path::PathBuf;
src/progress.rs +13 -0
diff --git a/src/progress.rs b/src/progress.rs
new file mode 100644
index 0000000..127b482
@@ -0,0 +1,13 @@
use indicatif::{ProgressBar, ProgressStyle};
/// A single-line progress bar (elapsed time, bar, position/total, rate, ETA) in the
/// style used by Brush's CLI, for a fixed-size loop with `len` items counted in `unit`.
pub fn new(len: u64, unit: &str) -> ProgressBar {
let template =
format!("[{{elapsed_precise}}] {{bar:40.cyan/blue}} {{pos}}/{{len}} {unit} ({{per_sec}}, {{eta}} remaining)");
ProgressBar::new(len).with_style(
ProgressStyle::with_template(&template)
.expect("valid indicatif template")
.progress_chars("◍○○"),
)
}