I'm attaching a new version. The biggest change is that this version 
doesn't import numpy. (Given the "no dependencies" of pyglet, I figured 
this may have been an unstated objection to my previous go-round.) Also, 
to the best of my knowledge, this supports the .pitch .format and .data 
attributes of the ImageData class. (The .data attribute is implemented 
as a property.) I have not bothered to finish implementing all the 
corner cases in the interests of gauging interest before investing more 
time. Alex, please let me know if there is hope for including this in 
pyglet -- otherwise I'll stop working on it and include it in my own 
project -- it already works more than sufficiently for my purposes.

-Andrew

Andrew Straw wrote:
> (I'm moving this thread from 
> http://code.google.com/p/pyglet/issues/detail?id=202 to email, which I 
> find more conducive to conversation.)
>
> Alex Holkner wrote
>
>   
>> I doesn't implement the ImageData interface, despite subclassing it (the 
>> format, pitch and data properties won't work as documented); this class 
>> should probably be a direct subclass of AbstractImage.  
>>     
>
> As submitted to the tracker, NumpyImage does in fact try to support the 
> format and pitch properties as advertised for ImageData. What about them 
> is different than the documentation? (I'm going by the docstrings in 
> image/__init__.py -- please point me to better docs if I've missed 
> them.) Any limitation in supported formats is an oversight on my part, 
> not non-support. Furthermore, one can explicitly pass a format argument 
> to the constructor if the default won't work. It is true that the format 
> and pitch properties are read only - is that a problem?
>
> I agree that the data property doesn't work as advertised, but my 
> motivation for this work is that I don't want the data to be copied to a 
> string before going to OpenGL. I originally tried to implement this with 
> the data property being the buffer object corresponding to a numpy 
> array, which is closer in spirit to the data property being a string, 
> but I abandoned this when trying to get ctypes to pass the pointer of 
> the buffer object. I can revisit this -- are there any show-stoppers you 
> see with the data property being either a buffer object or a string?
>
>   
>> It looks good, but I'm not convinced this is going to be useful for many 
>> people besides yourself.  In particular, 
>> it's not in pyglet's nature to fail if an optimal path can't be achieved (it 
>> should fall back to copying, and non-
>> contiguous and arbitrary-format arrays should be supported).
>>
>>     
>
> As mentioned, my main motivation is to have a non-copy path from 
> arbitrary data (numpy array as the container) to the video card. 
> However, I'd be fine with an option and default setting such as 
> "allows_copy=True" which, for my current use case, would always set to 
> False.
>
>   
>> The view_new_array() method seems awkward; why wouldn't the user create a 
>> new NumpyImage and toss the old one?
>>     
>
> To avoid the overhead of creating a new OpenGL texture if you're 
> displaying a new array of the same shape as the old one (e.g. digital 
> image frames).
>
>   
>> It seems to me that it would be useful to also provide functionality for 
>> obtaining a numpy array from another 
>> pyglet image (say, a texture, or the color buffer, for example).
>>     
>
> Agreed. Indeed it seems that's what Patrick Devine wants.
>
>   
>> I haven't been following the array interface discussion for a while, but if 
>> the latest PIL and numpy support it, 
>> is it possible to provide an image class that can make use of 
>> __array_interface__ instead of supporting numpy 
>> etc directly?
>>     
>
> I'm not sure what you're suggesting here. The numpy.asarray(arr) 
> explicitly consumes this interface already. Are you suggesting something 
> more/different? Certainly I have no objection to changing "numpy" to 
> "ArrayInterface" or whatever.
>
> >
>   


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pyglet-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/pyglet-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Index: pyglet/image/arrayimage.py
===================================================================
--- pyglet/image/arrayimage.py  (revision 0)
+++ pyglet/image/arrayimage.py  (revision 0)
@@ -0,0 +1,201 @@
+# ----------------------------------------------------------------------------
+# pyglet
+# Copyright (c) 2007 Andrew Straw
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions 
+# are met:
+#
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above copyright 
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+#  * Neither the name of the pyglet nor the names of its
+#    contributors may be used to endorse or promote products
+#    derived from this software without specific prior written
+#    permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# ----------------------------------------------------------------------------
+
+from pyglet.image import ImageData
+import ctypes
+
+__all__ = ['ArrayInterfaceImage']
+
+def is_c_contiguous(inter):
+    strides = inter.get('strides')
+    if strides is None:
+        return True
+    else:
+        raise NotImplementedError('XXX')
+
+def get_stride0(inter):
+    strides = inter.get('strides')
+    if strides is not None:
+        return strides[0]
+    else:
+        # C contiguous
+        shape = inter.get('shape')
+        cumproduct = 1
+        for i in range(1,len(shape)):
+            cumproduct *= shape[i]
+        return cumproduct
+
+class ArrayInterfaceImage(ImageData):
+    def __init__(self,arr,format=None,allow_copy=True):
+        '''Initialize image data from the numpy array interface
+
+        :Parameters:
+            `arr` : array
+                data supporting the __array_interface__ protocol. If
+                rank 2, the shape must be (height, width). If rank 3,
+                the shape is (height, width, depth). Typestr must be
+                '|u1' (uint8).
+            `format` : str or None
+                If specified, a format string describing the data
+                format array (e.g. 'L', 'RGB', or 'RGBA'). Defaults to
+                a format determined from the shape of the array.
+            `allow_copy` : bool
+                If False, no copies of the data will be made, possibly
+                resulting in exceptions being raised if the data is
+                unsuitable. In particular, the data must be C
+                contiguous in this case. If True (default), the data
+                may be copied to avoid such exceptions.
+
+        '''
+
+        self.inter = arr.__array_interface__
+        self.allow_copy = allow_copy
+        self.data_ptr = ctypes.c_void_p()
+        self.data_ptr.value = 0
+
+        if len(self.inter['shape'])==2:
+            height,width = self.inter['shape']
+            if format is None:
+                format = 'L'
+        elif len(self.inter['shape'])==3:
+            height,width,depth = self.inter['shape']
+            if format is None:
+                if depth==3:
+                    format = 'RGB'
+                elif depth==4:
+                    format = 'RGBA'
+                elif depth==1:
+                    format = 'L'
+                else:
+                    raise ValueError("could not determine a format for depth 
%d"%depth)
+        else:
+            raise ValueError("arr must have 2 or 3 dimensions")
+        data = None
+        pitch = get_stride0(self.inter)
+        super(ArrayInterfaceImage, self).__init__(width, height, format, data, 
pitch=pitch)
+
+        self.view_new_array( arr )
+
+    def get_data(self):
+        if self._real_string_data is not None:
+            return self._real_string_data
+
+        if not self.allow_copy:
+            raise ValueError("cannot get a view of the data without allowing 
copy")
+
+        # create a copy of the data in a Python str
+        shape = self.inter['shape']
+        print 'shape',shape
+        nbytes = 1
+        for i in range(len(shape)):
+            nbytes *= shape[i]
+        mydata = ctypes.create_string_buffer( nbytes )
+        ctypes.memmove( mydata, self.data_ptr, nbytes)
+        print 'nbytes',nbytes
+        return mydata.value
+    data = property(get_data,None,"string view of data")
+        
+    def _convert(self, format, pitch):
+        if format == self._current_format and pitch == self._current_pitch:
+            # do something with these values to convert to a ctypes.c_void_p
+            if self._real_string_data is None:
+                return self.data_ptr
+            else:
+                # XXX pyglet may copy this to create a pointer to the buffer?
+                return self._real_string_data
+        else:
+            if self.allow_copy:
+                raise NotImplementedError("XXX")
+            else:
+                raise ValueError("cannot convert to desired format/pitch 
without copying")
+
+    def _ensure_string_data(self):
+        if self.allow_copy:
+            raise NotImplementedError("XXX")
+        else:
+            raise ValueError("cannot create string data without copying")
+
+    def dirty(self):
+        '''Force an update of the texture data.
+        '''
+
+        texture = self.texture
+        internalformat = None
+        self.blit_to_texture( texture.target, texture.level, 0, 0, 0, 
internalformat )
+        
+    def view_new_array(self,arr):
+        '''View a new array of the same shape.
+
+        The same texture will be kept, but the data from the new array
+        will be loaded.
+
+        :Parameters:
+            `arr` : array
+                data supporting the __array_interface__ protocol. If
+                rank 2, the shape must be (height, width). If rank 3,
+                the shape is (height, width, depth). Typestr must be
+                '|u1' (uint8).
+        '''
+
+        inter = arr.__array_interface__
+
+        if not is_c_contiguous(inter):
+            if self.allow_copy:
+                raise NotImplementedError('')
+            else:
+                raise ValueError('copying is not allowed but data is not C 
contiguous')
+
+        if inter['typestr'] != '|u1':
+            raise ValueError("data is not type uint8 (typestr=='|u1')")
+
+        if inter['shape'] != self.inter['shape']:
+            raise ValueError("shape changed!")
+
+        self._real_string_data = None
+        self.data_ptr.value = 0
+
+        idata = inter['data']
+        if isinstance(idata,tuple):
+            data_ptr_int,readonly = idata
+            self.data_ptr.value = data_ptr_int
+        elif isinstance(idata,str):
+            self._real_string_data = idata
+        else:
+            raise ValueError("__array_interface__ data attribute was not tuple 
or string")
+
+        # maintain references so they're not de-allocated
+        self.inter = inter
+        self.arr = arr
+
+        self.dirty()
Index: examples/arrayimage_display.py
===================================================================
--- examples/arrayimage_display.py      (revision 0)
+++ examples/arrayimage_display.py      (revision 0)
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+'''Display a numpy array.
+
+Usage::
+
+    numpy_display.py [filename]
+
+A checkerboard background is visible behind any transparent areas of the
+image.
+
+If given, the filename parameter will load an image file using the
+Python Imaging Library and then display it. Otherwise, a numpy array
+will be generated and displayed. In both cases, the data is
+transferred using the __array_interface__ protocol specified by numpy.
+
+'''
+
+import sys
+
+from pyglet.gl import *
+from pyglet import window
+from pyglet import image
+from pyglet.image.arrayimage import ArrayInterfaceImage
+
+import numpy
+
+if __name__ == '__main__':
+    if len(sys.argv) > 2:
+        print __doc__
+        sys.exit(1)
+
+    filename = None
+    if len(sys.argv) > 1:
+        filename = sys.argv[1]
+
+    w = window.Window(visible=False, resizable=True)
+    
+    if filename is None:
+        # test constructor from numpy array
+        width, height= 320,240
+        depth=4 # 1, 3, or 4
+        arr = numpy.arange( width*height*depth, dtype=numpy.uint8)
+        arr.shape = height,width,depth
+        if depth==1 and 1:
+            # test 2D array
+            arr.shape = height,width
+    else:
+        # test constructor from PIL image
+        import Image
+        if Image.VERSION < '1.1.6':
+            # new PIL necessary for __array_interface__
+            raise ValueError("Need Image (PIL) version 1.1.6 ")
+        arr = Image.open(filename)
+        #arr = numpy.asarray( pil_image )
+    aii = ArrayInterfaceImage(arr)
+
+    img = aii.texture
+
+    checks = image.create(32, 32, image.CheckerImagePattern())
+    background = image.TileableTexture.create_for_image(checks)
+
+    w.width = img.width
+    w.height = img.height
+    w.set_visible()
+
+    glEnable(GL_BLEND)
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+
+    i=0
+    while not w.has_exit:
+        w.dispatch_events()
+        
+        background.blit_tiled(0, 0, 0, w.width, w.height)
+        img = aii.texture
+        img.blit(0, 0, 0)
+        w.flip()
+
+        if filename is None and 1:
+            # modify numpy array in-place
+
+            arr.fill(i)
+            aii.dirty() # dirty the ArrayInterfaceImage because the data 
changed
+            i=(i+1)%256
+
+            if i == 1 and 0:
+                arr = numpy.ones_like( arr ) # create a new array
+                aii.view_new_array(arr) # switch ArrayInterfaceImage to view 
the new array

Reply via email to