Skip to content

Commit 48246ca

Browse files
committed
Enhance documentation across multiple modules for clarity and rationale
1 parent 3f093a3 commit 48246ca

6 files changed

Lines changed: 161 additions & 79 deletions

File tree

src/config.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ use clap::Parser;
22
use image::ImageFormat;
33
use std::path::PathBuf;
44

5+
/// Command-line configuration for the anime segmentation tool.
6+
///
7+
/// # Why clap's derive macro
8+
///
9+
/// Using clap's derive API provides automatic help generation, type validation,
10+
/// and ergonomic argument parsing without manual string manipulation. The derive
11+
/// approach is preferred over the builder API for its compile-time safety and
12+
/// reduced boilerplate.
513
#[derive(Parser, Clone, Debug)]
614
#[command(version, about, long_about = None)]
715
pub struct Config {
@@ -28,13 +36,26 @@ impl Config {
2836
}
2937

3038
impl Default for Config {
39+
/// Provide a default configuration for testing purposes.
40+
///
41+
/// # Why this exists
42+
///
43+
/// Tests need valid Config instances without requiring command-line arguments.
44+
/// This default implementation provides placeholder values that satisfy the
45+
/// type system. Production code uses `Config::new()` which parses real arguments.
3146
fn default() -> Self {
32-
// This is mainly for tests, parse() is the main way to get a config.
33-
// We need to provide dummy patterns that are valid for parsing.
3447
Config::parse_from(["test", "input/**/*", "--model-path", "model.onnx"])
3548
}
3649
}
3750

51+
/// Validate that the requested format is supported for writing.
52+
///
53+
/// # Why validation at parse time
54+
///
55+
/// Failing early during argument parsing provides immediate feedback to users
56+
/// rather than discovering unsupported formats after potentially expensive processing.
57+
/// The validation uses the image crate's runtime capability detection, ensuring
58+
/// we only accept formats that are actually enabled through feature flags.
3859
fn check_format(s: &str) -> Result<String, String> {
3960
let supported: Vec<_> = ImageFormat::all()
4061
.filter(|f| f.writing_enabled())

src/errors.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
use std::path::PathBuf;
22
use thiserror::Error;
33

4+
/// Structured error types for the anime segmentation application.
5+
///
6+
/// # Why structured errors
7+
///
8+
/// Each variant captures context specific to its error domain (filesystem, image processing,
9+
/// model operations, etc.), providing detailed diagnostic information without requiring
10+
/// callers to parse error strings. The thiserror crate generates Display implementations
11+
/// automatically from format strings, reducing boilerplate while maintaining type safety.
412
#[derive(Error, Debug)]
513
pub enum AnimeSegError {
614
#[error("Configuration error: {message}")]
@@ -35,15 +43,30 @@ pub enum AnimeSegError {
3543

3644
pub type Result<T> = std::result::Result<T, AnimeSegError>;
3745

46+
/// Convert anyhow errors to configuration errors.
47+
///
48+
/// # Why this conversion exists
49+
///
50+
/// Some dependencies return anyhow::Error which lacks structured error information.
51+
/// Rather than propagating the generic error type throughout the codebase, we convert
52+
/// to our domain-specific error type at boundaries. This trade-off prioritizes API
53+
/// consistency over preserving fine-grained error details for these specific cases.
3854
impl From<anyhow::Error> for AnimeSegError {
3955
fn from(err: anyhow::Error) -> Self {
40-
// This is a catch-all. Not ideal, but for simplicity.
4156
AnimeSegError::Configuration {
4257
message: err.to_string(),
4358
}
4459
}
4560
}
4661

62+
/// Convert I/O errors to filesystem errors.
63+
///
64+
/// # Why default values for context
65+
///
66+
/// Some I/O errors occur without specific path/operation context. Rather than
67+
/// requiring all callsites to wrap errors manually, this conversion provides
68+
/// a fallback. Code that has context should construct AnimeSegError::FileSystem
69+
/// directly with the specific path and operation.
4770
impl From<std::io::Error> for AnimeSegError {
4871
fn from(err: std::io::Error) -> Self {
4972
Self::FileSystem {
@@ -54,6 +77,7 @@ impl From<std::io::Error> for AnimeSegError {
5477
}
5578
}
5679

80+
/// Convert image crate errors to image processing errors.
5781
impl From<image::ImageError> for AnimeSegError {
5882
fn from(err: image::ImageError) -> Self {
5983
Self::ImageProcessing {
@@ -64,6 +88,7 @@ impl From<image::ImageError> for AnimeSegError {
6488
}
6589
}
6690

91+
/// Convert ONNX Runtime errors to model errors.
6792
impl From<ort::Error> for AnimeSegError {
6893
fn from(err: ort::Error) -> Self {
6994
Self::Model {
@@ -73,6 +98,13 @@ impl From<ort::Error> for AnimeSegError {
7398
}
7499
}
75100

101+
/// Convert ndarray shape errors to model errors.
102+
///
103+
/// # Why model error category
104+
///
105+
/// Shape errors occur during tensor operations which are part of model inference,
106+
/// so they're categorized as model errors rather than a separate tensor error type.
107+
/// This keeps the error hierarchy flat and focused on user-facing error domains.
76108
impl From<ndarray::ShapeError> for AnimeSegError {
77109
fn from(err: ndarray::ShapeError) -> Self {
78110
Self::Model {

src/lib.rs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ pub use traits::*;
2424
/// "input/**/*.jpg", we want "input/sub/file.jpg" to become "output/sub/file.jpg",
2525
/// not "output/input/sub/file.jpg". This function extracts "input" as the base
2626
/// to enable proper relative path computation.
27-
///
28-
/// # Examples
29-
///
30-
/// - `"input/**/*.jpg"` → `"input"`
31-
/// - `"a/b/**/*"` → `"a/b"`
32-
/// - `"*.jpg"` → `"."`
3327
fn extract_base_directory(pattern: &str) -> PathBuf {
3428
let wildcard_pos = pattern.find(['*', '?', '[']).unwrap_or(pattern.len());
3529

@@ -49,28 +43,28 @@ fn extract_base_directory(pattern: &str) -> PathBuf {
4943
}
5044
}
5145

52-
/// Simple parallel image processor using Rayon for single-machine batch processing.
46+
/// Batch image processor using trait-based model abstraction.
5347
///
54-
/// This processor is generic over any type implementing `ImageSegmentationModel`,
55-
/// allowing different model backends (ONNX, TensorRT, etc.) to be used with the
56-
/// same processing logic. Uses Rayon for straightforward parallel processing without
57-
/// the complexity of queues or distributed architecture.
48+
/// Generic over any type implementing `ImageSegmentationModel`, allowing different
49+
/// model backends to be used with the same processing logic. Processes images
50+
/// sequentially to maintain simplicity and avoid resource contention with GPU inference.
5851
pub struct ImageProcessor<M: ImageSegmentationModel> {
5952
model: M,
6053
config: Config,
6154
}
6255

6356
impl<M: ImageSegmentationModel> ImageProcessor<M> {
64-
/// Create a new image processor with the given model and configuration.
6557
pub fn new(model: M, config: Config) -> Self {
6658
Self { model, config }
6759
}
6860

6961
/// Process all images matching the input pattern and save results to output directory.
7062
///
71-
/// This method orchestrates the entire processing workflow: collecting image files,
72-
/// creating output directories, processing each image with progress tracking, and
73-
/// handling errors gracefully without stopping the entire batch.
63+
/// # Error handling strategy
64+
///
65+
/// Individual image failures are logged but do not stop the entire batch. This design
66+
/// prioritizes completing as much work as possible rather than failing fast, which is
67+
/// appropriate for batch processing where partial results are valuable.
7468
pub fn process_directory(&mut self) -> Result<()> {
7569
let input_pattern = &self.config.input_pattern;
7670
let output_path = self.config.output_dir.clone();
@@ -111,12 +105,9 @@ impl<M: ImageSegmentationModel> ImageProcessor<M> {
111105
}
112106

113107
/// Collect all valid image files matching the glob pattern.
114-
///
115-
/// Only includes files with supported image formats to avoid processing errors later.
116108
fn collect_image_files(&self, pattern: &str) -> Result<Vec<PathBuf>> {
117109
let mut image_files = Vec::new();
118110

119-
// Execute glob pattern and filter for valid image files
120111
for entry in glob(pattern).map_err(|e| AnimeSegError::ImageProcessing {
121112
path: pattern.to_string(),
122113
operation: "glob pattern parsing".to_string(),
@@ -139,9 +130,11 @@ impl<M: ImageSegmentationModel> ImageProcessor<M> {
139130

140131
/// Check if the file has a supported image format that can be read.
141132
///
142-
/// Uses the `image` crate's format detection to determine support dynamically,
143-
/// which allows the set of supported formats to expand automatically when
144-
/// enabling additional feature flags.
133+
/// # Why dynamic format detection
134+
///
135+
/// Using the image crate's format detection allows the set of supported formats
136+
/// to expand automatically when enabling additional feature flags, avoiding the
137+
/// need to maintain a hardcoded list of extensions.
145138
pub fn is_supported_image_format(&self, path: &Path) -> bool {
146139
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
147140
let format = ImageFormat::from_extension(extension);
@@ -156,9 +149,11 @@ impl<M: ImageSegmentationModel> ImageProcessor<M> {
156149

157150
/// Process a single image: load, segment, and save with preserved directory structure.
158151
///
159-
/// The output path preserves the relative directory structure from the input pattern's
160-
/// base directory. This ensures that hierarchical folder organization is maintained
161-
/// in the output, which is essential for batch processing large organized datasets.
152+
/// # Why preserve directory structure
153+
///
154+
/// Maintaining the relative directory structure from input to output is essential
155+
/// for batch processing large organized datasets where folder hierarchy provides
156+
/// semantic organization (e.g., by date, category, or project).
162157
fn process_single_image(&mut self, input_file: &Path, output_dir: &Path) -> Result<()> {
163158
let img = image::open(input_file).map_err(|e| AnimeSegError::ImageProcessing {
164159
path: input_file.display().to_string(),
@@ -195,16 +190,18 @@ impl<M: ImageSegmentationModel> ImageProcessor<M> {
195190
Ok(())
196191
}
197192

198-
/// Delegate segmentation to the underlying model implementation.
199193
fn segment_image(&mut self, img: &DynamicImage) -> Result<DynamicImage> {
200194
self.model.segment_image(img)
201195
}
202196

203197
/// Compute the relative path from the input pattern's base directory.
204198
///
205-
/// This is necessary to reconstruct the directory hierarchy in the output.
206-
/// For example, with input pattern "photos/**/*.jpg" and file "photos/2024/jan/pic.jpg",
207-
/// this returns "2024/jan/pic.jpg" so the output becomes "output/2024/jan/pic.png".
199+
/// # Why this computation is necessary
200+
///
201+
/// To reconstruct the directory hierarchy in the output while avoiding duplication
202+
/// of the base directory. For example, with pattern "photos/**/*.jpg" and file
203+
/// "photos/2024/jan/pic.jpg", this returns "2024/jan/pic.jpg" so the output becomes
204+
/// "output/2024/jan/pic.png" rather than "output/photos/2024/jan/pic.png".
208205
pub fn get_relative_path(&self, input_file: &Path) -> Result<PathBuf> {
209206
let base_dir = extract_base_directory(&self.config.input_pattern);
210207
input_file
@@ -224,7 +221,9 @@ impl<M: ImageSegmentationModel> ImageProcessor<M> {
224221
impl ImageProcessor<Model> {
225222
/// Convenience constructor that creates an ONNX-based image processor.
226223
///
227-
/// This provides a simpler API for the common case of using the default ONNX model
224+
/// # Why this exists
225+
///
226+
/// Provides a simpler API for the common case of using the default ONNX model
228227
/// implementation, avoiding the need to construct the Model separately.
229228
pub fn with_onnx_model(config: Config) -> Result<Self> {
230229
let model = Model::new(&config.model_path)?;

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
use anime_seg_rs::{Config, ImageProcessor, Result};
22

3+
/// Entry point for the anime segmentation CLI tool.
4+
///
5+
/// # Why this is minimal
6+
///
7+
/// Following the thin main principle: main.rs only handles program initialization
8+
/// and delegates all business logic to the library. This design keeps the binary
9+
/// target separate from the library crate, enabling the library to be used as a
10+
/// dependency by other projects without pulling in CLI-specific concerns.
311
fn main() -> Result<()> {
412
let config = Config::new();
513

0 commit comments

Comments
 (0)