Batch RAW linear histogram and exposure‑clipping analysis (Python)

Batch RAW linear histogram and exposure‑clipping analysis (Python)

When you are testing a new target, telescope, camera, or filter, or even the same equipment on a different night, it is useful to know how far above the camera’s black level your sky background is, and how close you are to clipping pixels at the white level. This simple Python script reads a folder of RAW files, measures the important levels, and produces a combined linear histogram (on a log scale) so you can quickly see which exposure is about right.

With a DSLR, you at least get a rough on‑camera histogram (from which the 1/3 rule is derived), but you are usually limited to short test exposures like 30 seconds and you have no direct access to the true black point, white level, or a logarithmic y‑axis. Most capture tools like NINA and similar software will report statistics such as minimum, maximum, and median ADU for a frame, but they typically do not show how those values are distributed, or where they sit relative to the camera’s actual black and white levels. By adding a proper linear histogram on a log scale, with the real black level, white level, and sky background clearly marked, this script gives visual context to the numbers your normal tools report, making it much easier to judge whether your exposure settings are genuinely optimal rather than just “good enough on paper.”

What the script does

Instead of stretching a single CR2 frame, this updated script:

  • Works with a whole folder of RAW files (CR2, CR3, NEF, ARW, DNG and others).
  • Reads the RAW data in linear form using rawpy, bypassing in‑camera processing.
  • Uses each file’s own black level and white level from the RAW metadata (no hard‑coded 2048).
  • Estimates the sky background as the median RAW value and reports how many ADU above black it sits.
  • Counts how many pixels are at or above the white level and reports the saturation fraction.
  • Sorts the files by exposure time (if your filenames contain “120.0s” style tags) so the longest exposure is plotted first.
  • Plots all files together as linear RAW histograms on a log scale, with vertical lines marking a representative black level and the white level.

The result is a quick way to answer: “Is my sky background high enough above black, and am I clipping too many pixels at this exposure time?”

Requirements

You will need:

  • Python 3
  • The rawpy, numpy and matplotlib packages
  • The python script - download here

How to use it

  • Place the script somewhere on your computer.
  • Put a few test RAW files (ideally at different exposure times) in a folder.
  • Open a terminal and run:
bash
python batch_raw_linear_histogram.py /path/to/your/folder

The script prints a short report for each file and creates a PNG called batch_raw_linear_log.png in the same folder, showing all the histograms together.

You can organise your test exposures and the python file as in the image above.

From the same folder open a command line, type, and enter text e.g.

python '.\Batch Histogram Analyser.py' .

where the '.' refers to the folder you are currently in.

What to look for

Ideally, your sky background peak should be comfortably above the black level, but with only a tiny fraction of pixels at or above the white level. If the background peak is too close to black, increase exposure time or ISO. If you are clipping too many pixels at white, reduce exposure time or ISO. The combined plot makes it easy to see which of your test exposures hits that sweet spot.

Once you have identified which exposures give a background safely above black with only a tiny saturation fraction, the “best” exposure then becomes a practical decision rather than a purely theoretical one. Your experience and knowledge of your own equipment will often dictate the choice: some mounts and guiding setups simply will not deliver a high yield of sharp frames at 240 seconds, so you might deliberately settle on the longest exposure your tracking, guiding, and seeing can reliably support. The same applies on a windy night, where shorter subs at, say, 120 seconds might give you far more usable data than fewer, longer 240‑second subs that are regularly spoiled by gusts.

From there you can refine things with more granular tests if you wish. You might, for example, run additional exposures at 90 seconds and 150 seconds to see how the histogram and saturation fraction change, and decide whether the small gain in depth justifies the extra risk of trailing or wind‑blurred frames. You might also factor in how much the temperature is expected to fall over the night: if you know your camera’s behaviour changes significantly with temperature, you may choose an exposure that keeps the background comfortably above black even once the sensor cools and the noise characteristics improve, so you stay in a safe zone for the entire session rather than optimising only for the very first test frame. This is why it is useful to analyse a few sub‑exposures again after an hour or so of imaging, to check that the histograms are still performing well as the outside temperature drops, the sensor temperature reaches its imaging equilibrium, and the overall imaging environment stabilises.