[aider] docs: Add comprehensive README files

This commit is contained in:
2025-06-05 21:19:09 -04:00
committed by Tanishq Dubey
parent 0a93e9c664
commit d59bd215d8
5 changed files with 390 additions and 15 deletions

3
.gitignore vendored
View File

@ -10,4 +10,5 @@ wheels/
.venv
tools/
*.tiff
*.pp3
*.pp3
.aider*

101
README-filmcolor.md Normal file
View File

@ -0,0 +1,101 @@
# `filmcolor` - Film Stock Color Emulation
`filmcolor` is a Python script that transforms digital color positive images (like those from a digital camera) into simulated film negatives. It bases its transformations on datasheet parameters for specific film stocks, focusing on color response, H&D curves, spatial effects like halation and diffusion, and saturation.
## Features
* Applies film stock characteristics based on a JSON datasheet.
* Converts linear RGB input to log exposure.
* Applies H&D (HurterDriffield) curves for tonal mapping.
* Simulates dye coupler diffusion and interlayer effects.
* Applies halation effects.
* Adjusts saturation based on coupler properties.
* Supports RAW (Sony ARW, DNG), TIFF, PNG, and JPG inputs.
* Outputs TIFF (16-bit uncompressed), PNG (8-bit), or JPG (8-bit).
## Usage
The script is designed to be run with `uv`, which handles dependency management.
### Direct Execution with `uv` (Recommended)
You can run `filmcolor` directly from its Git repository URL:
```bash
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmcolor -- <input_image> <datasheet_json> <output_image>
```
**Example:**
```bash
# Ensure input.jpg and sim_data/portra_400.json are in your current directory or provide full paths
# Output will be output_negative.tiff
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmcolor -- input.jpg sim_data/portra_400.json output_negative.tiff
```
### Local Execution
If you have cloned the repository or downloaded the script:
1. Navigate to the script's directory or ensure it's in your PATH.
2. Run the script:
```bash
uv run ./filmcolor -- <input_image> <datasheet_json> <output_image>
```
Or, if `filmcolor` is in the current directory and executable:
```bash
./filmcolor <input_image> <datasheet_json> <output_image>
```
## Arguments
### Positional Arguments:
* `input_image`: Path to the input RGB image.
* Supported formats: TIFF, PNG, JPG, Sony ARW, DNG.
* Non-RAW images are assumed to be sRGB and will be linearized. RAW files are processed to sRGB.
* `datasheet_json`: Path to the film datasheet JSON file (e.g., `sim_data/portra_400.json`).
* `output_image`: Path to save the output emulated film negative.
* Output format is determined by the file extension:
* `.tiff` / `.tif`: 16-bit uncompressed TIFF.
* `.png`: 8-bit PNG.
* `.jpg` / `.jpeg`: 8-bit JPG (quality 95).
### Options:
The script currently does not have additional command-line options beyond the positional arguments. Configuration is primarily driven by the content of the `datasheet_json` file.
## Input Image Preparation
* **Linear RGB:** The script internally works with linear RGB data.
* If an 8-bit or 16-bit integer image (PNG, JPG, TIFF) is provided, it's assumed to be sRGB encoded and will be automatically linearized.
* If a float32/float64 image is provided, it's assumed to be already in linear RGB.
* RAW files (ARW, DNG) are demosaiced, white-balanced (using camera WB), and converted to linear sRGB.
* **Alpha Channel:** If an alpha channel is present, it will be discarded.
## Datasheet Format
The `datasheet_json` file contains detailed parameters for the film stock being simulated. This includes:
* Information (name, format, version)
* Processing parameters (gamma, balance)
* Physical properties (halation strength and size, coupler diffusion, interlayer diffusion)
* H&D curves
* Calibration data (ISO, middle gray log exposure)
An example datasheet (`sim_data/portra_400.json`) is provided in the repository.
## Workflow
1. **Load Image:** Reads the input image. RAW files are processed using `rawpy`. sRGB images are linearized.
2. **Load Datasheet:** Parses the specified JSON film datasheet.
3. **Convert to Log Exposure:** Transforms linear RGB pixel values to log exposure (LogE) space, anchored by the `middle_gray_logE` from the datasheet.
4. **Apply H&D Curves:** Maps LogE values to densities for each color channel (R, G, B) using the H&D curves from the datasheet. This step also incorporates processing gamma and balance shifts.
5. **Convert Density to Transmittance:** Converts the resulting densities back to linear transmittance values (0-1 range).
6. **Apply Spatial Effects:**
* **Diffusion Blur:** Simulates dye coupler and interlayer diffusion by applying a Gaussian blur. The blur radius is derived from `diffusion_um` values in the datasheet and the image dimensions/film format.
* **Halation:** Simulates light scattering within the film base by applying channel-specific Gaussian blurs, controlled by `halation.strength` and `halation.size_um`.
7. **Apply Saturation:** Adjusts image saturation based on the `couplers.amount` parameter.
8. **Save Output:** The processed image (now a simulated film negative) is saved to the specified output file, converting to 16-bit for TIFF or 8-bit for PNG/JPG.

115
README-filmgrain.md Normal file
View File

@ -0,0 +1,115 @@
# `filmgrain` - Realistic Film Grain Simulation
`filmgrain` is a Python script that applies realistic film grain to digital images. It implements the procedural grain synthesis method described by Zhang et al. (2023) in "Procedural Synthesis of Film Grain Noise". This method leverages GPU acceleration via the `warp` library for efficient processing.
## Features
* Implements the film grain model by Zhang et al. (2023).
* GPU accelerated using `warp-lang` (requires a CUDA-capable NVIDIA GPU).
* **Adjustable Grain Parameters:**
* `--mu_r`: Mean grain radius (relative to pixel size), controlling grain size.
* `--sigma`: Standard deviation of the Gaussian filter for noise blurring (`sigma_filter`), controlling grain softness/sharpness.
* **Monochrome Option:** Apply spectrally uniform (monochromatic) grain based on image luminance, or apply independent RGB grain.
* Precomputes a variance lookup table (LUT) for performance.
* Supports RAW (Sony ARW, DNG), TIFF, PNG, and JPG inputs.
* Outputs TIFF (16-bit uncompressed), PNG (8-bit), or JPG (8-bit).
## Requirements
* **NVIDIA GPU:** A CUDA-enabled NVIDIA graphics card is required as `warp-lang` currently targets CUDA.
* **CUDA Toolkit:** Ensure you have a compatible version of the CUDA Toolkit installed.
* **`uv`:** For easy execution and dependency management.
## Usage
The script is designed to be run with `uv`.
### Direct Execution with `uv` (Recommended)
You can run `filmgrain` directly from its Git repository URL:
```bash
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmgrain -- <input_image> <output_image> [options]
```
**Example:**
```bash
# Ensure input.png is in your current directory or provide a full path
# Output will be output_grained.jpg
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmgrain -- input.png output_grained.jpg --mu_r 0.1 --sigma 0.8 --mono
```
### Local Execution
If you have cloned the repository or downloaded the script:
1. Navigate to the script's directory or ensure it's in your PATH.
2. Run the script:
```bash
uv run ./filmgrain -- <input_image> <output_image> [options]
```
Or, if `filmgrain` is in the current directory and executable:
```bash
./filmgrain <input_image> <output_image> [options]
```
## Arguments
### Positional Arguments:
* `input_image`: Path to the input image.
* Supported formats: TIFF, PNG, JPG, Sony ARW, DNG.
* `output_image`: Path to save the output image with film grain.
* Output format is determined by the file extension:
* `.tiff` / `.tif`: 16-bit uncompressed TIFF.
* `.png`: 8-bit PNG.
* `.jpg` / `.jpeg`: 8-bit JPG (quality 95).
### Options:
* `--mu_r FLOAT`: Mean grain radius (relative to pixel size). (Default: `0.1`)
* Controls the apparent size of the grain particles. Larger values mean larger grains.
* Must be positive.
* `--sigma FLOAT`: Standard deviation of the Gaussian Filter for noise blurring (`sigma_filter`). (Default: `0.8`)
* Controls the sharpness or softness of the grain. Higher values result in softer, more blurred grain.
* Must be positive.
* The paper suggests `sigma_filter` should ideally be >= `3 * mu_r` for the model's approximations to hold well. The script will issue a warning if this condition is not met.
* `--seed INT`: Random seed for noise generation. (Default: `42`)
* Allows for reproducible grain patterns.
* `--mono`: Apply monochrome film grain across channels based on luminance. (Default: `False` - apply RGB grain)
* If enabled, grain is generated based on the image's luminance and applied uniformly to R, G, and B channels.
* If disabled, grain is generated and applied independently for each color channel (R, G, B), leading to colored grain.
## Workflow
1. **Load Image:**
* Reads the input image. RAW files (ARW, DNG) are post-processed using `rawpy` (16-bit output, camera WB, no auto-bright, linear gamma). Other formats (TIFF, PNG, JPG) are read using `imageio`.
* The image is converted to a 0-1 float32 range.
2. **Precompute Variance LUT:**
* Calculates a lookup table (LUT) for the variance of the Boolean-Poisson (BP) noise model. This involves numerically integrating a complex function derived from the grain model, significantly speeding up per-pixel variance calculation later.
3. **Prepare Gaussian Kernel:**
* Creates a 2D Gaussian kernel based on the `sigma_filter` parameter. This kernel is used to blur the initial noise, shaping the grain's appearance. The kernel radius is determined by `sigma_filter`.
4. **Process Channels (RGB or Monochrome):**
* **Monochrome Mode (`--mono`):**
* The input image is converted to grayscale (luminance).
* `generate_noise_kernel` (Warp kernel): Generates initial random noise for the grayscale image. The noise variance at each pixel is determined by the pixel's intensity (u_val) and the precomputed variance LUT, scaled by `mu_r` and `sigma_filter`.
* `convolve_2d_kernel` (Warp kernel): Convolves the generated noise with the 2D Gaussian kernel to produce the filtered (shaped) grain.
* This single-channel filtered noise is then used for all R, G, and B output channels.
* **RGB Mode (default):**
* The process above (noise generation and convolution) is performed independently for each color channel (R, G, B) of the input image.
* Different random seeds are used for each channel (seed, seed+1, seed+2) to ensure decorrelated grain patterns.
* If the input is grayscale, the R channel's noise is copied to G and B.
5. **Add Noise and Clip:**
* `add_rgb_noise_and_clip_kernel` (Warp kernel): Adds the filtered noise (either monochrome or per-channel RGB) to the original image channels.
* The result is clipped to the 0.0 - 1.0 range.
6. **Save Output:**
* The final image with added grain is copied from the GPU back to the CPU.
* It's converted to the appropriate bit depth (16-bit for TIFF, 8-bit for PNG/JPG) and saved.
## Technical Details (from Zhang et al. 2023)
* **Boolean-Poisson (BP) Model:** The grain is modeled as randomly placed circular grains (disks) of radius `mu_r`. The local density of these grains depends on the image intensity.
* **Variance Calculation:** The core of the model involves calculating the variance of the noise that would result from this BP process. This variance is intensity-dependent. The `w_func` and `CB_const_radius_unit` functions in the script are related to the geometric calculations of overlapping disks, which are part of this variance derivation. The integral of `CB_const_radius_unit * x` gives the key component for variance, which is precomputed into the LUT.
* **Filtering:** The raw noise generated based on this variance is then filtered (convolved) with a Gaussian kernel (`h` in the paper, controlled by `sigma_filter`) to achieve the final grain appearance. The paper notes that the filter's standard deviation `sigma_filter` should be significantly larger than `mu_r` (e.g., `sigma_filter >= 3 * mu_r`) for certain approximations in their model to be accurate.

111
README-filmscan.md Normal file
View File

@ -0,0 +1,111 @@
# `filmscan` - Film Scan and Negative Reversal Simulation
`filmscan` is a Python script that simulates the process of scanning a film negative and converting it into a positive image. It is heavily inspired by the "Negadoctor" module found in the popular open-source raw image processor, Darktable. This script aims to provide a "batteries included" experience by auto-detecting several key parameters required for the conversion.
## Features
* Implements a negative-to-positive conversion process similar to Darktable's Negadoctor.
* **Automatic Parameter Detection:**
* `Dmin` (film base color / brightest part of the negative)
* `Dmax` (determines the dynamic range, from darkest part of the negative)
* `Offset` (scan exposure bias)
* `Paper Black` (density correction for print black point)
* `Print Exposure` (overall print exposure adjustment)
* Color management: Converts input sRGB (assumed) to ACEScg for processing, then back to a chosen output colorspace (sRGB or Display P3).
* Supports TIFF, PNG, and JPG inputs.
* Outputs TIFF (16-bit uncompressed), PNG (8-bit), or JPG (8-bit).
## Usage
The script is designed to be run with `uv`, which handles dependency management.
### Direct Execution with `uv` (Recommended)
You can run `filmscan` directly from its Git repository URL:
```bash
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmscan -- <input_file> <output_file> [options]
```
**Example:**
```bash
# Ensure input_negative.tiff is in your current directory or provide a full path
# Output will be output_positive.jpg
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmscan -- input_negative.tiff output_positive.jpg --output_colorspace sRGB
```
### Local Execution
If you have cloned the repository or downloaded the script:
1. Navigate to the script's directory or ensure it's in your PATH.
2. Run the script:
```bash
uv run ./filmscan -- <input_file> <output_file> [options]
```
Or, if `filmscan` is in the current directory and executable:
```bash
./filmscan <input_file> <output_file> [options]
```
## Arguments
### Positional Arguments:
* `input_file`: Path to the input negative image.
* Supported formats: TIFF, PNG, JPG.
* The image is assumed to be an sRGB encoded film negative scan.
* `output_file`: Path to save the processed positive image.
* Output format is determined by the file extension:
* `.tiff` / `.tif`: 16-bit uncompressed TIFF.
* `.png`: 8-bit PNG.
* `.jpg` / `.jpeg`: 8-bit JPG.
### Options:
* `--patch_size_ratio FLOAT`: Ratio of image minimum dimension for patch size in auto-detection. (Default: `1/128`)
* Smaller values use smaller patches, which can be faster but potentially more susceptible to noise.
* `--min_patch_size INT`: Minimum patch size in pixels for auto-detection. (Default: `8`)
* `--max_patch_size INT`: Maximum patch size in pixels for auto-detection. (Default: `32`)
* `--aces_transform TEXT`: ACES working space. Currently, ACEScg is used internally and this option is noted for future flexibility but does not change behavior. (Default: `ACEScg`)
* `--output_colorspace TEXT`: Colorspace for the output image.
* Supported: `sRGB` (default), `Display P3`.
* The output image will be gamma-corrected for the chosen colorspace.
## Workflow
1. **Load Image:** Reads the input negative image. It's normalized to a 0-1 float range.
2. **Color Conversion (Input):**
* The input image (assumed sRGB) is decoded from its gamma (e.g., sRGB EOTF inverse).
* The linear sRGB data is then converted to the ACEScg color space for internal processing.
3. **Auto-Parameter Detection:**
* The script analyzes the ACEScg image to automatically determine optimal values for:
* `Dmin`: The color of the unexposed film base, found by looking for the brightest representative patch in the negative.
* `Dmax`: A measure of the film's maximum density range, calculated using `Dmin` and the darkest representative patch in the negative (scene highlight).
* `Offset`: A scan exposure bias, determined from `Dmin`, `Dmax`, and the brightest image content patch.
* `Paper Black`: Adjusts the black point of the final print, derived from `Dmin`, `Dmax`, `Offset`, and characteristics of the brightest image patch under default white balance.
* `Print Exposure`: Overall brightness adjustment for the final print, calculated using all previously determined parameters and the darkest image patch.
* Default white balance (`WB_HIGH`, `WB_LOW`), gamma (`DEFAULT_GAMMA`), and soft clip (`DEFAULT_SOFT_CLIP`) values are used alongside these auto-detected parameters.
4. **Negadoctor Process:**
* The core conversion logic, adapted from Darktable's `negadoctor.c`.
* **Density Conversion:** Input pixel values are converted to log densities relative to `Dmin`.
* **Density Correction:** Densities are adjusted using `Dmax`, `Offset`, and white balance parameters.
* **Print Simulation:** Corrected densities are transformed to simulate printing on paper, incorporating `Print Exposure` and `Paper Black`.
* **Paper Grade (Gamma):** A gamma curve (contrast adjustment) is applied.
* **Highlight Compression (Soft Clip):** Highlights are compressed to simulate the behavior of photographic paper gloss.
5. **Color Conversion (Output):**
* The processed image (now a positive in ACEScg) is converted to the chosen `output_colorspace` (e.g., sRGB, Display P3).
* The image is clipped to the 0-1 range.
* The appropriate gamma correction (e.g., sRGB OETF) is applied for the output colorspace.
6. **Save Output:** The final positive image is saved, converting to 16-bit for TIFF or 8-bit for PNG/JPG.
## Notes on Auto-Detection
The auto-detection routines (`auto_calculate_dmin`, `auto_calculate_dmax`, etc.) work by finding representative "brightest" or "darkest" patches in the negative image.
* "Brightest" on a negative typically corresponds to the film base (for `Dmin`) or unexposed areas that will become deep shadows in the positive.
* "Darkest" on a negative typically corresponds to scene highlights that will become bright areas in the positive.
The `patch_size_ratio`, `min_patch_size`, and `max_patch_size` arguments control how these patches are sampled.

View File

@ -1,23 +1,70 @@
# filmsim
This is an exploration into a few things:
- An LLM based project (I do minimal coding)
- Film simulation
- Real life film capture
`filmsim` is a collection of Python scripts designed for film simulation, offering a "batteries included" approach to transform digital images through various stages of a film-like pipeline. The project emphasizes ease of use, portability, and direct execution via `uv`.
This project seeks to create a fast, "batteries included", film simulation package that walks the user through a film simulation.
## Features
Currently we have the following pipeline components:
- `filmcolor` - Takes in a digital color positive (picture from your digital camera) and outputs a simulated film negative based on the film stock chosen
- `filmscan` - Simulates the film scan and negative reversal process by referencing the "[Negadoctor](https://github.com/darktable-org/darktable/blob/master/src/iop/negadoctor.c)" module from [Darktable](https://www.darktable.org/), but adding in a few auto features for "batteries included"
- `filmgrain` - Adds grain based on the filmgrain method by [Zhang et al. (2023)](https://dl.acm.org/doi/10.1145/3592127) in either RGB or monochrome
* **Modular Pipeline:** Consists of distinct scripts for different stages of film simulation:
* `filmcolor`: Simulates the color response of film stocks.
* `filmscan`: Emulates the film scanning and negative reversal process.
* `filmgrain`: Adds realistic film grain to images.
* **"Batteries Included":** Scripts aim to provide sensible defaults and auto-detection features where possible.
* **Portable & Self-Contained:** Each script includes its dependencies and can be run directly.
* **Direct Execution with `uv`:** Run any script remotely without cloning the repository using `uv run`.
* **Flexible I/O:** Supports common image formats like TIFF, PNG, and JPG. `filmcolor` also handles Sony ARW and DNG RAW files. TIFFs are output in uncompressed 16-bit by default.
All scripts are designed to take in TIFF/PNG/JPG, and output TIFF/PNG/JPG. TIFFs are output in uncompressed 16-bit.
## Scripts
`filmcolor` can additionally take in Sony ARW and various DNG camera RAW files.
Each script in the `filmsim` suite is designed to be a self-contained tool. Click the links below for detailed information on each script, including its specific parameters and usage examples.
All scripts are self contained and portable.
* **[`filmcolor`](README-filmcolor.md):** Transforms digital color positives into simulated film negatives based on chosen film stock data.
* **[`filmscan`](README-filmscan.md):** Simulates the film scanning process and reverses negatives to positives, inspired by Darktable's "Negadoctor" module with added auto-adjustment capabilities.
* **[`filmgrain`](README-filmgrain.md):** Applies film grain to images using the method described by Zhang et al. (2023), with options for RGB or monochrome grain.
Details about each script can be found in their respective readmes.
## Quick Start with `uv`
This project also contains test input images and outputs at various stages of development.
You can run any of these scripts directly from the web using `uv`. This is the recommended way to use `filmsim` if you don't need to modify the code.
Replace `{SCRIPT_NAME}` with `filmcolor`, `filmscan`, or `filmgrain`, and `{ARGUMENTS}` with the script-specific arguments.
```bash
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/{SCRIPT_NAME} -- {ARGUMENTS}
```
**Example (adding grain to an image):**
```bash
# Ensure you have an input.jpg in your current directory
# Output will be output_grained.jpg
uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmgrain -- input.jpg output_grained.jpg --mu_r 0.15 --sigma 1.0
```
## Local Usage
If you prefer to run the scripts locally:
1. **Clone the repository (optional):**
```bash
git clone https://git.dws.rip/dubey/filmsim.git
cd filmsim
```
2. **Ensure `uv` is installed.** ([Installation Guide](https://github.com/astral-sh/uv))
3. **Run the script:**
Navigate to the directory containing the script (if cloned) or use the path to the script.
```bash
uv run ./{SCRIPT_NAME} -- {ARGUMENTS}
```
Or, if not cloned, you can download the script and run it:
```bash
curl -O https://git.dws.rip/dubey/filmsim/raw/branch/main/{SCRIPT_NAME}
chmod +x {SCRIPT_NAME}
uv run ./{SCRIPT_NAME} -- {ARGUMENTS}
```
## Contributing
This project is an exploration and contributions or suggestions are welcome. Please feel free to open an issue or a pull request.
## Test Images
The repository includes test input images and sample outputs at various stages of development in the `test_images/` directory.