VIPS from Python

Using VIPS — How to use the VIPS library from Python

Introduction

VIPS comes with a convenient, high-level Python API based on gobject-introspection. As long as you can get GOI for your platform, you should be able to use vips. The Vips.py file needs to be copied to the overrides directory of your GOI install, and you need to have the vips typelib on your GI_TYPELIB_PATH. This may already have happened, depending on your platform.

#!/usr/bin/python

import sys

from gi.repository import Vips

im = Vips.Image.new_from_file(sys.argv[1])

im = im.extract_area(100, 100, im.width - 200, im.height - 200)
im = im.similarity(scale = 0.9)
mask = Vips.Image.new_from_array([[-1, -1,  -1], 
                                  [-1, 16,  -1], 
                                  [-1, -1,  -1]], scale = 8)
im = im.conv(mask)

im.write_to_file(sys.argv[2])

Reading this example, the first line loads the input file. You can append load options to the argument list as keyword arguments, for example:

im = Vips.Image.new_from_file(sys.argv[1], access = Vips.Access.SEQUENTIAL)

See the various loaders for a list of the available options for each file format. The C equivalent to this function, vips_image_new_from_file(), has more extensive documentation. Try help(Vips.Image) to see a list of all the image constructors --- you can load from memory, or create from an array, for example.

The next line crops 100 pixels off every edge. Try help(im.extract_area) and the C API docs for vips_extract_area() for details. You can use .crop() as a synonym, if you like. im.width gets the image width in pixels, see help(Vips.Image) and vips_image_get_width() and friends for a list of the other getters.

The similarity line shrinks by 10%. By default it uses bilinear interpolation, use interpolate to pick another interpolator, for example:

im = im.similarity(scale = 0.9, interpolate = Vips.Interpolate.new("bicubic"))

.new_from_array() makes an image from a 2D array. The scale keyword argument lets you set a divisor for convolution, handy for integer convolutions. You can set offset as well. See vips_conv() for details on the vips convolution operator.

Finally, .write_to_file() sends the image back to the filesystem. There's also .write_to_buffer() to make a string containing the formatted image, and .write() to write to another image.

pyvips8 basics

The Python interface comes in two main parts. First, the C source code to libvips has been marked up with special comments describing the interface in a standard way. These comments are read by gobject-introspection when libvips is compiled and used to generate a typelib, a description of how to call the library. When your Python program starts, the import line:

from gi.repository import Vips

loads the typelib and creates Python classes for all the objects and all the functions in the library. You can then call these functions from your code, and they will call into libvips for you. C functions become Python functions in an obvious way: vips_operation_new(), for example, the constructor for the class VipsOperation, becomes Vips.Operation.new(). See the C API docs for details.

Using libvips like this is possible, but a bit painful. To make the API seem more pythonesque, vips includes a set of overrides which form a layer over the bare functions created by gobject-introspection.

Automatic wrapping

The overrides intercept member lookup on the Vips.Image class and look for vips operations with that name. So the vips operation "add", which appears in the C API as vips_add(), appears in Python as image.add().

The first input image argument becomes the self argument. If there are no input image arguments, the operation appears as a class member. Optional input arguments become keyword arguments. The result is a list of all the output arguments, or a single output if there is only one.

Optional output arguments are enabled with a boolean keyword argument of that name. For example, "min" (the operation which appears in the C API as vips_min()), can be called like this:

min_value = im.min()

and min_value will be a floating point value giving the minimum value in the image. "min" can also find the position of the minimum value with the x and y optional output arguments. Call it like this:

min_value, x_pos, y_pos = im.min(x = True, y = True)

Although in this case, the .minpos() convenience function would be simpler.

Because operations are member functions and return the result image, you can chain them. For example, you can write:

result_image = image.sin().pow(2)

to calculate the square of the sine for each pixel. There is also a full set of arithmetic operator overloads, see below.

VIPS types are also automatically wrapped. The override looks at the type of argument required by the operation and converts the value you supply, when it can. For example, "linear" takes a VipsArrayDouble as an argument for the set of constants to use for multiplication. You can supply this value as an integer, a float, or some kind of compound object and it will be converted for you. You can write:

result_image = image.linear(1, 3)
result_image = image.linear(12.4, 13.9)
result_image = image.linear([1, 2, 3], [4, 5, 6])
result_image = image.linear(1, [4, 5, 6])

And so on. A set of overloads are defined for .linear(), see below.

It does a couple of more ambitious conversions. It will automatically convert to and from the various vips types, like VipsBlob and VipsArrayImage. For example, you can read the ICC profile out of an image like this:

profile = im.get_value("icc-profile-data")

and profile will be a string.

If an operation takes several input images, you can use a constant for all but one of them and the wrapper will expand the constant to an image for you. For example, .ifthenelse() uses a condition image to pick pixels between a then and an else image:

result_image = condition_image.ifthenelse(then_image, else_image)

You can use a constant instead of either the then or the else parts, and it will be expanded to an image for you. If you use a constant for both then and else, it will be expanded to match the condition image. For example:

result_image = condition_image.ifthenelse([0, 255, 0], [255, 0, 0])

Will make an image where true pixels are green and false pixels are red.

This is useful for .bandjoin(), the thing to join two or more images up bandwise. You can write:

rgba = rgb.bandjoin(255)

to add a constant 255 band to an image, perhaps to add an alpha channel. Of course you can also write:

result_image = image1.bandjoin(image2)
result_image = image1.bandjoin([image2, image3])
result_image = Vips.Image.bandjoin([image1, image2, image3])
result_image = image1.bandjoin([image2, 255])

and so on.

Automatic docstrings

Try help(Vips) for everything, help(Vips.Image) for something slightly more digestible, or something like help(Vips.Image.black) for help on a specific class member.

You can't get help on dynamically bound member functions like .add() this way. Instead, make an image and get help from that, for example:

image = Vips.Image.new_from_file("x.jpg")
help(image.add)

And you'll get a summary of the operator's behaviour and how the arguments are represented in Python. Use the C API docs for more detail.

Exceptions

The wrapper spots errors from vips operations and raises the Vips.Error exception. You can catch it in the usual way. The .detail member gives the detailed error message.

Draw operations

Paint operations like draw_circle and draw_line modify their input image. This makes them hard to use with the rest of libvips: you need to be very careful about the order in which operations execute or you can get nasty crashes.

The wrapper spots operations of this type and makes a private copy of the image in memory before calling the operation. This stops crashes, but it does make it inefficient. If you draw 100 lines on an image, for example, you'll copy the image 100 times. The wrapper does make sure that memory is recycled where possible, so you won't have 100 copies in memory. At least you can execute these operations.

If you want to avoid the copies, you'll need to call drawing operations yourself.

Overloads

The wrapper defines the usual set of arithmetic, boolean and relational overloads on image. You can mix images, constants and lists of constants (almost) freely. For example, you can write:

result_image = ((image * [1, 2, 3]).abs() < 128) | 4

Expansions

Some vips operators take an enum to select an action, for example .math() can be used to calculate sine of every pixel like this:

result_image = image.math(Vips.OperationMath.SIN)

This is annoying, so the wrapper expands all these enums into separate members named after the enum. So you can write:

result_image = image.sin()

See help(Vips.Image) for a list.

Convenience functions

The wrapper defines a few extra useful utility functions: .get_value(), .set_value() .bandsplit(), .maxpos() .minpos(). Again, see help(Vips.Image) for a list.

Command-line option parsing

GLib includes a command-line option parser, and Vips defines a set of standard flags you can use with it. For example:

import sys
from gi.repository import GLib, Vips

context = GLib.OptionContext(" - test stuff")
main_group = GLib.OptionGroup("main", 
                              "Main options", "Main options for this program", 
                              None)
context.set_main_group(main_group)
Vips.add_option_entries(main_group)
context.parse(sys.argv)