# `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 -- [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 -- [options] ``` Or, if `filmscan` is in the current directory and executable: ```bash ./filmscan [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.