Custom Integrations#

The methods convert_shape() and convert_image() are customizable. In addition to the built-in integrations, you can create new integrations that the convert_shape() and convert_image() methods will use to convert Python objects into objects that are compatible with py5.

This page documents how to create custom integrations for both methods. Both methods have similar customization designs.

This page will explain the customization design in detail, but if you are going to build a custom integration, it is recommended you also have a look at the source code for the built-in image and shape customizations for additional insight and examples. You can also ask for help in GitHub Discussions.

Your custom integration will require you to create two functions. The first function is a predicate function that accepts a Python object as its parameter and returns True or False to indicate if the object is convertible by your custom integration. The second function must accept the same Python object and **kwargs parameters, and return an object compatible with py5.

Your custom integrations will always take precedence over py5’s builtin integrations.

We will begin with some imports needed for our examples.

import numpy as np
from PIL import Image
from shapely import Point

import py5_tools
import py5

Custom Integrations for Image Conversion#

For our image conversion example, we will create a custom integration that converts PIL Image objects and by default rotates them 180 degrees. We will also support a keyword argument to rotate the image by a different angle.

First we create our predicate function. It will simply check if an object is a PIL Image object and return True or False. This function is identical to py5’s built-in customization for PIL Image objects.

def pillow_image_to_ndarray_precondition(obj):
    return isinstance(obj, Image.Image)

The second function’s parameters will be a sketch instance, the object to be converted, and **kwargs parameters. You do not have to return a Py5Image object. Instead, it is simpler and less coding for you to return a special NumpyImageArray object or just return the path (as a string or a pathlib.Path instance) to an image saved to disk that can be read and loaded by py5. If for some reason these options are not ideal for what you want to do, you can create the Py5Image object anyway and return that.

This NumpyImageArray class is a special class py5 uses internally to manage image data in numpy arrays. When creating an instance of NumpyImageArray, the first parameter should be the numpy array with three dimensions. The first two dimensions should be the image’s vertical and horizontal dimensions, respectively. The third should be the image’s color channels.

The second parameter should be a string indicating the color channel sequence of the array’s third dimension. Any value that the create_image_from_numpy() method’s bands parameter accepts is acceptable here. If the color channel sequence is L, the array’s third dimension is optional.

If it is more convenient, your code can save the converted image to disk in a temporary file. The Python library tempfile can help with this.

The conversion function below is very similar to py5’s built-in customization for PIL Image objects.

def pillow_image_to_ndarray_converter(sketch, img, **kwargs):
    rotate = kwargs.get('rotate', 180)
    if img.mode not in ["RGB", "RGBA"]:
        img = img.convert(mode="RGB")
    img = img.rotate(rotate)
    return py5.NumpyImageArray(np.asarray(img), img.mode)

The last step is to register the pair of functions with py5. After registering, when the convert_image() method is called, it will use this new conversion function we wrote to convert PIL Image objects.

py5.register_image_conversion(
    pillow_image_to_ndarray_precondition, pillow_image_to_ndarray_converter
)

Now that everything is configured correctly, let’s create a Sketch that uses our new PIL image conversion functionality.

def setup():
    py5.size(300, 300)

    pil_img = Image.open('images/rockies.jpg')
    img1 = py5.convert_image(pil_img)
    img2 = py5.convert_image(pil_img, rotate=45)
    py5.image(img1, 25, 100)
    py5.image(img2, 175, 100)
py5.run_sketch()

The result looks as we would expect. The left image is upside down and the right image is rotated 45 degrees.

py5_tools.screenshot()
../_images/53b91dff560b503534eb269bc2b91484e8d45aff5d85723911799a3bcaa259d2.png

Custom Integrations for Shape Conversion#

For our shape conversion example, we will create a custom integration that converts shapely Point objects into clouds of gaussian distributed points. We will support keyword arguments for the standard deviation and count of the points in the cloud.

First we create our predicate function. It will check if an object is a shapely Point object and return True or False.

def shapely_point_precondition(obj):
    return isinstance(obj, Point)

The second function’s parameters will be a sketch instance, the object to be converted, and **kwargs parameters. The conversion function must return a new Py5Shape object to be returned by convert_shape().

def shapely_point_converter(sketch, obj, **kwargs):
    count = kwargs.get('count', 2500)
    stdev = kwargs.get('stdev', 25)
    points = stdev * np.random.randn(count, 2) + [obj.x, obj.y]

    s = sketch.create_shape()
    with s.begin_shape(sketch.POINTS):
        s.vertices(points)

    return s

We again register the pair of functions with py5. When the convert_shape() method is called, it will use the conversion function we wrote to convert shapely Point objects.

py5.register_shape_conversion(
    shapely_point_precondition, shapely_point_converter
)

Now let’s create a Sketch that uses our shapely Point conversion functionality.

def setup():
    py5.size(300, 300)

    point1 = Point(130, 210)
    point2 = Point(210, 130)

    points1 = py5.convert_shape(point1)
    points2 = py5.convert_shape(point2, count=5000, stdev=10)
    py5.shape(points1)
    py5.shape(points2)
py5.run_sketch()

As expected, each point is rendered as a scattered field of individual points.

py5_tools.screenshot()
../_images/cc8f98a421336cd56336b1c1982fdd4beb881c96f767eff08757e9ac738e5f9a.png

Share Your Custom Integration Ideas!#

The Python ecosystem is vast, and we are always on the lookout for Python libraries we aren’t familiar with that can further enhance py5’s capabilities. If you know of a Python library that you think would be a good fit, please let us know in GitHub Discussions or elsewhere on Social Media!