Raspberry Pi Camera Tuning Tool
Project description
Raspberry Pi Camera Tuning Tool (CTT)
Camera Tuning Tool for generating JSON tuning files for Raspberry Pi cameras. Takes DNG calibration images and produces tuning parameters for the PiSP or VC4 ISP platforms.
Installation
Requires Python 3.11+.
pip install rpi-ctt
For development, install in editable mode from the repository:
pip install -e .
Usage
Full calibration
python3 -m ctt -i <image_dir> [-o <output_dir>] [--name <name>] [-t pisp] [-t vc4] [-c config.json]
All outputs (JSON tuning file, log file, Macbeth chart PNGs) are written to the
output directory. If -o is not specified, the current directory is used.
Output filenames are <name>_<target>.json and <name>_<target>.log. If
--name is not specified, the name is derived from the input directory. You
can pass -t multiple times or use a comma-separated list (e.g. -t pisp,vc4).
If no -t is given, both targets are run (e.g. imx219_pisp.json and
imx219_vc4.json).
ALSC-only calibration
python3 -m ctt --alsc-only -i <image_dir> [-o <output_dir>] [-t pisp] [-t vc4]
Colour-only calibration (AWB + CCM)
python3 -m ctt --colour-only -i <image_dir> [-o <output_dir>] [-t pisp] [-t vc4]
Update an existing tuning file
Re-run calibrations using an existing file as a template. The target (pisp or vc4) is read from the tuning file; do not use -t with --update. The file is updated in place.
python3 -m ctt -i <image_dir> --update <existing.json>
python3 -m ctt -i <image_dir> --update <existing.json> -o <output_dir> --name imx219
With --alsc-only or --colour-only, only that section is re-calibrated; all other algorithm blocks in the file are left unchanged.
python3 -m ctt --alsc-only -i <image_dir> --update <existing.json>
Use a custom template
python3 -m ctt -i <image_dir> -o <output_dir> -t pisp --template my_base.json
Convert between VC4 and PiSP
Interpolates ALSC grids, swaps denoise/AGC/HDR blocks:
python3 -m ctt --convert -t pisp input_vc4.json output_pisp.json
python3 -m ctt --convert -t vc4 input_pisp.json # in-place
Prettify a tuning file
python3 -m ctt --prettify [-t pisp|vc4] <input.json> [output.json]
Options
| Flag | Description |
|---|---|
-i, --input |
Calibration image directory |
-o, --output |
Output directory (default: current directory) |
--name |
Base name for output files (default: derived from input directory) |
-t, --target |
Target platform(s): repeat for multiple (e.g. -t pisp -t vc4) or use -t pisp,vc4. Default: both. |
-c, --config |
Configuration file (see below) |
--template |
Custom template JSON file |
--update |
Existing tuning file to update in place (target taken from file; cannot use -t) |
--plot |
Show matplotlib debug plot for algorithm (e.g. awb, alsc, geq, noise). Can be repeated or comma-separated. |
--alsc-only |
Run only ALSC (lens shading) calibration |
--colour-only |
Run only AWB and CCM calibrations |
--convert |
Convert tuning file between VC4 and PiSP |
--prettify |
Prettify an existing tuning file |
Configuration file
An optional JSON config file controls calibration behaviour. See
apps/ctt/ctt/data/config_example.json for the full set of options:
{
"disable": [],
"plot": [],
"alsc": {
"do_alsc_colour": 1,
"luminance_strength": 0.8,
"max_gain": 8.0
},
"awb": {
"greyworld": 0
},
"blacklevel": -1,
"macbeth": {
"small": 0,
"show": 0
}
}
- disable - List of algorithm keys to skip (e.g.
["rpi.noise"]) - plot - List of algorithm names for matplotlib debug plots. Use short names (
awb,alsc,geq,noise) or full keys (rpi.awb, etc.). Supported: rpi.awb (colour curve hatspace + dehatspace), rpi.alsc (3D vignetting surfaces), rpi.geq (fit and scaled fit), rpi.noise (per-image noise fit). Can also be set via--ploton the command line. - alsc.do_alsc_colour - Enable colour shading calibration (default: 1)
- alsc.luminance_strength - Luminance correction strength, 0.0-1.0 (default: 0.8)
- alsc.max_gain - Maximum lens shading gain (default: 8.0)
- awb.greyworld - Use grey world AWB instead of Macbeth-based (default: 0)
- blacklevel - Override black level; -1 to auto-detect (default: -1)
- macbeth.small - Use small Macbeth chart detection (default: 0)
- macbeth.show - Display detected Macbeth chart (default: 0)
Calibration images
The tool looks only in the root of the input directory and uses .dng files only (no subdirectories, no JPEGs).
- ALSC images: Filenames containing
alscand a colour temperature (e.g.alsc_3000k_0.dng). Uniform flat-field images at multiple colour temperatures. - Macbeth images: Filenames must encode colour temperature and lux in the form
...<temp>k_<lux>l.dng(e.g.d65_5858k_1344l.dng). Images of a Macbeth ColorChecker chart for AWB, CCM, noise, lux, and GEQ.
If a file is skipped (e.g. missing colour temp/lux in the filename, or Macbeth chart not found in the image), the tool prints a short message (e.g. colour temp/lux not in filename, or Macbeth not found) with the filename.
Calibrations performed
| Algorithm | Key | Description |
|---|---|---|
| ALSC | rpi.alsc |
Lens shading correction (colour and luminance) |
| AWB | rpi.awb |
Auto white balance calibration |
| CCM | rpi.ccm |
Colour correction matrices per illuminant |
| CAC | rpi.cac |
Chromatic aberration correction (PiSP only) |
| Noise | rpi.noise |
Noise profile characterisation |
| Lux | rpi.lux |
Lux level calibration |
| GEQ | rpi.geq |
Green equalisation threshold |
Web frontend (ctt-server)
ctt-server is an optional web UI for capturing, tagging and tuning calibration
images, served from the Raspberry Pi. It runs as a single process on the Pi: the
server previews and captures DNGs in-process with Picamera2, files them with
CTT-correct filenames, runs the tuner in-process, and serves downloadable tuning
files with result visualisations. The client is just a web browser on any machine
on the network.
Install and run (on the Pi)
pip install "rpi-ctt[server]" # from PyPI
# or, from a local checkout:
pip install -e ".[server]"
ctt-server # HTTPS on 0.0.0.0:5000
ctt-server is HTTPS-only; on first run it generates a self-signed
certificate under <workspace>/.tls (pass --cert/--key to use your own, or
--port to change the port). Browse to https://<pi-hostname>:5000 and accept
the one-time self-signed warning. Picamera2 ships with Raspberry Pi OS and is
imported lazily; if you use a virtualenv, create it with --system-site-packages
(or apt install python3-picamera2) so picamera2 is visible.
Workflow
- Project — create one per sensor (e.g.
imx708_wide); the name becomes the output filename base (imx708_wide_pisp.json). - Capture — frame with the live preview and histogram, set exposure/gain (or leave on auto), and capture. In Macbeth mode a live finder overlays the detected chart and flags low confidence or a too-small chart. Each shot is filed with a CTT-correct filename.
- Run — pick targets (PiSP/VC4/both) and mode (full / ALSC-only / colour-only); CTT progress streams live in the console.
- Results — download the tuning
.json/.logor the whole project as a zip, and inspect the AWB curve, per-CT CCM matrices, ALSC shading heatmap and lux/noise references parsed from the output JSON.
When a supported lightbox is attached (see below), the capture page shows a Lightbox control to pick the illuminant and set intensity; it is hidden when no device is present.
Lightbox control
ctt.devices provides a small, generic API for controlling an illumination lightbox
over USB from the Pi, so a calibration run can set repeatable illuminants
programmatically. Consumers use the abstract Lightbox interface and the
device-agnostic factory; concrete drivers (currently Image Engineering
lightSTUDIO-S) plug in behind it. A new lightbox is added as a driver package under
apps/ctt/ctt/devices/<model>/ registered in apps/ctt/ctt/devices/registry.py — no
change to the generic API or its consumers.
Install (on the Pi)
pip install "rpi-ctt[devices]" # from PyPI (or "rpi-ctt[server,devices]" with the web UI)
# or, from a local checkout:
pip install -e ".[devices]" # or ".[server,devices]" with the web UI
sudo apt install libusb-1.0-0 # pyusb's backend
# allow non-root USB access (lightSTUDIO-S)
sudo cp apps/ctt/ctt/devices/lightstudio_s/contrib/99-lightstudio.rules /etc/udev/rules.d/
sudo udevadm control --reload && sudo udevadm trigger
If the udev rule is missing but pyusb is installed and the device is plugged in, the
probe gets far enough to fail with usb.core.USBError: [Errno 13] Access denied
(rather than reporting absent) — install the rule above. If udevadm trigger doesn't
re-apply to the already-enumerated device, unplug and replug the lightbox so the rule
runs on re-enumeration. The user running the server must be in the plugdev group.
Use
from ctt.devices import get_lightbox
with get_lightbox() as box: # first attached, supported lightbox
box.set_illuminant('D65') # name → channel, at its default intensity
box.set_intensity(4, 50) # channel 4 (D65) at 50 %
print(box.info())
box.off()
ctt-lightbox probe # find the box, list illuminants
ctt-lightbox status
ctt-lightbox set 1 50 # channel 1 (F12) → 50 %
ctt-lightbox illuminant D65 # switch to D65 at its default
ctt-lightbox off
lightSTUDIO-S channels
| Ch | Illuminant | Default % | Ch | Illuminant | Default % | |
|---|---|---|---|---|---|---|
| 1 | F12 | 100 | 5 | Halogen (10 lux) | 2 | |
| 2 | F11 | 100 | 6 | Halogen (100 lux) | 25 | |
| 3 | D50 | 100 | 7 | Halogen (400 lux) | 100 | |
| 4 | D65 | 100 | 8 | Halogen + blue filter (400 lux) | 100 |
Development
Linting and formatting
The project uses ruff for linting and formatting:
# Check for lint errors
python3 -m ruff check .
# Auto-fix lint errors
python3 -m ruff check --fix .
# Format code
python3 -m ruff format .
# Check formatting without modifying files
python3 -m ruff format --check .
These same checks run in CI. To catch problems before committing, enable the pre-commit hooks (they run ruff lint + format on each commit, mirroring CI):
pip install -e ".[dev]"
pre-commit install # one-time, per clone
pre-commit run --all-files # optional: check the whole tree now
Running tests
Install with the test extra and run pytest:
pip install -e ".[test]"
pytest -v
Building a wheel package
pip install build
python3 -m build
This produces a .whl file in the dist/ directory.
License
BSD-2-Clause - Copyright (C) 2026, Raspberry Pi
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rpi_ctt-1.2.0.tar.gz.
File metadata
- Download URL: rpi_ctt-1.2.0.tar.gz
- Upload date:
- Size: 597.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73f92d0c3cfbab0f985173c9b155521fb192dd2074ec2e10798e7c0a3d2801db
|
|
| MD5 |
4810b3ecec6ae237975709c17f61e29d
|
|
| BLAKE2b-256 |
6a9d914ff0690296ad80e9f16a2cd50965d9a5c60eb9158531ac1fcf55333f93
|
Provenance
The following attestation bundles were made for rpi_ctt-1.2.0.tar.gz:
Publisher:
publish.yml on raspberrypi/ctt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rpi_ctt-1.2.0.tar.gz -
Subject digest:
73f92d0c3cfbab0f985173c9b155521fb192dd2074ec2e10798e7c0a3d2801db - Sigstore transparency entry: 1778677428
- Sigstore integration time:
-
Permalink:
raspberrypi/ctt@330a87654ebe86b732706dd6b66fd4c318cf5515 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/raspberrypi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@330a87654ebe86b732706dd6b66fd4c318cf5515 -
Trigger Event:
release
-
Statement type:
File details
Details for the file rpi_ctt-1.2.0-py3-none-any.whl.
File metadata
- Download URL: rpi_ctt-1.2.0-py3-none-any.whl
- Upload date:
- Size: 590.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21bd14d22fd8b8711f3bb256706eeb08edadb4dc02f42205e4210acd1c4b73b3
|
|
| MD5 |
b5f51740cfc609fa21d8a50be89ecbee
|
|
| BLAKE2b-256 |
3c40846816dd69abfecb5161d883fe5377e4043bc450bb3415fac0c51f08c944
|
Provenance
The following attestation bundles were made for rpi_ctt-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on raspberrypi/ctt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rpi_ctt-1.2.0-py3-none-any.whl -
Subject digest:
21bd14d22fd8b8711f3bb256706eeb08edadb4dc02f42205e4210acd1c4b73b3 - Sigstore transparency entry: 1778677667
- Sigstore integration time:
-
Permalink:
raspberrypi/ctt@330a87654ebe86b732706dd6b66fd4c318cf5515 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/raspberrypi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@330a87654ebe86b732706dd6b66fd4c318cf5515 -
Trigger Event:
release
-
Statement type: