1"""Numpy (new version) module implementation of the OpenGL-ctypes array interfaces 2 3XXX Need to register handlers for all of the scalar types that numpy returns, 4would like to have all return values be int/float if they are of compatible 5type as well. 6""" 7REGISTRY_NAME = 'numpy' 8import operator,logging 9from OpenGL import _configflags 10_log = logging.getLogger( __name__ ) 11try: 12 import numpy 13except ImportError as err: 14 raise ImportError( """No numpy module present: %s"""%(err)) 15import OpenGL 16import ctypes 17from OpenGL._bytes import long, integer_types 18from OpenGL.raw.GL import _types 19from OpenGL.raw.GL.VERSION import GL_1_1 20from OpenGL import constant, error 21from OpenGL.arrays import formathandler 22c_void_p = ctypes.c_void_p 23from OpenGL import acceleratesupport 24NumpyHandler = None 25if acceleratesupport.ACCELERATE_AVAILABLE: 26 try: 27 from OpenGL_accelerate.numpy_formathandler import NumpyHandler 28 except ImportError as err: 29 _log.warn( 30 "Unable to load numpy_formathandler accelerator from OpenGL_accelerate" 31 ) 32if NumpyHandler is None: 33 # numpy's array interface has changed over time :( 34 testArray = numpy.array( [1,2,3,4],'i' ) 35 # Numpy's "ctypes" interface actually creates a new ctypes object 36 # in python for every access of the .ctypes attribute... which can take 37 # ridiculously large periods when you multiply it by millions of iterations 38 if hasattr(testArray,'__array_interface__'): 39 def dataPointer( cls, instance ): 40 """Convert given instance to a data-pointer value (integer)""" 41 try: 42 return long(instance.__array_interface__['data'][0]) 43 except AttributeError as err: 44 instance = cls.asArray( instance ) 45 try: 46 return long(instance.__array_interface__['data'][0]) 47 except AttributeError as err: 48 return long(instance.__array_data__[0],0) 49 else: 50 def dataPointer( cls, instance ): 51 """Convert given instance to a data-pointer value (integer)""" 52 try: 53 return long(instance.__array_data__[0],0) 54 except AttributeError as err: 55 instance = cls.asArray( instance ) 56 try: 57 return long(instance.__array_interface__['data'][0]) 58 except AttributeError as err: 59 return long(instance.__array_data__[0],0) 60 try: 61 del testArray 62 except NameError as err: 63 pass 64 dataPointer = classmethod( dataPointer ) 65 66 67 class NumpyHandler( formathandler.FormatHandler ): 68 """Numpy-specific data-type handler for OpenGL 69 70 Attributes: 71 72 ERROR_ON_COPY -- if True, will raise errors 73 if we have to copy an array object in order to produce 74 a contiguous array of the correct type. 75 """ 76 HANDLED_TYPES = (numpy.ndarray,)# list, tuple ) 77 dataPointer = dataPointer 78 isOutput = True 79 ERROR_ON_COPY = _configflags.ERROR_ON_COPY 80 @classmethod 81 def zeros( cls, dims, typeCode ): 82 """Return Numpy array of zeros in given size""" 83 return numpy.zeros( dims, GL_TYPE_TO_ARRAY_MAPPING[typeCode]) 84 @classmethod 85 def arrayToGLType( cls, value ): 86 """Given a value, guess OpenGL type of the corresponding pointer""" 87 typeCode = value.dtype 88 constant = ARRAY_TO_GL_TYPE_MAPPING.get( typeCode ) 89 if constant is None: 90 raise TypeError( 91 """Don't know GL type for array of type %r, known types: %s\nvalue:%s"""%( 92 typeCode, list(ARRAY_TO_GL_TYPE_MAPPING.keys()), value, 93 ) 94 ) 95 return constant 96 97 @classmethod 98 def arraySize( cls, value, typeCode = None ): 99 """Given a data-value, calculate dimensions for the array""" 100 return value.size 101 @classmethod 102 def arrayByteCount( cls, value, typeCode = None ): 103 """Given a data-value, calculate number of bytes required to represent""" 104 try: 105 return value.nbytes 106 except AttributeError as err: 107 if cls.ERROR_ON_COPY: 108 raise error.CopyError( 109 """Non-numpy array passed to numpy arrayByteCount: %s""", 110 type(value), 111 ) 112 value = cls.asArray( value, typeCode ) 113 return value.nbytes 114 @classmethod 115 def asArray( cls, value, typeCode=None ): 116 """Convert given value to an array value of given typeCode""" 117 if value is None: 118 return value 119 else: 120 return cls.contiguous( value, typeCode ) 121 122 @classmethod 123 def contiguous( cls, source, typeCode=None ): 124 """Get contiguous array from source 125 126 source -- numpy Python array (or compatible object) 127 for use as the data source. If this is not a contiguous 128 array of the given typeCode, a copy will be made, 129 otherwise will just be returned unchanged. 130 typeCode -- optional 1-character typeCode specifier for 131 the numpy.array function. 132 133 All gl*Pointer calls should use contiguous arrays, as non- 134 contiguous arrays will be re-copied on every rendering pass. 135 Although this doesn't raise an error, it does tend to slow 136 down rendering. 137 """ 138 typeCode = GL_TYPE_TO_ARRAY_MAPPING[ typeCode ] 139 try: 140 contiguous = source.flags.contiguous 141 except AttributeError as err: 142 if typeCode: 143 return numpy.ascontiguousarray( source, typeCode ) 144 else: 145 return numpy.ascontiguousarray( source ) 146 else: 147 if contiguous and (typeCode is None or typeCode==source.dtype.char): 148 return source 149 elif (contiguous and cls.ERROR_ON_COPY): 150 from OpenGL import error 151 raise error.CopyError( 152 """Array of type %r passed, required array of type %r""", 153 source.dtype.char, typeCode, 154 ) 155 else: 156 # We have to do astype to avoid errors about unsafe conversions 157 # XXX Confirm that this will *always* create a new contiguous array 158 # XXX Guard against wacky conversion types like uint to float, where 159 # we really don't want to have the C-level conversion occur. 160 # XXX ascontiguousarray is apparently now available in numpy! 161 if cls.ERROR_ON_COPY: 162 from OpenGL import error 163 raise error.CopyError( 164 """Non-contiguous array passed""", 165 source, 166 ) 167 if typeCode is None: 168 typeCode = source.dtype.char 169 return numpy.ascontiguousarray( source, typeCode ) 170 @classmethod 171 def unitSize( cls, value, typeCode=None ): 172 """Determine unit size of an array (if possible)""" 173 return value.shape[-1] 174 @classmethod 175 def dimensions( cls, value, typeCode=None ): 176 """Determine dimensions of the passed array value (if possible)""" 177 return value.shape 178 @classmethod 179 def from_param( cls, instance, typeCode=None ): 180 try: 181 pointer = cls.dataPointer( instance ) 182 except TypeError as err: 183 array = cls.asArray( instance, typeCode ) 184 pp = cls.dataPointer( array ) 185 pp._temporary_array_ = (array,) 186 return pp 187 else: 188 if typeCode and instance.dtype != GL_TYPE_TO_ARRAY_MAPPING[ typeCode ]: 189 raise error.CopyError( 190 """Array of type %r passed, required array of type %r""", 191 instance.dtype.char, typeCode, 192 ) 193 return c_void_p( pointer ) 194 195try: 196 numpy.array( [1], 's' ) 197 SHORT_TYPE = 's' 198except TypeError as err: 199 SHORT_TYPE = 'h' 200 USHORT_TYPE = 'H' 201 202def lookupDtype( char ): 203 return numpy.zeros( (1,), dtype=char ).dtype 204 205ARRAY_TO_GL_TYPE_MAPPING = { 206 lookupDtype('d'): GL_1_1.GL_DOUBLE, 207 lookupDtype('f'): GL_1_1.GL_FLOAT, 208 lookupDtype('i'): GL_1_1.GL_INT, 209 lookupDtype(SHORT_TYPE): GL_1_1.GL_SHORT, 210 lookupDtype(USHORT_TYPE): GL_1_1.GL_UNSIGNED_SHORT, 211 lookupDtype('B'): GL_1_1.GL_UNSIGNED_BYTE, 212 lookupDtype('c'): GL_1_1.GL_UNSIGNED_BYTE, 213 lookupDtype('b'): GL_1_1.GL_BYTE, 214 lookupDtype('I'): GL_1_1.GL_UNSIGNED_INT, 215 #lookupDtype('P'), _types.GL_VOID_P, # normally duplicates another type (e.g. 'I') 216 None: None, 217} 218GL_TYPE_TO_ARRAY_MAPPING = { 219 GL_1_1.GL_DOUBLE: lookupDtype('d'), 220 GL_1_1.GL_FLOAT:lookupDtype('f'), 221 GL_1_1.GL_INT: lookupDtype('i'), 222 GL_1_1.GL_BYTE: lookupDtype('b'), 223 GL_1_1.GL_SHORT: lookupDtype(SHORT_TYPE), 224 GL_1_1.GL_UNSIGNED_INT: lookupDtype('I'), 225 GL_1_1.GL_UNSIGNED_BYTE: lookupDtype('B'), 226 GL_1_1.GL_UNSIGNED_SHORT: lookupDtype(USHORT_TYPE), 227 _types.GL_VOID_P: lookupDtype('P'), 228 None: None, 229} 230