Example #1
0
    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)
Example #2
0
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)
Example #3
0
    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)
Example #4
0
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)
Example #5
0
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)
Example #6
0
    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