112 lines
6.3 KiB
Markdown
112 lines
6.3 KiB
Markdown
# `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.
|