Skip to content

Commit 9755cb1

Browse files
narrowstacksclaude
andcommitted
refactor: modularize CLI crate into focused modules
Split lib.rs (917→19 lines) and main.rs (689→568 lines) into: - args/ - PipelineArgs, DebugArgs structs - types/ - WhiteBalance, ProcessingParams - parsers/ - parse_* functions (base, pipeline, CB options) - builders/ - build_convert_options, build_cb_options - processing/ - process_single_image, input utilities All new files are under 300 lines with single responsibilities. Updated CLAUDE.md docs to reflect current directory structure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 75bef13 commit 9755cb1

20 files changed

Lines changed: 1154 additions & 1048 deletions

File tree

CLAUDE.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ All processing uses f32 linear RGB (0.0-1.0 range).
3434

3535
**Pipeline changes MUST be implemented in both CPU and GPU codepaths.**
3636

37-
| Component | CPU | GPU |
38-
| --------- | ----------------- | -------------------- |
39-
| Pipeline | `pipeline/mod.rs` | `gpu/pipeline.rs` |
40-
| Shaders | N/A | `gpu/shaders/*.wgsl` |
37+
| Component | CPU | GPU |
38+
| --------- | ----------------- | ---------------------- |
39+
| Pipeline | `pipeline/mod.rs` | `gpu/pipeline/mod.rs` |
40+
| Shaders | N/A | `gpu/shaders/*.wgsl` |
4141

4242
When modifying pipeline logic:
4343

@@ -47,9 +47,11 @@ When modifying pipeline logic:
4747

4848
## Key Files
4949

50-
- `crates/invers-core/src/pipeline/` - CPU processing pipeline
51-
- `crates/invers-core/src/gpu/` - GPU acceleration (wgpu)
52-
- `crates/invers-core/src/models/` - Data structures (FilmPreset, ConvertOptions, etc.)
53-
- `crates/invers-core/src/auto_adjust/` - Auto-levels, color, exposure, white balance
50+
- `crates/invers-core/src/pipeline/` - CPU processing pipeline (base_estimation/, inversion/, tone_mapping/)
51+
- `crates/invers-core/src/gpu/` - GPU acceleration (context/, pipeline/, shaders/)
52+
- `crates/invers-core/src/models/` - Data structures (cb/, convert_options/, scan_profile/)
53+
- `crates/invers-core/src/auto_adjust/` - Auto-adjustments (levels/, white_balance/, color.rs, exposure.rs)
54+
- `crates/invers-core/src/decoders/` - Image decoders (tiff.rs, png.rs, raw.rs)
55+
- `crates/invers-core/src/color/` - Color space conversions (hsl.rs, lab.rs)
5456
- `crates/invers-cli/src/commands/` - CLI command implementations
5557
- `config/pipeline_defaults.yml` - Default pipeline configuration
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Debug-only argument structs for CLI commands.
2+
3+
#[cfg(debug_assertions)]
4+
use clap::Args;
5+
6+
/// Debug-only arguments (only available in debug builds).
7+
/// These flags are hidden from default help output.
8+
#[cfg(debug_assertions)]
9+
#[derive(Args, Clone, Debug, Default)]
10+
pub struct DebugArgs {
11+
/// [DEBUG] Skip tone curve application
12+
#[arg(long, hide = true)]
13+
pub no_tonecurve: bool,
14+
15+
/// [DEBUG] Skip color matrix correction
16+
#[arg(long, hide = true)]
17+
pub no_colormatrix: bool,
18+
19+
/// [DEBUG] Inversion mode override: "mask-aware", "linear", "log", "divide-blend", or "bw"
20+
#[arg(long, value_name = "MODE", hide = true)]
21+
pub inversion: Option<String>,
22+
23+
/// [DEBUG] Enable automatic white balance correction
24+
#[arg(long, hide = true)]
25+
pub auto_wb: bool,
26+
27+
/// [DEBUG] Strength of auto white balance correction (0.0-1.0)
28+
#[arg(long, value_name = "FLOAT", default_value = "1.0", hide = true)]
29+
pub auto_wb_strength: f32,
30+
31+
/// [DEBUG] Auto white balance mode: "gray", "avg", or "pct" (percentile-based)
32+
/// "pct" uses 98th percentile (robust white patch) - robust for varied scenes
33+
#[arg(long, value_name = "MODE", default_value = "gray", hide = true)]
34+
pub auto_wb_mode: String,
35+
36+
/// [DEBUG] Tone curve type: "neutral", "log", "cinematic", "linear", "asymmetric"
37+
/// "log"/"cinematic" provides cinematic log-style tone profile
38+
#[arg(long, value_name = "TYPE", hide = true)]
39+
pub tone_curve: Option<String>,
40+
41+
/// [DEBUG] Enable debug output (detailed pipeline parameters)
42+
#[arg(long, hide = true)]
43+
pub debug: bool,
44+
}
45+
46+
/// Placeholder for release builds - provides default values.
47+
#[cfg(not(debug_assertions))]
48+
#[derive(Clone, Debug, Default)]
49+
pub struct DebugArgs {
50+
pub no_tonecurve: bool,
51+
pub no_colormatrix: bool,
52+
pub inversion: Option<String>,
53+
pub auto_wb: bool,
54+
pub auto_wb_strength: f32,
55+
pub auto_wb_mode: String,
56+
pub tone_curve: Option<String>,
57+
pub debug: bool,
58+
}
59+
60+
#[cfg(not(debug_assertions))]
61+
impl DebugArgs {
62+
pub fn release_defaults() -> Self {
63+
Self {
64+
no_tonecurve: false,
65+
no_colormatrix: false,
66+
inversion: None,
67+
auto_wb: true,
68+
auto_wb_strength: 1.0,
69+
auto_wb_mode: "avg".to_string(),
70+
tone_curve: None,
71+
debug: false,
72+
}
73+
}
74+
}

crates/invers-cli/src/args/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! CLI argument structs.
2+
3+
mod debug;
4+
mod pipeline;
5+
6+
pub use debug::DebugArgs;
7+
pub use pipeline::PipelineArgs;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Pipeline argument structs for CLI commands.
2+
3+
use clap::Args;
4+
5+
/// Common pipeline arguments shared between Convert and Batch commands.
6+
/// These are advanced/research options hidden from default help output.
7+
#[derive(Args, Clone, Debug)]
8+
pub struct PipelineArgs {
9+
/// Pipeline mode: "legacy" (default), "research", or "cb"
10+
/// Research pipeline uses density balance BEFORE inversion for better color accuracy
11+
/// CB pipeline uses curve-based processing for film-like rendering
12+
#[arg(long, value_name = "MODE", default_value = "legacy", hide = true)]
13+
pub pipeline: String,
14+
15+
/// [RESEARCH] Density balance red exponent (R^db_r)
16+
/// Typical range: 0.8-1.3, default 1.05
17+
#[arg(long, value_name = "FLOAT", hide = true)]
18+
pub db_red: Option<f32>,
19+
20+
/// [RESEARCH] Density balance blue exponent (B^db_b)
21+
/// Typical range: 0.7-1.1, default 0.90
22+
#[arg(long, value_name = "FLOAT", hide = true)]
23+
pub db_blue: Option<f32>,
24+
25+
/// [RESEARCH] Neutral point ROI for auto-calculating density balance (x,y,width,height)
26+
/// Sample a known gray area to auto-compute density balance
27+
#[arg(long, value_name = "X,Y,W,H", hide = true)]
28+
pub neutral_roi: Option<String>,
29+
30+
/// [CB] Tone profile preset (standard, logarithmic, log-rich, log-flat, linear,
31+
/// linear-gamma, linear-flat, linear-deep, all-soft, all-hard, highlight-hard,
32+
/// highlight-soft, shadow-hard, shadow-soft, auto)
33+
#[arg(long, value_name = "PROFILE", hide = true)]
34+
pub cb_tone: Option<String>,
35+
36+
/// [CB] Enhanced profile/LUT (none, natural, frontier, crystal, pakon)
37+
#[arg(long, value_name = "PROFILE", hide = true)]
38+
pub cb_lut: Option<String>,
39+
40+
/// [CB] Color model (none, basic, frontier, noritsu, bw)
41+
#[arg(long, value_name = "MODEL", hide = true)]
42+
pub cb_color: Option<String>,
43+
44+
/// [CB] Film character (none, generic, kodak, fuji, cinestill-50d, cinestill-800t)
45+
#[arg(long, value_name = "CHARACTER", hide = true)]
46+
pub cb_film: Option<String>,
47+
48+
/// [CB] White balance preset (none, auto, neutral, warm, cool, mix, standard, kodak, fuji, cine-t, cine-d)
49+
#[arg(long, value_name = "PRESET", hide = true)]
50+
pub cb_wb: Option<String>,
51+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! CB (curve-based) pipeline options builder.
2+
3+
use crate::parsers::{
4+
parse_cb_color_model, parse_cb_enhanced_profile, parse_cb_film_character,
5+
parse_cb_tone_profile, parse_cb_wb_preset,
6+
};
7+
8+
/// Build CbOptions from parsed command line arguments
9+
pub fn build_cb_options(
10+
tone_profile: Option<&str>,
11+
enhanced_profile: Option<&str>,
12+
color_model: Option<&str>,
13+
film_character: Option<&str>,
14+
wb_preset: Option<&str>,
15+
) -> Result<invers_core::models::CbOptions, String> {
16+
let tone = parse_cb_tone_profile(tone_profile)?;
17+
let lut = parse_cb_enhanced_profile(enhanced_profile)?;
18+
let color = parse_cb_color_model(color_model)?;
19+
let film = parse_cb_film_character(film_character)?;
20+
let wb = parse_cb_wb_preset(wb_preset)?;
21+
22+
let mut opts = invers_core::models::CbOptions::from_presets(
23+
tone,
24+
lut,
25+
color,
26+
film,
27+
invers_core::models::CbEnginePreset::V3_1,
28+
);
29+
opts.wb_preset = wb;
30+
Ok(opts)
31+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Builder functions for CLI options.
2+
3+
mod cb;
4+
mod options;
5+
6+
pub use cb::build_cb_options;
7+
pub use options::build_convert_options_full_with_gpu;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! ConvertOptions builder functions.
2+
3+
use std::path::PathBuf;
4+
5+
/// Build a ConvertOptions struct with all options including GPU control
6+
///
7+
/// This is the canonical function for building ConvertOptions with all
8+
/// pipeline defaults, making it reusable across CLI and GUI applications.
9+
#[allow(clippy::too_many_arguments)]
10+
pub fn build_convert_options_full_with_gpu(
11+
input: PathBuf,
12+
output_dir: PathBuf,
13+
export: &str,
14+
colorspace: String,
15+
base_estimation: Option<invers_core::models::BaseEstimation>,
16+
film_preset: Option<invers_core::models::FilmPreset>,
17+
scan_profile: Option<invers_core::models::ScanProfile>,
18+
no_tonecurve: bool,
19+
no_colormatrix: bool,
20+
exposure: f32,
21+
inversion_mode: Option<invers_core::models::InversionMode>,
22+
no_auto_levels: bool,
23+
preserve_headroom: bool,
24+
no_clip: bool,
25+
auto_wb: bool,
26+
auto_wb_strength: f32,
27+
debug: bool,
28+
use_gpu: bool,
29+
) -> Result<invers_core::models::ConvertOptions, String> {
30+
let config_handle = invers_core::config::pipeline_config_handle();
31+
let defaults = config_handle.config.defaults.clone();
32+
33+
// Parse output format
34+
let output_format = match export {
35+
"tiff16" | "tiff" => invers_core::models::OutputFormat::Tiff16,
36+
"dng" => invers_core::models::OutputFormat::LinearDng,
37+
_ => return Err(format!("Unknown export format: {}", export)),
38+
};
39+
40+
// Use provided inversion mode, or scan profile preference, or fall back to config default
41+
let inversion_mode = inversion_mode
42+
.or_else(|| {
43+
scan_profile
44+
.as_ref()
45+
.and_then(|sp| sp.preferred_inversion_mode)
46+
})
47+
.unwrap_or(defaults.inversion_mode);
48+
49+
// Auto-levels: disabled if --no-auto-levels is set
50+
let enable_auto_levels = !no_auto_levels && defaults.enable_auto_levels;
51+
52+
Ok(invers_core::models::ConvertOptions {
53+
input_paths: vec![input],
54+
output_dir,
55+
output_format,
56+
working_colorspace: colorspace,
57+
bit_depth_policy: invers_core::models::BitDepthPolicy::Force16Bit,
58+
film_preset,
59+
scan_profile,
60+
base_estimation,
61+
num_threads: None,
62+
skip_tone_curve: no_tonecurve || defaults.skip_tone_curve,
63+
skip_color_matrix: no_colormatrix || defaults.skip_color_matrix,
64+
exposure_compensation: defaults.exposure_compensation * exposure,
65+
debug,
66+
enable_auto_levels,
67+
auto_levels_clip_percent: defaults.auto_levels_clip_percent,
68+
preserve_headroom: preserve_headroom || defaults.preserve_headroom,
69+
enable_auto_color: defaults.enable_auto_color,
70+
auto_color_strength: defaults.auto_color_strength,
71+
auto_color_min_gain: defaults.auto_color_min_gain,
72+
auto_color_max_gain: defaults.auto_color_max_gain,
73+
auto_color_max_divergence: defaults.auto_color_max_divergence,
74+
base_brightest_percent: defaults.base_brightest_percent,
75+
base_sampling_mode: defaults.base_sampling_mode,
76+
base_estimation_method: invers_core::models::BaseEstimationMethod::default(),
77+
auto_levels_mode: invers_core::models::AutoLevelsMode::default(),
78+
inversion_mode,
79+
shadow_lift_mode: defaults.shadow_lift_mode,
80+
shadow_lift_value: defaults.shadow_lift_value,
81+
highlight_compression: defaults.highlight_compression,
82+
enable_auto_exposure: defaults.enable_auto_exposure,
83+
auto_exposure_target_median: defaults.auto_exposure_target_median,
84+
auto_exposure_strength: defaults.auto_exposure_strength,
85+
auto_exposure_min_gain: defaults.auto_exposure_min_gain,
86+
auto_exposure_max_gain: defaults.auto_exposure_max_gain,
87+
no_clip,
88+
enable_auto_wb: auto_wb,
89+
auto_wb_strength,
90+
auto_wb_mode: invers_core::models::AutoWbMode::default(),
91+
use_gpu,
92+
// Research pipeline options (defaults for now, CLI args coming soon)
93+
pipeline_mode: invers_core::models::PipelineMode::Legacy,
94+
density_balance: None,
95+
neutral_point: None,
96+
density_balance_red: None,
97+
density_balance_blue: None,
98+
tone_curve_override: None,
99+
// CB-style pipeline options
100+
cb_options: None,
101+
})
102+
}

0 commit comments

Comments
 (0)