numpy array is shown incorrect with pyglet

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-03 16:42:56

I think pyglet is expecting uint8, have you tried?

IMG = ( random((X,Y)) * 255 ).astype('uint8')

I've spent the last week playing around with using NumPy to generate random textures. I came across this post and tried the accepted answers.

I can confirm that the previously accepted answer is NOT CORRECT.

It seems correct because you are using grey-scale images. But if you were to use a colour image (RGBA for example) and zero the GBA channels you would have discovered this because you would still be getting green and blue showing up in your texture.

By using __str__() you are actually sending garbage and not the values you really want.

I'll use my code to demonstrate this.

import numpy
import pyglet
from pyglet.gl import *

# the size of our texture
dimensions = (16, 16)

# we need RGBA textures
# which has 4 channels
format_size = 4
bytes_per_channel = 1

# populate our array with some random data
data = numpy.random.random_integers(
    low = 0,
    high = 1,
    size = (dimensions[ 0 ] * dimensions[ 1 ], format_size)
    )

# convert any 1's to 255
data *= 255

# set the GB channels (from RGBA) to 0
data[ :, 1:-1 ] = 0

# ensure alpha is always 255
data[ :, 3 ] = 255

# we need to flatten the array
data.shape = -1

Using the answer above, you would do the following

DON'T DO THIS!

tex_data = data.astype('uint8').__str__()

If you try the code out, you will get all colours, not just red!

Do this instead!

The proper way is to convert to the ctype GLubytes.

# convert to GLubytes
tex_data = (GLubyte * data.size)( *data.astype('uint8') )

You can then pass this into your texture.

# create an image
# pitch is 'texture width * number of channels per element * per channel size in bytes'
return pyglet.image.ImageData(
    dimensions[ 0 ],
    dimensions[ 1 ],
    "RGBA",
    tex_data,
    pitch = dimensions[ 1 ] * format_size * bytes_per_channel
    )

According to the Pyglet docs on pyglet.image, if you want greyscale, you should use the 'L' format code, not the 'RGB', since you have only one channel.

I've been playing with this to get a dynamic view of a numpy array. The answer by @Rebs worked, but became inefficient when I wanted to update the image at every frame. After profiling, I found that the ctypes value casting was the rate limiting step, and could be sped up by instead using the from_buffer method of the ctype type object to share the underlying bits in memory between the numpy array and the GLubyte array.

Here is a class that will map between a 2d numpy array and a pyglet image, using matplotlib's colour maps to do so. If you have a numpy array, create an ArrayView wrapper around it and then update and blit it in the window on_draw method:

my_arr = np.random.random((nx, ny))
arr_img = ArrayImage(my_arr)

@window.event
def on_draw():
    arr_img.update()
    arr_img.image.blit(x, y)

Full class implementation:

import numpy as np
import matplotlib.cm as cmaps
from matplotlib.colors import Normalize
import pyglet
import pyglet.gl

class ArrayImage:
    """Dynamic pyglet image of a 2d numpy array using matplotlib colormaps."""
    def __init__(self, array, cmap=cmaps.viridis, norm=None, rescale=True):
        self.array = array
        self.cmap = cmap
        if norm is None:
            norm = Normalize()
        self.norm = norm
        self.rescale = rescale

        self._array_normed = np.zeros(array.shape+(4,), dtype=np.uint8)
        # this line below was the bottleneck...
        # we have removed it by setting the _tex_data array to share the buffer
        # of the normalised data _array_normed
        # self._tex_data = (pyglet.gl.GLubyte * self._array_normed_data.size)( *self._array_normed_data )
        self._tex_data = (pyglet.gl.GLubyte * self._array_normed.size).from_buffer(self._array_normed)
        self._update_array()

        format_size = 4
        bytes_per_channel = 1
        self.pitch = array.shape[1] * format_size * bytes_per_channel
        self.image = pyglet.image.ImageData(array.shape[0], array.shape[1], "RGBA", self._tex_data)
        self._update_image()

    def set_array(self, data):
        self.array = data
        self.update()

    def _update_array(self):
        if self.rescale:
            self.norm.autoscale(self.array)
        self._array_normed[:] = self.cmap(self.norm(self.array), bytes=True)
        # don't need the below any more as _tex_data points to _array_normed memory
        # self._tex_data[:] = self._array_normed

    def _update_image(self):
        self.image.set_data("RGBA", self.pitch, self._tex_data)

    def update(self):
        self._update_array()
        self._update_image()

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!