Faster image pixel data access

Anyone know of a better way of writing and reading image data from and to Blender images? Namely, to Numpy arrays, and from Numpy arrays.

Currently simply reading and writing takes most of the time in my scripts.

If there was an as_pointer() for pixels or something similar it would be possible (but unsafe).

Ideally, there would be a image.to_numpy and image.from_numpy, but pointer access would be enough, I think.

import bpy
import numpy as np
import cProfile
import pstats
import io

def profiling_start():
    pr = cProfile.Profile()
    pr.enable()
    return pr


def profiling_end(pr):
    pr.disable()
    s = io.StringIO()
    sortby = "cumulative"
    ps = pstats.Stats(pr, stream=s)
    ps.strip_dirs().sort_stats(sortby).print_stats(20)
    print(s.getvalue())

context = bpy.context

teximage = None
for area in context.screen.areas:
    if area.type == "IMAGE_EDITOR":
        teximage = area.spaces.active.image
        break
    
pr = profiling_start()
    
# is there as_pointer() for pixels?
# arr = (ctypes.c_byte * dims[0] * dims[1] * 4).from_address(teximage.pixels.as_pointer())
# pixels = np.ndarray(buffer=arr, dtype=np.uint8, shape=(dims[0], dims[1], 4)).astype(np.float64)

dims = teximage.size[0], teximage.size[1]
print("Loading... Please wait... *plays some jazzy chiptune music*")
pixels = np.array(teximage.pixels[:], copy=False)
print("Done loading.")
pixels = pixels.reshape((dims[0], dims[1], 4))

for i in range(2):
    print("Loop:", i)
    pixels[:,2:] += pixels[:,:-2]
    pixels[:,:-2] += pixels[:,2:]
    pixels/=4
    
print("Reshape...")
out = pixels.reshape((pixels.shape[0] * pixels.shape[1] * pixels.shape[2]))
print("Write...")
teximage.pixels = out
profiling_end(pr)

Hello ambi,

I’m currently also looking into this topic. I in my case need a lot of individual pixel accesses and this is a case where numpy also doesn’t really shine. That’s why I don’t use numpy at all and I’m accessing the pixels just as a normal Python list:
paint_pixels = list(paint_image.pixels)
To update the changed image I use
paint_image.pixels[:] = paint_pixels

My script also needs to incrementally update the image while it’s being interactively processed. In that case, I’ve noticed, that setting the image into float mode is a bit faster (still not what I would expect for a good image update pipeline):
paint_image.use_generated_float=True
This at least makes updating the internal image a memcpy call instead of a for loop performing also type conversion from float to the internal pixel type. At the end of my script I still need to unset the float mode and re-update the image. So utilizing this flag probably is nothing you would want to use, if you have a one-shot image processing script.

Maybe try with:
paint_pixels = paint_image.pixels[:]

The problem is that I need Numpy arrays and Blender is giving me only a Python list (or tuple it seems).

If you replace the loop part “for i in range(2)” with:

print("5x5 box blur...")
pixels = np.apply_along_axis(lambda m: np.convolve(m, np.ones(5)/5, mode="same"), axis=1, arr=pixels)
pixels = np.apply_along_axis(lambda m: np.convolve(m, np.ones(5)/5, mode="same"), axis=0, arr=pixels)

It’s a real world example of how useful image data manipulation with Numpy is. 4k textures the actual algorithm part takes a lot less than reading and writing from Blender.

As you guys said, the pixels[:] seems to be the current fastest method and I haven’t found anything outside that.

As one alternative I’ve been thinking of digging through Blender source code and using ctypes to dig the pixel data pointer from the Image struct with as_pointer(), would that work?

There was this thread a few weeks ago: https://devtalk.blender.org/t/bpy-data-images-perf-issues/6459

Might want to ping the op in that thread to see if his suggested product change made it in?

Thanks for letting me know. Looks like it’s exactly the same issue I’m having.

Looks like there’s a much improved version to get and set pixels now since 2 weeks in the master branch of blender, making it up to 40 times faster:
https://developer.blender.org/rB9075ec8269e7cb029f4fab6c1289eb2f1ae2858a
There are 2 new functions foreach_get and foreach_set that we can utilize now. Haven’t tried them yet, but I’m looking forward trying it out, as soon as I find some time to spend.

1 Like