Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

numpy array is shown incorrect with pyglet

I have problems with displaying a numpy array with pyglet. I have found a very similar topic (how to display a numpy array with pyglet?) that I used. I want to display the array in greyscale, but pyglet displays it with colours see the image: http://i.stack.imgur.com/pL6Yr.jpg

def create(self, X,Y):

    IMG = random((X,Y)) * 255
    self.IMG = dstack((IMG,IMG,IMG))

    return self.IMG

def image(self):

    self.img_data = self.create(X,Y).data.__str__()
    self.image = pyglet.image.ImageData(X,Y, 'RGB', self.img_data, pitch = -X*3)

    return self.image

If I save and load the array instead it works (but it is horrobly slower):

def image(self):

    self.im_save=scipy.misc.toimage(self.create(X,Y),cmin=0, cmax=255)
    self.im_save.save('outfile.png')
    self.image = pyglet.image.load('outfile.png')

    return self.image

And I get what I wanted:

i.stack.imgur.com/FCY1v.jpg

I can't find the mistake in the first code example :(

EDIT:

Many thanks for your answers. With the hint from Bago I got this to code to work :) And indeed nfirvine suggestion is reasonable, since I only want to display the matrix in greyscale.

def create(self, X,Y):

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

        return self.IMG


def image(self):

        self.img_data = self.create(X,Y).data.__str__()
        self.image = pyglet.image.ImageData(X,Y, 'L', self.img_data)

        return self.image
like image 451
T-Lo Avatar asked Jan 27 '12 15:01

T-Lo


2 Answers

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
    )
like image 136
Rebs Avatar answered Sep 23 '22 21:09

Rebs


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()

like image 23
James P Avatar answered Sep 22 '22 21:09

James P