- Add CLAUDE.md with project overview, tech stack, build commands, architecture description, coding standards, and sample images section - Add full directory structure: src/, docs/, tests/, import/ - Add CMakeLists.txt with C++20, OpenCV/LibRaw/Qt6 dependencies, converter_core static lib, optional GUI, and GTest tests - Add architecture documentation: ARCHITECTURE.md, PIPELINE.md, MODULES.md - Add source skeletons for all pipeline stages: RawLoader, Preprocessor, NegativeDetector, Inverter, ColorCorrector, CropProcessor, OutputWriter, Pipeline, MainWindow, CliRunner, main.cpp - Add initial test stubs for pipeline and rawloader - Add sample ARW files in import/ for integration testing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
266 lines
9.8 KiB
Markdown
266 lines
9.8 KiB
Markdown
# Module Catalog
|
|
|
|
## Module Overview
|
|
|
|
| Module | Location | Responsibility | Dependencies |
|
|
|--------|----------|---------------|--------------|
|
|
| Pipeline | `src/converter/pipeline/` | Stage orchestration, data types | OpenCV (core) |
|
|
| RawLoader | `src/converter/rawloader/` | File loading (RAW + standard) | OpenCV, LibRaw |
|
|
| Preprocessor | `src/converter/preprocess/` | Format normalization, deskew | OpenCV |
|
|
| NegativeDetector | `src/converter/negative/` | Film type classification | OpenCV |
|
|
| Inverter | `src/converter/invert/` | Negative-to-positive inversion | OpenCV |
|
|
| ColorCorrector | `src/converter/color/` | White balance, C-41 correction | OpenCV |
|
|
| CropProcessor | `src/converter/crop/` | Auto-crop, levels, sharpening | OpenCV |
|
|
| OutputWriter | `src/converter/output/` | File output | OpenCV |
|
|
| MainWindow | `src/gui/` | Qt GUI | Qt 6.8, converter_core |
|
|
| CliRunner | `src/cli/` | CLI batch processing | converter_core |
|
|
|
|
## Dependency Graph
|
|
|
|
```
|
|
MainWindow ──> Pipeline ──> PipelineStage (interface)
|
|
| | ^
|
|
| | |
|
|
v | +--------+--------+--------+--------+--------+--------+
|
|
Qt 6.8 | | | | | | | |
|
|
| Preproc Detect Invert Color Crop Output (future)
|
|
| | | | | | |
|
|
+-----+--------+--------+--------+--------+--------+
|
|
|
|
|
v
|
|
RawLoader
|
|
|
|
|
+----+----+
|
|
| |
|
|
LibRaw OpenCV
|
|
|
|
CliRunner ──> Pipeline ──> (same stages as above)
|
|
|
|
|
+──> RawLoader
|
|
```
|
|
|
|
## Module Details
|
|
|
|
---
|
|
|
|
### Pipeline Infrastructure (`src/converter/pipeline/`)
|
|
|
|
#### Error.h
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `enum class ErrorCode` | Error classification (30+ codes across all stages) |
|
|
| `struct Error` | Error with code, message, and source location |
|
|
| `make_error()` | Factory function with automatic source_location capture |
|
|
|
|
#### ImageData.h
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `enum class FilmType` | Film classification: Unknown, ColorNegative, BWNegative, ColorPositive, BWPositive |
|
|
| `struct RawMetadata` | Camera make/model, ISO, shutter, aperture, WB multipliers, dimensions |
|
|
| `struct ImageData` | Core carrier: cv::Mat rgb (CV_16UC3), source_path, metadata, film_type, crop_region |
|
|
|
|
#### PipelineStage.h
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `using StageResult` | `std::expected<ImageData, Error>` |
|
|
| `class PipelineStage` | Abstract interface: `process(ImageData) -> StageResult`, `name() -> string` |
|
|
| `using ProgressCallback` | `function<void(string, float)>` for GUI progress reporting |
|
|
|
|
#### Pipeline.h / Pipeline.cpp
|
|
|
|
| Class | `Pipeline` |
|
|
|-------|-----------|
|
|
| **Pattern** | Chain of Responsibility + Strategy |
|
|
| **Owns** | `vector<unique_ptr<PipelineStage>>` |
|
|
| **Methods** | |
|
|
| `add_stage(unique_ptr<PipelineStage>)` | Append a stage |
|
|
| `execute(ImageData, ProgressCallback) -> StageResult` | Run all stages sequentially |
|
|
| `stage_count() -> size_t` | Number of registered stages |
|
|
| **Thread safety** | Not thread-safe (single-threaded execution) |
|
|
| **Copyable** | No (deleted copy ctor/assignment) |
|
|
| **Movable** | Yes |
|
|
|
|
---
|
|
|
|
### RawLoader (`src/converter/rawloader/`)
|
|
|
|
| Class | `RawLoader` |
|
|
|-------|------------|
|
|
| **Pattern** | Factory Method (routes to load_raw or load_standard) |
|
|
| **Methods** | |
|
|
| `load(path) -> expected<ImageData, Error>` | Main entry point |
|
|
| `load_raw(path)` | LibRaw loader (private) |
|
|
| `load_standard(path)` | OpenCV loader (private) |
|
|
| `is_raw_format(path) -> bool` | Extension check (static) |
|
|
| `is_standard_format(path) -> bool` | Extension check (static) |
|
|
| `log_metadata(RawMetadata)` | Print metadata to stdout (static) |
|
|
| **Constants** | |
|
|
| `kMaxRawFileSize` | 4 GB (4,294,967,296 bytes) |
|
|
| `kRawExtensions[]` | cr2, cr3, nef, arw, dng, orf, rw2, raf, pef |
|
|
| `kStandardExtensions[]` | jpg, jpeg, png, tif, tiff |
|
|
| **Internal** | |
|
|
| `LibRawGuard` | RAII class ensuring `LibRaw::recycle()` |
|
|
|
|
**LibRaw configuration:**
|
|
```cpp
|
|
params.use_camera_wb = 1; // Use camera white balance
|
|
params.output_bps = 16; // 16-bit output
|
|
params.no_auto_bright = 1; // No auto-brightness
|
|
params.half_size = 0; // Full resolution
|
|
params.output_color = 1; // sRGB color space
|
|
```
|
|
|
|
---
|
|
|
|
### Preprocessor (`src/converter/preprocess/`)
|
|
|
|
| Class | `Preprocessor` : `PipelineStage` |
|
|
|-------|----------------------------------|
|
|
| **Stage name** | "Preprocess" |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Validate + deskew |
|
|
| `validate_and_convert(ImageData)` | Ensure CV_16UC3 (static, private) |
|
|
| `deskew(ImageData)` | Geometric correction (static, private, TODO) |
|
|
|
|
---
|
|
|
|
### NegativeDetector (`src/converter/negative/`)
|
|
|
|
| Class | `NegativeDetector` : `PipelineStage` |
|
|
|-------|--------------------------------------|
|
|
| **Stage name** | "Detect" |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Classify film type |
|
|
| `is_negative_histogram(Mat) -> bool` | Mean intensity analysis (static, private) |
|
|
| `has_orange_mask(Mat) -> bool` | R/B ratio check (static, private) |
|
|
| `is_monochrome(Mat) -> bool` | HSV saturation check (static, private) |
|
|
| **Constants** | |
|
|
| `kOrangeMaskThreshold` | 1.4f |
|
|
| `kColorSaturationThreshold` | 15.0f |
|
|
|
|
---
|
|
|
|
### Inverter (`src/converter/invert/`)
|
|
|
|
| Class | `Inverter` : `PipelineStage` |
|
|
|-------|------------------------------|
|
|
| **Stage name** | "Invert" |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Route by film type |
|
|
| `invert_color_negative(ImageData)` | Orange mask removal + bitwise_not (static, private) |
|
|
| `invert_bw_negative(ImageData)` | Simple bitwise_not (static, private) |
|
|
|
|
---
|
|
|
|
### ColorCorrector (`src/converter/color/`)
|
|
|
|
| Class | `ColorCorrector` : `PipelineStage` |
|
|
|-------|-------------------------------------|
|
|
| **Stage name** | "ColorCorrection" |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Route by film type |
|
|
| `correct_c41(ImageData)` | C-41 orange cast removal (static, private, TODO) |
|
|
| `auto_white_balance(ImageData)` | Gray-world AWB (static, private) |
|
|
| `apply_exif_wb(ImageData)` | EXIF-based WB (static, private) |
|
|
|
|
---
|
|
|
|
### CropProcessor (`src/converter/crop/`)
|
|
|
|
| Class | `CropProcessor` : `PipelineStage` |
|
|
|-------|-------------------------------------|
|
|
| **Stage name** | "PostProcess" |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Auto-crop + levels + sharpen |
|
|
| `auto_crop(ImageData)` | Frame detection via contours (static, private, TODO) |
|
|
| `adjust_levels(ImageData)` | Percentile-based levels (static, private, TODO) |
|
|
| `sharpen(ImageData)` | Unsharp mask (static, private, TODO) |
|
|
| **Constants** | |
|
|
| `kMinFrameAreaRatio` | 0.3 |
|
|
| `kSharpenSigma` | 1.5 |
|
|
| `kSharpenStrength` | 0.5 |
|
|
| `kBlackPointPercentile` | 0.5 |
|
|
| `kWhitePointPercentile` | 99.5 |
|
|
|
|
---
|
|
|
|
### OutputWriter (`src/converter/output/`)
|
|
|
|
| Class | `OutputWriter` : `PipelineStage` |
|
|
|-------|-----------------------------------|
|
|
| **Stage name** | "Output" |
|
|
| **Constructor** | `OutputWriter(OutputConfig config)` |
|
|
| **Methods** | |
|
|
| `process(ImageData) -> StageResult` | Write file to disk |
|
|
| `build_output_path(string) -> path` | Construct output filename (private) |
|
|
| `format_extension(OutputFormat) -> string` | Map format to extension (static, private) |
|
|
| **Config** | |
|
|
| `OutputConfig::output_dir` | Target directory |
|
|
| `OutputConfig::format` | PNG_16bit, PNG_8bit, TIFF_16bit, JPEG |
|
|
| `OutputConfig::jpeg_quality` | 0-100 (default 95) |
|
|
|
|
---
|
|
|
|
### MainWindow (`src/gui/`)
|
|
|
|
| Class | `MainWindow` : `QMainWindow` |
|
|
|-------|-------------------------------|
|
|
| **Qt slots** | |
|
|
| `on_open_files()` | File dialog with RAW filter |
|
|
| `on_convert()` | Execute pipeline on all selected files |
|
|
| `on_select_output_dir()` | Directory picker |
|
|
| **Private** | |
|
|
| `setup_ui()` | Construct widgets and layout |
|
|
| `setup_pipeline()` | Build default pipeline |
|
|
| `update_preview(Mat)` | Convert 16-bit BGR to QPixmap for display |
|
|
| **State** | |
|
|
| `input_files_` | Selected file paths |
|
|
| `output_dir_` | Chosen output directory |
|
|
| `pipeline_` | Pipeline instance |
|
|
|
|
---
|
|
|
|
### CliRunner (`src/cli/`)
|
|
|
|
| Class | `CliRunner` |
|
|
|-------|------------|
|
|
| **Nested types** | `Config` (input_files, output_dir, output_format, jpeg_quality, verbose) |
|
|
| **Static methods** | |
|
|
| `parse_args(argc, argv) -> expected<Config, Error>` | Parse CLI arguments |
|
|
| **Methods** | |
|
|
| `run(Config) -> expected<int, Error>` | Execute batch processing, return success count |
|
|
|
|
**CLI usage:**
|
|
```
|
|
photo-converter --cli -i file1.arw file2.cr2 -o output/ --format png16
|
|
```
|
|
|
|
---
|
|
|
|
## Build Targets
|
|
|
|
| Target | Type | Sources | Dependencies |
|
|
|--------|------|---------|-------------|
|
|
| `converter_core` | Static library | All `src/converter/` + `src/cli/` | OpenCV, LibRaw |
|
|
| `photo-converter` | Executable | `src/main.cpp` + `src/gui/` | converter_core, Qt6::Widgets |
|
|
| `test_pipeline` | Test executable | `tests/test_pipeline.cpp` | converter_core, GTest |
|
|
| `test_rawloader` | Test executable | `tests/test_rawloader.cpp` | converter_core, GTest |
|
|
|
|
## Implementation Status
|
|
|
|
| Module | Status | Notes |
|
|
|--------|--------|-------|
|
|
| Pipeline | Complete | Orchestration, error propagation, progress |
|
|
| RawLoader | Complete | Full LibRaw + OpenCV loading |
|
|
| Preprocessor | Skeleton | Validation done, deskew TODO |
|
|
| NegativeDetector | Functional | Basic histogram + orange mask detection |
|
|
| Inverter | Skeleton | Basic bitwise_not, C-41 mask removal TODO |
|
|
| ColorCorrector | Partial | Gray-world AWB implemented, C-41 TODO |
|
|
| CropProcessor | Skeleton | All sub-stages TODO (pass-through) |
|
|
| OutputWriter | Complete | All formats supported |
|
|
| MainWindow | Complete | File dialogs, preview, progress |
|
|
| CliRunner | Complete | Argument parsing, batch loop |
|