6
$\begingroup$

I tried filling a numpy array with the foreach_get() method on an image, but get the following error:
foreach_get/set() takes exactly 1 argument (2 given)

def read_pixels(img):
    resolution = img.size[0] * img.size[1]  
    colors = np.zeros(resolution*4, dtype=np.float)
    img.pixels.foreach_get("pixels", colors)
    colors = np.reshape(colors, (resolution, 4))
    return colors
 
img = bpy.data.images['TestImg.png']
print(read_pixels(img))

foreach_get() should take 2 arguments, doesn't it? The name of the attribute ('pixels') and the array to be filled?!

$\endgroup$
1
  • 2
    $\begingroup$ I'm not sure, but you call foreach_get method on the pixels object, so the method uses its self argument which is set automatically to the pixels object. From what I could google, other people use foreach_get by only passing the numpy array as a temporary buffer, so you should try img.pixels.foreach_get(colors) $\endgroup$ Mar 9, 2021 at 10:33

2 Answers 2

9
$\begingroup$

"The python console check"

In case Markus is busy can get a lot of info using autocomplete Tab in the python console, with an image as img

>>> img.pixels.foreach_get(
foreach_get(seq)
.. method:: foreach_get(seq)
This is a function to give fast access to array data.

which is plainly different from, for instance a mesh me vertices, which asks for an attribute attr , eg for vert coordinates "co"

>>> me.vertices.foreach_get(
foreach_get(attr, seq)
.. method:: foreach_get(attr, seq)
This is a function to give fast access to attributes within a collection.

A very slight improvement in speed can be attained by using np.empty to initialize the array, since any garbage will be filled by the foreach get.

import time
import numpy as np
    
REPS = 100

def time_it(func):
    def wrapper(*arg, **kw):
        t1 = time.time()
        for i in range(REPS):
            func(*arg, **kw)
        t2 = time.time()
        print(func.__name__, (t2 - t1))
    return wrapper

@time_it
def read_pixels(img):
    resolution = img.size[0] * img.size[1]  
    colors = np.zeros(resolution*4, dtype=np.float32)
    img.pixels.foreach_get(colors)
    colors = np.reshape(colors, (resolution, 4))
    return colors

@time_it
def read_pixels2(img):
    x, y = img.size
    pixels = np.empty(x * y << 2, dtype=np.float32)
    img.pixels.foreach_get(pixels)
    return pixels.reshape((x, y, 4))

@time_it
def nptest1(img):
    x, y = img.size
    return np.array(img.pixels, dtype=np.float32).reshape((x, y, 4))
    
@time_it    
def nptest2(img):
    x, y = img.size
    return np.array(img.pixels[:], dtype=np.float32).reshape((x, y, 4))

import bpy
for img in bpy.data.images:
    nptest1(img)
    nptest2(img)
    read_pixels(img)
    read_pixels2(img)
    #break

Results

nptest1 8.4240
nptest2 7.0605
read_pixels 0.2264
read_pixels2 0.2222

Note on posting noticed I had reshaped to image x, y, 4, however reshape is an almost time "free" method, will get back if otherwise. (Got a squeak more using bitwise shift to quadruple)

$\endgroup$
3
$\begingroup$

as Markus von Broady pointed out, ommiting the attribute name and just passing the numpy array works. Thank you.

I was confused, because specifying the the attribute name worked in other areas, e.g.

sculpt_vertex_colors[name].data.foreach_get('color', colors)

But I guess that's a different structure.
Had to change the dtype to float32 instead of just float.
Here is the corrected example

def read_pixels(img):
    resolution = img.size[0] * img.size[1]  
    colors = np.zeros(resolution*4, dtype=np.float32)
    img.pixels.foreach_get(colors)
    colors = np.reshape(colors, (resolution, 4))
    return colors
 
img = bpy.data.images['TestImg.png']
print(read_pixels(img))
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .