def __init__(self, parent, imagescene2d): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance """ QGraphicsView.__init__(self, parent) self.setScene(imagescene2d) self.mousePos = QPointF(0,0) # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging... self.x, self.y = (0,0) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._isRubberBandZoom = False self._cursorBackup = None #these attributes are exposed as public properties above self._sliceShape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._hud = None self._crossHairCursor = None self._sliceIntersectionMarker = None self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) # # Setup the Viewport for fast painting # #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene()) self._sliceIntersectionMarker.setZValue(100) self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0,0) #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor)
class ImageView2D(QGraphicsView): focusChanged = pyqtSignal() """ Shows a ImageScene2D to the user and allows for interactive scrolling, panning, zooming etc. """ @property def sliceShape(self): """ (width, height) of the scene. Specifying the shape is necessary to allow for correct scrollbars """ return self._sliceShape @sliceShape.setter def sliceShape(self, s): self._sliceShape = s self.scene().dataShape = s self._crossHairCursor.dataShape = s self._sliceIntersectionMarker.dataShape = s @property def hud(self): return self._hud @hud.setter def hud(self, hud): """ Sets up a heads up display at the upper left corner of the view hud -- a QWidget """ self._hud = hud self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0,0,0,0) self.layout().addWidget(self._hud) self.layout().addStretch() scene = self.scene() hud.zoomToFitButtonClicked.connect(self.fitImage) hud.resetZoomButtonClicked.connect(self.doScaleTo) hud.rotLeftButtonClicked.connect(scene._onRotateLeft) hud.rotRightButtonClicked.connect(scene._onRotateRight) hud.swapAxesButtonClicked.connect(scene._onSwapAxes) scene.axesChanged.connect(hud.setAxes) def __init__(self, parent, imagescene2d): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance """ QGraphicsView.__init__(self, parent) self.setScene(imagescene2d) self.mousePos = QPointF(0,0) # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging... self.x, self.y = (0,0) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._isRubberBandZoom = False self._cursorBackup = None #these attributes are exposed as public properties above self._sliceShape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._hud = None self._crossHairCursor = None self._sliceIntersectionMarker = None self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) # # Setup the Viewport for fast painting # #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene()) self._sliceIntersectionMarker.setZValue(100) self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0,0) #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor) # For screen recording BlankCursor doesn't work #self.hiddenCursor = QCursor(Qt.ArrowCursor) def _cleanUp(self): self._ticker.stop() del self._ticker def setZoomFactor(self,zoom): if self._hud is not None: self._hud.zoomLevelIndicator.updateLevel(zoom) self._zoomFactor = zoom def indicateSlicingPositionSettled(self, settled): self.scene().indicateSlicingPositionSettled(settled) def viewportRect(self): """ Return a QRectF giving the part of the scene currently displayed in this widget's viewport in the scene's coordinates """ r = self.mapToScene(self.viewport().geometry()).boundingRect() return r def mapScene2Data(self, pos): return self.scene().scene2data.map(pos) def mapMouseCoordinates2Data(self, pos): return self.mapScene2Data(self.mapToScene(pos)) def _panning(self): hBar = self.horizontalScrollBar() vBar = self.verticalScrollBar() vBar.setValue(vBar.value() - self._deltaPan.y()) if self.isRightToLeft(): hBar.setValue(hBar.value() + self._deltaPan.x()) else: hBar.setValue(hBar.value() - self._deltaPan.x()) def _deaccelerate(self, speed, a=1, maxVal=64): x = self._qBound(-maxVal, speed.x(), maxVal) y = self._qBound(-maxVal, speed.y(), maxVal) ax ,ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a) if x > 0: x = max(0.0, x - a*ax) elif x < 0: x = min(0.0, x + a*ax) if y > 0: y = max(0.0, y - a*ay) elif y < 0: y = min(0.0, y + a*ay) return QPointF(x, y) def _qBound(self, minVal, current, maxVal): """PyQt4 does not wrap the qBound function from Qt's global namespace This is equivalent.""" return max(min(current, maxVal), minVal) def _setdeaccelerateAxAy(self, x, y, a): x = abs(x) y = abs(y) if x > y: if y > 0: ax = int(x / y) if ax != 0: return ax, 1 else: return x/a, 1 if y > x: if x > 0: ay = int(y/x) if ay != 0: return 1, ay else: return 1, y/a return 1, 1 def _tickerEvent(self): if self._deltaPan.x() == 0.0 and self._deltaPan.y() == 0.0 or self._dragMode == True: self._ticker.stop() else: self._deltaPan = self._deaccelerate(self._deltaPan) self._panning() def zoomOut(self): self.doScale(0.9) def zoomIn(self): self.doScale(1.1) def fitImage(self): self.fitInView(self.sceneRect(), Qt.KeepAspectRatio) width, height = self.size().width() / self.sceneRect().width(), self.height() / self.sceneRect().height() self.setZoomFactor(min(width, height)) def centerImage(self): self.centerOn(self.sceneRect().width()/2 + self.sceneRect().x(), self.sceneRect().height()/2 + self.sceneRect().y()) def toggleHud(self): if self._hud is not None: self._hud.setVisible(not self._hud.isVisible()) def setHudVisible(self, visible): if self._hud is not None: self._hud.setVisible(visible) def hudVisible(self): return self._hud.isVisible() def focusInEvent(self, event): self.setStyleSheet(".QFrame {border: 2px solid white; border-radius: 4px;}") self.focusChanged.emit() def focusOutEvent(self, event): self.setStyleSheet(".QFrame {}") def changeViewPort(self,qRectf): self.fitInView(qRectf,mode = Qt.KeepAspectRatio) width, height = self.size().width() / qRectf.width(), self.height() / qRectf.height() self.setZoomFactor(min(width, height)) def doScale(self, factor): self.setZoomFactor(self._zoomFactor * factor) self.scale(factor, factor) def doScaleTo(self, zoom=1): factor = ( 1 / self._zoomFactor ) * zoom self.setZoomFactor(zoom) self.scale(factor, factor)
def __init__(self, parent, imagescene2d): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance """ QGraphicsView.__init__(self, parent) self.setScene(imagescene2d) self.mousePos = QPointF(0, 0) # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging... self.x, self.y = (0, 0) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._isRubberBandZoom = False self._cursorBackup = None #these attributes are exposed as public properties above self._sliceShape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._hud = None self._crossHairCursor = None self._sliceIntersectionMarker = None self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) # # Setup the Viewport for fast painting # #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene()) self._sliceIntersectionMarker.setZValue(100) self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0, 0) #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor)
class ImageView2D(QGraphicsView): focusChanged = pyqtSignal() """ Shows a ImageScene2D to the user and allows for interactive scrolling, panning, zooming etc. """ @property def sliceShape(self): """ (width, height) of the scene. Specifying the shape is necessary to allow for correct scrollbars """ return self._sliceShape @sliceShape.setter def sliceShape(self, s): self._sliceShape = s self.scene().dataShape = s self._crossHairCursor.dataShape = s self._sliceIntersectionMarker.dataShape = s @property def hud(self): return self._hud @hud.setter def hud(self, hud): """ Sets up a heads up display at the upper left corner of the view hud -- a QWidget """ self._hud = hud self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self._hud) self.layout().addStretch() scene = self.scene() hud.zoomToFitButtonClicked.connect(self.fitImage) hud.resetZoomButtonClicked.connect(self.doScaleTo) hud.rotLeftButtonClicked.connect(scene._onRotateLeft) hud.rotRightButtonClicked.connect(scene._onRotateRight) hud.swapAxesButtonClicked.connect(scene._onSwapAxes) scene.axesChanged.connect(hud.setAxes) def __init__(self, parent, imagescene2d): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance """ QGraphicsView.__init__(self, parent) self.setScene(imagescene2d) self.mousePos = QPointF(0, 0) # FIXME: These int members shadow QWidget.x() and QWidget.y(), which can lead to confusion when debugging... self.x, self.y = (0, 0) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._isRubberBandZoom = False self._cursorBackup = None #these attributes are exposed as public properties above self._sliceShape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._hud = None self._crossHairCursor = None self._sliceIntersectionMarker = None self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) # # Setup the Viewport for fast painting # #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker(self.scene()) self._sliceIntersectionMarker.setZValue(100) self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0, 0) #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor) # For screen recording BlankCursor doesn't work #self.hiddenCursor = QCursor(Qt.ArrowCursor) def _cleanUp(self): self._ticker.stop() del self._ticker def setZoomFactor(self, zoom): if self._hud is not None: self._hud.zoomLevelIndicator.updateLevel(zoom) self._zoomFactor = zoom def indicateSlicingPositionSettled(self, settled): self.scene().indicateSlicingPositionSettled(settled) def viewportRect(self): """ Return a QRectF giving the part of the scene currently displayed in this widget's viewport in the scene's coordinates """ r = self.mapToScene(self.viewport().geometry()).boundingRect() return r def mapScene2Data(self, pos): return self.scene().scene2data.map(pos) def mapMouseCoordinates2Data(self, pos): return self.mapScene2Data(self.mapToScene(pos)) def _panning(self): hBar = self.horizontalScrollBar() vBar = self.verticalScrollBar() vBar.setValue(vBar.value() - self._deltaPan.y()) if self.isRightToLeft(): hBar.setValue(hBar.value() + self._deltaPan.x()) else: hBar.setValue(hBar.value() - self._deltaPan.x()) def _deaccelerate(self, speed, a=1, maxVal=64): x = self._qBound(-maxVal, speed.x(), maxVal) y = self._qBound(-maxVal, speed.y(), maxVal) ax, ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a) if x > 0: x = max(0.0, x - a * ax) elif x < 0: x = min(0.0, x + a * ax) if y > 0: y = max(0.0, y - a * ay) elif y < 0: y = min(0.0, y + a * ay) return QPointF(x, y) def _qBound(self, minVal, current, maxVal): """PyQt4 does not wrap the qBound function from Qt's global namespace This is equivalent.""" return max(min(current, maxVal), minVal) def _setdeaccelerateAxAy(self, x, y, a): x = abs(x) y = abs(y) if x > y: if y > 0: ax = int(x / y) if ax != 0: return ax, 1 else: return x / a, 1 if y > x: if x > 0: ay = int(y / x) if ay != 0: return 1, ay else: return 1, y / a return 1, 1 def _tickerEvent(self): if self._deltaPan.x() == 0.0 and self._deltaPan.y( ) == 0.0 or self._dragMode == True: self._ticker.stop() else: self._deltaPan = self._deaccelerate(self._deltaPan) self._panning() def zoomOut(self): self.doScale(0.9) def zoomIn(self): self.doScale(1.1) def fitImage(self): self.fitInView(self.sceneRect(), Qt.KeepAspectRatio) width, height = self.size().width() / self.sceneRect().width( ), self.height() / self.sceneRect().height() self.setZoomFactor(min(width, height)) def centerImage(self): self.centerOn(self.sceneRect().width() / 2 + self.sceneRect().x(), self.sceneRect().height() / 2 + self.sceneRect().y()) def toggleHud(self): if self._hud is not None: self._hud.setVisible(not self._hud.isVisible()) def setHudVisible(self, visible): if self._hud is not None: self._hud.setVisible(visible) def hudVisible(self): return self._hud.isVisible() def focusInEvent(self, event): self.setStyleSheet( ".QFrame {border: 2px solid white; border-radius: 4px;}") self.focusChanged.emit() def focusOutEvent(self, event): self.setStyleSheet(".QFrame {}") def changeViewPort(self, qRectf): self.fitInView(qRectf, mode=Qt.KeepAspectRatio) width, height = self.size().width() / qRectf.width(), self.height( ) / qRectf.height() self.setZoomFactor(min(width, height)) def doScale(self, factor): self.setZoomFactor(self._zoomFactor * factor) self.scale(factor, factor) def doScaleTo(self, zoom=1): factor = (1 / self._zoomFactor) * zoom self.setZoomFactor(zoom) self.scale(factor, factor)
class ImageView2D(QGraphicsView): """ Shows a ImageScene2D to the user and allows for interactive scrolling, panning, zooming etc. It intercepts all meaningful events on the widget for further interpretation (e.g. the view does not know which slicing axis it represents in the 3D slice viewer setting). """ #notifies about the relative change in the slicing position #that is requested changeSliceDelta = pyqtSignal(int) drawing = pyqtSignal(QPointF) beginDraw = pyqtSignal(QPointF, object) endDraw = pyqtSignal(QPointF) erasingToggled = pyqtSignal(bool) #notifies that the mouse has moved to 2D coordinate x,y mouseMoved = pyqtSignal(int, int) #notifies that the user has double clicked on the 2D coordinate x,y mouseDoubleClicked = pyqtSignal(int, int) drawUpdateInterval = 300 #ms @property def shape(self): """ (width, height) of the scene. Specifying the shape is necessary to allow for correct scrollbars """ return self._shape @shape.setter def shape(self, s): self._shape = s self.scene().shape = s self._crossHairCursor.shape = (s[1], s[0]) self._sliceIntersectionMarker.shape = (s[1], s[0]) #FIXME unused? @property def name(self): return self._name @name.setter def name(self, n): self._name = n @property def hud(self): return self._hud @hud.setter def hud(self, hud): """ Sets up a heads up display at the upper left corner of the view hud -- a QWidget """ self._hud = hud self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0,0,0,0) self.layout().addWidget(self._hud) self.layout().addStretch() @property def drawingEnabled(self): return self._drawingEnabled @drawingEnabled.setter def drawingEnabled(self, enable): self._drawingEnabled = enable def __init__(self, imagescene2d, useGL=False): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance useGL -- wether to enable OpenGL rendering """ QGraphicsView.__init__(self) self._useGL = useGL self.setScene(imagescene2d) #these attributes are exposed as public properties above self._shape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._name = '' self._hud = None self._drawingEnabled = False self._crossHairCursor = None self._sliceIntersectionMarker = None # # Setup the Viewport for fast painting # if self._useGL: self.openglWidget = QGLWidget(self) self.setViewport(self.openglWidget) #we clear the background ourselves self.viewport().setAutoFillBackground(False) #QGraphicsView cannot use partial updates when using #an OpenGL widget as a viewport #http://doc.qt.nokia.com/qq/qq26-openglcanvas.html self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) else: #Unfortunately, setting these flags has no effect when #Cache background is turned on. #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) #Intitialize the scene if self._useGL: self.scene().activateOpenGL( self.openglWidget ) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker() self._sliceIntersectionMarker.setZValue(100) self.scene().addItem(self._sliceIntersectionMarker) #FIXME: Use a QAction here so that we do not have to synchronize #between this initial state and the toggle button's initial state self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._isDrawing = False self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0,0) #Unfortunately, setting the style like this make the scroll bars look #really crappy... #self.setStyleSheet("QWidget:!focus { border: 2px solid " + self._axisColor[self._axis].name() +"; border-radius: 4px; }\ # QWidget:focus { border: 2px solid white; border-radius: 4px; }") #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) #label updates while drawing, needed for interactive segmentation self._drawTimer = QTimer(self) self._drawTimer.timeout.connect(self.notifyDrawing) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor) # For screen recording BlankCursor doesn't work #self.hiddenCursor = QCursor(Qt.ArrowCursor) self._tempErase = False def swapAxes(self): """ Displays this image as if the x and y axes were swapped. """ #FIXME: This is needed for the current arrangements of the three # 3D slice views. Can this be made more elegant self.rotate(90.0) self.scale(1.0,-1.0) def _cleanUp(self): self._ticker.stop() self._drawTimer.stop() del self._drawTimer del self._ticker def viewportRect(self): """ Return a QRectF giving the part of the scene currently displayed in this widget's viewport in the scene's coordinates """ return self.mapToScene(self.viewport().geometry()).boundingRect() def saveSlice(self, filename): """Legacy code.""" #print "Saving in ", filename, "slice #", self.sliceNumber, "axis", self._axis result_image = QImage(self.scene().image.size(), self.scene().image.format()) p = QPainter(result_image) for patchNr in range(self.patchAccessor.patchCount): bounds = self.patchAccessor.getPatchBounds(patchNr) if self.openglWidget is None: p.drawImage(0, 0, self.scene().image) else: p.drawImage(bounds[0], bounds[2], self.imagePatches[patchNr]) p.end() #horrible way to transpose an image. but it works. transform = QTransform() transform.rotate(90) result_image = result_image.mirrored() result_image = result_image.transformed(transform) result_image.save(QString(filename)) def notifyDrawing(self): print "ImageView2D.notifyDrawing" #FIXME: resurrect self.drawing.emit(self.mousePos) def beginDrawing(self, pos): self.mousePos = pos self._isDrawing = True self.beginDraw.emit(pos, self.shape) def endDrawing(self, pos): InteractionLogger.log("%f: endDrawing()" % (time.clock())) self._drawTimer.stop() self._isDrawing = False self.endDraw.emit(pos) def wheelEvent(self, event): keys = QApplication.keyboardModifiers() k_alt = (keys == Qt.AltModifier) k_ctrl = (keys == Qt.ControlModifier) self.mousePos = self.mapToScene(event.pos()) grviewCenter = self.mapToScene(self.viewport().rect().center()) if event.delta() > 0: if k_alt: self.changeSlice(10) elif k_ctrl: scaleFactor = 1.1 self.doScale(scaleFactor) else: self.changeSlice(1) else: if k_alt: self.changeSlice(-10) elif k_ctrl: scaleFactor = 0.9 self.doScale(scaleFactor) else: self.changeSlice(-1) if k_ctrl: mousePosAfterScale = self.mapToScene(event.pos()) offset = self.mousePos - mousePosAfterScale newGrviewCenter = grviewCenter + offset self.centerOn(newGrviewCenter) self.mouseMoveEvent(event) def mousePressEvent(self, event): if event.button() == Qt.MidButton: self.setCursor(QCursor(Qt.SizeAllCursor)) self._lastPanPoint = event.pos() self._crossHairCursor.setVisible(False) self._dragMode = True if self._ticker.isActive(): self._deltaPan = QPointF(0, 0) if event.buttons() == Qt.RightButton: #make sure that we have the cursor at the correct position #before we call the context menu self.mouseMoveEvent(event) self.customContextMenuRequested.emit(event.pos()) return if not self.drawingEnabled: print "ImageView2D.mousePressEvent: drawing is not enabled" return if event.buttons() == Qt.LeftButton: #don't draw if flicker the view if self._ticker.isActive(): return if QApplication.keyboardModifiers() == Qt.ShiftModifier: self.erasingToggled.emit(True) self._tempErase = True mousePos = self.mapToScene(event.pos()) self.beginDrawing(mousePos) def mouseMoveEvent(self,event): if self._dragMode == True: #the mouse was moved because the user wants to change #the viewport self._deltaPan = QPointF(event.pos() - self._lastPanPoint) self._panning() self._lastPanPoint = event.pos() return if self._ticker.isActive(): #the view is still scrolling #do nothing until it comes to a complete stop return self.mousePos = mousePos = self.mapToScene(event.pos()) x = self.x = mousePos.x() y = self.y = mousePos.y() self.mouseMoved.emit(x,y) if self._isDrawing: self.drawing.emit(mousePos) def mouseReleaseEvent(self, event): if event.button() == Qt.MidButton: self.setCursor(QCursor()) releasePoint = event.pos() self._lastPanPoint = releasePoint self._dragMode = False self._ticker.start(20) if self._isDrawing: mousePos = self.mapToScene(event.pos()) self.endDrawing(mousePos) if self._tempErase: self.erasingToggled.emit(False) self._tempErase = False def _panning(self): hBar = self.horizontalScrollBar() vBar = self.verticalScrollBar() vBar.setValue(vBar.value() - self._deltaPan.y()) if self.isRightToLeft(): hBar.setValue(hBar.value() + self._deltaPan.x()) else: hBar.setValue(hBar.value() - self._deltaPan.x()) def _deaccelerate(self, speed, a=1, maxVal=64): x = self._qBound(-maxVal, speed.x(), maxVal) y = self._qBound(-maxVal, speed.y(), maxVal) ax ,ay = self._setdeaccelerateAxAy(speed.x(), speed.y(), a) if x > 0: x = max(0.0, x - a*ax) elif x < 0: x = min(0.0, x + a*ax) if y > 0: y = max(0.0, y - a*ay) elif y < 0: y = min(0.0, y + a*ay) return QPointF(x, y) def _qBound(self, minVal, current, maxVal): """PyQt4 does not wrap the qBound function from Qt's global namespace This is equivalent.""" return max(min(current, maxVal), minVal) def _setdeaccelerateAxAy(self, x, y, a): x = abs(x) y = abs(y) if x > y: if y > 0: ax = int(x / y) if ax != 0: return ax, 1 else: return x/a, 1 if y > x: if x > 0: ay = int(y/x) if ay != 0: return 1, ay else: return 1, y/a return 1, 1 def _tickerEvent(self): if self._deltaPan.x() == 0.0 and self._deltaPan.y() == 0.0 or self._dragMode == True: self._ticker.stop() else: self._deltaPan = self._deaccelerate(self._deltaPan) self._panning() def mouseDoubleClickEvent(self, event): mousePos = self.mapToScene(event.pos()) self.mouseDoubleClicked.emit(mousePos.x(), mousePos.y()) def changeSlice(self, delta): if self._isDrawing: self.endDrawing(self.mousePos) self._isDrawing = True #FIXME: #self._drawManager.beginDrawing(self.mousePos, self.self.shape2D) self.changeSliceDelta.emit(delta) #FIXME resurrect #InteractionLogger.log("%f: changeSliceDelta(axis, num) %d, %d" % (time.clock(), self._axis, delta)) def zoomOut(self): self.doScale(0.9) def zoomIn(self): self.doScale(1.1) def changeViewPort(self,qRectf): self.fitInView(qRectf,mode = Qt.KeepAspectRatio) def doScale(self, factor): self._zoomFactor = self._zoomFactor * factor InteractionLogger.log("%f: zoomFactor(factor) %f" % (time.clock(), self._zoomFactor)) self.scale(factor, factor)
def __init__(self, imagescene2d, useGL=False): """ Constructs a view upon a ImageScene2D imagescene2d -- a ImgeScene2D instance useGL -- wether to enable OpenGL rendering """ QGraphicsView.__init__(self) self._useGL = useGL self.setScene(imagescene2d) #these attributes are exposed as public properties above self._shape = None #2D shape of this view's shown image self._slices = None #number of slices that are stacked self._name = '' self._hud = None self._drawingEnabled = False self._crossHairCursor = None self._sliceIntersectionMarker = None # # Setup the Viewport for fast painting # if self._useGL: self.openglWidget = QGLWidget(self) self.setViewport(self.openglWidget) #we clear the background ourselves self.viewport().setAutoFillBackground(False) #QGraphicsView cannot use partial updates when using #an OpenGL widget as a viewport #http://doc.qt.nokia.com/qq/qq26-openglcanvas.html self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) else: #Unfortunately, setting these flags has no effect when #Cache background is turned on. #With these flags turned on we could handle the drawing of the #white background ourselves thus removing the flicker #when scrolling fast through the slices #self.viewport().setAttribute(Qt.WA_OpaquePaintEvent) #self.viewport().setAttribute(Qt.WA_NoSystemBackground) #self.viewport().setAttribute(Qt.WA_PaintOnScreen) #self.viewport().setAutoFillBackground(False) self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) #as rescaling images is slow if done in software, #we use Qt's built-in background caching mode so that the cached #image need only be blitted on the screen when we only move #the cursor self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHint(QPainter.Antialiasing, False) #Intitialize the scene if self._useGL: self.scene().activateOpenGL( self.openglWidget ) self._crossHairCursor = CrossHairCursor(self.scene()) self._crossHairCursor.setZValue(99) self._sliceIntersectionMarker = SliceIntersectionMarker() self._sliceIntersectionMarker.setZValue(100) self.scene().addItem(self._sliceIntersectionMarker) #FIXME: Use a QAction here so that we do not have to synchronize #between this initial state and the toggle button's initial state self._sliceIntersectionMarker.setVisibility(True) #FIXME: this should be private, but is currently used from # within the image scene renderer self.tempImageItems = [] self._isDrawing = False self._zoomFactor = 1.0 #for panning self._lastPanPoint = QPoint() self._dragMode = False self._deltaPan = QPointF(0,0) #Unfortunately, setting the style like this make the scroll bars look #really crappy... #self.setStyleSheet("QWidget:!focus { border: 2px solid " + self._axisColor[self._axis].name() +"; border-radius: 4px; }\ # QWidget:focus { border: 2px solid white; border-radius: 4px; }") #FIXME: Is there are more elegant way to handle this? self.setMouseTracking(True) self._ticker = QTimer(self) self._ticker.timeout.connect(self._tickerEvent) #label updates while drawing, needed for interactive segmentation self._drawTimer = QTimer(self) self._drawTimer.timeout.connect(self.notifyDrawing) # invisible cursor to enable custom cursor self._hiddenCursor = QCursor(Qt.BlankCursor) # For screen recording BlankCursor doesn't work #self.hiddenCursor = QCursor(Qt.ArrowCursor) self._tempErase = False