def updateVertices(self, *a): """Called by :meth:`__init__`, and when certain display properties change. (Re-)generates the mesh vertices, indices and normals (if being displayed in 3D). They are stored as attributes called ``vertices``, ``indices``, and ``normals`` respectively. """ overlay = self.overlay vertices = overlay.vertices indices = overlay.indices normals = self.overlay.vnormals xform = self.opts.getTransform('mesh', 'display') if not np.all(np.isclose(xform, np.eye(4))): vertices = affine.transform(vertices, xform) if self.threedee: nmat = affine.invert(xform).T normals = affine.transform(normals, nmat, vector=True) self.vertices = np.asarray(vertices, dtype=np.float32) self.indices = np.asarray(indices.flatten(), dtype=np.uint32) self.vertices = dutils.makeWriteable(self.vertices) self.indices = dutils.makeWriteable(self.indices) if self.threedee: self.normals = np.array(normals, dtype=np.float32)
def updateVertices(self, *a): """Called by :meth:`__init__`, and when certain display properties change. (Re-)generates the mesh vertices, indices and normals (if being displayed in 3D). They are stored as attributes called ``vertices``, ``indices``, and ``normals`` respectively. """ overlay = self.overlay opts = self.opts threedee = self.threedee vertices = overlay.vertices indices = overlay.indices normals = self.overlay.vnormals vdata = opts.getVertexData('vertex') xform = opts.getTransform('mesh', 'display') if not np.all(np.isclose(xform, np.eye(4))): vertices = affine.transform(vertices, xform) if self.threedee: nmat = affine.invert(xform).T normals = affine.transform(normals, nmat, vector=True) self.origIndices = indices indices = np.asarray(indices.flatten(), dtype=np.uint32) # If flatShading is active, we cannot share # vertices between triangles, so we generate # a set of unique vertices for each triangle, # and then re-generate the triangle indices. # The original indices are saved above, as # they will be used by the getVertexData # method to duplicate the vertex data. if threedee and (vdata is not None) and opts.flatShading: self.vertices = vertices[indices].astype(np.float32) self.indices = np.arange(0, len(self.vertices), dtype=np.uint32) normals = normals[indices, :] else: self.vertices = np.asarray(vertices, dtype=np.float32) self.indices = indices self.vertices = dutils.makeWriteable(self.vertices) self.indices = dutils.makeWriteable(self.indices) if self.threedee: self.normals = np.array(normals, dtype=np.float32)
def test_makeWriteable(): robuf = bytes(b'\01\02\03\04') wbuf = bytearray(b'\01\02\03\04') roarr = np.ndarray((4, ), dtype=np.uint8, buffer=robuf) warr = np.ndarray((4, ), dtype=np.uint8, buffer=wbuf) warr.flags['WRITEABLE'] = False rocopy = dutils.makeWriteable(roarr) wcopy = dutils.makeWriteable(warr) assert rocopy.base is not roarr.base assert wcopy.base is warr.base rocopy[1] = 100 wcopy[1] = 100
def doRefresh(self): """Overrides :meth:`.Texture.doRefresh`. (Re-)configures the OpenGL texture. """ data = self.preparedData if data is None: return log.debug('Configuring 3D texture (id %s) for %s (data shape: %s)', self.name, self.handle, self.shape) # First dimension for multi- # valued textures if self.nvals > 1: shape = data.shape[1:] else: shape = data.shape # The image data is flattened, with # fortran dimension ordering, so the # data, as stored on the GPU, has its # first dimension as the fastest # changing. data = np.array(data.ravel(order='F'), copy=False) # PyOpenGL needs the data array # to be writeable, as it uses # PyArray_ISCARRAY to check # for contiguousness. but if the # data has come from a nibabel # ArrayProxy, the writeable flag # will be set to False for some # reason. data = dutils.makeWriteable(data) interp = self.interp intFmt = self.internalFormat baseFmt = self.baseFormat ttype = self.textureType if interp is None: interp = gl.GL_NEAREST with self.bound(): # Enable storage of tightly packed data of any size (i.e. # our texture shape does not have to be divisible by 4). gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_MAG_FILTER, interp) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_MIN_FILTER, interp) # Clamp texture borders to # the specified border value(s) if self.border is not None: gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_BORDER) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_BORDER) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_R, gl.GL_CLAMP_TO_BORDER) gl.glTexParameterfv(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_BORDER_COLOR, np.asarray(self.border, dtype=np.float32)) # Clamp texture borders to the edge # values - it is the responsibility # of the rendering logic to not draw # anything outside of the image space else: gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_TEXTURE_WRAP_R, gl.GL_CLAMP_TO_EDGE) # The macOS GL driver sometimes corrupts # the texture data if we don't generate # mipmaps gl.glTexParameteri(gl.GL_TEXTURE_3D, gl.GL_GENERATE_MIPMAP, gl.GL_TRUE) # create the texture according to # the format determined by the # determineTextureType method. # # note: The ancient Chromium driver (still # in use by VirtualBox) will improperly # create 3D textures without two calls # (to glTexImage3D and glTexSubImage3D). # If I specify the texture size and set # the data in a single call, it seems to # expect that the data or texture # dimensions always have even size - odd # sized images will be displayed # incorrectly. gl.glTexImage3D(gl.GL_TEXTURE_3D, 0, intFmt, shape[0], shape[1], shape[2], 0, baseFmt, ttype, None) gl.glTexSubImage3D(gl.GL_TEXTURE_3D, 0, 0, 0, 0, shape[0], shape[1], shape[2], baseFmt, ttype, data)
def doRefresh(self): """Overrides :meth:`.Texture.doRefresh`. Configures this ``Texture2D``. This includes setting up interpolation, and setting the texture size and data. """ data = self.preparedData if data is None: width, height = self.shape elif self.nvals == 1: width, height = data.shape else: width, height = data.shape[1:] if data is not None: data = np.array(data.ravel('F'), copy=False) data = dutils.makeWriteable(data) interp = self.interp if interp is None: interp = gl.GL_NEAREST with self.bound(): gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, interp) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, interp) if self.border is not None: gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_BORDER) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_BORDER) gl.glTexParameterfv(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_BORDER_COLOR, self.border) else: gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) # If the width and height have not # changed, then we don't need to # re-define the texture. But we can # use glTexSubImage2D if we have # data to upload if width == self.__width and \ height == self.__height and \ data is not None: gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, width, height, self.baseFormat, self.textureType, data) # If the width and/or height have # changed, we need to re-define # the texture properties else: self.__width = width self.__height = height gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, self.internalFormat, width, height, 0, self.baseFormat, self.textureType, data)
def __vdataChanged(self, value, valid, ctx, name): """Called when the :attr:`vertexData` or :attr:`modulateData` properties changes. Attempts to load the data if possible. The data may subsequently be retrieved via the :meth:`getVertexData` method. """ vdata = None vdataRange = None overlay = self.overlay vdfile = value if name == 'vertexData': key = 'vertex' elif name == 'modulateData': key = 'modulate' else: raise RuntimeError() try: if vdfile is not None: if vdfile not in overlay.vertexDataSets(): log.debug('Loading vertex data: {}'.format(vdfile)) vdata = overlay.loadVertexData(vdfile) else: vdata = overlay.getVertexData(vdfile) vdataRange = np.nanmin(vdata), np.nanmax(vdata) if len(vdata.shape) == 1: vdata = vdata.reshape(-1, 1) vdata = dutils.makeWriteable(vdata) except Exception as e: # TODO show a warning log.warning('Unable to load vertex data from {}: {}'.format( vdfile, e, exc_info=True)) vdata = None vdataRange = None self.__vdata[key] = vdata self.__vdataRange[key] = vdataRange if key == 'vertex': if vdata is not None: npoints = vdata.shape[1] else: npoints = 1 self.vertexDataIndex = 0 self.setAttribute('vertexDataIndex', 'maxval', npoints - 1) # if modulate data has changed, # don't update display/clipping # ranges (unless modulateData is # None, meaning that it is using # vertexData) if key == 'vertex': drange = True mrange = self.modulateData is None # and vice versa else: drange = False mrange = True self.updateDataRange(drange, drange, mrange)