- 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>
9.5 KiB
Pipeline Documentation
Overview
The processing pipeline transforms a digitized film negative into a color-corrected digital positive. Data flows through seven stages, each receiving and returning an ImageData struct wrapped in std::expected.
ImageData flow
|
+----------------------v-----------------------+
| 1. LOADER (RawLoader) |
| Input: file path (string) |
| Output: ImageData { CV_16UC3, metadata } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 2. PREPROCESS (Preprocessor) |
| Input: ImageData (possibly wrong depth) |
| Output: ImageData { CV_16UC3 guaranteed } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 3. DETECT (NegativeDetector) |
| Input: ImageData { film_type = Unknown } |
| Output: ImageData { film_type = detected } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 4. INVERT (Inverter) |
| Input: ImageData { negative or positive } |
| Output: ImageData { inverted if negative } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 5. COLOR (ColorCorrector) |
| Input: ImageData { inverted positive } |
| Output: ImageData { color-balanced } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 6. POST-PROCESS (CropProcessor) |
| Input: ImageData { color-corrected } |
| Output: ImageData { cropped, sharpened } |
+----------------------+-----------------------+
|
+----------------------v-----------------------+
| 7. OUTPUT (OutputWriter) |
| Input: ImageData { final } |
| Output: ImageData (unchanged) + file on disk |
+----------------------+-----------------------+
Core Data Structures
ImageData
struct ImageData {
cv::Mat rgb; // Always CV_16UC3 (16-bit BGR)
std::string source_path; // Original file path
RawMetadata metadata; // Camera EXIF/RAW data
FilmType film_type{FilmType::Unknown}; // Set by Detect stage
std::optional<cv::Rect> crop_region; // Set by Post-Process
};
RawMetadata
struct RawMetadata {
std::string camera_make; // "Sony", "Canon", "Nikon"
std::string camera_model; // "ILCE-7M3"
float iso_speed;
float shutter_speed; // seconds
float aperture; // f-number
float focal_length; // mm
float wb_red, wb_green, wb_blue; // White balance multipliers
int raw_width, raw_height;
int raw_bit_depth;
std::string timestamp; // ISO 8601
};
FilmType
enum class FilmType {
Unknown, // Not yet classified
ColorNegative, // C-41 process film
BWNegative, // Black & white negative
ColorPositive, // Slide / E-6 film
BWPositive, // Black & white positive
};
StageResult
using StageResult = std::expected<ImageData, Error>;
Stage Details
Stage 1: Loader (RawLoader)
Purpose: Read image files from disk into the pipeline.
Input: File path (std::filesystem::path)
Output: std::expected<ImageData, Error>
Behavior:
- Validate file exists and size < 4GB
- Detect format from extension
- For RAW files:
- Initialize LibRaw with lossless settings (16-bit, full resolution, sRGB)
- Open, unpack, and process (demosaic)
- Extract image data as cv::Mat CV_16UC3
- Extract all metadata (camera, exposure, WB multipliers)
- Log metadata to stdout
- Guarantee
LibRaw::recycle()via RAII guard
- For standard files (JPG/PNG/TIFF):
- Load via
cv::imread(IMREAD_UNCHANGED) - Convert to CV_16UC3 (scale 8-bit to 16-bit if needed)
- Populate minimal metadata
- Load via
Supported RAW formats: CR2, CR3, NEF, ARW, DNG, ORF, RW2, RAF, PEF Supported standard formats: JPG, JPEG, PNG, TIF, TIFF
Error codes: FileNotFound, FileTooLarge, UnsupportedFormat, LibRawInitFailed, LibRawUnpackFailed, LibRawProcessFailed, DemosaicingFailed, FileReadError
Stage 2: Preprocess (Preprocessor)
Purpose: Normalize image format and correct geometric distortion.
Input: ImageData (possibly wrong bit depth or channel count) Output: ImageData (guaranteed CV_16UC3)
Behavior:
- Validate image is non-empty
- Convert to CV_16UC3 if necessary:
- Grayscale -> BGR
- 8-bit -> 16-bit (scale x257)
- 4-channel -> 3-channel
- Apply deskew correction (future):
- Canny edge detection
- HoughLinesP for dominant angles
- Affine warp if skew > 0.5 degrees
Error codes: InvalidBitDepth, ConversionFailed
Stage 3: Detect (NegativeDetector)
Purpose: Classify the image as negative or positive, color or B&W.
Input: ImageData with film_type = Unknown
Output: ImageData with film_type set to one of: ColorNegative, BWNegative, ColorPositive, BWPositive
Detection algorithm:
- Negative detection: Compare mean intensity to midpoint (32768 for 16-bit). Negatives have high mean intensity because dark scene regions become bright on the film.
- Orange mask detection: For negatives, compute R/B channel ratio. C-41 film has an orange dye mask with R/B ratio > 1.4.
- Monochrome detection: Convert to HSV, check mean saturation. If saturation < 15, classify as B&W.
Named constants:
kOrangeMaskThreshold = 1.4fkColorSaturationThreshold = 15.0f
Error codes: DetectionFailed, HistogramError
Stage 4: Invert (Inverter)
Purpose: Convert negatives to positives via bitwise inversion.
Input: ImageData with film_type set Output: ImageData (inverted if negative, unchanged if positive)
Behavior by film type:
- ColorNegative: Remove orange mask, then
cv::bitwise_not() - BWNegative: Simple
cv::bitwise_not() - ColorPositive / BWPositive: Pass through unchanged
Error codes: InversionFailed
Stage 5: Color Correction (ColorCorrector)
Purpose: Remove color casts and balance white.
Input: ImageData (inverted positive) Output: ImageData (color-balanced)
Behavior by film type:
- ColorNegative: C-41 correction (LAB-space orange removal) + auto WB
- ColorPositive: Auto white balance only
- BWNegative / BWPositive: Skipped (no color to correct)
Auto white balance algorithm (Gray World):
- Compute mean of each BGR channel
- Compute overall gray mean = (B + G + R) / 3
- Scale each channel:
ch *= gray_mean / ch_mean - Clamp to [0, 65535]
Error codes: ColorCorrectionFailed, WhiteBalanceFailed
Stage 6: Post-Process (CropProcessor)
Purpose: Auto-crop, levels adjustment, and sharpening.
Input: ImageData (color-corrected) Output: ImageData (cropped, levels-adjusted, sharpened)
Sub-stages (executed in order):
-
Auto-crop:
- Edge detection (Canny) on grayscale
- Contour analysis to find largest rectangle
- Validate area > 30% of total (not a noise contour)
- Crop to bounding rect
-
Levels adjustment:
- Compute cumulative histogram per channel
- Black point at 0.5th percentile
- White point at 99.5th percentile
- Remap:
output = (input - black) * 65535 / (white - black)
-
Sharpening (unsharp mask):
- Gaussian blur with sigma = 1.5
sharpened = original + 0.5 * (original - blurred)- Clamp to 16-bit range
Named constants:
kMinFrameAreaRatio = 0.3kSharpenSigma = 1.5kSharpenStrength = 0.5kBlackPointPercentile = 0.5kWhitePointPercentile = 99.5
Error codes: CropFailed, FrameDetectionFailed, SharpeningFailed
Stage 7: Output (OutputWriter)
Purpose: Write the final image to disk.
Input: ImageData + OutputConfig (directory, format, quality) Output: ImageData (unchanged) + file written to disk
Supported output formats:
| Format | Extension | Bit Depth | Compression | Use Case |
|---|---|---|---|---|
| PNG 16-bit | .png | 16-bit | Lossless | Archival quality |
| PNG 8-bit | .png | 8-bit | Lossless | Web/sharing |
| TIFF 16-bit | .tif | 16-bit | None | Professional editing |
| JPEG | .jpg | 8-bit | Lossy | Quick preview |
Output filename: {stem}_converted.{ext} (e.g., DSC09246_converted.png)
Error codes: OutputWriteFailed, OutputPathInvalid
Error Propagation
Every stage returns std::expected<ImageData, Error>. The Pipeline executes stages sequentially and stops at the first error:
Stage 1 OK -> Stage 2 OK -> Stage 3 ERROR -> [stop, return error]
The Error struct contains:
ErrorCode code-- machine-readable classificationstd::string message-- human-readable, actionable descriptionstd::string source_file-- source file where error originatedint source_line-- line number for diagnostics
Memory Constraints
- Maximum RAW file size: 4 GB (validated at load time)
- A 16-bit, 6000x4000 pixel image occupies ~144 MB in memory
- The pipeline processes one image at a time; batch processing is sequential
cv::Matuses reference counting; stage handoffs are efficient (move semantics)