Skip to content

Commit 0161a5d

Browse files
Add mmlogger - and log some basic metrics for the camera solver.
The mmlogger is intended to be a zero-cost feature that is used as an explicit resource and passed to each function and used.
1 parent 17b4e82 commit 0161a5d

File tree

10 files changed

+132
-5
lines changed

10 files changed

+132
-5
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"lib/mmsolverlibs",
99
"lib/rust/mmcamerasolve-bin",
1010
"lib/rust/mmcholmod",
11+
"lib/rust/mmlogger",
1112
"lib/rust/mmcore",
1213
"lib/rust/mmimage",
1314
"lib/rust/mmio",
@@ -60,6 +61,7 @@ mmcore_cppbind = { path = "./lib/cppbind/mmcore" }
6061
mmcore_rust = { path = "./lib/rust/mmcore" }
6162
mmimage_cppbind = { path = "./lib/cppbind/mmimage" }
6263
mmimage_rust = { path = "./lib/rust/mmimage" }
64+
mmlogger = { path = "./lib/rust/mmlogger" }
6365
mmio_rust = { path = "./lib/rust/mmio" }
6466
mmlens_cppbind = { path = "./lib/cppbind/mmlens" }
6567
mmlens_rust = { path = "./lib/rust/mmlens" }

lib/rust/mmcamerasolve-bin/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ path = "src/main.rs"
1111

1212
[features]
1313
default = []
14+
logging = []
1415
visualization = ["mmsfm/visualization"]
1516

1617
[dependencies]
1718
anyhow = { workspace = true }
19+
mmlogger = { workspace = true }
1820
mmio = { package = "mmio_rust", path = "../mmio" }
1921
mmlens = { package = "mmlens_cppbind", path = "../../cppbind/mmlens" }
2022
mmsfm = { package = "mmsfm_rust", path = "../mmsfm", default-features = false }

lib/rust/mmcamerasolve-bin/src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub struct CliArgs {
142142

143143
/// Optional path to a Nuke .nk lens distortion file.
144144
pub nuke_lens_file: Option<String>,
145+
145146
}
146147

147148
impl Default for CliArgs {

lib/rust/mmcamerasolve-bin/src/main.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use std::time::Instant;
3737
use cli::{
3838
parse_args, print_help, print_version, CliArgs, ParseResult, SolverType,
3939
};
40+
use mmlogger::Logger;
4041
use mmio::bundle_writer::{write_bundle_file, BundlePoint};
4142
use mmio::kuper_writer::{
4243
extract_zxy_euler_angles, maya_pose_to_kuper, write_kuper_file,
@@ -115,7 +116,26 @@ fn run() -> Result<()> {
115116
}
116117
};
117118

118-
run_camera_solve(&args)
119+
#[cfg(feature = "logging")]
120+
{
121+
std::fs::create_dir_all(&args.output_dir)
122+
.with_context(|| format!("Failed to create output directory: {}", args.output_dir))?;
123+
let log_filename = match &args.prefix {
124+
Some(prefix) => format!("{}_solve.log", prefix),
125+
None => "solve.log".to_string(),
126+
};
127+
let log_path = format!("{}/{}", args.output_dir, log_filename);
128+
let file = std::fs::File::create(&log_path)
129+
.with_context(|| format!("Failed to create log file: {}", log_path))?;
130+
let mut log = mmlogger::TeeLogger::new(file, std::io::stderr());
131+
run_camera_solve(&args, &mut log)
132+
}
133+
134+
#[cfg(not(feature = "logging"))]
135+
{
136+
let mut log = mmlogger::NoOpLogger;
137+
run_camera_solve(&args, &mut log)
138+
}
119139
}
120140

121141
fn setup_thread_pool(threads: Option<usize>) -> Result<()> {
@@ -233,7 +253,7 @@ impl IntermediateResultWriter for FileIntermediateResultWriter {
233253
}
234254
}
235255

236-
fn run_camera_solve(args: &CliArgs) -> Result<()> {
256+
fn run_camera_solve<L: Logger>(args: &CliArgs, logger: &mut L) -> Result<()> {
237257
let total_start = Instant::now();
238258

239259
// Load solver settings file if provided.
@@ -242,6 +262,7 @@ fn run_camera_solve(args: &CliArgs) -> Result<()> {
242262
if !args.quiet {
243263
println!("Loading solver settings from: {}", settings_path);
244264
}
265+
logger.log("INFO", &format!("Loading solver settings from: {}", settings_path));
245266
let s = parse_mmsettings_file(settings_path)
246267
.map_err(|e| anyhow::anyhow!("{}", e))?;
247268
Some(s)
@@ -284,6 +305,8 @@ fn run_camera_solve(args: &CliArgs) -> Result<()> {
284305
let (file_info, mut markers) = parse_file(&args.uv_file)
285306
.with_context(|| format!("Failed to load UV file: {}", args.uv_file))?;
286307

308+
logger.log("INFO", &format!("Loaded {} markers from: {}", markers.len(), args.uv_file));
309+
287310
if !args.quiet {
288311
println!(" Loaded {} markers", markers.len());
289312
println!(" Format version: {:?}", file_info.version);
@@ -534,6 +557,7 @@ fn run_camera_solve(args: &CliArgs) -> Result<()> {
534557
println!();
535558
println!("Running camera solve...");
536559
}
560+
logger.log("INFO", "Running camera solve...");
537561

538562
// Create intermediate result writer if enabled.
539563
let intermediate_writer: Option<Arc<dyn IntermediateResultWriter>> = if args
@@ -602,6 +626,14 @@ fn run_camera_solve(args: &CliArgs) -> Result<()> {
602626
None => camera_intrinsics,
603627
};
604628

629+
logger.log("INFO", &format!(
630+
"Solve completed in {:.2}s: {} cameras, {} bundles, mean_err={:.4}px",
631+
solve_duration.as_secs_f64(),
632+
camera_poses.len(),
633+
bundle_positions.len(),
634+
quality_metrics.mean_reprojection_error,
635+
));
636+
605637
if !args.quiet {
606638
println!();
607639
println!("Solve completed!");
@@ -898,6 +930,8 @@ fn run_camera_solve(args: &CliArgs) -> Result<()> {
898930
println!("Done!");
899931
}
900932

933+
logger.log("INFO", &format!("Total time: {:.2}s", total_time_secs));
934+
901935
Ok(())
902936
}
903937

lib/rust/mmcamerasolve-bin/test.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fi
3333
# Build release version for faster execution.
3434
echo "Building mmcamerasolve-bin (release mode)..."
3535
# cargo build --release -p mmcamerasolve-bin
36-
cargo build --release -p mmcamerasolve-bin --features visualization
36+
cargo build --release -p mmcamerasolve-bin --features visualization,logging
3737
echo ""
3838

3939
MM_CAMERA_SOLVE="${PROJECT_ROOT}/target/release/mmcamerasolve"

lib/rust/mmlogger/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "mmlogger"
3+
version.workspace = true
4+
edition.workspace = true
5+
authors.workspace = true
6+
publish.workspace = true

lib/rust/mmlogger/src/lib.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// Copyright (C) 2026 David Cattermole.
3+
//
4+
// This file is part of mmSolver.
5+
//
6+
// mmSolver is free software: you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// mmSolver is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Lesser General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with mmSolver. If not, see <https://www.gnu.org/licenses/>.
18+
// ====================================================================
19+
//
20+
21+
//! Zero-cost structured logger with compile-time feature gating.
22+
//!
23+
//! All types are generic over `std::io::Write` with static dispatch
24+
//! (no vtable overhead). When using `NoOpLogger`, calls are
25+
//! completely eliminated by the compiler.
26+
27+
use std::io::Write;
28+
29+
/// Trait for structured logging with zero-cost when disabled.
30+
pub trait Logger {
31+
fn log(&mut self, level: &str, msg: &str);
32+
}
33+
34+
/// Logger that writes to any `Write` implementor.
35+
pub struct BaseLogger<W: Write> {
36+
writer: W,
37+
}
38+
39+
impl<W: Write> BaseLogger<W> {
40+
pub fn new(writer: W) -> Self {
41+
Self { writer }
42+
}
43+
}
44+
45+
impl<W: Write> Logger for BaseLogger<W> {
46+
fn log(&mut self, level: &str, msg: &str) {
47+
let _ = writeln!(self.writer, "[{}] {}", level, msg);
48+
}
49+
}
50+
51+
/// Logger that writes to two outputs simultaneously.
52+
pub struct TeeLogger<W1: Write, W2: Write> {
53+
writer1: W1,
54+
writer2: W2,
55+
}
56+
57+
impl<W1: Write, W2: Write> TeeLogger<W1, W2> {
58+
pub fn new(writer1: W1, writer2: W2) -> Self {
59+
Self { writer1, writer2 }
60+
}
61+
}
62+
63+
impl<W1: Write, W2: Write> Logger for TeeLogger<W1, W2> {
64+
fn log(&mut self, level: &str, msg: &str) {
65+
let _ = writeln!(self.writer1, "[{}] {}", level, msg);
66+
let _ = writeln!(self.writer2, "[{}] {}", level, msg);
67+
}
68+
}
69+
70+
/// No-op logger with zero runtime cost.
71+
pub struct NoOpLogger;
72+
73+
impl Logger for NoOpLogger {
74+
#[inline(always)]
75+
fn log(&mut self, _level: &str, _msg: &str) {}
76+
}

scripts/internal/build_mmSolver_linux.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ ${RUST_CARGO_EXE} build --release --target-dir ${BUILD_MMSOLVER_RUST_DIR}
176176
# Build mmcamerasolve Rust binary.
177177
echo "Building mmcamerasolve Rust binary... (${PROJECT_ROOT})"
178178
cd ${MMCAMERASOLVE_RUST_ROOT}
179-
${RUST_CARGO_EXE} build --release --package mmcamerasolve-bin --target-dir ${BUILD_MMSOLVER_RUST_DIR}
179+
${RUST_CARGO_EXE} build --release --package mmcamerasolve-bin --features logging --target-dir ${BUILD_MMSOLVER_RUST_DIR}
180180

181181
# Optionally use "UNIX Makefiles" as the build system generator.
182182
CMAKE_GENERATOR="Ninja"

scripts/internal/build_mmSolver_windows64.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ if errorlevel 1 goto failed_to_build_rust
172172
:: Build mmcamerasolve Rust binary.
173173
ECHO Building mmcamerasolve Rust binary... (%PROJECT_ROOT%)
174174
CHDIR "%MMCAMERASOLVE_RUST_ROOT%"
175-
%RUST_CARGO_EXE% build %RELEASE_FLAG% --package mmcamerasolve-bin --target-dir "%BUILD_MMSOLVER_RUST_DIR%"
175+
%RUST_CARGO_EXE% build %RELEASE_FLAG% --package mmcamerasolve-bin --features logging --target-dir "%BUILD_MMSOLVER_RUST_DIR%"
176176
if errorlevel 1 goto failed_to_build_rust
177177

178178
:: MinGW is a common install for developers on Windows and

0 commit comments

Comments
 (0)