pyvips icon indicating copy to clipboard operation
pyvips copied to clipboard

Numpy array of vips images doesn't work in later numpy versions

Open hroskes opened this issue 4 years ago • 4 comments

import pyvips, numpy as np
i = pyvips.Image.new_from_memory(np.zeros((3, 3), dtype=np.uint8), format='uchar', width=3, height=3, bands=1)
a = np.array([i], dtype=object)

In older numpy versions, up to at least 1.19.3, this works fine. In 1.20.3, I get

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    a = np.array([i], dtype=object)
ValueError: invalid __array_struct__

As a workaround, I am able to do

a = np.zeros(dtype=object, shape=1)
a[0] = i

hroskes avatar Nov 22 '21 17:11 hroskes

Hi @hroskes, thank you for reporting this.

Your sample code is working for me on ubuntu 21.10 (numpy 1.19.5). I'll see if I can get 1.20 installed.

jcupitt avatar Nov 27 '21 10:11 jcupitt

Yes, I see a failure too for numpy 1.21.4.

I'm actually surprised this ever worked, so I think I'd tag this as not-a-bug. The numpy / pyvips interface that pyvips supports is documented here:

https://libvips.github.io/pyvips/intro.html#numpy-and-pil

So I would write:

#!/usr/bin/python3

import numpy as np
import pyvips

array = np.zeros((3, 3, 1), dtype=np.uint8)

# to pyvips
height, width, bands = array.shape
linear = array.reshape(width * height * bands)
image = pyvips.Image.new_from_memory(linear.data, width, height, bands, "uchar")

# back to an array
array = np.ndarray(buffer=image.write_to_memory(),
                   dtype=np.uint8,
                   shape=[image.height, image.width, image.bands])

This will share memory areas between pyvips and numpy, so there are no extra copy operations. pyvips has some extra logic to keep references to shared memory areas of this type, so it should also be safe.

jcupitt avatar Nov 27 '21 10:11 jcupitt

Thanks, actually what I was trying to do was make a 1D array of dtype object containing vips images rather than an array containing their data. The reason was I wanted to do linear algebra with the nice numpy shorthand, something like this:

images = np.array([pyvips.Image.new_from_memory(np.zeros((3, 3), dtype=np.uint8), format='uchar', width=3, height=3, bands=1) for i in range(5)], dtype=object)
mixingmatrix = np.array([[1, 0, 0, 0.5, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0.5]])
mixed = mixingmatrix @ images

And then mixed is also a vips array of three images (which I'd like to combine into one image with bands, if there's a way to do that...)

hroskes avatar Nov 28 '21 18:11 hroskes

Ah gotcha. Yes, I'd make a vips_to_numpy() conversion function and pass everything through that.

To combine after your @, go back to vips images again and use bandjoin:

rgb = mixed[0].bandjoin(mixed[1:])

There are some notes about bandjoin near the end of this section in the intro:

https://libvips.github.io/pyvips/intro.html#calling-libvips-operations

But the docs seem to be missing, I don't know why. I'll have a look.

jcupitt avatar Nov 28 '21 18:11 jcupitt