A Python toolkit for analyzing noise characteristics and dynamic range of digital camera sensors from raw image files.
This project provides tools to measure and compare the noise performance of different camera sensors by analyzing raw image files (DNG, ERF, etc.). It calculates the sensor's dynamic range in Exposure Value (EV) units and provides professional visualizations for comparison.
- Automated Raw File Scanning: Process directories of raw images from multiple cameras
- Noise Analysis: Calculate standard deviation, mean, min, and max values from raw sensor data
- Dynamic Range Calculation: Compute Exposure Value (EV) as a measure of sensor dynamic range
- GPU Acceleration: Optional CUDA support for faster processing of large raw files
- Result Caching: Automatically saves and loads analysis results to avoid reprocessing
- Progress Tracking: Visual progress bars during file processing
- Professional Visualizations: Interactive plots with grouped legends and consistent styling
- Multi-Camera Comparison: Aggregate analysis across multiple camera models and variants
- Python 3.8+
- ExifTool command-line tool
- NVIDIA GPU with CUDA (optional, for GPU acceleration)
Linux (Ubuntu/Debian):
sudo apt update && sudo apt install libimage-exiftool-perlmacOS:
brew install exiftoolpip install -r requirements.txtIf you have an NVIDIA GPU with CUDA installed:
# Check your CUDA version
nvcc --version
# Install CuPy (for CUDA 12.x/13.x)
pip install cupy-cuda12ximport sensor_camera
from collections import OrderedDict
# Create Analysis object with path to your raw files
analysis = sensor_camera.Analysis('path/to/raw/files')
# Define cameras to scan
scan_specs = OrderedDict([
('Leica M11 (60MP)', {'path': 'M11-60MP', 'suffix': 'DNG'}),
('Leica Q3 (36MP)', {'path': 'Q3-36MP', 'suffix': 'DNG'}),
])
# Scan all cameras
scan_results = analysis.scan(scan_specs)
# Create aggregate dataset
data = analysis.create_aggregate()
# Save results
analysis.save_aggregate('results.csv')
# Create plots
figures = analysis.plot_ev_vs_iso() # EV vs ISO for all exposure times
for fig in figures:
fig.show()The Sensor class handles scanning individual camera directories:
sensor = Sensor(path='path/to/camera/files')
data = sensor.scan(path='subdirectory', suffix='DNG', force_rescan=False)Parameters:
path: Base directory or subdirectory containing raw filessuffix: File extension to scan for (e.g., 'DNG', 'ERF')force_rescan: IfTrue, rescan even if cached results exist
Features:
- Automatically crops sensor data for known camera artifacts
- Extracts EXIF metadata (ISO, exposure time, dimensions)
- Computes noise statistics (std, mean, min, max)
- Calculates Exposure Value (EV)
- Saves results to
noise_results.csvin the scanned directory
The Analysis class manages aggregate analysis across multiple cameras:
analysis = Analysis(base_path='path/to/data')scan(scan_specs, force_rescan=False)
- Scans multiple cameras according to specifications
- Returns OrderedDict with results for each camera
create_aggregate(camera_list=None)
- Combines data from selected cameras into single DataFrame
- Preserves camera variant information
save_aggregate(filename='aggregate_analysis.csv')
- Saves aggregate data to CSV file
plot_ev_vs_iso(data=None, exposure_time=None, title=None, height=700, ev_range=None)
- Creates professional EV vs ISO plot(s)
exposure_time: Single value, list, or None (all times)- Returns single figure or list of figures
plot_ev_vs_time(data=None, iso=None, title=None, height=700, ev_range=None)
- Creates professional EV vs Exposure Time plot(s)
iso: Single value, list, or None (all ISOs)- Returns single figure or list of figures
get_aliases(short_names=None)
- Creates convenient variable aliases for backward compatibility
The sensor's dynamic range is quantified as:
Where:
-
$W$ = white level (sensor saturation point) -
$B$ = black level (sensor baseline) -
$\sigma$ = standard deviation of raw sensor data (noise)
This represents the signal-to-noise ratio in stops (each stop = 2× light).
The toolkit includes optimized handling for:
- Leica: M11, M10, Q, Q3, SL2-S, CL, TL2, M Monochrome
- Ricoh: GR III, GXR
- Epson: R-D1
- Apple: iPhone 13 Pro
- Pixii: Pixii cameras
Camera-specific crops are applied to remove known sensor artifacts.
- Grouped Legends: Camera variants grouped by base model
- Consistent Colors: Same color for all variants of a model
- Line Style Differentiation: Different dash patterns for variants
- Interactive: Hover to compare, click to toggle cameras
- Auto-scaling: Y-axis automatically adjusts to show all data
- Publication Ready: Clean styling suitable for presentations and papers
- EV vs ISO: Shows how dynamic range degrades with higher ISO
- EV vs Exposure Time: Shows noise characteristics at different shutter speeds
camera-char/
├── sensor_camera.py # Main module with Sensor and Analysis classes
├── sensor_noise.ipynb # Jupyter notebook for analysis
├── requirements.txt # Python dependencies
├── aggregate_analysis.csv # Combined results from all cameras
└── detector-noise/ # Directory containing raw image files
├── M11-60MP/
│ ├── *.DNG
│ └── noise_results.csv
├── Q3-36MP/
└── ...
With CuPy installed, statistical calculations are performed on GPU:
- 5-10x speedup for large raw files (20-60MP)
- Automatic fallback to CPU if GPU unavailable
- Explicit memory management to prevent GPU memory issues
Results are automatically cached in noise_results.csv:
- First scan: Processes all raw files (slower)
- Subsequent scans: Loads from CSV (instant)
- Use
force_rescan=Trueto regenerate
This is a research project for camera sensor noise analysis. Contributions are welcome!
See repository for license information.
- ISO 12232 - Photography — Digital still cameras — Determination of exposure index, ISO speed ratings, standard output sensitivity, and recommended exposure index
Created for systematic camera sensor noise characterization and comparison.