def test_linear_nearest_switch(): import matplotlib.pyplot as plt N = 32 d = .4*np.random.rand(N ** 3).reshape((N,) * 3).astype(np.float32) rend = VolumeRenderer((400, 400)) rend.rebuild_program(interpolation="linear") rend.set_data(d) rend.render() out1 = rend.output.copy() rend.rebuild_program(interpolation="nearest") rend.render() out2 = rend.output plt.subplot(1, 2, 1) plt.imshow(out1, cmap = "magma") plt.axis("off") plt.subplot(1,2,2) plt.imshow(out2, cmap = "magma") plt.axis("off") plt.show() plt.pause(.1) plt.close() return rend
def test_linear_nearest_switch(): import matplotlib.pyplot as plt N = 32 d = .4 * np.random.rand(N**3).reshape((N, ) * 3).astype(np.float32) rend = VolumeRenderer((400, 400)) rend.rebuild_program(interpolation="linear") rend.set_data(d) rend.render() out1 = rend.output.copy() rend.rebuild_program(interpolation="nearest") rend.render() out2 = rend.output plt.subplot(1, 2, 1) plt.imshow(out1, cmap="magma") plt.axis("off") plt.subplot(1, 2, 2) plt.imshow(out2, cmap="magma") plt.axis("off") plt.show() plt.pause(.1) plt.close() return rend
class GLWidget(QtOpenGL.QGLWidget): _dataModelChanged = QtCore.pyqtSignal() _BACKGROUND_BLACK = (0., 0., 0., 0.) _BACKGROUND_WHITE = (1., 1., 1., 0.) def __init__(self, parent=None, N_PREFETCH=0, interpolation="linear", **kwargs): logger.debug("init") # # fmt = QtOpenGL.QGLFormat(QtOpenGL.QGL.AlphaChannel) # # super(GLWidget, self).__init__(fmt,parent, **kwargs) super(GLWidget, self).__init__(parent, **kwargs) self.parent = parent self.texture_LUT = None self.setAcceptDrops(True) self.renderer = VolumeRenderer( (spimagine.config.__DEFAULT_TEXTURE_WIDTH__, spimagine.config.__DEFAULT_TEXTURE_WIDTH__), interpolation=interpolation) self.renderer.set_projection(mat4_perspective(60, 1., .1, 100)) # self.renderer.set_projection(projMatOrtho(-2,2,-2,2,-10,10)) self.output = np.zeros([self.renderer.height, self.renderer.width], dtype=np.float32) self.output_alpha = np.zeros( [self.renderer.height, self.renderer.width], dtype=np.float32) self.sliceOutput = np.zeros((100, 100), dtype=np.float32) self.setTransform(TransformModel()) self.renderTimer = QtCore.QTimer(self) self.renderTimer.setInterval(10) self.renderTimer.timeout.connect(self.onRenderTimer) self.renderTimer.start() self.renderedSteps = 0 self.N_PREFETCH = N_PREFETCH self.NSubrenderSteps = 1 self.dataModel = None self.meshes = [] # self.setMouseTracking(True) self._dataModelChanged.connect(self.dataModelChanged) self.refresh() # self.installEventFilter(self) def set_background_mode_black(self, mode_back=True): self._background_mode_black = mode_back self.refresh() def setModel(self, dataModel): logger.debug("setModel to %s" % dataModel) if self.dataModel is None or (self.dataModel != dataModel): self.dataModel = dataModel self.transform.setModel(dataModel) self.dataModel._dataSourceChanged.connect(self.dataSourceChanged) self.dataModel._dataPosChanged.connect(self.dataPosChanged) self._dataModelChanged.emit() def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, event): for url in event.mimeData().urls(): # path = url.toLocalFile().toLocal8Bit().data() path = url.toLocalFile() if spimagine.config.__SYSTEM_DARWIN__: path = spimagine.config._parseFileNameFix(path) self.setCursor(QtCore.Qt.BusyCursor) if self.dataModel: self.dataModel.loadFromPath(path, prefetchSize=self.N_PREFETCH) else: self.setModel( DataModel.fromPath(path, prefetchSize=self.N_PREFETCH)) self.setCursor(QtCore.Qt.ArrowCursor) def set_colormap(self, name): """name should be either jet, hot, gray, coolwarm""" try: arr = spimagine.config.__COLORMAPDICT__[name] self._set_colormap_array(arr) except KeyError: print("could not load colormap '%s'" % name) print("valid names: %s" % list(spimagine.config.__COLORMAPDICT__.keys())) def set_colormap_rgb(self, color=[1., 1., 1.]): self._set_colormap_array( np.outer(np.linspace(0, 1., 255), np.array(color))) def _set_colormap_array(self, arr): """arr should be of shape (N,3) and gives the rgb components of the colormap""" self.makeCurrent() self.texture_LUT = fillTexture2d(arr.reshape((1, ) + arr.shape), self.texture_LUT) self.refresh() def _shader_from_file(self, fname_vert, fname_frag): shader = QOpenGLShaderProgram() shader.addShaderFromSourceFile(QOpenGLShader.Vertex, fname_vert) shader.addShaderFromSourceFile(QOpenGLShader.Fragment, fname_frag) shader.link() shader.bind() logger.debug("GLSL program log:%s", shader.log()) return shader def initializeGL(self): self.resized = True logger.debug("initializeGL") self.programTex = self._shader_from_file( absPath("shaders/texture.vert"), absPath("shaders/texture.frag")) self.programCube = self._shader_from_file(absPath("shaders/box.vert"), absPath("shaders/box.frag")) self.programSlice = self._shader_from_file( absPath("shaders/slice.vert"), absPath("shaders/slice.frag")) self.programMesh = self._shader_from_file(absPath("shaders/mesh.vert"), absPath("shaders/mesh.frag")) self.programMeshLight = self._shader_from_file( absPath("shaders/mesh_light.vert"), absPath("shaders/mesh_light.frag")) self.texture = None self.textureAlpha = None self.textureSlice = None self.quadCoord = np.array([[-1., -1., 0.], [1., -1., 0.], [1., 1., 0.], [1., 1., 0.], [-1., 1., 0.], [-1., -1., 0.]]) self.quadCoordTex = np.array([[0, 0], [1., 0.], [1., 1.], [1., 1.], [0, 1.], [0, 0]]) # self.cubeCoords = create_cube_coords([-1,1,-1,1,-1,1]) self.set_colormap(spimagine.config.__DEFAULTCOLORMAP__) glEnable(GL_BLEND) # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # self.set_interpolation(spimagine.config.__DEFAULT_INTERP__ != "nearest") # glLineWidth(1.0); glBlendFunc(GL_ONE, GL_ONE) glEnable(GL_LINE_SMOOTH) glDisable(GL_DEPTH_TEST) glLineWidth(spimagine.config.__DEFAULT_BOX_LINEWIDTH__) # self.set_background_color(0,0,0,.0) self.set_background_mode_black(True) self.clear_canvas() # self.set_background_color(1,1,1,.6) def clear_canvas(self): if self._background_mode_black: glClearColor(*self._BACKGROUND_BLACK) else: glClearColor(*self._BACKGROUND_WHITE) if glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def set_interpolation(self, interpolate=True): interp = "linear" if interpolate else "nearest" self.renderer.rebuild_program(interpolation=interp) self.refresh() def setTransform(self, transform): self.transform = transform self.transform._transformChanged.connect(self.refresh) self.transform._stackUnitsChanged.connect(self.setStackUnits) self.transform._boundsChanged.connect(self.setBounds) def dataModelChanged(self): logger.debug("+++++++++ data model changed") if self.dataModel: logger.debug("dataModelchanged") self.renderer.set_data(self.dataModel[0], autoConvert=True) mi, ma = self._get_min_max() self.transform.reset(minVal=mi, maxVal=ma, stackUnits=self.dataModel.stackUnits()) self.meshes = [] self.refresh() def _get_min_max(self): # as amax is too slow for bug arrays, do it on the gpu if self.dataModel: try: im = self.renderer.dataImg tmp_buf = OCLArray.empty(im.shape, im.dtype) tmp_buf.copy_image(im) mi = float(cl_array.min(tmp_buf).get()) ma = float(cl_array.max(tmp_buf).get()) except Exception as e: print(e) mi = np.amin(self.dataModel[0]) ma = np.amax(self.dataModel[0]) return mi, ma def set_background_color(self, r, g, b, a=1.): self._background_color = (r, g, b, a) glClearColor(r, g, b, a) def dataSourceChanged(self): logger.debug("dataSourcechanged") self.renderer.set_data(self.dataModel[0], autoConvert=True) mi, ma = self._get_min_max() self.transform.reset(minVal=mi, maxVal=ma, stackUnits=self.dataModel.stackUnits()) self.refresh() def setBounds(self, x1, x2, y1, y2, z1, z2): self.cubeCoords = create_cube_coords([x1, x2, y1, y2, z1, z2]) self.renderer.set_box_boundaries([x1, x2, y1, y2, z1, z2]) def setStackUnits(self, px, py, pz): logger.debug("setStackUnits to %s" % [px, py, pz]) self.renderer.set_units([px, py, pz]) def dataPosChanged(self, pos): self.renderer.update_data(self.dataModel[pos]) self.refresh() def refresh(self): # if self.parentWidget() and self.dataModel: # self.parentWidget().setWindowTitle("SpImagine %s"%self.dataModel.name()) self.renderUpdate = True self.renderedSteps = 0 def resizeGL(self, width, height): # somehow in qt5 the OpenGLWidget width/height parameters above are double the value of self.width/height self._viewport_width, self._viewport_height = width, height def add_mesh(self, mesh=SphericalMesh()): """ adds a mesh with vertices and facecolor/edgecolor to be drawn mesh is an instance of spimagine.gui.Mesh, e.g. mesh = Mesh(vertices = [[0,1,0],[0,1,0],...], normals = [[0,1,0],[0,1,0],...], facecolor = (1.,.4,.4,.2), edgecolor = None,...) there are some predefined meshes like SphericalMesh, EllipsoidMesh ... """ self.meshes.append([ mesh, glvbo.VBO(mesh.vertices.astype(np.float32, copy=False)), glvbo.VBO(np.array(mesh.normals).astype(np.float32, copy=False)), glvbo.VBO(np.array(mesh.indices).astype(np.uint32, copy=False), target=GL_ELEMENT_ARRAY_BUFFER) ]) self.refresh() # sort according to opacity as the opaque objects should be drawn first # self.meshes.sort(key=lambda x: x[0].alpha, reverse=True) def _paintGL_render(self): # Draw the render texture self.programTex.bind() self.texture = fillTexture2d(self.output, self.texture) # self.textureAlpha = fillTexture2d(self.output_alpha, self.textureAlpha) glEnable(GL_BLEND) glEnable(GL_TEXTURE_2D) glDisable(GL_DEPTH_TEST) self.programTex.enableAttributeArray("position") self.programTex.enableAttributeArray("texcoord") self.programTex.setAttributeArray("position", self.quadCoord) self.programTex.setAttributeArray("texcoord", self.quadCoordTex) self.programTex.setUniformValue("is_mode_black", self._background_mode_black) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.texture) self.programTex.setUniformValue("texture", 0) glActiveTexture(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, self.textureAlpha) self.programTex.setUniformValue("texture_alpha", 1) glActiveTexture(GL_TEXTURE2) glBindTexture(GL_TEXTURE_2D, self.texture_LUT) self.programTex.setUniformValue("texture_LUT", 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDrawArrays(GL_TRIANGLES, 0, len(self.quadCoord)) def _paintGL_slice(self): # draw the slice self.programSlice.bind() self.programSlice.setUniformValue( "mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) self.programSlice.setUniformValue("is_mode_black", self._background_mode_black) self.programSlice.enableAttributeArray("position") pos, dim = self.transform.slicePos, self.transform.sliceDim coords = slice_coords(1. * pos / self.dataModel.size()[2 - dim + 1], dim) texcoords = [[0., 0.], [1, 0.], [1., 1.], [1., 1.], [0., 1.], [0., 0.]] self.programSlice.setAttributeArray("position", coords) self.programSlice.setAttributeArray("texcoord", texcoords) self.textureSlice = fillTexture2d(self.sliceOutput, self.textureSlice) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.textureSlice) self.programSlice.setUniformValue("texture", 0) glActiveTexture(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, self.texture_LUT) self.programSlice.setUniformValue("texture_LUT", 1) glDrawArrays(GL_TRIANGLES, 0, len(coords)) def _paintGL_box(self): glEnable(GL_BLEND) # Draw the cube self.programCube.bind() self.programCube.setUniformValue( "mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) self.programCube.enableAttributeArray("position") if self._background_mode_black: self.programCube.setUniformValue("color", QtGui.QVector4D(1, 1, 1, 0.6)) else: self.programCube.setUniformValue("color", QtGui.QVector4D(0, 0, 0, 0.6)) self.programCube.setAttributeArray("position", self.cubeCoords) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.textureAlpha) self.programCube.setUniformValue("texture_alpha", 0) glEnable(GL_DEPTH_TEST) # glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDrawArrays(GL_LINES, 0, len(self.cubeCoords)) glDisable(GL_DEPTH_TEST) def _paintGL_mesh(self, mesh, vbo_vertices, vbo_normals, vbo_indices): """ paint a mesh (which has all the coordinates and colors in it """ glEnable(GL_DEPTH_TEST) glDisable(GL_BLEND) prog = self.programMeshLight prog.bind() prog.setUniformValue( "mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) prog.setUniformValue("mvMatrix", QtGui.QMatrix4x4(*self._mat_modelview.flatten())) prog.setUniformValue("normMatrix", QtGui.QMatrix4x4(*self._mat_normal.flatten())) if mesh.light: prog.setUniformValue("light", QtGui.QVector3D(*mesh.light)) prog.setUniformValue("light_components", QtGui.QVector3D(.2, .5, .3)) else: prog.setUniformValue("light", QtGui.QVector3D(0, 0, 0)) prog.setUniformValue("light_components", QtGui.QVector3D(1., 0, 0)) if not mesh.facecolor is None: r, g, b = mesh.facecolor[:3] a = mesh.alpha prog.setUniformValue("color", QtGui.QVector4D(r, g, b, a)) prog.enableAttributeArray("position") vbo_vertices.bind() glVertexAttribPointer(prog.attributeLocation("position"), 3, GL_FLOAT, GL_FALSE, 0, vbo_vertices) prog.enableAttributeArray("normal") vbo_normals.bind() glVertexAttribPointer(prog.attributeLocation("normal"), 3, GL_FLOAT, GL_FALSE, 0, vbo_normals) vbo_indices.bind() glDrawElements(GL_TRIANGLES, len(vbo_indices.data), GL_UNSIGNED_INT, None) vbo_indices.unbind() vbo_vertices.unbind() glDisable(GL_DEPTH_TEST) prog.disableAttributeArray("position") prog.disableAttributeArray("normal") # # if not mesh.edgecolor is None: # r, g, b = mesh.edgecolor # a = mesh.alpha # # prog.enableAttributeArray("position") # vbo_vertices.bind() # glVertexAttribPointer(prog.attributeLocation("position"), 2, GL_FLOAT, GL_FALSE, 0, vbo_edges) # # prog.setUniformValue("color", # QtGui.QVector4D(r, g, b, a)) # # glDrawArrays(GL_LINES, 0, len(mesh.edges)) def paintGL(self): self.makeCurrent() if not glCheckFramebufferStatus( GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE: return w = max(self._viewport_width, self._viewport_height) # force viewport to always be a square glViewport((self._viewport_width - w) // 2, (self._viewport_height - w) // 2, w, w) self.clear_canvas() self._mat_modelview = self.transform.getModelView() self._mat_proj = self.transform.getProjection() self._mat_normal = np.linalg.inv(self._mat_modelview).T self._mat_modelviewproject = np.dot(self._mat_proj, self._mat_modelview) if self.dataModel: self.textureAlpha = fillTexture2d(self.output_alpha, self.textureAlpha) if self.transform.isBox: self._paintGL_box() if self.transform.isSlice and self.sliceOutput is not None: self._paintGL_slice() self._paintGL_render() for (m, vbo_verts, vbo_normals, vbo_indices) in self.meshes: self._paintGL_mesh(m, vbo_verts, vbo_normals, vbo_indices) def render(self): logger.debug("render") if self.dataModel: self.renderer.set_modelView(self.transform.getUnscaledModelView()) self.renderer.set_projection(self.transform.getProjection()) self.renderer.set_min_val(self.transform.minVal) self.renderer.set_max_val(self.transform.maxVal) self.renderer.set_gamma(self.transform.gamma) self.renderer.set_alpha_pow(self.transform.alphaPow) self.renderer.set_occ_strength(self.transform.occ_strength) self.renderer.set_occ_radius(self.transform.occ_radius) self.renderer.set_occ_n_points(self.transform.occ_n_points) if self.transform.isIso: renderMethod = "iso_surface" else: renderMethod = "max_project" self.renderer.render( method=renderMethod, return_alpha=True, numParts=self.NSubrenderSteps, currentPart=(self.renderedSteps * _next_golden(self.NSubrenderSteps)) % self.NSubrenderSteps) self.output, self.output_alpha = self.renderer.output, self.renderer.output_alpha if self.transform.isSlice: if self.transform.sliceDim == 0: out = self.dataModel[ self.transform.dataPos][:, :, self.transform.slicePos] elif self.transform.sliceDim == 1: out = self.dataModel[ self.transform.dataPos][:, self.transform.slicePos, :] elif self.transform.sliceDim == 2: out = self.dataModel[self.transform.dataPos][ self.transform.slicePos, :, :] min_out, max_out = np.amin(out), np.amax(out) if max_out > min_out: self.sliceOutput = (1. * (out - min_out) / (max_out - min_out)) else: self.sliceOutput = np.zeros_like(out) # def getFrame(self): # self.render() # self.paintGL() # glFlush() # im = self.grabFrameBuffer() # im = im.convertToFormat(QtGui.QImage.Format_RGB32) # # width = im.width() # height = im.height() # # ptr = im.bits() # ptr.setsize(im.byteCount()) # arr = np.array(ptr).reshape(height, width, 4) # Copies the data # return arr[..., [2, 1, 0, 3]].copy() def saveFrame(self, fName, with_alpha=False): """FIXME: scaling behaviour still hast to be implemented (e.g. after setGamma)""" logger.info("saving frame as %s", fName) # has to be png name, ext = os.path.splitext(fName) if ext != ".png": fName = name + ".png" self.render() self.paintGL() glFlush() im = self.grabFrameBuffer(withAlpha=with_alpha) im.save(fName) def onRenderTimer(self): # if self.renderUpdate: # self.render() # self.renderUpdate = False # self.updateGL() if self.renderedSteps < self.NSubrenderSteps: # print ((self.renderedSteps*7)%self.NSubrenderSteps) s = time.time() self.render() logger.debug("time to render: %.2f" % (1000. * (time.time() - s))) self.renderedSteps += 1 self.updateGL() def wheelEvent(self, event): """ self.transform.zoom should be within [1,2]""" newZoom = self.transform.zoom * 1.2**(event.angleDelta().y() / 1000.) newZoom = np.clip(newZoom, .4, 3) self.transform.setZoom(newZoom) logger.debug("newZoom: %s", newZoom) # self.refresh() def posToVec3(self, x, y, r0=.8, isRot=True): x, y = 2. * x / self.width() - 1., 1. - 2. * y / self.width() r = np.sqrt(x * x + y * y) if r > r0 - 1.e-7: x, y = 1. * x * r0 / r, 1. * y * r0 / r z = np.sqrt(max(0, r0**2 - x * x - y * y)) if isRot: M = np.linalg.inv(self.transform.quatRot.toRotation3()) x, y, z = np.dot(M, [x, y, z]) return x, y, z def posToVec2(self, x, y): x, y = 2. * x / self.width() - 1., 1. - 2. * y / self.width() return x, y def mousePressEvent(self, event): super(GLWidget, self).mousePressEvent(event) if event.buttons() == QtCore.Qt.LeftButton: self._x0, self._y0, self._z0 = self.posToVec3(event.x(), event.y()) if event.buttons() == QtCore.Qt.RightButton: (self._x0, self._y0), self._invRotM = self.posToVec2( event.x(), event.y()), linalg.inv(self.transform.quatRot.toRotation3()) # self.setCursor(QtCore.Qt.ClosedHandCursor) def mouseReleaseEvent(self, event): super(GLWidget, self).mouseReleaseEvent(event) # self.setCursor(QtCore.Qt.ArrowCursor) def mouseMoveEvent(self, event): # c = append(self.cubeCoords,ones(24)[:,newaxis],axis=1) # cUser = dot(c,self.finalMat) # cUser = cUser[:,:3]/cUser[:,-1,newaxis] # print self.finalMat # print c[0], cUser[0] # Rotation if event.buttons() == QtCore.Qt.LeftButton: x1, y1, z1 = self.posToVec3(event.x(), event.y()) logger.debug("mouse position: %s %s %s " % (x1, y1, z1)) n = np.cross(np.array([self._x0, self._y0, self._z0]), np.array([x1, y1, z1])) nnorm = linalg.norm(n) if np.abs(nnorm) >= 1.: nnorm *= 1. / np.abs(nnorm) w = np.arcsin(nnorm) n *= 1. / (nnorm + 1.e-10) q = Quaternion(np.cos(.5 * w), *(np.sin(.5 * w) * n)) self.transform.setQuaternion(self.transform.quatRot * q) # Translation if event.buttons() == QtCore.Qt.RightButton: x, y = self.posToVec2(event.x(), event.y()) dx, dy, foo = np.dot(self._invRotM, [x - self._x0, y - self._y0, 0]) self.transform.addTranslate(dx, dy, foo) self._x0, self._y0 = x, y self.refresh() def resizeEvent(self, event): # enforce each dimension to be divisable by 4 (and so the saved frames) super(GLWidget, self).resizeEvent(event) size = event.size() w, h = size.width(), size.height() if not ((w % 4 == 0) and (h % 4 == 0)): self.resize(QtCore.QSize((w // 4) * 4, (h // 4) * 4)) def _enforce_resize(self): """ this is to enforce the resizeGL event """ self.resize(self.width() + 1, self.height()) self.resize(self.width() - 1, self.height()) def onScreenNumberChange(self, evt): self._enforce_resize() def _get_screen_number(self): return QtGui.QGuiApplication.instance().desktop().screenNumber( QtGui.QCursor.pos()) def moveEvent(self, evt): current_screen = self._get_screen_number() if hasattr( self, "_current_screen") and self._current_screen != current_screen: self.onScreenNumberChange(evt) self._current_screen = current_screen
class GLWidget(QtOpenGL.QGLWidget): _dataModelChanged = QtCore.pyqtSignal() _BACKGROUND_BLACK = (0., 0., 0., 0.) _BACKGROUND_WHITE = (1., 1., 1., 0.) def __init__(self, parent=None, N_PREFETCH=0, interpolation="linear", **kwargs): logger.debug("init") # # fmt = QtOpenGL.QGLFormat(QtOpenGL.QGL.AlphaChannel) # # super(GLWidget, self).__init__(fmt,parent, **kwargs) super(GLWidget, self).__init__(parent, **kwargs) self.parent = parent self.texture_LUT = None self.setAcceptDrops(True) self.renderer = VolumeRenderer((spimagine.config.__DEFAULT_TEXTURE_WIDTH__, spimagine.config.__DEFAULT_TEXTURE_WIDTH__), interpolation=interpolation) self.renderer.set_projection(mat4_perspective(60, 1., .1, 100)) # self.renderer.set_projection(projMatOrtho(-2,2,-2,2,-10,10)) self.output = np.zeros([self.renderer.height, self.renderer.width], dtype=np.float32) self.output_alpha = np.zeros([self.renderer.height, self.renderer.width], dtype=np.float32) self.sliceOutput = np.zeros((100, 100), dtype=np.float32) self.setTransform(TransformModel()) self.renderTimer = QtCore.QTimer(self) self.renderTimer.setInterval(10) self.renderTimer.timeout.connect(self.onRenderTimer) self.renderTimer.start() self.renderedSteps = 0 self.N_PREFETCH = N_PREFETCH self.NSubrenderSteps = 1 self.dataModel = None self.meshes = [] # self.setMouseTracking(True) self._dataModelChanged.connect(self.dataModelChanged) self.refresh() # self.installEventFilter(self) def set_background_mode_black(self, mode_back=True): self._background_mode_black = mode_back self.refresh() def setModel(self, dataModel): logger.debug("setModel to %s" % dataModel) if self.dataModel is None or (self.dataModel != dataModel): self.dataModel = dataModel self.transform.setModel(dataModel) self.dataModel._dataSourceChanged.connect(self.dataSourceChanged) self.dataModel._dataPosChanged.connect(self.dataPosChanged) self._dataModelChanged.emit() def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, event): def _url_to_path(url): path = url.toLocalFile() if spimagine.config.__SYSTEM_DARWIN__: path = spimagine.config._parseFileNameFix(path) return path self.setCursor(QtCore.Qt.BusyCursor) urls = event.mimeData().urls() if len(urls) == 0: return elif len(urls) == 1: path = _url_to_path(urls[0]) elif len(urls) > 1: path = tuple(_url_to_path(url) for url in urls) try: if self.dataModel: self.dataModel.loadFromPath(path, prefetchSize=self.N_PREFETCH) else: self.setModel(DataModel.fromPath(path, prefetchSize=self.N_PREFETCH)) self.setCursor(QtCore.Qt.ArrowCursor) except Exception as e: QtWidgets.QMessageBox.warning(self, "", "Error loading Data:\n %s" % str(e)) def set_colormap(self, name): """name should be either jet, hot, gray, coolwarm""" try: arr = spimagine.config.__COLORMAPDICT__[name] self._set_colormap_array(arr) except KeyError: print("could not load colormap '%s'" % name) print("valid names: %s" % list(spimagine.config.__COLORMAPDICT__.keys())) def set_colormap_rgb(self, color=[1., 1., 1.]): self._set_colormap_array(np.outer(np.linspace(0, 1., 255), np.array(color))) def _set_colormap_array(self, arr): """arr should be of shape (N,3) and gives the rgb components of the colormap""" if not arr.ndim == 2 and arr.shape[-1] == 3: raise ValueError("wrong shape of color array: should be (N,3) but is %s") self.makeCurrent() self.texture_LUT = fillTexture2d(arr.reshape((1,) + arr.shape), self.texture_LUT) self.refresh() def _shader_from_file(self, fname_vert, fname_frag): shader = QOpenGLShaderProgram() shader.addShaderFromSourceFile(QOpenGLShader.Vertex, fname_vert) shader.addShaderFromSourceFile(QOpenGLShader.Fragment, fname_frag) shader.link() shader.bind() logger.debug("GLSL program log:%s", shader.log()) return shader def initializeGL(self): self.resized = True logger.debug("initializeGL") self.programTex = self._shader_from_file(absPath("shaders/texture.vert"), absPath("shaders/texture.frag")) self.programCube = self._shader_from_file(absPath("shaders/box.vert"), absPath("shaders/box.frag")) self.programSlice = self._shader_from_file(absPath("shaders/slice.vert"), absPath("shaders/slice.frag")) self.programMesh = self._shader_from_file(absPath("shaders/mesh.vert"), absPath("shaders/mesh.frag")) self.programMeshLight = self._shader_from_file( absPath("shaders/mesh_light.vert"), absPath("shaders/mesh_light.frag")) self.texture = None self.textureAlpha = None self.textureSlice = None self.quadCoord = np.array([[-1., -1., 0.], [1., -1., 0.], [1., 1., 0.], [1., 1., 0.], [-1., 1., 0.], [-1., -1., 0.]]) self.quadCoordTex = np.array([[0, 0], [1., 0.], [1., 1.], [1., 1.], [0, 1.], [0, 0]]) # self.cubeCoords = create_cube_coords([-1,1,-1,1,-1,1]) self.set_colormap(spimagine.config.__DEFAULTCOLORMAP__) glEnable(GL_BLEND) # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # self.set_interpolation(spimagine.config.__DEFAULT_INTERP__ != "nearest") # glLineWidth(1.0); glBlendFunc(GL_ONE, GL_ONE) glEnable(GL_LINE_SMOOTH); glDisable(GL_DEPTH_TEST) glLineWidth(spimagine.config.__DEFAULT_BOX_LINEWIDTH__) # self.set_background_color(0,0,0,.0) self.set_background_mode_black(True) self.clear_canvas() # self.set_background_color(1,1,1,.6) def clear_canvas(self): if self._background_mode_black: glClearColor(*self._BACKGROUND_BLACK) else: glClearColor(*self._BACKGROUND_WHITE) if glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def set_interpolation(self, interpolate=True): interp = "linear" if interpolate else "nearest" self.renderer.rebuild_program(interpolation=interp) self.refresh() def setTransform(self, transform): self.transform = transform self.transform._transformChanged.connect(self.refresh) self.transform._stackUnitsChanged.connect(self.setStackUnits) self.transform._boundsChanged.connect(self.setBounds) def dataModelChanged(self): logger.debug("+++++++++ data model changed") if self.dataModel: logger.debug("dataModelchanged") self.renderer.set_data(self.dataModel[0], autoConvert=True) mi, ma = self._get_min_max() self.transform.reset(minVal=mi, maxVal=ma, stackUnits=self.dataModel.stackUnits()) self.meshes = [] self.refresh() def _get_min_max(self): # as amax is too slow for bug arrays, do it on the gpu if self.dataModel: try: im = self.renderer.dataImg tmp_buf = OCLArray.empty(im.shape, im.dtype) tmp_buf.copy_image(im) mi = float(cl_array.min(tmp_buf).get()) ma = float(cl_array.max(tmp_buf).get()) except Exception as e: print(e) mi = np.amin(self.dataModel[0]) ma = np.amax(self.dataModel[0]) return mi, ma def set_background_color(self, r, g, b, a=1.): self._background_color = (r, g, b, a) glClearColor(r, g, b, a) def dataSourceChanged(self): logger.debug("dataSourcechanged") self.renderer.set_data(self.dataModel[0], autoConvert=True) mi, ma = self._get_min_max() self.transform.reset(minVal=mi, maxVal=ma, stackUnits=self.dataModel.stackUnits()) self.refresh() def setBounds(self, x1, x2, y1, y2, z1, z2): self.cubeCoords = create_cube_coords([x1, x2, y1, y2, z1, z2]) self.renderer.set_box_boundaries([x1, x2, y1, y2, z1, z2]) def setStackUnits(self, px, py, pz): logger.debug("setStackUnits to %s" % [px, py, pz]) self.renderer.set_units([px, py, pz]) def dataPosChanged(self, pos): self.renderer.update_data(self.dataModel[pos]) self.refresh() def refresh(self): # if self.parentWidget() and self.dataModel: # self.parentWidget().setWindowTitle("SpImagine %s"%self.dataModel.name()) self.renderUpdate = True self.renderedSteps = 0 def resizeGL(self, width, height): # somehow in qt5 the OpenGLWidget width/height parameters above are double the value of self.width/height self._viewport_width, self._viewport_height = width, height def add_mesh(self, mesh=SphericalMesh()): """ adds a mesh with vertices and facecolor/edgecolor to be drawn mesh is an instance of spimagine.gui.Mesh, e.g. mesh = Mesh(vertices = [[0,1,0],[0,1,0],...], normals = [[0,1,0],[0,1,0],...], facecolor = (1.,.4,.4,.2), edgecolor = None,...) there are some predefined meshes like SphericalMesh, EllipsoidMesh ... """ self.meshes.append([mesh, glvbo.VBO(mesh.vertices.astype(np.float32, copy=False)), glvbo.VBO(np.array(mesh.normals).astype(np.float32, copy=False)), glvbo.VBO(np.array(mesh.indices).astype(np.uint32, copy=False), target=GL_ELEMENT_ARRAY_BUFFER)]) self.refresh() # sort according to opacity as the opaque objects should be drawn first # self.meshes.sort(key=lambda x: x[0].alpha, reverse=True) def _paintGL_render(self): # Draw the render texture self.programTex.bind() self.texture = fillTexture2d(self.output, self.texture) # self.textureAlpha = fillTexture2d(self.output_alpha, self.textureAlpha) glEnable(GL_BLEND) glEnable(GL_TEXTURE_2D) glDisable(GL_DEPTH_TEST) self.programTex.enableAttributeArray("position") self.programTex.enableAttributeArray("texcoord") self.programTex.setAttributeArray("position", self.quadCoord) self.programTex.setAttributeArray("texcoord", self.quadCoordTex) self.programTex.setUniformValue("is_mode_black", self._background_mode_black) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.texture) self.programTex.setUniformValue("texture", 0) glActiveTexture(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, self.textureAlpha) self.programTex.setUniformValue("texture_alpha", 1) glActiveTexture(GL_TEXTURE2) glBindTexture(GL_TEXTURE_2D, self.texture_LUT) self.programTex.setUniformValue("texture_LUT", 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDrawArrays(GL_TRIANGLES, 0, len(self.quadCoord)) def _paintGL_slice(self): # draw the slice self.programSlice.bind() self.programSlice.setUniformValue("mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) self.programSlice.setUniformValue("is_mode_black", self._background_mode_black) self.programSlice.enableAttributeArray("position") pos, dim = self.transform.slicePos, self.transform.sliceDim coords = slice_coords(1. * pos / self.dataModel.size()[2 - dim + 1], dim) texcoords = [[0., 0.], [1, 0.], [1., 1.], [1., 1.], [0., 1.], [0., 0.]] self.programSlice.setAttributeArray("position", coords) self.programSlice.setAttributeArray("texcoord", texcoords) self.textureSlice = fillTexture2d(self.sliceOutput, self.textureSlice) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.textureSlice) self.programSlice.setUniformValue("texture", 0) glActiveTexture(GL_TEXTURE1) glBindTexture(GL_TEXTURE_2D, self.texture_LUT) self.programSlice.setUniformValue("texture_LUT", 1) glDrawArrays(GL_TRIANGLES, 0, len(coords)) def _paintGL_box(self): glEnable(GL_BLEND) # Draw the cube self.programCube.bind() self.programCube.setUniformValue("mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) self.programCube.enableAttributeArray("position") if self._background_mode_black: self.programCube.setUniformValue("color", QtGui.QVector4D(1, 1, 1, 0.6)) else: self.programCube.setUniformValue("color", QtGui.QVector4D(0, 0, 0, 0.6)) self.programCube.setAttributeArray("position", self.cubeCoords) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.textureAlpha) self.programCube.setUniformValue("texture_alpha", 0) glEnable(GL_DEPTH_TEST) # glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDrawArrays(GL_LINES, 0, len(self.cubeCoords)) glDisable(GL_DEPTH_TEST) def _paintGL_mesh(self, mesh, vbo_vertices, vbo_normals, vbo_indices): """ paint a mesh (which has all the coordinates and colors in it """ glEnable(GL_DEPTH_TEST) glDisable(GL_BLEND) prog = self.programMeshLight prog.bind() prog.setUniformValue("mvpMatrix", QtGui.QMatrix4x4(*self._mat_modelviewproject.flatten())) prog.setUniformValue("mvMatrix", QtGui.QMatrix4x4(*self._mat_modelview.flatten())) prog.setUniformValue("normMatrix", QtGui.QMatrix4x4(*self._mat_normal.flatten())) if mesh.light: prog.setUniformValue("light", QtGui.QVector3D(*mesh.light)) prog.setUniformValue("light_components", QtGui.QVector3D(.2, .5, .3)) else: prog.setUniformValue("light", QtGui.QVector3D(0, 0, 0)) prog.setUniformValue("light_components", QtGui.QVector3D(1., 0, 0)) if not mesh.facecolor is None: r, g, b = mesh.facecolor[:3] a = mesh.alpha prog.setUniformValue("color", QtGui.QVector4D(r, g, b, a)) prog.enableAttributeArray("position") vbo_vertices.bind() glVertexAttribPointer(prog.attributeLocation("position"), 3, GL_FLOAT, GL_FALSE, 0, vbo_vertices) prog.enableAttributeArray("normal") vbo_normals.bind() glVertexAttribPointer(prog.attributeLocation("normal"), 3, GL_FLOAT, GL_FALSE, 0, vbo_normals) vbo_indices.bind() glDrawElements(GL_TRIANGLES, len(vbo_indices.data), GL_UNSIGNED_INT, None) vbo_indices.unbind() vbo_vertices.unbind() glDisable(GL_DEPTH_TEST) prog.disableAttributeArray("position") prog.disableAttributeArray("normal") # # if not mesh.edgecolor is None: # r, g, b = mesh.edgecolor # a = mesh.alpha # # prog.enableAttributeArray("position") # vbo_vertices.bind() # glVertexAttribPointer(prog.attributeLocation("position"), 2, GL_FLOAT, GL_FALSE, 0, vbo_edges) # # prog.setUniformValue("color", # QtGui.QVector4D(r, g, b, a)) # # glDrawArrays(GL_LINES, 0, len(mesh.edges)) def paintGL(self): self.makeCurrent() if not glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE: return w = max(self._viewport_width, self._viewport_height) # force viewport to always be a square glViewport((self._viewport_width - w) // 2, (self._viewport_height - w) // 2, w, w) self.clear_canvas() self._mat_modelview = self.transform.getModelView() self._mat_proj = self.transform.getProjection() self._mat_normal = np.linalg.inv(self._mat_modelview).T self._mat_modelviewproject = np.dot(self._mat_proj, self._mat_modelview) if self.dataModel: self.textureAlpha = fillTexture2d(self.output_alpha, self.textureAlpha) if self.transform.isBox: self._paintGL_box() if self.transform.isSlice and self.sliceOutput is not None: self._paintGL_slice() self._paintGL_render() for (m, vbo_verts, vbo_normals, vbo_indices) in self.meshes: self._paintGL_mesh(m, vbo_verts, vbo_normals, vbo_indices) def render(self): logger.debug("render") if self.dataModel: self.renderer.set_modelView(self.transform.getUnscaledModelView()) self.renderer.set_projection(self.transform.getProjection()) self.renderer.set_min_val(self.transform.minVal) self.renderer.set_max_val(self.transform.maxVal) self.renderer.set_gamma(self.transform.gamma) self.renderer.set_alpha_pow(self.transform.alphaPow) self.renderer.set_occ_strength(self.transform.occ_strength) self.renderer.set_occ_radius(self.transform.occ_radius) self.renderer.set_occ_n_points(self.transform.occ_n_points) if self.transform.isIso: renderMethod = "iso_surface" else: renderMethod = "max_project" self.renderer.render(method=renderMethod, return_alpha=True, numParts=self.NSubrenderSteps, currentPart=( self.renderedSteps * _next_golden( self.NSubrenderSteps)) % self.NSubrenderSteps) self.output, self.output_alpha = self.renderer.output, self.renderer.output_alpha if self.transform.isSlice: if self.transform.sliceDim == 0: out = self.dataModel[self.transform.dataPos][:, :, self.transform.slicePos] elif self.transform.sliceDim == 1: out = self.dataModel[self.transform.dataPos][:, self.transform.slicePos, :] elif self.transform.sliceDim == 2: out = self.dataModel[self.transform.dataPos][self.transform.slicePos, :, :] min_out, max_out = np.amin(out), np.amax(out) if max_out > min_out: self.sliceOutput = (1. * (out - min_out) / (max_out - min_out)) else: self.sliceOutput = np.zeros_like(out) # def getFrame(self): # self.render() # self.paintGL() # glFlush() # im = self.grabFrameBuffer() # im = im.convertToFormat(QtGui.QImage.Format_RGB32) # # width = im.width() # height = im.height() # # ptr = im.bits() # ptr.setsize(im.byteCount()) # arr = np.array(ptr).reshape(height, width, 4) # Copies the data # return arr[..., [2, 1, 0, 3]].copy() def saveFrame(self, fName, with_alpha=False): """FIXME: scaling behaviour still hast to be implemented (e.g. after setGamma)""" logger.info("saving frame as %s", fName) # has to be png name, ext = os.path.splitext(fName) if ext != ".png": fName = name + ".png" self.render() self.paintGL() glFlush() im = self.grabFrameBuffer(withAlpha=with_alpha) im.save(fName) def onRenderTimer(self): # if self.renderUpdate: # self.render() # self.renderUpdate = False # self.updateGL() if self.renderedSteps < self.NSubrenderSteps: # print ((self.renderedSteps*7)%self.NSubrenderSteps) s = time.time() self.render() logger.debug("time to render: %.2f" % (1000. * (time.time() - s))) self.renderedSteps += 1 self.updateGL() def wheelEvent(self, event): """ self.transform.zoom should be within [1,2]""" newZoom = self.transform.zoom * 1.2 ** (event.angleDelta().y() / 1000.) newZoom = np.clip(newZoom, .4, 3) self.transform.setZoom(newZoom) logger.debug("newZoom: %s", newZoom) # self.refresh() def posToVec3(self, x, y, r0=.8, isRot=True): x, y = 2. * x / self.width() - 1., 1. - 2. * y / self.width() r = np.sqrt(x * x + y * y) if r > r0 - 1.e-7: x, y = 1. * x * r0 / r, 1. * y * r0 / r z = np.sqrt(max(0, r0 ** 2 - x * x - y * y)) if isRot: M = np.linalg.inv(self.transform.quatRot.toRotation3()) x, y, z = np.dot(M, [x, y, z]) return x, y, z def posToVec2(self, x, y): x, y = 2. * x / self.width() - 1., 1. - 2. * y / self.width() return x, y def mousePressEvent(self, event): super(GLWidget, self).mousePressEvent(event) if event.buttons() == QtCore.Qt.LeftButton: self._x0, self._y0, self._z0 = self.posToVec3(event.x(), event.y()) if event.buttons() == QtCore.Qt.RightButton: (self._x0, self._y0), self._invRotM = self.posToVec2(event.x(), event.y()), linalg.inv( self.transform.quatRot.toRotation3()) # self.setCursor(QtCore.Qt.ClosedHandCursor) def mouseReleaseEvent(self, event): super(GLWidget, self).mouseReleaseEvent(event) # self.setCursor(QtCore.Qt.ArrowCursor) def mouseMoveEvent(self, event): # c = append(self.cubeCoords,ones(24)[:,newaxis],axis=1) # cUser = dot(c,self.finalMat) # cUser = cUser[:,:3]/cUser[:,-1,newaxis] # print self.finalMat # print c[0], cUser[0] # Rotation if event.buttons() == QtCore.Qt.LeftButton: x1, y1, z1 = self.posToVec3(event.x(), event.y()) logger.debug("mouse position: %s %s %s " % (x1, y1, z1)) n = np.cross(np.array([self._x0, self._y0, self._z0]), np.array([x1, y1, z1])) nnorm = linalg.norm(n) if np.abs(nnorm) >= 1.: nnorm *= 1. / np.abs(nnorm) w = np.arcsin(nnorm) n *= 1. / (nnorm + 1.e-10) q = Quaternion(np.cos(.5 * w), *(np.sin(.5 * w) * n)) self.transform.setQuaternion(self.transform.quatRot * q) # Translation if event.buttons() == QtCore.Qt.RightButton: x, y = self.posToVec2(event.x(), event.y()) dx, dy, foo = np.dot(self._invRotM, [x - self._x0, y - self._y0, 0]) self.transform.addTranslate(dx, dy, foo) self._x0, self._y0 = x, y self.refresh() def resizeEvent(self, event): # enforce each dimension to be divisable by 4 (and so the saved frames) super(GLWidget, self).resizeEvent(event) size = event.size() w, h = size.width(), size.height() if not ((w % 4 == 0) and (h % 4 == 0)): self.resize(QtCore.QSize((w // 4) * 4, (h // 4) * 4)) def _enforce_resize(self): """ this is to enforce the resizeGL event """ self.resize(self.width() + 1, self.height()) self.resize(self.width() - 1, self.height()) def onScreenNumberChange(self, evt): self._enforce_resize() def _get_screen_number(self): return QtGui.QGuiApplication.instance().desktop().screenNumber(QtGui.QCursor.pos()) def moveEvent(self, evt): current_screen = self._get_screen_number() if hasattr(self, "_current_screen") and self._current_screen != current_screen: self.onScreenNumberChange(evt) self._current_screen = current_screen