Renamed Transform -> SRTTransform to better reflect its function.

Added SRTTransform3D
This commit is contained in:
Luke Campagnola 2012-05-31 16:22:50 -04:00
parent 6129df2019
commit 7c87b1d04a
7 changed files with 378 additions and 32 deletions

View File

@ -4,7 +4,7 @@ from .Point import Point
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
class Transform(QtGui.QTransform): class SRTTransform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved. This transform has no shear; angles are always preserved.
""" """
@ -16,7 +16,7 @@ class Transform(QtGui.QTransform):
return return
elif isinstance(init, dict): elif isinstance(init, dict):
self.restoreState(init) self.restoreState(init)
elif isinstance(init, Transform): elif isinstance(init, SRTTransform):
self._state = { self._state = {
'pos': Point(init._state['pos']), 'pos': Point(init._state['pos']),
'scale': Point(init._state['scale']), 'scale': Point(init._state['scale']),
@ -28,7 +28,7 @@ class Transform(QtGui.QTransform):
elif isinstance(init, QtGui.QMatrix4x4): elif isinstance(init, QtGui.QMatrix4x4):
self.setFromMatrix4x4(init) self.setFromMatrix4x4(init)
else: else:
raise Exception("Cannot create Transform from input type: %s" % str(type(init))) raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init)))
def getScale(self): def getScale(self):
@ -73,9 +73,10 @@ class Transform(QtGui.QTransform):
self.update() self.update()
def setFromMatrix4x4(self, m): def setFromMatrix4x4(self, m):
m = pg.Transform3D(m) m = pg.SRTTransform3D(m)
angle, axis = m.getRotation() angle, axis = m.getRotation()
if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1):
print angle, axis
raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.")
self._state = { self._state = {
'pos': Point(m.getTranslation()), 'pos': Point(m.getTranslation()),
@ -128,10 +129,10 @@ class Transform(QtGui.QTransform):
def __div__(self, t): def __div__(self, t):
"""A / B == B^-1 * A""" """A / B == B^-1 * A"""
dt = t.inverted()[0] * self dt = t.inverted()[0] * self
return Transform(dt) return SRTTransform(dt)
def __mul__(self, t): def __mul__(self, t):
return Transform(QtGui.QTransform.__mul__(self, t)) return SRTTransform(QtGui.QTransform.__mul__(self, t))
def saveState(self): def saveState(self):
p = self._state['pos'] p = self._state['pos']
@ -203,12 +204,12 @@ if __name__ == '__main__':
s.addItem(l1) s.addItem(l1)
s.addItem(l2) s.addItem(l2)
tr1 = Transform() tr1 = SRTTransform()
tr2 = Transform() tr2 = SRTTransform()
tr3 = QtGui.QTransform() tr3 = QtGui.QTransform()
tr3.translate(20, 0) tr3.translate(20, 0)
tr3.rotate(45) tr3.rotate(45)
print("QTransform -> Transform:", Transform(tr3)) print("QTransform -> Transform:", SRTTransform(tr3))
print("tr1:", tr1) print("tr1:", tr1)
@ -221,7 +222,7 @@ if __name__ == '__main__':
print("tr2 * tr1 = ", tr2*tr1) print("tr2 * tr1 = ", tr2*tr1)
tr4 = Transform() tr4 = SRTTransform()
tr4.scale(-1, 1) tr4.scale(-1, 1)
tr4.rotate(30) tr4.rotate(30)
print("tr1 * tr4 = ", tr1*tr4) print("tr1 * tr4 = ", tr1*tr4)

302
SRTTransform3D.py Normal file
View File

@ -0,0 +1,302 @@
# -*- coding: utf-8 -*-
from Qt import QtCore, QtGui
from Vector import Vector
from SRTTransform import SRTTransform
import pyqtgraph as pg
import numpy as np
import scipy.linalg
class SRTTransform3D(QtGui.QMatrix4x4):
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved.
"""
def __init__(self, init=None):
QtGui.QMatrix4x4.__init__(self)
self.reset()
if init is None:
return
if init.__class__ is QtGui.QTransform:
init = SRTTransform(init)
if isinstance(init, dict):
self.restoreState(init)
elif isinstance(init, SRTTransform3D):
self._state = {
'pos': Vector(init._state['pos']),
'scale': Vector(init._state['scale']),
'angle': init._state['angle'],
'axis': Vector(init._state['axis']),
}
self.update()
elif isinstance(init, SRTTransform):
self._state = {
'pos': Vector(init._state['pos']),
'scale': Vector(init._state['scale']),
'angle': init._state['angle'],
'axis': Vector(0, 0, 1),
}
self.update()
elif isinstance(init, QtGui.QMatrix4x4):
self.setFromMatrix(init)
else:
raise Exception("Cannot build SRTTransform3D from argument type:", type(init))
def getScale(self):
return pg.Vector(self._state['scale'])
def getRotation(self):
"""Return (angle, axis) of rotation"""
return self._state['angle'], pg.Vector(self._state['axis'])
def getTranslation(self):
return pg.Vector(self._state['pos'])
def reset(self):
self._state = {
'pos': Vector(0,0,0),
'scale': Vector(1,1,1),
'angle': 0.0, ## in degrees
'axis': (0, 0, 1)
}
self.update()
def translate(self, *args):
"""Adjust the translation of this transform"""
t = Vector(*args)
self.setTranslate(self._state['pos']+t)
def setTranslate(self, *args):
"""Set the translation of this transform"""
self._state['pos'] = Vector(*args)
self.update()
def scale(self, *args):
"""adjust the scale of this transform"""
## try to prevent accidentally setting 0 scale on z axis
if len(args) == 1 and hasattr(args[0], '__len__'):
args = args[0]
if len(args) == 2:
args = args + (1,)
s = Vector(*args)
self.setScale(self._state['scale'] * s)
def setScale(self, *args):
"""Set the scale of this transform"""
if len(args) == 1 and hasattr(args[0], '__len__'):
args = args[0]
if len(args) == 2:
args = args + (1,)
self._state['scale'] = Vector(*args)
self.update()
def rotate(self, angle, axis=(0,0,1)):
"""Adjust the rotation of this transform"""
origAxis = self._state['axis']
if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]:
self.setRotate(self._state['angle'] + angle)
else:
m = QtGui.QMatrix4x4()
m.translate(*self._state['pos'])
m.rotate(self._state['angle'], *self._state['axis'])
m.rotate(angle, *axis)
m.scale(*self._state['scale'])
self.setFromMatrix(m)
def setRotate(self, angle, axis=(0,0,1)):
"""Set the transformation rotation to angle (in degrees)"""
self._state['angle'] = angle
self._state['axis'] = Vector(axis)
self.update()
def setFromMatrix(self, m):
"""
Set this transform mased on the elements of *m*
The input matrix must be affine AND have no shear,
otherwise the conversion will most likely fail.
"""
for i in range(4):
self.setRow(i, m.row(i))
m = self.matrix().reshape(4,4)
## translation is 4th column
self._state['pos'] = m[:3,3]
## scale is vector-length of first three columns
scale = (m[:3,:3]**2).sum(axis=0)**0.5
## see whether there is an inversion
z = np.cross(m[0, :3], m[1, :3])
if np.dot(z, m[2, :3]) < 0:
scale[1] *= -1 ## doesn't really matter which axis we invert
self._state['scale'] = scale
## rotation axis is the eigenvector with eigenvalue=1
r = m[:3, :3] / scale[:, np.newaxis]
try:
evals, evecs = scipy.linalg.eig(r)
except:
print "Rotation matrix:", r
print "Scale:", scale
print "Original matrix:", m
raise
eigIndex = np.argwhere(np.abs(evals-1) < 1e-7)
if len(eigIndex) < 1:
print "eigenvalues:", evals
print "eigenvectors:", evecs
print "index:", eigIndex, evals-1
raise Exception("Could not determine rotation axis.")
axis = evecs[eigIndex[0,0]].real
axis /= ((axis**2).sum())**0.5
self._state['axis'] = axis
## trace(r) == 2 cos(angle) + 1, so:
self._state['angle'] = np.arccos((r.trace()-1)*0.5) * 180 / np.pi
if self._state['angle'] == 0:
self._state['axis'] = (0,0,1)
def as2D(self):
"""Return a QTransform representing the x,y portion of this transform (if possible)"""
return pg.SRTTransform(self)
#def __div__(self, t):
#"""A / B == B^-1 * A"""
#dt = t.inverted()[0] * self
#return SRTTransform(dt)
#def __mul__(self, t):
#return SRTTransform(QtGui.QTransform.__mul__(self, t))
def saveState(self):
p = self._state['pos']
s = self._state['scale']
ax = self._state['axis']
#if s[0] == 0:
#raise Exception('Invalid scale: %s' % str(s))
return {
'pos': (p[0], p[1], p[2]),
'scale': (s[0], s[1], s[2]),
'angle': self._state['angle'],
'axis': (ax[0], ax[1], ax[2])
}
def restoreState(self, state):
self._state['pos'] = Vector(state.get('pos', (0.,0.,0.)))
scale = state.get('scale', (1.,1.,1.))
scale = scale + (1.,) * (3-len(scale))
self._state['scale'] = Vector(scale)
self._state['angle'] = state.get('angle', 0.)
self._state['axis'] = state.get('axis', (0, 0, 1))
self.update()
def update(self):
QtGui.QMatrix4x4.setToIdentity(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here.
QtGui.QMatrix4x4.translate(self, *self._state['pos'])
QtGui.QMatrix4x4.rotate(self, self._state['angle'], *self._state['axis'])
QtGui.QMatrix4x4.scale(self, *self._state['scale'])
def __repr__(self):
return str(self.saveState())
def matrix(self, nd=3):
if nd == 3:
return np.array(self.copyDataTo())
elif nd == 2:
m = np.array(self.copyDataTo())
m[2] = m[3]
m[:,2] = n[:,3]
return m[:3,:3]
else:
raise Exception("Argument 'nd' must be 2 or 3")
if __name__ == '__main__':
import widgets
import GraphicsView
from functions import *
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
win.show()
cw = GraphicsView.GraphicsView()
#cw.enableMouse()
win.setCentralWidget(cw)
s = QtGui.QGraphicsScene()
cw.setScene(s)
win.resize(600,600)
cw.enableMouse()
cw.setRange(QtCore.QRectF(-100., -100., 200., 200.))
class Item(QtGui.QGraphicsItem):
def __init__(self):
QtGui.QGraphicsItem.__init__(self)
self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self)
self.b.setPen(QtGui.QPen(mkPen('y')))
self.t1 = QtGui.QGraphicsTextItem(self)
self.t1.setHtml('<span style="color: #F00">R</span>')
self.t1.translate(20, 20)
self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self)
self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self)
self.l1.setPen(QtGui.QPen(mkPen('y')))
self.l2.setPen(QtGui.QPen(mkPen('y')))
def boundingRect(self):
return QtCore.QRectF()
def paint(self, *args):
pass
#s.addItem(b)
#s.addItem(t1)
item = Item()
s.addItem(item)
l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0)
l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10)
l1.setPen(QtGui.QPen(mkPen('r')))
l2.setPen(QtGui.QPen(mkPen('r')))
s.addItem(l1)
s.addItem(l2)
tr1 = SRTTransform()
tr2 = SRTTransform()
tr3 = QtGui.QTransform()
tr3.translate(20, 0)
tr3.rotate(45)
print "QTransform -> Transform:", SRTTransform(tr3)
print "tr1:", tr1
tr2.translate(20, 0)
tr2.rotate(45)
print "tr2:", tr2
dt = tr2/tr1
print "tr2 / tr1 = ", dt
print "tr2 * tr1 = ", tr2*tr1
tr4 = SRTTransform()
tr4.scale(-1, 1)
tr4.rotate(30)
print "tr1 * tr4 = ", tr1*tr4
w1 = widgets.TestROI((19,19), (22, 22), invertible=True)
#w2 = widgets.TestROI((0,0), (150, 150))
w1.setZValue(10)
s.addItem(w1)
#s.addItem(w2)
w1Base = w1.getState()
#w2Base = w2.getState()
def update():
tr1 = w1.getGlobalTransform(w1Base)
#tr2 = w2.getGlobalTransform(w2Base)
item.setTransform(tr1)
#def update2():
#tr1 = w1.getGlobalTransform(w1Base)
#tr2 = w2.getGlobalTransform(w2Base)
#t1.setTransform(tr1)
#w1.setState(w1Base)
#w1.applyGlobalTransform(tr2)
w1.sigRegionChanged.connect(update)
#w2.sigRegionChanged.connect(update2)

View File

@ -19,12 +19,12 @@ if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1]
from . import python2_3 from . import python2_3
## in general openGL is poorly supported in Qt. ## in general openGL is poorly supported with Qt+GraphicsView.
## we only enable it where the performance benefit is critical. ## we only enable it where the performance benefit is critical.
## Note this only applies to 2D graphics; 3D graphics always use OpenGL. ## Note this only applies to 2D graphics; 3D graphics always use OpenGL.
if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation
useOpenGL = False useOpenGL = False
elif 'darwin' in sys.platform: ## openGL greatly speeds up display on mac elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs
useOpenGL = True useOpenGL = True
else: else:
useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff.
@ -111,8 +111,8 @@ from .imageview import *
from .WidgetGroup import * from .WidgetGroup import *
from .Point import Point from .Point import Point
from .Vector import Vector from .Vector import Vector
from .Transform import Transform from .SRTTransform import SRTTransform
from .Transform3D import Transform3D from .SRTTransform3D import SRTTransform3D
from .functions import * from .functions import *
from .graphicsWindows import * from .graphicsWindows import *
from .SignalProxy import * from .SignalProxy import *

View File

@ -92,7 +92,7 @@ class CanvasItem(QtCore.QObject):
if 'transform' in self.opts: if 'transform' in self.opts:
self.baseTransform = self.opts['transform'] self.baseTransform = self.opts['transform']
else: else:
self.baseTransform = pg.Transform() self.baseTransform = pg.SRTTransform()
if 'pos' in self.opts and self.opts['pos'] is not None: if 'pos' in self.opts and self.opts['pos'] is not None:
self.baseTransform.translate(self.opts['pos']) self.baseTransform.translate(self.opts['pos'])
if 'angle' in self.opts and self.opts['angle'] is not None: if 'angle' in self.opts and self.opts['angle'] is not None:
@ -120,8 +120,8 @@ class CanvasItem(QtCore.QObject):
self.itemScale = QtGui.QGraphicsScale() self.itemScale = QtGui.QGraphicsScale()
self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) self._graphicsItem.setTransformations([self.itemRotation, self.itemScale])
self.tempTransform = pg.Transform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. self.tempTransform = pg.SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done.
self.userTransform = pg.Transform() ## stores the total transform of the object self.userTransform = pg.SRTTransform() ## stores the total transform of the object
self.resetUserTransform() self.resetUserTransform()
## now happens inside resetUserTransform -> selectBoxToItem ## now happens inside resetUserTransform -> selectBoxToItem
@ -196,7 +196,7 @@ class CanvasItem(QtCore.QObject):
#flip = self.transformGui.mirrorImageCheck.isChecked() #flip = self.transformGui.mirrorImageCheck.isChecked()
#tr = self.userTransform.saveState() #tr = self.userTransform.saveState()
inv = pg.Transform() inv = pg.SRTTransform()
inv.scale(-1, 1) inv.scale(-1, 1)
self.userTransform = self.userTransform * inv self.userTransform = self.userTransform * inv
self.updateTransform() self.updateTransform()
@ -226,7 +226,7 @@ class CanvasItem(QtCore.QObject):
if not self.isMovable(): if not self.isMovable():
return return
self.rotate(180.) self.rotate(180.)
# inv = pg.Transform() # inv = pg.SRTTransform()
# inv.scale(-1, -1) # inv.scale(-1, -1)
# self.userTransform = self.userTransform * inv #flip lr/ud # self.userTransform = self.userTransform * inv #flip lr/ud
# s=self.updateTransform() # s=self.updateTransform()
@ -311,7 +311,7 @@ class CanvasItem(QtCore.QObject):
def resetTemporaryTransform(self): def resetTemporaryTransform(self):
self.tempTransform = pg.Transform() ## don't use Transform.reset()--this transform might be used elsewhere. self.tempTransform = pg.SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere.
self.updateTransform() self.updateTransform()
def transform(self): def transform(self):
@ -363,7 +363,7 @@ class CanvasItem(QtCore.QObject):
try: try:
#self.userTranslate = pg.Point(tr['trans']) #self.userTranslate = pg.Point(tr['trans'])
#self.userRotate = tr['rot'] #self.userRotate = tr['rot']
self.userTransform = pg.Transform(tr) self.userTransform = pg.SRTTransform(tr)
self.updateTransform() self.updateTransform()
self.selectBoxFromUser() ## move select box to match self.selectBoxFromUser() ## move select box to match
@ -372,7 +372,7 @@ class CanvasItem(QtCore.QObject):
except: except:
#self.userTranslate = pg.Point([0,0]) #self.userTranslate = pg.Point([0,0])
#self.userRotate = 0 #self.userRotate = 0
self.userTransform = pg.Transform() self.userTransform = pg.SRTTransform()
debug.printExc("Failed to load transform:") debug.printExc("Failed to load transform:")
#print "set transform", self, self.userTranslate #print "set transform", self, self.userTranslate

View File

@ -3,7 +3,7 @@ from ..Node import Node
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
import numpy as np import numpy as np
from .common import * from .common import *
from pyqtgraph.Transform import Transform from pyqtgraph.SRTTransform import SRTTransform
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
from pyqtgraph.widgets.TreeWidget import TreeWidget from pyqtgraph.widgets.TreeWidget import TreeWidget
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem

View File

@ -355,7 +355,6 @@ class ImageItem(GraphicsObject):
self.drawAt(ev.pos(), ev) self.drawAt(ev.pos(), ev)
def raiseContextMenu(self, ev): def raiseContextMenu(self, ev):
## only raise menu if this terminal is removable
menu = self.getMenu() menu = self.getMenu()
if menu is None: if menu is None:
return False return False
@ -443,4 +442,8 @@ class ImageItem(GraphicsObject):
self.drawMask = mask self.drawMask = mask
def removeClicked(self): def removeClicked(self):
self.sigRemoveRequested.emit(self) ## Send remove event only after we have exited the menu event handler
self.removeTimer = QtCore.QTimer()
self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self))
self.removeTimer.start(0)

View File

@ -19,7 +19,7 @@ import numpy as np
from numpy.linalg import norm from numpy.linalg import norm
import scipy.ndimage as ndimage import scipy.ndimage as ndimage
from pyqtgraph.Point import * from pyqtgraph.Point import *
from pyqtgraph.Transform import Transform from pyqtgraph.SRTTransform import SRTTransform
from math import cos, sin from math import cos, sin
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
@ -45,8 +45,9 @@ class ROI(GraphicsObject):
sigRegionChanged = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object)
sigHoverEvent = QtCore.Signal(object) sigHoverEvent = QtCore.Signal(object)
sigClicked = QtCore.Signal(object, object) sigClicked = QtCore.Signal(object, object)
sigRemoveRequested = QtCore.Signal(object)
def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True): def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True, removable=False):
#QObjectWorkaround.__init__(self) #QObjectWorkaround.__init__(self)
GraphicsObject.__init__(self, parent) GraphicsObject.__init__(self, parent)
self.setAcceptedMouseButtons(QtCore.Qt.NoButton) self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
@ -55,6 +56,8 @@ class ROI(GraphicsObject):
self.aspectLocked = False self.aspectLocked = False
self.translatable = movable self.translatable = movable
self.rotateAllowed = True self.rotateAllowed = True
self.removable = removable
self.menu = None
self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted.
self.mouseHovering = False self.mouseHovering = False
@ -387,13 +390,19 @@ class ROI(GraphicsObject):
if not ev.isExit(): if not ev.isExit():
if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton): if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton):
hover=True hover=True
for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]:
if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn):
hover=True hover=True
if self.contextMenuEnabled():
ev.acceptClicks(QtCore.Qt.RightButton)
if hover: if hover:
self.setMouseHover(True) self.setMouseHover(True)
self.sigHoverEvent.emit(self) self.sigHoverEvent.emit(self)
ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion.
ev.acceptClicks(QtCore.Qt.RightButton)
ev.acceptClicks(QtCore.Qt.MidButton)
else: else:
self.setMouseHover(False) self.setMouseHover(False)
@ -408,6 +417,34 @@ class ROI(GraphicsObject):
self.currentPen = self.pen self.currentPen = self.pen
self.update() self.update()
def contextMenuEnabled(self):
return self.removable
def raiseContextMenu(self, ev):
if not self.contextMenuEnabled():
return
menu = self.getMenu()
menu = self.scene().addParentContextMenus(self, menu, ev)
pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
def getMenu(self):
if self.menu is None:
self.menu = QtGui.QMenu()
self.menu.setTitle("ROI")
remAct = QtGui.QAction("Remove ROI", self.menu)
remAct.triggered.connect(self.removeClicked)
self.menu.addAction(remAct)
self.menu.remAct = remAct
return self.menu
def removeClicked(self):
## Send remove event only after we have exited the menu event handler
self.removeTimer = QtCore.QTimer()
self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self))
self.removeTimer.start(0)
def mouseDragEvent(self, ev): def mouseDragEvent(self, ev):
if ev.isStart(): if ev.isStart():
@ -442,6 +479,9 @@ class ROI(GraphicsObject):
if ev.button() == QtCore.Qt.RightButton and self.isMoving: if ev.button() == QtCore.Qt.RightButton and self.isMoving:
ev.accept() ev.accept()
self.cancelMove() self.cancelMove()
if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled():
self.raiseContextMenu(ev)
ev.accept()
elif int(ev.button() & self.acceptedMouseButtons()) > 0: elif int(ev.button() & self.acceptedMouseButtons()) > 0:
ev.accept() ev.accept()
self.sigClicked.emit(self, ev) self.sigClicked.emit(self, ev)
@ -933,8 +973,8 @@ class ROI(GraphicsObject):
t1 = Transform(relativeTo) t1 = SRTTransform(relativeTo)
t2 = Transform(st) t2 = SRTTransform(st)
return t2/t1 return t2/t1
@ -962,7 +1002,7 @@ class ROI(GraphicsObject):
st = self.getState() st = self.getState()
st['scale'] = st['size'] st['scale'] = st['size']
st = Transform(st) st = SRTTransform(st)
st = (st * tr).saveState() st = (st * tr).saveState()
st['size'] = st['scale'] st['size'] = st['scale']
self.setState(st) self.setState(st)