sync changes from acq4:

- numerous fixes in close() functions
 - added Transform class
 - ROI widgets now operate in degrees instead of radians for easier Qt compatibility
This commit is contained in:
Luke Campagnola 2011-04-25 08:51:18 -04:00
parent 349561e133
commit 6783f4fa26
19 changed files with 418 additions and 54 deletions

View File

@ -9,8 +9,8 @@ class ColorButton(QtGui.QPushButton):
sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog
sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK)
def __init__(self, color=(128,128,128)):
QtGui.QPushButton.__init__(self)
def __init__(self, parent=None, color=(128,128,128)):
QtGui.QPushButton.__init__(self, parent)
self.setColor(color)
self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)

View File

@ -91,6 +91,11 @@ class GraphicsView(QtGui.QGraphicsView):
#prof.finish()
def close(self):
self.centralWidget = None
self.scene().clear()
#print " ", self.scene().itemCount()
self.currentItem = None
self.sceneObj = None
self.closed = True
def useOpenGL(self, b=True):

View File

@ -152,6 +152,7 @@ class ImageView(QtGui.QWidget):
#QtGui.QWidget.__dtor__(self)
def close(self):
self.ui.roiPlot.close()
self.ui.graphicsView.close()
self.ui.gradientWidget.sigGradientChanged.disconnect(self.updateImage)
self.scene.clear()
@ -159,7 +160,6 @@ class ImageView(QtGui.QWidget):
del self.imageDisp
#self.image = None
#self.imageDisp = None
self.ui.roiPlot.close()
self.setParent(None)
def keyPressEvent(self, ev):

View File

@ -66,4 +66,6 @@ class MultiPlotItem(QtGui.QGraphicsWidget):
def close(self):
for p in self.plots:
p[0].close()
self.plots = None
for i in range(self.layout.count()):
self.layout.removeAt(i)

View File

@ -40,4 +40,6 @@ class MultiPlotWidget(GraphicsView):
def close(self):
self.mPlotItem.close()
self.mPlotItem = None
self.setParent(None)
GraphicsView.close(self)

0
PIL_Fix/Image.py-1.6 Executable file → Normal file
View File

View File

@ -60,12 +60,13 @@ class PlotItem(QtGui.QGraphicsWidget):
self.autoBtn = QtGui.QToolButton()
self.autoBtn.setText('A')
self.autoBtn.hide()
self.proxies = []
for b in [self.ctrlBtn, self.autoBtn]:
proxy = QtGui.QGraphicsProxyWidget(self)
proxy.setWidget(b)
proxy.setAcceptHoverEvents(False)
b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt")
self.proxies.append(proxy)
#QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked)
self.ctrlBtn.clicked.connect(self.ctrlBtnClicked)
#QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale)
@ -155,9 +156,9 @@ class PlotItem(QtGui.QGraphicsWidget):
c.setupUi(w)
dv = QtGui.QDoubleValidator(self)
self.ctrlMenu = QtGui.QMenu()
ac = QtGui.QWidgetAction(self)
ac.setDefaultWidget(w)
self.ctrlMenu.addAction(ac)
self.menuAction = QtGui.QWidgetAction(self)
self.menuAction.setDefaultWidget(w)
self.ctrlMenu.addAction(self.menuAction)
if HAVE_WIDGETGROUP:
self.stateGroup = WidgetGroup(self.ctrlMenu)
@ -284,9 +285,46 @@ class PlotItem(QtGui.QGraphicsWidget):
def close(self):
#print "delete", self
## All this crap is needed to avoid PySide trouble.
## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets)
## the solution is to manually remove all widgets before scene.clear() is called
if self.ctrlMenu is None: ## already shut down
return
self.ctrlMenu.setParent(None)
self.ctrlMenu = None
self.ctrlBtn.setParent(None)
self.ctrlBtn = None
self.autoBtn.setParent(None)
self.autoBtn = None
for k in self.scales:
i = self.scales[k]['item']
i.close()
self.scales = None
self.scene().removeItem(self.vb)
self.vb = None
for i in range(self.layout.count()):
self.layout.removeAt(i)
for p in self.proxies:
try:
p.setWidget(None)
except RuntimeError:
break
self.scene().removeItem(p)
self.proxies = []
self.menuAction.releaseWidget(self.menuAction.defaultWidget())
self.menuAction.setParent(None)
self.menuAction = None
if self.manager is not None:
self.manager.sigWidgetListChanged.disconnect(self.updatePlotList)
self.manager.removeWidget(self.name)
#else:
#print "no manager"
def registerPlot(self, name):
self.name = name
@ -1042,6 +1080,8 @@ class PlotItem(QtGui.QGraphicsWidget):
#ev.accept()
def resizeEvent(self, ev):
if self.ctrlBtn is None: ## already closed down
return
self.ctrlBtn.move(0, self.size().height() - self.ctrlBtn.size().height())
self.autoBtn.move(self.ctrlBtn.width(), self.size().height() - self.autoBtn.size().height())
@ -1190,6 +1230,8 @@ class PlotWidgetManager(QtCore.QObject):
del self.widgets[name]
#self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
self.sigWidgetListChanged.emit(self.widgets.keys())
else:
print "plot %s not managed" % name
def listWidgets(self):

View File

@ -39,6 +39,7 @@ class PlotWidget(GraphicsView):
#self.scene().clear()
#self.mPlotItem.close()
self.setParent(None)
GraphicsView.close(self)
def __getattr__(self, attr): ## implicitly wrap methods from plotItem
if hasattr(self.plotItem, attr):

View File

@ -6,7 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
"""
from PyQt4 import QtCore
from math import acos
import numpy as np
def clip(x, mn, mx):
if x > mx:
@ -99,17 +99,17 @@ class Point(QtCore.QPointF):
return (self[0]**2 + self[1]**2) ** 0.5
def angle(self, a):
"""Returns the angle between this vector and the vector a."""
"""Returns the angle in degrees between this vector and the vector a."""
n1 = self.length()
n2 = a.length()
if n1 == 0. or n2 == 0.:
return None
## Probably this should be done with arctan2 instead..
ang = acos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians
ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians
c = self.cross(a)
if c > 0:
ang *= -1.
return ang
return ang * 180. / np.pi
def dot(self, a):
"""Returns the dot product of a and this Point."""
@ -120,6 +120,11 @@ class Point(QtCore.QPointF):
a = Point(a)
return self[0]*a[1] - self[1]*a[0]
def proj(self, b):
"""Return the projection of this vector onto the vector b"""
b1 = b / b.length()
return self.dot(b1) * b1
def __repr__(self):
return "Point(%f, %f)" % (self[0], self[1])
@ -129,3 +134,6 @@ class Point(QtCore.QPointF):
def max(self):
return max(self[0], self[1])
def copy(self):
return Point(self)

190
Transform.py Normal file
View File

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
from Point import Point
import numpy as np
class Transform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform always has 0 shear."""
def __init__(self, init=None):
QtGui.QTransform.__init__(self)
self.reset()
if isinstance(init, dict):
self.restoreState(init)
elif isinstance(init, Transform):
self._state = {
'pos': Point(init._state['pos']),
'scale': Point(init._state['scale']),
'angle': init._state['angle']
}
self.update()
elif isinstance(init, QtGui.QTransform):
self.setFromQTransform(init)
def reset(self):
self._state = {
'pos': Point(0,0),
'scale': Point(1,1),
'angle': 0.0 ## in degrees
}
self.update()
def setFromQTransform(self, tr):
p1 = Point(tr.map(0., 0.))
p2 = Point(tr.map(1., 0.))
p3 = Point(tr.map(0., 1.))
dp2 = Point(p2-p1)
dp3 = Point(p3-p1)
## detect flipped axes
if dp2.angle(dp3) > 0:
da = 180
sy = -1.0
else:
da = 0
sy = 1.0
self._state = {
'pos': Point(p1),
'scale': Point(dp2.length(), dp3.length() * sy),
'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da
}
self.update()
def translate(self, *args):
"""Acceptable arguments are:
x, y
[x, y]
Point(x,y)"""
t = Point(*args)
self.setTranslate(self._state['pos']+t)
def setTranslate(self, *args):
"""Acceptable arguments are:
x, y
[x, y]
Point(x,y)"""
self._state['pos'] = Point(*args)
self.update()
def scale(self, *args):
"""Acceptable arguments are:
x, y
[x, y]
Point(x,y)"""
s = Point(*args)
self.setScale(self._state['scale'] * s)
def setScale(self, *args):
"""Acceptable arguments are:
x, y
[x, y]
Point(x,y)"""
self._state['scale'] = Point(*args)
self.update()
def rotate(self, angle):
"""Rotate the transformation by angle (in degrees)"""
self.setRotate(self._state['angle'] + angle)
def setRotate(self, angle):
"""Set the transformation rotation to angle (in degrees)"""
self._state['angle'] = angle
self.update()
def __div__(self, t):
"""A / B == B^-1 * A"""
dt = t.inverted()[0] * self
return Transform(dt)
def __mul__(self, t):
return Transform(QtGui.QTransform.__mul__(self, t))
def saveState(self):
p = self._state['pos']
s = self._state['scale']
if s[0] == 0:
raise Exception('Invalid scale')
return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']}
def restoreState(self, state):
self._state['pos'] = Point(state.get('pos', (0,0)))
self._state['scale'] = Point(state.get('scale', (1.,1.)))
self._state['angle'] = state.get('angle', 0)
self.update()
def update(self):
QtGui.QTransform.reset(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here.
QtGui.QTransform.translate(self, *self._state['pos'])
QtGui.QTransform.rotate(self, self._state['angle'])
QtGui.QTransform.scale(self, *self._state['scale'])
def __repr__(self):
return str(self.saveState())
def matrix(self):
return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]])
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)
b = QtGui.QGraphicsRectItem(-5, -5, 10, 10)
b.setPen(QtGui.QPen(mkPen('y')))
t1 = QtGui.QGraphicsTextItem()
t1.setHtml('<span style="color: #F00">R</span>')
s.addItem(b)
s.addItem(t1)
tr1 = Transform()
tr2 = Transform()
tr3 = QtGui.QTransform()
tr3.translate(20, 0)
tr3.rotate(45)
print "QTransform -> Transform:", Transform(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 = Transform()
tr4.scale(-1, 1)
tr4.rotate(30)
print "tr1 * tr4 = ", tr1*tr4
w1 = widgets.TestROI((0,0), (50, 50))
w2 = widgets.TestROI((0,0), (150, 150))
s.addItem(w1)
s.addItem(w2)
w1Base = w1.getState()
w2Base = w2.getState()
def update():
tr1 = w1.getGlobalTransform(w1Base)
tr2 = w2.getGlobalTransform(w2Base)
t1.setTransform(tr1 * tr2)
w1.setState(w1Base)
w1.applyGlobalTransform(tr2)
w1.sigRegionChanged.connect(update)
w2.sigRegionChanged.connect(update)

View File

@ -7,6 +7,8 @@ from graphicsWindows import *
#import PlotWidget
#import ImageView
from PyQt4 import QtGui
from Point import Point
from Transform import Transform
plots = []
images = []

0
examples/test_Arrow.py Normal file → Executable file
View File

0
examples/test_ImageItem.py Normal file → Executable file
View File

0
examples/test_ImageView.py Normal file → Executable file
View File

0
examples/test_PlotWidget.py Normal file → Executable file
View File

0
examples/test_draw.py Normal file → Executable file
View File

0
examples/test_scatterPlot.py Normal file → Executable file
View File

View File

@ -256,8 +256,11 @@ class ImageItem(QtGui.QGraphicsObject):
def getLevels(self):
return self.whiteLevel, self.blackLevel
def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None):
axh = {'x': 0, 'y': 1, 'c': 2}
def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None, axes=None):
if axes is None:
axh = {'x': 0, 'y': 1, 'c': 2}
else:
axh = axes
#print "Update image", black, white
if white is not None:
self.whiteLevel = white
@ -280,8 +283,12 @@ class ImageItem(QtGui.QGraphicsObject):
# Determine scale factors
if autoRange or self.blackLevel is None:
self.blackLevel = self.image.min()
self.whiteLevel = self.image.max()
if self.image.dtype is np.ubyte:
self.blackLevel = 0
self.whiteLevel = 255
else:
self.blackLevel = self.image.min()
self.whiteLevel = self.image.max()
#print "Image item using", self.blackLevel, self.whiteLevel
if self.blackLevel != self.whiteLevel:
@ -325,7 +332,6 @@ class ImageItem(QtGui.QGraphicsObject):
self.image.shape = shape
im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte)
try:
im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte)
except:
@ -341,10 +347,13 @@ class ImageItem(QtGui.QGraphicsObject):
im1[..., 3] = alpha
elif im.ndim == 3: #color image
im2 = im.transpose(axh['y'], axh['x'], axh['c'])
## [B G R A] Reorder colors
order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image.
for i in range(0, im.shape[axh['c']]):
im1[..., 2-i] = im2[..., i] ## for some reason, the colors line up as BGR in the final image.
im1[..., order[i]] = im2[..., i]
## fill in unused channels with 0 or alpha
for i in range(im.shape[axh['c']], 3):
im1[..., i] = 0
if im.shape[axh['c']] < 4:
@ -781,7 +790,7 @@ class PlotCurveItem(GraphicsObject):
def mouseMoveEvent(self, ev):
#GraphicsObject.mouseMoveEvent(self, ev)
self.mouseMoved = True
print "move"
#print "move"
def mouseReleaseEvent(self, ev):
#GraphicsObject.mouseReleaseEvent(self, ev)
@ -848,9 +857,9 @@ class CurvePoint(QtGui.QGraphicsObject):
p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1]))
p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2]))
ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x())
ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians
self.resetTransform()
self.rotate(180+ ang * 180 / np.pi)
self.rotate(180+ ang * 180 / np.pi) ## takes degrees
QtGui.QGraphicsItem.setPos(self, *newPos)
return True
@ -940,7 +949,7 @@ class CurveArrow(CurvePoint):
class ScatterPlotItem(QtGui.QGraphicsWidget):
sigPointClicked = QtCore.Signal(object)
sigPointClicked = QtCore.Signal(object, object)
def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5):
QtGui.QGraphicsWidget.__init__(self)
@ -1027,7 +1036,7 @@ class ScatterPlotItem(QtGui.QGraphicsWidget):
pass
def pointClicked(self, point):
self.sigPointClicked.emit(point)
self.sigPointClicked.emit(self, point)
def points(self):
return self.spots[:]
@ -1044,6 +1053,7 @@ class SpotItem(QtGui.QGraphicsWidget):
self.pen = pen
self.brush = brush
self.path = QtGui.QPainterPath()
self.size = size
#s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
self.scale(size, size)
@ -1236,7 +1246,7 @@ class LabelItem(QtGui.QGraphicsWidget):
def setAngle(self, angle):
self.angle = angle
self.item.resetMatrix()
self.item.resetTransform()
self.item.rotate(angle)
self.updateMin()
@ -1311,6 +1321,11 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.grid = False
def close(self):
self.scene().removeItem(self.label)
self.label = None
self.scene().removeItem(self)
def setGrid(self, grid):
"""Set the alpha value for the grid, or False to disable."""
self.grid = grid
@ -1722,7 +1737,7 @@ class ViewBox(QtGui.QGraphicsWidget):
m = QtGui.QTransform()
## First center the viewport at 0
self.childGroup.resetMatrix()
self.childGroup.resetTransform()
center = self.transform().inverted()[0].map(bounds.center())
#print " transform to center:", center
if self.yInverted:
@ -2009,6 +2024,7 @@ class InfiniteLine(GraphicsObject):
self.currentPen = self.pen
def setAngle(self, angle):
"""Takes angle argument in degrees."""
self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135
self.updateLine()

View File

@ -9,12 +9,15 @@ for use as region-of-interest markers. ROI class automatically handles extractio
of array data from ImageItems.
"""
from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg
from PyQt4 import QtCore, QtGui
if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal
#from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2
import numpy as np
from numpy.linalg import norm
import scipy.ndimage as ndimage
from Point import *
from Transform import Transform
from math import cos, sin
import functions as fn
#from ObjectWorkaround import *
@ -35,6 +38,8 @@ def rectStr(r):
class ROI(QtGui.QGraphicsObject):
"""Generic region-of-interest widget.
Can be used for implementing many types of selection box with rotate/translate/scale handles."""
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object)
@ -54,10 +59,11 @@ class ROI(QtGui.QGraphicsObject):
self.pen = fn.mkPen(pen)
self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255))
self.handles = []
self.state = {'pos': pos, 'size': size, 'angle': angle}
self.state = {'pos': pos, 'size': size, 'angle': angle} ## angle is in degrees for ease of Qt integration
self.lastState = None
self.setPos(pos)
self.rotate(-angle * 180. / np.pi)
#self.rotate(-angle * 180. / np.pi)
self.rotate(-angle)
self.setZValue(10)
self.isMoving = False
@ -114,7 +120,8 @@ class ROI(QtGui.QGraphicsObject):
def setAngle(self, angle, update=True):
self.state['angle'] = angle
tr = QtGui.QTransform()
tr.rotate(-angle * 180 / np.pi)
#tr.rotate(-angle * 180 / np.pi)
tr.rotate(angle)
self.setTransform(tr)
if update:
self.updateHandles()
@ -129,10 +136,10 @@ class ROI(QtGui.QGraphicsObject):
pos = Point(pos)
return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item})
def addScaleHandle(self, pos, center, axes=None, item=None, name=None):
def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False):
pos = Point(pos)
center = Point(center)
info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item}
info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect}
if pos.x() == center.x():
info['xoff'] = True
if pos.y() == center.y():
@ -212,6 +219,11 @@ class ROI(QtGui.QGraphicsObject):
h['item'].hide()
def mousePressEvent(self, ev):
## Bug: sometimes we get events we shouldn't.
p = ev.pos()
if not self.isMoving and not self.shape().contains(p):
ev.ignore()
return
if ev.button() == QtCore.Qt.LeftButton:
self.setSelected(True)
if self.translatable:
@ -345,6 +357,11 @@ class ROI(QtGui.QGraphicsObject):
lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize
lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize
## preserve aspect ratio (this can override snapping)
if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier):
#arv = Point(self.preMoveState['size']) -
lp1 = lp1.proj(lp0)
## determine scale factors and new size of ROI
hs = h['pos'] - c
if hs[0] == 0:
@ -392,15 +409,16 @@ class ROI(QtGui.QGraphicsObject):
return
## determine new rotation angle, constrained if necessary
ang = newState['angle'] + lp0.angle(lp1)
ang = newState['angle'] - lp0.angle(lp1)
if ang is None: ## this should never happen..
return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (np.pi/12.)) * (np.pi/12.)
ang = round(ang / 15.) * 15. ## 180/12 = 15
## create rotation transform
tr = QtGui.QTransform()
tr.rotate(-ang * 180. / np.pi)
#tr.rotate(-ang * 180. / np.pi)
tr.rotate(ang)
## mvoe ROI so that center point remains stationary after rotate
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
@ -473,7 +491,8 @@ class ROI(QtGui.QGraphicsObject):
if ang is None:
return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (np.pi/12.)) * (np.pi/12.)
#ang = round(ang / (np.pi/12.)) * (np.pi/12.)
ang = round(ang / 15.) * 15.
hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
newState['size'][scaleAxis] = lp1.length() / hs
@ -484,7 +503,8 @@ class ROI(QtGui.QGraphicsObject):
c1 = c * newState['size']
tr = QtGui.QTransform()
tr.rotate(-ang * 180. / np.pi)
#tr.rotate(-ang * 180. / np.pi)
tr.rotate(-ang)
cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
newState['angle'] = ang
@ -576,7 +596,8 @@ class ROI(QtGui.QGraphicsObject):
def stateRect(self, state):
r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
tr = QtGui.QTransform()
tr.rotate(-state['angle'] * 180 / np.pi)
#tr.rotate(-state['angle'] * 180 / np.pi)
tr.rotate(-state['angle'])
r = tr.mapRect(r)
return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])
@ -751,11 +772,60 @@ class ROI(QtGui.QGraphicsObject):
### Untranspose array before returning
#return arr5.transpose(tr2)
def getGlobalTransform(self, relativeTo=None):
"""Return global transformation (rotation angle+translation) required to move from relative state to current state. If relative state isn't specified,
then we use the state of the ROI when mouse is pressed."""
if relativeTo == None:
relativeTo = self.preMoveState
st = self.getState()
## this is only allowed because we will be comparing the two
relativeTo['scale'] = relativeTo['size']
st['scale'] = st['size']
t1 = Transform(relativeTo)
t2 = Transform(st)
return t2/t1
#st = self.getState()
### rotation
#ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358
#rot = QtGui.QTransform()
#rot.rotate(-ang)
### We need to come up with a universal transformation--one that can be applied to other objects
### such that all maintain alignment.
### More specifically, we need to turn the ROI's position and angle into
### a rotation _around the origin_ and a translation.
#p0 = Point(relativeTo['pos'])
### base position, rotated
#p1 = rot.map(p0)
#trans = Point(st['pos']) - p1
#return trans, ang
def applyGlobalTransform(self, tr):
st = self.getState()
st['scale'] = st['size']
st = Transform(st)
#trans = QtGui.QTransform()
#trans.translate(*translate)
#trans.rotate(-rotate)
#x2, y2 = trans.map(*st['pos'])
#self.setAngle(st['angle']+rotate*np.pi/180.)
#self.setPos([x2, y2])
st = (st * tr).saveState()
st['size'] = st['scale']
self.setState(st)
class Handle(QtGui.QGraphicsItem):
@ -783,6 +853,7 @@ class Handle(QtGui.QGraphicsItem):
self.pen.setCosmetic(True)
self.isMoving = False
self.sides, self.startAng = self.types[typ]
self.buildPath()
def connectROI(self, roi, i):
self.roi.append((roi, i))
@ -791,6 +862,12 @@ class Handle(QtGui.QGraphicsItem):
return self.bounds
def mousePressEvent(self, ev):
# Bug: sometimes we get events not meant for us!
p = ev.pos()
if not self.isMoving and not self.path.contains(p):
ev.ignore()
return
#print "handle press"
if ev.button() == QtCore.Qt.LeftButton:
self.isMoving = True
@ -833,6 +910,20 @@ class Handle(QtGui.QGraphicsItem):
for r in self.roi:
r[0].movePoint(r[1], pos, modifiers)
def buildPath(self):
size = self.radius
self.path = QtGui.QPainterPath()
ang = self.startAng
dt = 2*np.pi / self.sides
for i in range(0, self.sides+1):
x = size * cos(ang)
y = size * sin(ang)
ang += dt
if i == 0:
self.path.moveTo(x, y)
else:
self.path.lineTo(x, y)
def paint(self, p, opt, widget):
## determine rotation of transform
m = self.sceneTransform()
@ -851,15 +942,19 @@ class Handle(QtGui.QGraphicsItem):
self.prepareGeometryChange()
p.setRenderHints(p.Antialiasing, True)
p.setPen(self.pen)
ang = self.startAng + va
dt = 2*np.pi / self.sides
for i in range(0, self.sides):
x1 = size * cos(ang)
y1 = size * sin(ang)
x2 = size * cos(ang+dt)
y2 = size * sin(ang+dt)
ang += dt
p.drawLine(Point(x1, y1), Point(x2, y2))
p.rotate(va * 180. / 3.1415926)
p.drawPath(self.path)
#ang = self.startAng + va
#dt = 2*np.pi / self.sides
#for i in range(0, self.sides):
#x1 = size * cos(ang)
#y1 = size * sin(ang)
#x2 = size * cos(ang+dt)
#y2 = size * sin(ang+dt)
#ang += dt
#p.drawLine(Point(x1, y1), Point(x2, y2))
@ -902,10 +997,11 @@ class LineROI(ROI):
d = pos2-pos1
l = d.length()
ang = Point(1, 0).angle(d)
c = Point(-width/2. * sin(ang), -width/2. * cos(ang))
ra = ang * np.pi / 180.
c = Point(-width/2. * sin(ra), -width/2. * cos(ra))
pos1 = pos1 + c
ROI.__init__(self, pos1, size=Point(l, width), angle=ang*180/np.pi, **args)
ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args)
self.addScaleRotateHandle([0, 0.5], [1, 0.5])
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
self.addScaleHandle([0.5, 1], [0.5, 0.5])