# pyvips: streaming image pipelines through libvips from Python

> pyvips is an MIT licensed cffi binding to the libvips image processing library, where Python programs describe a chain of operations on a source image and the whole chain executes in one parallel pass once it is connected to a destination. It suits a Python team that wants libvips throughput without writing C, and it is a poor fit if you need a wheel that includes PDF and OpenSlide support out of the box.

**libvips/pyvips** — python binding for libvips using cffi

- Repository: https://github.com/libvips/pyvips
- Stars: 814 · Forks: 65
- Language: Python
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/libvips-pyvips

## A binding, not a reimplementation of image processing

pyvips is described as a binding for the libvips image processing library, and it uses cffi to talk to it.

That distinction is the whole point. libvips is a C library with a large catalogue of image operations, written with demand driven evaluation and a threaded pipeline architecture. pyvips does not reimplement any of it. What it does is expose the library's operations as Python methods and objects, so the algorithm work stays in the library and the binding stays thin.

The author is John Cupitt, who is also the author of libvips itself, and the project is MIT licensed with a LICENSE.txt in the repository root. The primary language is Python, the last push to the repository was on 2026-10-17, and there are no GitHub releases, so the package is consumed from PyPI or from conda-forge rather than from a tagged source release.

Two distribution channels are named in the README, the PyPI package pyvips and the conda package, which the README points at on conda-forge. The conda route is worth knowing about because the conda package includes a matching libvips binary, which removes the separate system library dependency entirely.

The claim about correctness is specific and testable. The binding passes the vips test suite cleanly and with no leaks under python3 and pypy3 on Windows, macOS and Linux. The package metadata declares Development Status 5, Production and Stable, and the classifiers cover CPython and PyPy, Python 3.7 through 3.15, with a requires-python of 3.7 or later.

## Operations build a pipeline, and the pipeline runs when it is connected

The How it works section is the part of the README that changes how you write the code, so it is worth reading closely.

Programs that use pyvips do not manipulate images directly. Instead they create pipelines of image processing operations building on a source image. When the end of the pipe is connected to a destination, the whole pipeline executes at once, streaming the image in parallel from source to destination a section at a time.

So the mental model is functional and lazy. You describe what should happen, nothing happens until something consumes the result, and then the entire chain runs in one pass with the work spread across threads and the image never fully resident in memory.

The example in the README makes the shape concrete.

```python
import pyvips

image = pyvips.Image.new_from_file('some-image.jpg', access='sequential')
image *= [1, 2, 1]
mask = pyvips.Image.new_from_array([
    [-1, -1, -1],
    [-1, 16, -1],
    [-1, -1, -1],
], scale=8)
image = image.conv(mask, precision='integer')
image.write_to_file('x.jpg')
```

It loads a JPG, doubles the value of every green pixel with the array multiplication, sharpens with a convolution against a 3 by 3 mask, and writes the result back out. The access='sequential' argument on the load is the streaming hint, and it is the option that matters most for large files, because sequential access lets the library read and process in one forward pass instead of seeking.

The performance claim in the README comes from the libvips speed and memory use benchmark page, where the test loads a large tiff image, shrinks it by 10 percent, sharpens and saves again, and on that test pyvips is typically 3 times faster than ImageMagick and needs 5 times less memory. That is the library's own benchmark of its own engine, so treat it as a reference point rather than a promise about your workload.

## API mode and ABI mode, and the twenty percent you give up

The binding has two modes, and the choice between them is made at install time by the setup script.

If you have the development headers for libvips installed and a working C compiler, the module uses the cffi API mode to build a libvips binary extension for your Python. If it is unable to build a binary extension, it uses the cffi ABI mode instead, which only needs the libvips shared library.

The cost of the fallback is stated: ABI mode takes longer to start up and is typically around 20 percent slower in execution. That is a specific number, and it is the number to plan around if your deployment is latency sensitive at import time or runs many small images.

You can find out which mode you have with one property.

```python
import pyvips

print(pyvips.API_mode)
```

The setup.py shows the mechanism in three lines: it tries the API mode first by passing the cffi builder module in cffi_modules, and on any exception it prints that it is falling back to ABI mode and calls setup again without it.

The practical implication is that a broken compiler or missing headers does not fail your install, it silently gives you the slower mode. If you are benchmarking, or you are chasing a performance difference, check API_mode before you start looking anywhere else, because a machine that quietly fell back to ABI mode will look like a bad code path and will not be one.

## The binary wheel is quick, and it leaves features out

The quickest way to start is the binary package.

```bash
pip install "pyvips[binary]"
```

That installs a self-contained package with the most commonly needed libraries, and the README says it should just work on most platforms, including Linux, Windows and macOS, with 64 and 32 bit x64 and ARM CPUs.

Then comes the qualification, and it is the sentence to read before you choose this route. That libvips is missing features like PDF load and OpenSlide support.

OpenSlide is the one that will hurt if you work with pathology or large microscopy scans, since it is the reference implementation for whole slide imaging. PDF load matters for document processing, which is a common reason to reach for an image library in the first place. Neither is in the wheel, and neither can be added to it without building your own libvips.

So the decision rule is simple. If you process ordinary photographs, thumbnails or generated images, the wheel removes the entire native dependency problem. If you need PDF, OpenSlide, or some other optional libvips feature, you are installing a system libvips, and the rest of this section is for you.

The versioning rule for a system install is also stated. You need the libvips shared library on your library search path, version 8.2 or later, though at least version 8.9 is required for all features to work.

The repository also keeps the two installation paths in step with a build system that requires setuptools 61 or later for pyproject.toml support, plus cffi 1.0.0 or later and pkgconfig 1.5 or later, and the comment in the file says the build requirements must be kept in sync with the project dependencies, which is the kind of detail that quietly breaks a build when one side is edited alone.

## Installing a system libvips differs on every platform

The per-platform instructions are where the real friction is, and each platform has a wrinkle worth knowing before you start.

On Linux the package is libvips-dev installed without recommended packages, followed by a pip install of pyvips. With Python 3.11 and later you need to create a virtual environment first and add it to your PATH, something like creating a venv in the local home directory and then installing pyvips into it.

```bash
sudo apt install libvips-dev --no-install-recommends
pip install pyvips
```

On macOS the README asks for vips, python and pkg-config from Homebrew, then a pip install.

```bash
brew install vips python pkg-config
pip install pyvips
```

On Windows you download a pre-compiled binary from the libvips website, and you need a 64-bit Python. The README then gives advice that is easy to get wrong: you can add the vips bin directory to your PATH, but that adds a lot of extra DLLs to your search path and they might conflict with other programs, so it is usually safer to set PATH in your program.

```python
import os
vipsbin = r'c:\vips-dev-8.16\bin'
add_dll_dir = getattr(os, 'add_dll_directory', None)
if callable(add_dll_dir):
    add_dll_dir(vipsbin)
else:
    os.environ['PATH'] = os.pathsep.join((vipsbin, os.environ['PATH']))
```

The Python 3.8 and later branch uses add_dll_directory when it is available and falls back to prepending the path otherwise, which is the difference between the DLL search path being scoped to a process handle and being a global environment change.

And the conda route, for people who already use conda.

```bash
conda install --channel conda-forge pyvips
```

The conda package includes a matching libvips binary, so the version mismatch problem that plagues system installs simply does not arise there.

## Type hints that know less than the runtime accepts

pyvips includes type hints through PEP 561 stub files, and the README explains how to use them.

The stubs live at pyvips/__init__.pyi, and you install a type checker such as mypy alongside the package and then run it over your own script.

```bash
pip install mypy pyvips
```

```bash
mypy your_script.py
```

Then there is the caveat, and it is a real one for anyone who turns on strict checking. The pyvips methods accept arbitrary keyword arguments for libvips options, which may not be fully covered by the type hints.

That is a structural consequence of the design rather than an oversight. libvips has a very large operation surface, and its options are passed as keyword arguments with names that come from the C library rather than from Python. The stubs can describe the common cases, and they are regenerated when the library changes, with a script to do it.

```bash
python examples/generate_type_stubs.py
```

The README also documents the surrounding maintenance tasks, which tells you what maintaining a binding like this involves. There is a script to regenerate the enums from the GIR file of a libvips built with all optional packages enabled, a script to regenerate the autodocs by calling pyvips.Operation.generate_sphinx_all(), the Sphinx build itself with sphinx-build -bhtml, a version number to update in two files, and a python3 -m build to produce the PyPI artefact.

The examples directory is a good map of the library's capabilities, with files for affine transforms, annotation and join animations, colour operations, connection and stream handling, convolution, drawing lines, orientation, progress reporting, reading profiles, shepard interpolation, a soak test, a thumbnail directory tool, a PIL and numpy interoperability example, and a long series of numbered try files that read as a guided tour.

## Conclusion

pyvips suits a Python team that has outgrown Pillow on large images and wants libvips behaviour without writing an extension in C, since the binding is thin, the operation set is the whole libvips API, and the pipeline model means you describe the work rather than the memory. It does not suit a project that needs PDF loading or OpenSlide support and cannot install a system libvips, because the prebuilt binary wheel omits those. Before you adopt it, check whether your platform is covered by the binary wheel and which features you actually need, and decide deliberately between API mode and ABI mode, because the README states that ABI mode takes longer to start up and is typically about 20 percent slower in execution, and confirm that the type stubs cover the operations you use, since the binding accepts arbitrary keyword arguments for libvips options that the stubs may not describe.

## FAQ

### What is pyvips?

It is a Python binding for the libvips image processing library, written with cffi, so libvips operations are available as Python methods and objects. The binding is MIT licensed, and the README states that it passes the vips test suite cleanly and with no leaks under python3 and pypy3 on Windows, macOS and Linux.

### How does the pyvips pipeline model work?

Programs do not manipulate images directly. They create pipelines of operations on a source image, and when the end of the pipe is connected to a destination the whole pipeline executes at once, streaming the image in parallel from source to destination a section at a time rather than holding the whole image in memory.

### What is the difference between API mode and ABI mode in pyvips?

With the libvips development headers and a C compiler, pyvips builds a binary extension using the cffi API mode. If that fails it falls back to cffi ABI mode, which only needs the libvips shared library, and the README says ABI mode takes longer to start up and is typically about 20 percent slower in execution. You can check which one is in use with pyvips.API_mode.

### How do I install pyvips with a prebuilt binary?

Run pip install "pyvips[binary]", which installs a self-contained package with the most commonly needed libraries and should work on most platforms with 64 and 32 bit x64 and ARM CPUs. The README notes that this libvips is missing features like PDF load and OpenSlide support, and that for a system install you need libvips 8.2 or later, with at least 8.9 for all features.

### Does pyvips support type checking?

It ships PEP 561 type stub files at pyvips/__init__.pyi, and the README shows installing mypy and running it over your own script. It also notes that pyvips methods accept arbitrary keyword arguments for libvips options, which may not be fully covered by the type hints.

## Sources

- [Issues](https://github.com/libvips/pyvips/issues)
- [libvips/pyvips on GitHub](https://github.com/libvips/pyvips)
- [License: MIT](https://github.com/libvips/pyvips/blob/master/LICENSE)
- [README](https://github.com/libvips/pyvips/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/libvips-pyvips
