rawpy: Bringing LibRaw RAW Decoding to Python
📷 RAW image processing for Python, a wrapper for libraw
At a glance
- What is it?
- rawpy is an MIT licensed Python wrapper around the LibRaw C library, with binary wheels for Linux, macOS and Windows plus tooling for finding and repairing hot and dead pixels. It suits photographers and imaging researchers who need to read camera RAW files programmatically.
- Who is it for?
- rawpy fits anyone who needs camera RAW files decoded in a script rather than by hand, and it is the shortest route to that because the wheels bundle LibRaw for you. It is the wrong choice if you need the GPL demosaic packs, which the MIT licence excludes from every platform, or if you need OpenMP on macOS, where the wheels do not provide it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A thin Python shell over a C decoding library
rawpy is described as an easy-to-use Python wrapper for the LibRaw library, and it adds functionality of its own for finding and repairing hot and dead pixels. The division of labour is worth understanding before you write anything. LibRaw is the C library that actually decodes camera RAW files, and rawpy is the Python layer that exposes it, plus a small amount of extra work of its own. The package is written in Cython, which the project classifiers confirm, and it links against a library named `libraw_r` rather than plain `libraw`. That detail shows up later when you install from source. The wrapper also ships API documentation and a separate repository of Jupyter notebook tutorials, so learning the surface does not have to happen inside your own script.
Reading a RAW file and writing the processed result
The core loop is a context manager that opens the file and a postprocess call that returns the image array.
import rawpy
import imageio.v3 as iio
path = 'image.nef'
with rawpy.imread(path) as raw:
rgb = raw.postprocess()
iio.imwrite('default.tiff', rgb)The postprocess call takes LibRaw parameters, and the defaults produce the familiar look. Changing them gives you a different result rather than a different file format, which is the part that matters for scientific work. Saving as a 16-bit linear image, for example, is a matter of passing gamma and bit depth.
with rawpy.imread(path) as raw:
rgb = raw.postprocess(gamma=(1,1), no_auto_bright=True, output_bps=16)
iio.imwrite('linear.tiff', rgb)The `gamma=(1,1)` value removes the tone curve, `no_auto_bright=True` stops LibRaw applying its automatic brightening, and `output_bps=16` keeps more precision than the default. Writing is deliberately left to imageio rather than handled by rawpy, so you choose your own output format.
Thumbnails come back in one of two shapes
Camera files carry an embedded preview, and rawpy can extract it rather than decoding the full image. The result is not a single consistent object, and the sample code shows both shapes you will meet. The `thumb.format` attribute tells you which one you have. When the format is `rawpy.ThumbFormat.JPEG`, the `thumb.data` is already JPEG encoded and you write those bytes straight to a file. When the format is `rawpy.ThumbFormat.BITMAP`, the data is an RGB numpy array and you pass it to imageio instead.
with rawpy.imread(path) as raw:
# raises rawpy.LibRawNoThumbnailError if thumbnail missing
# raises rawpy.LibRawUnsupportedThumbnailError if unsupported format
thumb = raw.extract_thumb()
if thumb.format == rawpy.ThumbFormat.JPEG:
# thumb.data is already in JPEG format, save as-is
with open('thumb.jpeg', 'wb') as f:
f.write(thumb.data)
elif thumb.format == rawpy.ThumbFormat.BITMAP:
# thumb.data is an RGB numpy array, convert with imageio
iio.imwrite('thumb.jpeg', thumb.data)The two exception types are explicit: `LibRawNoThumbnailError` when the file has no embedded preview, and `LibRawUnsupportedThumbnailError` when the preview exists in a format rawpy cannot hand back. Handling both is cheap, and skipping the check means an exception from deep inside your pipeline.
Hot and dead pixels found across several frames
The extra functionality rawpy adds is pixel defect handling. A bad pixel is a fixed sensor artefact that stays in the same place across every frame from the same camera, which means you can identify it by comparing several files and then repair each frame with the same map. The API has two halves: `find_bad_pixels` takes a list of paths and returns the map, and `repair_bad_pixels` applies that map to a single file with a chosen method.
import rawpy.enhance
paths = ['image1.nef', 'image2.nef', 'image3.nef']
bad_pixels = rawpy.enhance.find_bad_pixels(paths)
for path in paths:
with rawpy.imread(path) as raw:
rawpy.enhance.repair_bad_pixels(raw, bad_pixels, method='median')
rgb = raw.postprocess()
iio.imwrite(path + '.tiff', rgb)The ordering matters. The repair happens on the raw object while it is open, before postprocess runs, so the defect map influences the decoding rather than the finished image. The `median` method is one option passed as a keyword, and the mapping of defects across frames is what makes this usable for measuring rather than just for looks.
Installing from a wheel, and the features you give up
Installation is a single pip command, and 64-bit binary wheels are provided for Linux, macOS and Windows.
pip install rawpyThe wheel matters because LibRaw itself has optional features, and the wheels are not identical across platforms. The documented table shows the LCMS color engine, the RedCine codec, the DNG deflate codec and the DNG lossy codec present everywhere, and OpenMP present on Windows and Linux but not on macOS. The Demosaic Pack GPL2 and GPL3 entries are marked no on every platform, and the reason given is licensing: rawpy is MIT licensed, which is incompatible with GPL, so the GPL demosaic algorithms cannot be bundled. The GPL code path still exists in the build script behind the `RAWPY_BUILD_GPL_CODE` environment variable, but it is off by default. You can query what your install actually supports at runtime by inspecting the `rawpy.flags` dictionary rather than trusting the table.
The LibRaw version you run may not be the one you compiled against
Version reporting is split into two values, and the reason is specific. All stable rawpy releases are built against a stable LibRaw release, but the library is loaded dynamically, so the version actually in memory can differ from the headers used at compile time. The runtime value comes from `rawpy.libraw_version` and the compiled one from `rawpy.libraw_version_compiled`. Comparing them is the quickest way to know whether you are running the LibRaw you think you are. Pre-releases follow a different convention, with version numbers like `0.15.0a1` built against a recent LibRaw snapshot, and they are installed with `pip install --pre rawpy`. That distinction matters if you pin versions: a stable install and a pre-release install are testing different things, not just different code versions.
The fork and OpenMP deadlock on Linux
There is a documented failure mode that will cost you an afternoon if you meet it unprepared. On Linux, rawpy with Python's `multiprocessing` can deadlock, and the cause is an interaction between OpenMP, which is enabled in the Linux wheels, and the default `fork` start method. When a process that uses OpenMP is forked, the OpenMP thread pool state becomes inconsistent in the child, and later calls into rawpy hang. The fix is to use the `spawn` or `forkserver` start method.
import multiprocessing as mp
import rawpy
def process_raw(filename):
with rawpy.imread(filename) as raw:
rgb = raw.postprocess()
return rgb
if __name__ == '__main__':
# Set the start method to 'spawn' before creating any processes
mp.set_start_method('spawn')
with mp.Pool(processes=4) as pool:
results = pool.map(process_raw, ['image1.nef', 'image2.nef'])Two constraints come with the fix: the start method can only be set once per program, and it has to be set inside the `__main__` guard. Setting it later, after other code has already started processes, raises. The `spawn` method starts a fresh interpreter, which is why it avoids the problem entirely.
Building from source when the wheel does not cover you
The source routes are documented separately for Linux, macOS and Windows, and they differ in what you must provide. On macOS, LibRaw is built as part of the rawpy build, with the sources under `external/`. On Linux you install LibRaw yourself, either from the distro package, which the documentation flags as outdated, or from the source repositories at a pinned release, and then install rawpy from a clone with numpy and Cython in place. If the import fails with a missing `libraw.so`, the fix is to add `/usr/local/lib` to the loader path and run `ldconfig`, because some distributions do not search that folder by default. The Windows instructions are marked experimental and unsupported, and they assume Visual Studio plus Python, with a PowerShell script that builds the wheel. Given that PyPI wheels cover the common cases, the practical advice is to build from source only when you need a LibRaw version or feature the wheels do not carry.
Editorial conclusion
rawpy fits anyone who needs camera RAW files decoded in a script rather than by hand, and it is the shortest route to that because the wheels bundle LibRaw for you. It is the wrong choice if you need the GPL demosaic packs, which the MIT licence excludes from every platform, or if you need OpenMP on macOS, where the wheels do not provide it. Before building a pipeline on it, print rawpy.libraw_version and rawpy.libraw_version_compiled to confirm the runtime library matches what you expect, and check rawpy.flags for the features your workflow depends on.
Frequently asked questions
How do I install rawpy and decode a RAW file?
Run pip install rawpy, then open the file with rawpy.imread and call postprocess on the result. The sample code writes the returned array with imageio.v3, so the output format is yours to choose.
Which LibRaw features are available in the rawpy binary wheels?
The LCMS color engine, RedCine codec and the DNG deflate and lossy codecs are present on Windows, macOS and Linux, and OpenMP is present on Windows and Linux but not macOS. The GPL2 and GPL3 demosaic packs are absent everywhere because the MIT licence is incompatible with GPL.
How do I fix deadlocks when using rawpy with multiprocessing on Linux?
Use the spawn or forkserver start method instead of fork, since the deadlock comes from OpenMP thread pool state surviving the fork. The start method can only be set once per program and must be called inside an if __name__ == '__main__' guard.
How can I find and repair hot or dead pixels with rawpy?
Call rawpy.enhance.find_bad_pixels with a list of file paths to build the defect map, then call rawpy.enhance.repair_bad_pixels on each raw object with that map and a method such as median. The repair is applied before postprocess runs.
How do I know which LibRaw version rawpy is using?
Print rawpy.libraw_version for the dynamically loaded library and rawpy.libraw_version_compiled for the headers used to build rawpy. Stable releases are built against a stable LibRaw release, while pre-releases use a recent snapshot.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/letmaik-rawpy)Was this useful?