sync with lp:~luke-campagnola/pyqtgraph/main
This commit is contained in:
commit
3b820c2972
@ -66,8 +66,14 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
self.updateMatrix()
|
self.updateMatrix()
|
||||||
self.sceneObj = QtGui.QGraphicsScene()
|
self.sceneObj = QtGui.QGraphicsScene()
|
||||||
self.setScene(self.sceneObj)
|
self.setScene(self.sceneObj)
|
||||||
|
|
||||||
|
## by default we set up a central widget with a grid layout.
|
||||||
|
## this can be replaced if needed.
|
||||||
self.centralWidget = None
|
self.centralWidget = None
|
||||||
self.setCentralItem(QtGui.QGraphicsWidget())
|
self.setCentralItem(QtGui.QGraphicsWidget())
|
||||||
|
self.centralLayout = QtGui.QGraphicsGridLayout()
|
||||||
|
self.centralWidget.setLayout(self.centralLayout)
|
||||||
|
|
||||||
self.mouseEnabled = False
|
self.mouseEnabled = False
|
||||||
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
|
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
|
||||||
self.clickAccepted = False
|
self.clickAccepted = False
|
||||||
@ -85,6 +91,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
ev.ignore()
|
ev.ignore()
|
||||||
|
|
||||||
def setCentralItem(self, item):
|
def setCentralItem(self, item):
|
||||||
|
"""Sets a QGraphicsWidget to automatically fill the entire view."""
|
||||||
if self.centralWidget is not None:
|
if self.centralWidget is not None:
|
||||||
self.scene().removeItem(self.centralWidget)
|
self.scene().removeItem(self.centralWidget)
|
||||||
self.centralWidget = item
|
self.centralWidget = item
|
||||||
@ -304,22 +311,21 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
#self.currentItem = None
|
#self.currentItem = None
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
|
if self.lastMousePos is None:
|
||||||
|
self.lastMousePos = Point(ev.pos())
|
||||||
|
delta = Point(ev.pos()) - self.lastMousePos
|
||||||
|
self.lastMousePos = Point(ev.pos())
|
||||||
|
|
||||||
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
|
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
|
||||||
if not self.mouseEnabled:
|
if not self.mouseEnabled:
|
||||||
return
|
return
|
||||||
self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
|
self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
|
||||||
#print "moved. Grabber:", self.scene().mouseGrabberItem()
|
#print "moved. Grabber:", self.scene().mouseGrabberItem()
|
||||||
|
|
||||||
if self.lastMousePos is None:
|
|
||||||
self.lastMousePos = Point(ev.pos())
|
|
||||||
|
|
||||||
if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it.
|
if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it.
|
||||||
return
|
return
|
||||||
|
|
||||||
delta = Point(ev.pos()) - self.lastMousePos
|
|
||||||
|
|
||||||
self.lastMousePos = Point(ev.pos())
|
|
||||||
|
|
||||||
if ev.buttons() == QtCore.Qt.RightButton:
|
if ev.buttons() == QtCore.Qt.RightButton:
|
||||||
delta = Point(clip(delta[0], -50, 50), clip(-delta[1], -50, 50))
|
delta = Point(clip(delta[0], -50, 50), clip(-delta[1], -50, 50))
|
||||||
scale = 1.01 ** delta
|
scale = 1.01 ** delta
|
||||||
@ -328,7 +334,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
|
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
|
||||||
self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
|
self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
|
||||||
|
|
||||||
|
|
||||||
elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button.
|
elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button.
|
||||||
tr = -delta / self.currentScale
|
tr = -delta / self.currentScale
|
||||||
|
|
||||||
@ -376,6 +381,10 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||||||
self.render(painter)
|
self.render(painter)
|
||||||
painter.end()
|
painter.end()
|
||||||
|
|
||||||
|
def dragEnterEvent(self, ev):
|
||||||
|
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#def getFreehandLine(self):
|
#def getFreehandLine(self):
|
||||||
|
|
||||||
|
18
ImageView.py
18
ImageView.py
@ -106,12 +106,12 @@ class ImageView(QtGui.QWidget):
|
|||||||
setattr(self, fn, getattr(self.ui.graphicsView, fn))
|
setattr(self, fn, getattr(self.ui.graphicsView, fn))
|
||||||
|
|
||||||
#QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged)
|
#QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged)
|
||||||
self.timeLine.connect(QtCore.SIGNAL('positionChanged'), self.timeLineChanged)
|
self.timeLine.connect(self.timeLine, QtCore.SIGNAL('positionChanged'), self.timeLineChanged)
|
||||||
#QtCore.QObject.connect(self.ui.whiteSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
|
#QtCore.QObject.connect(self.ui.whiteSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
|
||||||
#QtCore.QObject.connect(self.ui.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
|
#QtCore.QObject.connect(self.ui.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
|
||||||
QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage)
|
QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage)
|
||||||
QtCore.QObject.connect(self.ui.roiBtn, QtCore.SIGNAL('clicked()'), self.roiClicked)
|
QtCore.QObject.connect(self.ui.roiBtn, QtCore.SIGNAL('clicked()'), self.roiClicked)
|
||||||
self.roi.connect(QtCore.SIGNAL('regionChanged'), self.roiChanged)
|
self.roi.connect(self.roi, QtCore.SIGNAL('regionChanged'), self.roiChanged)
|
||||||
QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled)
|
QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled)
|
||||||
QtCore.QObject.connect(self.ui.normDivideRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
|
QtCore.QObject.connect(self.ui.normDivideRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
|
||||||
QtCore.QObject.connect(self.ui.normSubtractRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
|
QtCore.QObject.connect(self.ui.normSubtractRadio, QtCore.SIGNAL('clicked()'), self.updateNorm)
|
||||||
@ -124,7 +124,7 @@ class ImageView(QtGui.QWidget):
|
|||||||
##QtCore.QObject.connect(self.ui.normStartSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
|
##QtCore.QObject.connect(self.ui.normStartSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
|
||||||
#QtCore.QObject.connect(self.ui.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
|
#QtCore.QObject.connect(self.ui.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
|
||||||
self.normProxy = proxyConnect(self.normRgn, QtCore.SIGNAL('regionChanged'), self.updateNorm)
|
self.normProxy = proxyConnect(self.normRgn, QtCore.SIGNAL('regionChanged'), self.updateNorm)
|
||||||
self.normRoi.connect(QtCore.SIGNAL('regionChangeFinished'), self.updateNorm)
|
self.normRoi.connect(self.normRoi, QtCore.SIGNAL('regionChangeFinished'), self.updateNorm)
|
||||||
|
|
||||||
self.ui.roiPlot.registerPlot(self.name + '_ROI')
|
self.ui.roiPlot.registerPlot(self.name + '_ROI')
|
||||||
|
|
||||||
@ -347,7 +347,19 @@ class ImageView(QtGui.QWidget):
|
|||||||
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
|
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
|
||||||
elif img.ndim == 4:
|
elif img.ndim == 4:
|
||||||
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
|
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
|
||||||
|
else:
|
||||||
|
raise Exception("Can not interpret image with dimensions %s" % (str(img)))
|
||||||
|
elif isinstance(axes, dict):
|
||||||
|
self.axes = axes.copy()
|
||||||
|
elif isinstance(axes, list) or isinstance(axes, tuple):
|
||||||
|
self.axes = {}
|
||||||
|
for i in range(len(axes)):
|
||||||
|
self.axes[axes[i]] = i
|
||||||
|
else:
|
||||||
|
raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes)))
|
||||||
|
|
||||||
|
for x in ['t', 'x', 'y', 'c']:
|
||||||
|
self.axes[x] = self.axes.get(x, None)
|
||||||
|
|
||||||
self.imageDisp = None
|
self.imageDisp = None
|
||||||
if autoLevels:
|
if autoLevels:
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
|
"""For circumventing PyQt's lack of multiple inheritance (just until PySide becomes stable)"""
|
||||||
|
|
||||||
|
|
||||||
|
class Obj(QtCore.QObject):
|
||||||
|
def event(self, ev):
|
||||||
|
self.emit(QtCore.SIGNAL('event'), ev)
|
||||||
|
return QtCore.QObject.event(self, ev)
|
||||||
|
|
||||||
class QObjectWorkaround:
|
class QObjectWorkaround:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._qObj_ = QtCore.QObject()
|
self._qObj_ = Obj()
|
||||||
|
self.connect(QtCore.SIGNAL('event'), self.event)
|
||||||
def connect(self, *args):
|
def connect(self, *args):
|
||||||
if args[0] is self:
|
if args[0] is self:
|
||||||
return QtCore.QObject.connect(self._qObj_, *args[1:])
|
return QtCore.QObject.connect(self._qObj_, *args[1:])
|
||||||
@ -15,8 +23,20 @@ class QObjectWorkaround:
|
|||||||
return QtCore.QObject.emit(self._qObj_, *args)
|
return QtCore.QObject.emit(self._qObj_, *args)
|
||||||
def blockSignals(self, b):
|
def blockSignals(self, b):
|
||||||
return self._qObj_.blockSignals(b)
|
return self._qObj_.blockSignals(b)
|
||||||
|
def setProperty(self, prop, val):
|
||||||
|
return self._qObj_.setProperty(prop, val)
|
||||||
|
def property(self, prop):
|
||||||
|
return self._qObj_.property(prop)
|
||||||
|
def event(self, ev):
|
||||||
|
pass
|
||||||
|
|
||||||
class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround):
|
#class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||||
def __init__(self, *args):
|
#def __init__(self, *args):
|
||||||
QtGui.QGraphicsItem.__init__(self, *args)
|
#QtGui.QGraphicsItem.__init__(self, *args)
|
||||||
QObjectWorkaround.__init__(self)
|
#QObjectWorkaround.__init__(self)
|
||||||
|
|
||||||
|
class QGraphicsObject(QtGui.QGraphicsWidget):
|
||||||
|
def shape(self):
|
||||||
|
return QtGui.QGraphicsItem.shape(self)
|
||||||
|
|
||||||
|
#QGraphicsObject = QtGui.QGraphicsObject
|
@ -52,3 +52,6 @@ class PlotWidget(GraphicsView):
|
|||||||
|
|
||||||
def getPlotItem(self):
|
def getPlotItem(self):
|
||||||
return self.plotItem
|
return self.plotItem
|
||||||
|
|
||||||
|
|
||||||
|
|
6
Point.py
6
Point.py
@ -92,20 +92,24 @@ class Point(QtCore.QPointF):
|
|||||||
return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1]))
|
return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1]))
|
||||||
|
|
||||||
def length(self):
|
def length(self):
|
||||||
|
"""Returns the vector length of this Point."""
|
||||||
return (self[0]**2 + self[1]**2) ** 0.5
|
return (self[0]**2 + self[1]**2) ** 0.5
|
||||||
|
|
||||||
def angle(self, a):
|
def angle(self, a):
|
||||||
|
"""Returns the angle between this vector and the vector a."""
|
||||||
n1 = self.length()
|
n1 = self.length()
|
||||||
n2 = a.length()
|
n2 = a.length()
|
||||||
if n1 == 0. or n2 == 0.:
|
if n1 == 0. or n2 == 0.:
|
||||||
return None
|
return None
|
||||||
ang = acos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0))
|
## Probably this should be done with arctan2 instead..
|
||||||
|
ang = acos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians
|
||||||
c = self.cross(a)
|
c = self.cross(a)
|
||||||
if c > 0:
|
if c > 0:
|
||||||
ang *= -1.
|
ang *= -1.
|
||||||
return ang
|
return ang
|
||||||
|
|
||||||
def dot(self, a):
|
def dot(self, a):
|
||||||
|
"""Returns the dot product of a and this Point."""
|
||||||
a = Point(a)
|
a = Point(a)
|
||||||
return self[0]*a[0] + self[1]*a[1]
|
return self[0]*a[0] + self[1]*a[1]
|
||||||
|
|
||||||
|
37
__init__.py
37
__init__.py
@ -0,0 +1,37 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
### import all the goodies and add some helper functions for easy CLI use
|
||||||
|
|
||||||
|
from functions import *
|
||||||
|
from graphicsItems import *
|
||||||
|
from graphicsWindows import *
|
||||||
|
#import PlotWidget
|
||||||
|
#import ImageView
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
|
plots = []
|
||||||
|
images = []
|
||||||
|
QAPP = None
|
||||||
|
|
||||||
|
def plot(*args, **kargs):
|
||||||
|
mkQApp()
|
||||||
|
if 'title' in kargs:
|
||||||
|
w = PlotWindow(title=kargs['title'])
|
||||||
|
del kargs['title']
|
||||||
|
else:
|
||||||
|
w = PlotWindow()
|
||||||
|
w.plot(*args, **kargs)
|
||||||
|
plots.append(w)
|
||||||
|
w.show()
|
||||||
|
return w
|
||||||
|
|
||||||
|
def show(*args, **kargs):
|
||||||
|
mkQApp()
|
||||||
|
w = ImageWindow(*args, **kargs)
|
||||||
|
images.append(w)
|
||||||
|
w.show()
|
||||||
|
return w
|
||||||
|
|
||||||
|
def mkQApp():
|
||||||
|
if QtGui.QApplication.instance() is None:
|
||||||
|
global QAPP
|
||||||
|
QAPP = QtGui.QApplication([])
|
@ -89,6 +89,6 @@ t.start(50)
|
|||||||
for i in range(0, 5):
|
for i in range(0, 5):
|
||||||
for j in range(0, 3):
|
for j in range(0, 3):
|
||||||
yd, xd = rand(10000)
|
yd, xd = rand(10000)
|
||||||
pw2.plot(yd*(j+1), xd, params={'iter': i, 'val': j})
|
pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j})
|
||||||
|
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
import sys, os
|
import sys, os
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||||
|
|
||||||
from scipy import zeros
|
from numpy import random
|
||||||
|
from scipy import zeros, ones
|
||||||
from pyqtgraph.graphicsWindows import *
|
from pyqtgraph.graphicsWindows import *
|
||||||
from pyqtgraph.graphicsItems import *
|
from pyqtgraph.graphicsItems import *
|
||||||
from pyqtgraph.widgets import *
|
from pyqtgraph.widgets import *
|
||||||
@ -84,6 +85,7 @@ rois.append(MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=mkPen(2)))
|
|||||||
rois.append(EllipseROI([110, 10], [30, 20], pen=mkPen(3)))
|
rois.append(EllipseROI([110, 10], [30, 20], pen=mkPen(3)))
|
||||||
rois.append(CircleROI([110, 50], [20, 20], pen=mkPen(4)))
|
rois.append(CircleROI([110, 50], [20, 20], pen=mkPen(4)))
|
||||||
rois.append(PolygonROI([[2,0], [2.1,0], [2,.1]], pen=mkPen(5)))
|
rois.append(PolygonROI([[2,0], [2.1,0], [2,.1]], pen=mkPen(5)))
|
||||||
|
#rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0)))
|
||||||
for r in rois:
|
for r in rois:
|
||||||
s.addItem(r)
|
s.addItem(r)
|
||||||
c = pi1.plot(pen=r.pen)
|
c = pi1.plot(pen=r.pen)
|
||||||
|
102
functions.py
102
functions.py
@ -5,6 +5,18 @@ Copyright 2010 Luke Campagnola
|
|||||||
Distributed under MIT/X11 license. See license.txt for more infomation.
|
Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
colorAbbrev = {
|
||||||
|
'b': (0,0,255,255),
|
||||||
|
'g': (0,255,0,255),
|
||||||
|
'r': (255,0,0,255),
|
||||||
|
'c': (0,255,255,255),
|
||||||
|
'm': (255,0,255,255),
|
||||||
|
'y': (255,255,0,255),
|
||||||
|
'k': (0,0,0,255),
|
||||||
|
'w': (255,255,255,255),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
from numpy import clip, floor, log
|
from numpy import clip, floor, log
|
||||||
|
|
||||||
@ -26,11 +38,25 @@ def siScale(x, minVal=1e-25):
|
|||||||
p = .001**m
|
p = .001**m
|
||||||
return (p, pref)
|
return (p, pref)
|
||||||
|
|
||||||
|
def mkBrush(color):
|
||||||
|
return QtGui.QBrush(mkColor(color))
|
||||||
|
|
||||||
|
def mkPen(arg=None, color=None, width=1, style=None, cosmetic=True, hsv=None, ):
|
||||||
|
"""Convenience function for making pens. Examples:
|
||||||
|
mkPen(color)
|
||||||
|
mkPen(color, width=2)
|
||||||
|
mkPen(cosmetic=False, width=4.5, color='r')
|
||||||
|
mkPen({'color': "FF0", width: 2})
|
||||||
|
"""
|
||||||
|
if isinstance(arg, dict):
|
||||||
|
return mkPen(**arg)
|
||||||
|
elif arg is not None:
|
||||||
|
if isinstance(arg, QtGui.QPen):
|
||||||
|
return arg
|
||||||
|
color = arg
|
||||||
|
|
||||||
def mkPen(color=None, width=1, style=None, cosmetic=True, hsv=None, ):
|
|
||||||
if color is None:
|
if color is None:
|
||||||
color = [255, 255, 255]
|
color = mkColor(200, 200, 200)
|
||||||
if hsv is not None:
|
if hsv is not None:
|
||||||
color = hsvColor(*hsv)
|
color = hsvColor(*hsv)
|
||||||
else:
|
else:
|
||||||
@ -48,20 +74,64 @@ def hsvColor(h, s=1.0, v=1.0, a=1.0):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
def mkColor(*args):
|
def mkColor(*args):
|
||||||
"""make a QColor from a variety of argument types"""
|
"""make a QColor from a variety of argument types
|
||||||
|
accepted types are:
|
||||||
|
r, g, b, [a]
|
||||||
|
(r, g, b, [a])
|
||||||
|
float (greyscale, 0.0-1.0)
|
||||||
|
int (uses intColor)
|
||||||
|
(int, hues) (uses intColor)
|
||||||
|
QColor
|
||||||
|
"c" (see colorAbbrev dictionary)
|
||||||
|
"RGB" (strings may optionally begin with "#")
|
||||||
|
"RGBA"
|
||||||
|
"RRGGBB"
|
||||||
|
"RRGGBBAA"
|
||||||
|
"""
|
||||||
err = 'Not sure how to make a color from "%s"' % str(args)
|
err = 'Not sure how to make a color from "%s"' % str(args)
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
if isinstance(args[0], QtGui.QColor):
|
if isinstance(args[0], QtGui.QColor):
|
||||||
return QtGui.QColor(args[0])
|
return QtGui.QColor(args[0])
|
||||||
|
elif isinstance(args[0], float):
|
||||||
|
r = g = b = int(args[0] * 255)
|
||||||
|
a = 255
|
||||||
|
elif isinstance(args[0], basestring):
|
||||||
|
c = args[0]
|
||||||
|
if c[0] == '#':
|
||||||
|
c = c[1:]
|
||||||
|
if len(c) == 1:
|
||||||
|
(r, g, b, a) = colorAbbrev[c]
|
||||||
|
if len(c) == 3:
|
||||||
|
r = int(c[0]*2, 16)
|
||||||
|
g = int(c[1]*2, 16)
|
||||||
|
b = int(c[2]*2, 16)
|
||||||
|
a = 255
|
||||||
|
elif len(c) == 4:
|
||||||
|
r = int(c[0]*2, 16)
|
||||||
|
g = int(c[1]*2, 16)
|
||||||
|
b = int(c[2]*2, 16)
|
||||||
|
a = int(c[3]*2, 16)
|
||||||
|
elif len(c) == 6:
|
||||||
|
r = int(c[0:2], 16)
|
||||||
|
g = int(c[2:4], 16)
|
||||||
|
b = int(c[4:6], 16)
|
||||||
|
a = 255
|
||||||
|
elif len(c) == 8:
|
||||||
|
r = int(c[0:2], 16)
|
||||||
|
g = int(c[2:4], 16)
|
||||||
|
b = int(c[4:6], 16)
|
||||||
|
a = int(c[6:8], 16)
|
||||||
elif hasattr(args[0], '__len__'):
|
elif hasattr(args[0], '__len__'):
|
||||||
if len(args[0]) == 3:
|
if len(args[0]) == 3:
|
||||||
(r, g, b) = args[0]
|
(r, g, b) = args[0]
|
||||||
a = 255
|
a = 255
|
||||||
elif len(args[0]) == 4:
|
elif len(args[0]) == 4:
|
||||||
(r, g, b, a) = args[0]
|
(r, g, b, a) = args[0]
|
||||||
|
elif len(args[0]) == 2:
|
||||||
|
return intColor(*args[0])
|
||||||
else:
|
else:
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
if type(args[0]) == int:
|
elif type(args[0]) == int:
|
||||||
return intColor(args[0])
|
return intColor(args[0])
|
||||||
else:
|
else:
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
@ -74,19 +144,27 @@ def mkColor(*args):
|
|||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
return QtGui.QColor(r, g, b, a)
|
return QtGui.QColor(r, g, b, a)
|
||||||
|
|
||||||
|
def colorTuple(c):
|
||||||
|
return (c.red(), c.blue(), c.green(), c.alpha())
|
||||||
|
|
||||||
def colorStr(c):
|
def colorStr(c):
|
||||||
"""Generate a hex string code from a QColor"""
|
"""Generate a hex string code from a QColor"""
|
||||||
return ('%02x'*4) % (c.red(), c.blue(), c.green(), c.alpha())
|
return ('%02x'*4) % colorTuple(c)
|
||||||
|
|
||||||
def intColor(ind, colors=9, values=3, maxValue=255, minValue=150, sat=255):
|
def intColor(index, hues=9, values=3, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255):
|
||||||
"""Creates a QColor from a single index. Useful for stepping through a predefined list of colors."""
|
"""Creates a QColor from a single index. Useful for stepping through a predefined list of colors.
|
||||||
colors = int(colors)
|
- The argument "index" determines which color from the set will be returned
|
||||||
|
- All other arguments determine what the set of predefined colors will be
|
||||||
|
|
||||||
|
Colors are chosen by cycling across hues while varying the value (brightness). By default, there
|
||||||
|
are 9 hues and 3 values for a total of 27 different colors. """
|
||||||
|
hues = int(hues)
|
||||||
values = int(values)
|
values = int(values)
|
||||||
ind = int(ind) % (colors * values)
|
ind = int(index) % (hues * values)
|
||||||
indh = ind % colors
|
indh = ind % hues
|
||||||
indv = ind / colors
|
indv = ind / hues
|
||||||
v = minValue + indv * ((maxValue-minValue) / (values-1))
|
v = minValue + indv * ((maxValue-minValue) / (values-1))
|
||||||
h = (indh * 360) / colors
|
h = minHue + (indh * (maxHue-minHue)) / hues
|
||||||
|
|
||||||
c = QtGui.QColor()
|
c = QtGui.QColor()
|
||||||
c.setHsv(h, sat, v)
|
c.setHsv(h, sat, v)
|
||||||
|
315
graphicsItems.py
315
graphicsItems.py
@ -9,6 +9,8 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
|
|||||||
|
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
|
if not hasattr(QtCore, 'Signal'):
|
||||||
|
QtCore.Signal = QtCore.pyqtSignal
|
||||||
from ObjectWorkaround import *
|
from ObjectWorkaround import *
|
||||||
#tryWorkaround(QtCore, QtGui)
|
#tryWorkaround(QtCore, QtGui)
|
||||||
#from numpy import *
|
#from numpy import *
|
||||||
@ -178,7 +180,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
|||||||
else:
|
else:
|
||||||
useWeave = False
|
useWeave = False
|
||||||
|
|
||||||
def __init__(self, image=None, copy=True, parent=None, *args):
|
def __init__(self, image=None, copy=True, parent=None, border=None, *args):
|
||||||
QObjectWorkaround.__init__(self)
|
QObjectWorkaround.__init__(self)
|
||||||
self.qimage = QtGui.QImage()
|
self.qimage = QtGui.QImage()
|
||||||
self.pixmap = None
|
self.pixmap = None
|
||||||
@ -189,6 +191,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
|||||||
self.image = None
|
self.image = None
|
||||||
self.clipLevel = None
|
self.clipLevel = None
|
||||||
self.drawKernel = None
|
self.drawKernel = None
|
||||||
|
if border is not None:
|
||||||
|
border = mkPen(border)
|
||||||
|
self.border = border
|
||||||
|
|
||||||
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
|
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
|
||||||
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
|
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
|
||||||
@ -248,9 +253,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
|||||||
else:
|
else:
|
||||||
gotNewData = True
|
gotNewData = True
|
||||||
if copy:
|
if copy:
|
||||||
self.image = image.copy()
|
self.image = image.view(np.ndarray).copy()
|
||||||
else:
|
else:
|
||||||
self.image = image
|
self.image = image.view(np.ndarray)
|
||||||
#print " image max:", self.image.max(), "min:", self.image.min()
|
#print " image max:", self.image.max(), "min:", self.image.min()
|
||||||
|
|
||||||
# Determine scale factors
|
# Determine scale factors
|
||||||
@ -381,22 +386,36 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
|||||||
def setDrawKernel(self, kernel=None):
|
def setDrawKernel(self, kernel=None):
|
||||||
self.drawKernel = kernel
|
self.drawKernel = kernel
|
||||||
|
|
||||||
|
def paint(self, p, *args):
|
||||||
|
|
||||||
|
#QtGui.QGraphicsPixmapItem.paint(self, p, *args)
|
||||||
|
if self.pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
p.drawPixmap(self.boundingRect(), self.pixmap, QtCore.QRectF(0, 0, self.pixmap.width(), self.pixmap.height()))
|
||||||
|
if self.border is not None:
|
||||||
|
p.setPen(self.border)
|
||||||
|
p.drawRect(self.boundingRect())
|
||||||
|
|
||||||
|
|
||||||
class PlotCurveItem(GraphicsObject):
|
class PlotCurveItem(GraphicsObject):
|
||||||
"""Class representing a single plot curve."""
|
"""Class representing a single plot curve."""
|
||||||
def __init__(self, y=None, x=None, copy=False, pen=None, shadow=None, parent=None, color=None):
|
|
||||||
|
sigClicked = QtCore.Signal(object)
|
||||||
|
|
||||||
|
def __init__(self, y=None, x=None, copy=False, pen=None, shadow=None, parent=None, color=None, clickable=False):
|
||||||
GraphicsObject.__init__(self, parent)
|
GraphicsObject.__init__(self, parent)
|
||||||
|
#GraphicsWidget.__init__(self, parent)
|
||||||
self.free()
|
self.free()
|
||||||
#self.dispPath = None
|
#self.dispPath = None
|
||||||
|
|
||||||
if pen is None:
|
if pen is None:
|
||||||
if color is None:
|
if color is None:
|
||||||
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
|
self.setPen((200,200,200))
|
||||||
else:
|
else:
|
||||||
pen = QtGui.QPen(color)
|
self.setPen(color)
|
||||||
self.pen = pen
|
else:
|
||||||
|
self.setPen(pen)
|
||||||
|
|
||||||
self.shadow = shadow
|
self.shadow = shadow
|
||||||
if y is not None:
|
if y is not None:
|
||||||
@ -414,8 +433,13 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
'alphaMode': False
|
'alphaMode': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.setClickable(clickable)
|
||||||
#self.fps = None
|
#self.fps = None
|
||||||
|
|
||||||
|
def setClickable(self, s):
|
||||||
|
self.clickable = s
|
||||||
|
|
||||||
|
|
||||||
def getData(self):
|
def getData(self):
|
||||||
if self.xData is None:
|
if self.xData is None:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
@ -497,7 +521,7 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
return self.metaData
|
return self.metaData
|
||||||
|
|
||||||
def setPen(self, pen):
|
def setPen(self, pen):
|
||||||
self.pen = pen
|
self.pen = mkPen(pen)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setColor(self, color):
|
def setColor(self, color):
|
||||||
@ -547,6 +571,13 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
x = np.array(x)
|
x = np.array(x)
|
||||||
if not isinstance(data, np.ndarray) or data.ndim > 2:
|
if not isinstance(data, np.ndarray) or data.ndim > 2:
|
||||||
raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape))
|
raise Exception("Plot data must be 1 or 2D ndarray (data shape is %s)" % str(data.shape))
|
||||||
|
if x == None:
|
||||||
|
if 'complex' in str(data.dtype):
|
||||||
|
raise Exception("Can not plot complex data types.")
|
||||||
|
else:
|
||||||
|
if 'complex' in str(data.dtype)+str(x.dtype):
|
||||||
|
raise Exception("Can not plot complex data types.")
|
||||||
|
|
||||||
if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows.
|
if data.ndim == 2: ### If data is 2D array, then assume x and y values are in first two columns or rows.
|
||||||
if x is not None:
|
if x is not None:
|
||||||
raise Exception("Plot data may be 2D only if no x argument is supplied.")
|
raise Exception("Plot data may be 2D only if no x argument is supplied.")
|
||||||
@ -691,10 +722,181 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
self.path = None
|
self.path = None
|
||||||
#del self.xData, self.yData, self.xDisp, self.yDisp, self.path
|
#del self.xData, self.yData, self.xDisp, self.yDisp, self.path
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
#GraphicsObject.mousePressEvent(self, ev)
|
||||||
|
if not self.clickable:
|
||||||
|
ev.ignore()
|
||||||
|
if ev.button() != QtCore.Qt.LeftButton:
|
||||||
|
ev.ignore()
|
||||||
|
self.mousePressPos = ev.pos()
|
||||||
|
self.mouseMoved = False
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
#GraphicsObject.mouseMoveEvent(self, ev)
|
||||||
|
self.mouseMoved = True
|
||||||
|
print "move"
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, ev):
|
||||||
|
#GraphicsObject.mouseReleaseEvent(self, ev)
|
||||||
|
if not self.mouseMoved:
|
||||||
|
self.sigClicked.emit(self)
|
||||||
|
|
||||||
|
|
||||||
|
class CurvePoint(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||||
|
"""A GraphicsItem that sets its location to a point on a PlotCurveItem.
|
||||||
|
The position along the curve is a property, and thus can be easily animated."""
|
||||||
|
|
||||||
|
def __init__(self, curve, index=0, pos=None):
|
||||||
|
"""Position can be set either as an index referring to the sample number or
|
||||||
|
the position 0.0 - 1.0"""
|
||||||
|
|
||||||
class ScatterPlotItem(QtGui.QGraphicsItem):
|
|
||||||
def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5):
|
|
||||||
QtGui.QGraphicsItem.__init__(self)
|
QtGui.QGraphicsItem.__init__(self)
|
||||||
|
QObjectWorkaround.__init__(self)
|
||||||
|
self.curve = None
|
||||||
|
self.setProperty('position', 0.0)
|
||||||
|
self.setProperty('index', 0)
|
||||||
|
|
||||||
|
if hasattr(self, 'ItemHasNoContents'):
|
||||||
|
self.setFlags(self.flags() | self.ItemHasNoContents)
|
||||||
|
|
||||||
|
self.curve = curve
|
||||||
|
self.setParentItem(curve)
|
||||||
|
if pos is not None:
|
||||||
|
self.setPos(pos)
|
||||||
|
else:
|
||||||
|
self.setIndex(index)
|
||||||
|
|
||||||
|
def setPos(self, pos):
|
||||||
|
self.setProperty('position', pos)
|
||||||
|
|
||||||
|
def setIndex(self, index):
|
||||||
|
self.setProperty('index', index)
|
||||||
|
|
||||||
|
def event(self, ev):
|
||||||
|
if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if ev.propertyName() == 'index':
|
||||||
|
index = self.property('index').toInt()[0]
|
||||||
|
elif ev.propertyName() == 'position':
|
||||||
|
index = None
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
(x, y) = self.curve.getData()
|
||||||
|
if index is None:
|
||||||
|
#print self.property('position').toDouble()[0], self.property('position').typeName()
|
||||||
|
index = (len(x)-1) * clip(self.property('position').toDouble()[0], 0.0, 1.0)
|
||||||
|
|
||||||
|
if index != int(index):
|
||||||
|
i1 = int(index)
|
||||||
|
i2 = clip(i1+1, 0, len(x)-1)
|
||||||
|
s2 = index-i1
|
||||||
|
s1 = 1.0-s2
|
||||||
|
newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2)
|
||||||
|
else:
|
||||||
|
index = int(index)
|
||||||
|
i1 = clip(index-1, 0, len(x)-1)
|
||||||
|
i2 = clip(index+1, 0, len(x)-1)
|
||||||
|
newPos = (x[index], y[index])
|
||||||
|
|
||||||
|
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())
|
||||||
|
self.resetTransform()
|
||||||
|
self.rotate(180+ ang * 180 / np.pi)
|
||||||
|
QtGui.QGraphicsItem.setPos(self, *newPos)
|
||||||
|
|
||||||
|
def boundingRect(self):
|
||||||
|
return QtCore.QRectF()
|
||||||
|
|
||||||
|
def paint(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1):
|
||||||
|
anim = QtCore.QPropertyAnimation(self._qObj_, prop)
|
||||||
|
anim.setDuration(duration)
|
||||||
|
anim.setStartValue(start)
|
||||||
|
anim.setEndValue(end)
|
||||||
|
anim.setLoopCount(loop)
|
||||||
|
return anim
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ArrowItem(QtGui.QGraphicsPolygonItem):
|
||||||
|
def __init__(self, **opts):
|
||||||
|
QtGui.QGraphicsPolygonItem.__init__(self)
|
||||||
|
defOpts = {
|
||||||
|
'style': 'tri',
|
||||||
|
'pxMode': True,
|
||||||
|
'size': 20,
|
||||||
|
'angle': -150,
|
||||||
|
'pos': (0,0),
|
||||||
|
'width': 8,
|
||||||
|
'tipAngle': 25,
|
||||||
|
'baseAngle': 90,
|
||||||
|
'pen': (200,200,200),
|
||||||
|
'brush': (50,50,200),
|
||||||
|
}
|
||||||
|
defOpts.update(opts)
|
||||||
|
|
||||||
|
self.setStyle(**defOpts)
|
||||||
|
|
||||||
|
self.setPen(mkPen(defOpts['pen']))
|
||||||
|
self.setBrush(mkBrush(defOpts['brush']))
|
||||||
|
|
||||||
|
self.rotate(self.opts['angle'])
|
||||||
|
self.moveBy(*self.opts['pos'])
|
||||||
|
|
||||||
|
def setStyle(self, **opts):
|
||||||
|
self.opts = opts
|
||||||
|
|
||||||
|
if opts['style'] == 'tri':
|
||||||
|
points = [
|
||||||
|
QtCore.QPointF(0,0),
|
||||||
|
QtCore.QPointF(opts['size'],-opts['width']/2.),
|
||||||
|
QtCore.QPointF(opts['size'],opts['width']/2.),
|
||||||
|
]
|
||||||
|
poly = QtGui.QPolygonF(points)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception("Unrecognized arrow style '%s'" % opts['style'])
|
||||||
|
|
||||||
|
self.setPolygon(poly)
|
||||||
|
|
||||||
|
if opts['pxMode']:
|
||||||
|
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||||
|
else:
|
||||||
|
self.setFlags(self.flags() & ~self.ItemIgnoresTransformations)
|
||||||
|
|
||||||
|
def paint(self, p, *args):
|
||||||
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||||
|
QtGui.QGraphicsPolygonItem.paint(self, p, *args)
|
||||||
|
|
||||||
|
class CurveArrow(CurvePoint):
|
||||||
|
"""Provides an arrow that points to any specific sample on a PlotCurveItem.
|
||||||
|
Provides properties that can be animated."""
|
||||||
|
|
||||||
|
def __init__(self, curve, index=0, pos=None, **opts):
|
||||||
|
CurvePoint.__init__(self, curve, index=index, pos=pos)
|
||||||
|
if opts.get('pxMode', True):
|
||||||
|
opts['pxMode'] = False
|
||||||
|
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||||
|
opts['angle'] = 0
|
||||||
|
self.arrow = ArrowItem(**opts)
|
||||||
|
self.arrow.setParentItem(self)
|
||||||
|
|
||||||
|
def setStyle(**opts):
|
||||||
|
return self.arrow.setStyle(**opts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ScatterPlotItem(QtGui.QGraphicsWidget):
|
||||||
|
|
||||||
|
sigPointClicked = QtCore.Signal(object)
|
||||||
|
|
||||||
|
def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5):
|
||||||
|
QtGui.QGraphicsWidget.__init__(self)
|
||||||
self.spots = []
|
self.spots = []
|
||||||
self.range = [[0,0], [0,0]]
|
self.range = [[0,0], [0,0]]
|
||||||
|
|
||||||
@ -740,7 +942,8 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
|||||||
brush = s.get('brush', self.brush)
|
brush = s.get('brush', self.brush)
|
||||||
pen = s.get('pen', self.pen)
|
pen = s.get('pen', self.pen)
|
||||||
pen.setCosmetic(True)
|
pen.setCosmetic(True)
|
||||||
item = self.mkSpot(pos, size, self.pxMode, brush, pen)
|
data = s.get('data', None)
|
||||||
|
item = self.mkSpot(pos, size, self.pxMode, brush, pen, data)
|
||||||
self.spots.append(item)
|
self.spots.append(item)
|
||||||
if xmn is None:
|
if xmn is None:
|
||||||
xmn = pos[0]-size
|
xmn = pos[0]-size
|
||||||
@ -755,10 +958,11 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
|||||||
self.range = [[xmn, xmx], [ymn, ymx]]
|
self.range = [[xmn, xmx], [ymn, ymx]]
|
||||||
|
|
||||||
|
|
||||||
def mkSpot(self, pos, size, pxMode, brush, pen):
|
def mkSpot(self, pos, size, pxMode, brush, pen, data):
|
||||||
item = SpotItem(size, pxMode, brush, pen)
|
item = SpotItem(size, pxMode, brush, pen, data)
|
||||||
item.setParentItem(self)
|
item.setParentItem(self)
|
||||||
item.setPos(pos)
|
item.setPos(pos)
|
||||||
|
item.sigClicked.connect(self.pointClicked)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
@ -769,11 +973,17 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
|||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def pointClicked(self, point):
|
||||||
|
self.sigPointClicked.emit(point)
|
||||||
|
|
||||||
|
def points(self):
|
||||||
|
return self.spots[:]
|
||||||
|
|
||||||
class SpotItem(QtGui.QGraphicsItem):
|
class SpotItem(QtGui.QGraphicsWidget):
|
||||||
def __init__(self, size, pxMode, brush, pen):
|
sigClicked = QtCore.Signal(object)
|
||||||
QtGui.QGraphicsItem.__init__(self)
|
|
||||||
|
def __init__(self, size, pxMode, brush, pen, data):
|
||||||
|
QtGui.QGraphicsWidget.__init__(self)
|
||||||
if pxMode:
|
if pxMode:
|
||||||
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||||
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
|
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
|
||||||
@ -782,6 +992,15 @@ class SpotItem(QtGui.QGraphicsItem):
|
|||||||
self.path = QtGui.QPainterPath()
|
self.path = QtGui.QPainterPath()
|
||||||
s2 = size/2.
|
s2 = size/2.
|
||||||
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size))
|
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size))
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def setBrush(self, brush):
|
||||||
|
self.brush = mkBrush(brush)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def setPen(self, pen):
|
||||||
|
self.pen = mkPen(pen)
|
||||||
|
self.update()
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.path.boundingRect()
|
return self.path.boundingRect()
|
||||||
@ -794,9 +1013,27 @@ class SpotItem(QtGui.QGraphicsItem):
|
|||||||
p.setBrush(self.brush)
|
p.setBrush(self.brush)
|
||||||
p.drawPath(self.path)
|
p.drawPath(self.path)
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
QtGui.QGraphicsItem.mousePressEvent(self, ev)
|
||||||
|
if ev.button() == QtCore.Qt.LeftButton:
|
||||||
|
self.mouseMoved = False
|
||||||
|
ev.accept()
|
||||||
|
else:
|
||||||
|
ev.ignore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
QtGui.QGraphicsItem.mouseMoveEvent(self, ev)
|
||||||
|
self.mouseMoved = True
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, ev):
|
||||||
|
QtGui.QGraphicsItem.mouseReleaseEvent(self, ev)
|
||||||
|
if not self.mouseMoved:
|
||||||
|
self.sigClicked.emit(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ROIPlotItem(PlotCurveItem):
|
class ROIPlotItem(PlotCurveItem):
|
||||||
def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
|
def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
|
||||||
@ -806,7 +1043,7 @@ class ROIPlotItem(PlotCurveItem):
|
|||||||
self.axes = axes
|
self.axes = axes
|
||||||
self.xVals = xVals
|
self.xVals = xVals
|
||||||
PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color)
|
PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color)
|
||||||
roi.connect(QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
|
roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
|
||||||
#self.roiChangedEvent()
|
#self.roiChangedEvent()
|
||||||
|
|
||||||
def getRoiData(self):
|
def getRoiData(self):
|
||||||
@ -1371,7 +1608,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
|||||||
#self.picture = None
|
#self.picture = None
|
||||||
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
|
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
|
||||||
|
|
||||||
self.drawFrame = True
|
self.drawFrame = False
|
||||||
|
|
||||||
self.mouseEnabled = [True, True]
|
self.mouseEnabled = [True, True]
|
||||||
|
|
||||||
@ -1519,6 +1756,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
|||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
|
QtGui.QGraphicsWidget.mouseMoveEvent(self, ev)
|
||||||
pos = np.array([ev.pos().x(), ev.pos().y()])
|
pos = np.array([ev.pos().x(), ev.pos().y()])
|
||||||
dif = pos - self.mousePos
|
dif = pos - self.mousePos
|
||||||
dif *= -1
|
dif *= -1
|
||||||
@ -1549,11 +1787,14 @@ class ViewBox(QtGui.QGraphicsWidget):
|
|||||||
ev.ignore()
|
ev.ignore()
|
||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
|
QtGui.QGraphicsWidget.mousePressEvent(self, ev)
|
||||||
|
|
||||||
self.mousePos = np.array([ev.pos().x(), ev.pos().y()])
|
self.mousePos = np.array([ev.pos().x(), ev.pos().y()])
|
||||||
self.pressPos = self.mousePos.copy()
|
self.pressPos = self.mousePos.copy()
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
|
QtGui.QGraphicsWidget.mouseReleaseEvent(self, ev)
|
||||||
pos = np.array([ev.pos().x(), ev.pos().y()])
|
pos = np.array([ev.pos().x(), ev.pos().y()])
|
||||||
#if sum(abs(self.pressPos - pos)) < 3: ## Detect click
|
#if sum(abs(self.pressPos - pos)) < 3: ## Detect click
|
||||||
#if ev.button() == QtCore.Qt.RightButton:
|
#if ev.button() == QtCore.Qt.RightButton:
|
||||||
@ -1645,14 +1886,12 @@ class InfiniteLine(GraphicsObject):
|
|||||||
self.maxRange = [None, None]
|
self.maxRange = [None, None]
|
||||||
else:
|
else:
|
||||||
self.maxRange = bounds
|
self.maxRange = bounds
|
||||||
self.movable = movable
|
self.setMovable(movable)
|
||||||
self.view = weakref.ref(view)
|
self.view = weakref.ref(view)
|
||||||
self.p = [0, 0]
|
self.p = [0, 0]
|
||||||
self.setAngle(angle)
|
self.setAngle(angle)
|
||||||
self.setPos(pos)
|
self.setPos(pos)
|
||||||
|
|
||||||
if movable:
|
|
||||||
self.setAcceptHoverEvents(True)
|
|
||||||
|
|
||||||
self.hasMoved = False
|
self.hasMoved = False
|
||||||
|
|
||||||
@ -1666,6 +1905,11 @@ class InfiniteLine(GraphicsObject):
|
|||||||
#QtCore.QObject.connect(p, QtCore.SIGNAL('viewChanged'), self.updateLine)
|
#QtCore.QObject.connect(p, QtCore.SIGNAL('viewChanged'), self.updateLine)
|
||||||
QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateLine)
|
QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateLine)
|
||||||
|
|
||||||
|
def setMovable(self, m):
|
||||||
|
self.movable = m
|
||||||
|
self.setAcceptHoverEvents(m)
|
||||||
|
|
||||||
|
|
||||||
def setBounds(self, bounds):
|
def setBounds(self, bounds):
|
||||||
self.maxRange = bounds
|
self.maxRange = bounds
|
||||||
self.setValue(self.value())
|
self.setValue(self.value())
|
||||||
@ -1851,7 +2095,6 @@ class LinearRegionItem(GraphicsObject):
|
|||||||
self.rect.setParentItem(self)
|
self.rect.setParentItem(self)
|
||||||
self.bounds = QtCore.QRectF()
|
self.bounds = QtCore.QRectF()
|
||||||
self.view = weakref.ref(view)
|
self.view = weakref.ref(view)
|
||||||
|
|
||||||
self.setBrush = self.rect.setBrush
|
self.setBrush = self.rect.setBrush
|
||||||
self.brush = self.rect.brush
|
self.brush = self.rect.brush
|
||||||
|
|
||||||
@ -1867,17 +2110,22 @@ class LinearRegionItem(GraphicsObject):
|
|||||||
|
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
l.setParentItem(self)
|
l.setParentItem(self)
|
||||||
l.connect(QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
|
l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
|
||||||
l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved)
|
l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved)
|
||||||
|
|
||||||
if brush is None:
|
if brush is None:
|
||||||
brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
|
brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
|
||||||
self.setBrush(brush)
|
self.setBrush(brush)
|
||||||
|
self.setMovable(movable)
|
||||||
|
|
||||||
def setBounds(self, bounds):
|
def setBounds(self, bounds):
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
l.setBounds(bounds)
|
l.setBounds(bounds)
|
||||||
|
|
||||||
|
def setMovable(self, m):
|
||||||
|
for l in self.lines:
|
||||||
|
l.setMovable(m)
|
||||||
|
self.movable = m
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.rect.boundingRect()
|
return self.rect.boundingRect()
|
||||||
@ -1904,6 +2152,9 @@ class LinearRegionItem(GraphicsObject):
|
|||||||
self.rect.setRect(vb)
|
self.rect.setRect(vb)
|
||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
|
if not self.movable:
|
||||||
|
ev.ignore()
|
||||||
|
return
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
l.mousePressEvent(ev) ## pass event to both lines so they move together
|
l.mousePressEvent(ev) ## pass event to both lines so they move together
|
||||||
#if self.movable and ev.button() == QtCore.Qt.LeftButton:
|
#if self.movable and ev.button() == QtCore.Qt.LeftButton:
|
||||||
@ -1918,6 +2169,8 @@ class LinearRegionItem(GraphicsObject):
|
|||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
#print "move", ev.pos()
|
#print "move", ev.pos()
|
||||||
|
if not self.movable:
|
||||||
|
return
|
||||||
self.lines[0].blockSignals(True) # only want to update once
|
self.lines[0].blockSignals(True) # only want to update once
|
||||||
for l in self.lines:
|
for l in self.lines:
|
||||||
l.mouseMoveEvent(ev)
|
l.mouseMoveEvent(ev)
|
||||||
@ -1945,7 +2198,7 @@ class VTickGroup(QtGui.QGraphicsPathItem):
|
|||||||
if xvals is None:
|
if xvals is None:
|
||||||
xvals = []
|
xvals = []
|
||||||
if pen is None:
|
if pen is None:
|
||||||
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
|
pen = (200, 200, 200)
|
||||||
self.ticks = []
|
self.ticks = []
|
||||||
self.xvals = []
|
self.xvals = []
|
||||||
if view is None:
|
if view is None:
|
||||||
@ -1958,14 +2211,9 @@ class VTickGroup(QtGui.QGraphicsPathItem):
|
|||||||
self.setXVals(xvals)
|
self.setXVals(xvals)
|
||||||
self.valid = False
|
self.valid = False
|
||||||
|
|
||||||
|
def setPen(self, pen):
|
||||||
#def setPen(self, pen=None):
|
pen = mkPen(pen)
|
||||||
#if pen is None:
|
QtGui.QGraphicsPathItem.setPen(self, pen)
|
||||||
#pen = self.pen
|
|
||||||
#self.pen = pen
|
|
||||||
#for t in self.ticks:
|
|
||||||
#t.setPen(pen)
|
|
||||||
##self.update()
|
|
||||||
|
|
||||||
def setXVals(self, vals):
|
def setXVals(self, vals):
|
||||||
self.xvals = vals
|
self.xvals = vals
|
||||||
@ -2091,7 +2339,6 @@ class GridItem(UIGraphicsItem):
|
|||||||
x = ul[1]
|
x = ul[1]
|
||||||
ul[1] = br[1]
|
ul[1] = br[1]
|
||||||
br[1] = x
|
br[1] = x
|
||||||
|
|
||||||
for i in range(2, -1, -1): ## Draw three different scales of grid
|
for i in range(2, -1, -1): ## Draw three different scales of grid
|
||||||
|
|
||||||
dist = br-ul
|
dist = br-ul
|
||||||
|
@ -9,30 +9,119 @@ from PyQt4 import QtCore, QtGui
|
|||||||
from PlotWidget import *
|
from PlotWidget import *
|
||||||
from ImageView import *
|
from ImageView import *
|
||||||
QAPP = None
|
QAPP = None
|
||||||
class PlotWindow(QtGui.QMainWindow):
|
|
||||||
def __init__(self, title=None):
|
def mkQApp():
|
||||||
if QtGui.QApplication.instance() is None:
|
if QtGui.QApplication.instance() is None:
|
||||||
global QAPP
|
global QAPP
|
||||||
QAPP = QtGui.QApplication([])
|
QAPP = QtGui.QApplication([])
|
||||||
|
|
||||||
|
class GraphicsLayoutWidget(GraphicsView):
|
||||||
|
def __init__(self):
|
||||||
|
GraphicsView.__init__(self)
|
||||||
|
self.items = {}
|
||||||
|
self.currentRow = 0
|
||||||
|
self.currentCol = 0
|
||||||
|
|
||||||
|
def nextRow(self):
|
||||||
|
"""Advance to next row for automatic item placement"""
|
||||||
|
self.currentRow += 1
|
||||||
|
self.currentCol = 0
|
||||||
|
|
||||||
|
def nextCol(self, colspan=1):
|
||||||
|
"""Advance to next column, while returning the current column number
|
||||||
|
(generally only for internal use)"""
|
||||||
|
self.currentCol += colspan
|
||||||
|
return self.currentCol-colspan
|
||||||
|
|
||||||
|
def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
|
||||||
|
plot = PlotItem(**kargs)
|
||||||
|
self.addItem(plot, row, col, rowspan, colspan)
|
||||||
|
return plot
|
||||||
|
|
||||||
|
def addItem(self, item, row=None, col=None, rowspan=1, colspan=1):
|
||||||
|
if row not in self.items:
|
||||||
|
self.items[row] = {}
|
||||||
|
self.items[row][col] = item
|
||||||
|
|
||||||
|
if row is None:
|
||||||
|
row = self.currentRow
|
||||||
|
if col is None:
|
||||||
|
col = self.nextCol(colspan)
|
||||||
|
self.centralLayout.addItem(item, row, col, rowspan, colspan)
|
||||||
|
|
||||||
|
def getItem(self, row, col):
|
||||||
|
return self.items[row][col]
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicsWindow(GraphicsLayoutWidget):
|
||||||
|
def __init__(self, title=None, size=(800,600)):
|
||||||
|
mkQApp()
|
||||||
|
self.win = QtGui.QMainWindow()
|
||||||
|
GraphicsLayoutWidget.__init__(self)
|
||||||
|
self.win.setCentralWidget(self)
|
||||||
|
self.win.resize(*size)
|
||||||
|
if title is not None:
|
||||||
|
self.win.setWindowTitle(title)
|
||||||
|
self.win.show()
|
||||||
|
|
||||||
|
|
||||||
|
class TabWindow(QtGui.QMainWindow):
|
||||||
|
def __init__(self, title=None, size=(800,600)):
|
||||||
|
mkQApp()
|
||||||
QtGui.QMainWindow.__init__(self)
|
QtGui.QMainWindow.__init__(self)
|
||||||
self.cw = PlotWidget()
|
self.resize(*size)
|
||||||
|
self.cw = QtGui.QTabWidget()
|
||||||
self.setCentralWidget(self.cw)
|
self.setCentralWidget(self.cw)
|
||||||
for m in ['plot', 'autoRange', 'addItem', 'removeItem', 'setLabel', 'clear']:
|
|
||||||
setattr(self, m, getattr(self.cw, m))
|
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
class ImageWindow(QtGui.QMainWindow):
|
def __getattr__(self, attr):
|
||||||
def __init__(self, title=None):
|
if hasattr(self.cw, attr):
|
||||||
if QtGui.QApplication.instance() is None:
|
return getattr(self.cw, attr)
|
||||||
global QAPP
|
else:
|
||||||
QAPP = QtGui.QApplication([])
|
raise NameError(attr)
|
||||||
QtGui.QMainWindow.__init__(self)
|
|
||||||
self.cw = ImageView()
|
|
||||||
self.setCentralWidget(self.cw)
|
#class PlotWindow(QtGui.QMainWindow):
|
||||||
for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']:
|
#def __init__(self, title=None, **kargs):
|
||||||
setattr(self, m, getattr(self.cw, m))
|
#mkQApp()
|
||||||
|
#QtGui.QMainWindow.__init__(self)
|
||||||
|
#self.cw = PlotWidget(**kargs)
|
||||||
|
#self.setCentralWidget(self.cw)
|
||||||
|
#for m in ['plot', 'autoRange', 'addItem', 'removeItem', 'setLabel', 'clear', 'viewRect']:
|
||||||
|
#setattr(self, m, getattr(self.cw, m))
|
||||||
|
#if title is not None:
|
||||||
|
#self.setWindowTitle(title)
|
||||||
|
#self.show()
|
||||||
|
|
||||||
|
|
||||||
|
class PlotWindow(PlotWidget):
|
||||||
|
def __init__(self, title=None, **kargs):
|
||||||
|
mkQApp()
|
||||||
|
self.win = QtGui.QMainWindow()
|
||||||
|
PlotWidget.__init__(self, **kargs)
|
||||||
|
self.win.setCentralWidget(self)
|
||||||
|
for m in ['resize']:
|
||||||
|
setattr(self, m, getattr(self.win, m))
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.setWindowTitle(title)
|
self.win.setWindowTitle(title)
|
||||||
self.show()
|
self.win.show()
|
||||||
|
|
||||||
|
|
||||||
|
class ImageWindow(ImageView):
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
mkQApp()
|
||||||
|
self.win = QtGui.QMainWindow()
|
||||||
|
if 'title' in kargs:
|
||||||
|
self.win.setWindowTitle(kargs['title'])
|
||||||
|
del kargs['title']
|
||||||
|
ImageView.__init__(self, self.win)
|
||||||
|
if len(args) > 0 or len(kargs) > 0:
|
||||||
|
self.setImage(*args, **kargs)
|
||||||
|
self.win.setCentralWidget(self)
|
||||||
|
for m in ['resize']:
|
||||||
|
setattr(self, m, getattr(self.win, m))
|
||||||
|
#for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']:
|
||||||
|
#setattr(self, m, getattr(self.cw, m))
|
||||||
|
self.win.show()
|
||||||
|
251
widgets.py
251
widgets.py
@ -53,6 +53,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
self.setPos(pos)
|
self.setPos(pos)
|
||||||
self.rotate(-angle * 180. / np.pi)
|
self.rotate(-angle * 180. / np.pi)
|
||||||
self.setZValue(10)
|
self.setZValue(10)
|
||||||
|
self.isMoving = False
|
||||||
|
|
||||||
self.handleSize = 5
|
self.handleSize = 5
|
||||||
self.invertible = invertible
|
self.invertible = invertible
|
||||||
@ -144,6 +145,11 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.")
|
raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.")
|
||||||
return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item})
|
return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item})
|
||||||
|
|
||||||
|
def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None):
|
||||||
|
pos = Point(pos)
|
||||||
|
center = Point(center)
|
||||||
|
return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item})
|
||||||
|
|
||||||
def addHandle(self, info):
|
def addHandle(self, info):
|
||||||
if not info.has_key('item') or info['item'] is None:
|
if not info.has_key('item') or info['item'] is None:
|
||||||
#print "BEFORE ADD CHILD:", self.childItems()
|
#print "BEFORE ADD CHILD:", self.childItems()
|
||||||
@ -203,15 +209,23 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
if ev.button() == QtCore.Qt.LeftButton:
|
if ev.button() == QtCore.Qt.LeftButton:
|
||||||
self.setSelected(True)
|
self.setSelected(True)
|
||||||
if self.translatable:
|
if self.translatable:
|
||||||
|
self.isMoving = True
|
||||||
|
self.preMoveState = self.getState()
|
||||||
self.cursorOffset = self.scenePos() - ev.scenePos()
|
self.cursorOffset = self.scenePos() - ev.scenePos()
|
||||||
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
elif ev.button() == QtCore.Qt.RightButton:
|
||||||
|
if self.isMoving:
|
||||||
|
ev.accept()
|
||||||
|
self.cancelMove()
|
||||||
|
else:
|
||||||
|
ev.ignore()
|
||||||
else:
|
else:
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
#print "mouse move", ev.pos()
|
#print "mouse move", ev.pos()
|
||||||
if self.translatable:
|
if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
|
||||||
snap = None
|
snap = None
|
||||||
if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
|
if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
|
||||||
snap = Point(self.snapSize, self.snapSize)
|
snap = Point(self.snapSize, self.snapSize)
|
||||||
@ -221,18 +235,25 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
if self.translatable:
|
if self.translatable:
|
||||||
|
self.isMoving = False
|
||||||
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
||||||
|
|
||||||
|
def cancelMove(self):
|
||||||
|
self.isMoving = False
|
||||||
|
self.setState(self.preMoveState)
|
||||||
|
|
||||||
def pointPressEvent(self, pt, ev):
|
def pointPressEvent(self, pt, ev):
|
||||||
#print "press"
|
#print "press"
|
||||||
|
self.isMoving = True
|
||||||
|
self.preMoveState = self.getState()
|
||||||
|
|
||||||
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
||||||
#self.pressPos = self.mapFromScene(ev.scenePos())
|
#self.pressPos = self.mapFromScene(ev.scenePos())
|
||||||
#self.pressHandlePos = self.handles[pt]['item'].pos()
|
#self.pressHandlePos = self.handles[pt]['item'].pos()
|
||||||
|
|
||||||
def pointReleaseEvent(self, pt, ev):
|
def pointReleaseEvent(self, pt, ev):
|
||||||
#print "release"
|
#print "release"
|
||||||
|
self.isMoving = False
|
||||||
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
||||||
|
|
||||||
def stateCopy(self):
|
def stateCopy(self):
|
||||||
@ -262,14 +283,20 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
|
|
||||||
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
||||||
#print "movePoint() called."
|
#print "movePoint() called."
|
||||||
|
## pos is the new position of the handle in scene coords, as requested by the handle.
|
||||||
|
|
||||||
newState = self.stateCopy()
|
newState = self.stateCopy()
|
||||||
h = self.handles[pt]
|
h = self.handles[pt]
|
||||||
#p0 = self.mapToScene(h['item'].pos())
|
#p0 = self.mapToScene(h['item'].pos())
|
||||||
|
## p0 is current (before move) position of handle in scene coords
|
||||||
p0 = self.mapToScene(h['pos'] * self.state['size'])
|
p0 = self.mapToScene(h['pos'] * self.state['size'])
|
||||||
p1 = Point(pos)
|
p1 = Point(pos)
|
||||||
|
|
||||||
|
## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why.
|
||||||
p0 = self.mapSceneToParent(p0)
|
p0 = self.mapSceneToParent(p0)
|
||||||
p1 = self.mapSceneToParent(p1)
|
p1 = self.mapSceneToParent(p1)
|
||||||
|
|
||||||
|
## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1)
|
||||||
if h.has_key('center'):
|
if h.has_key('center'):
|
||||||
c = h['center']
|
c = h['center']
|
||||||
cs = c * self.state['size']
|
cs = c * self.state['size']
|
||||||
@ -296,15 +323,18 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
#cs = c * self.state['size']
|
#cs = c * self.state['size']
|
||||||
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
|
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
|
||||||
|
|
||||||
|
## If a handle and its center have the same x or y value, we can't scale across that axis.
|
||||||
if h['center'][0] == h['pos'][0]:
|
if h['center'][0] == h['pos'][0]:
|
||||||
lp1[0] = 0
|
lp1[0] = 0
|
||||||
if h['center'][1] == h['pos'][1]:
|
if h['center'][1] == h['pos'][1]:
|
||||||
lp1[1] = 0
|
lp1[1] = 0
|
||||||
|
|
||||||
|
## snap
|
||||||
if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
|
if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
|
||||||
lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize
|
lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize
|
||||||
lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize
|
lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize
|
||||||
|
|
||||||
|
## determine scale factors and new size of ROI
|
||||||
hs = h['pos'] - c
|
hs = h['pos'] - c
|
||||||
if hs[0] == 0:
|
if hs[0] == 0:
|
||||||
hs[0] = 1
|
hs[0] = 1
|
||||||
@ -312,6 +342,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
hs[1] = 1
|
hs[1] = 1
|
||||||
newSize = lp1 / hs
|
newSize = lp1 / hs
|
||||||
|
|
||||||
|
## Perform some corrections and limit checks
|
||||||
if newSize[0] == 0:
|
if newSize[0] == 0:
|
||||||
newSize[0] = newState['size'][0]
|
newSize[0] = newState['size'][0]
|
||||||
if newSize[1] == 0:
|
if newSize[1] == 0:
|
||||||
@ -324,10 +355,12 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
if self.aspectLocked:
|
if self.aspectLocked:
|
||||||
newSize[0] = newSize[1]
|
newSize[0] = newSize[1]
|
||||||
|
|
||||||
|
## Move ROI so the center point occupies the same scene location after the scale
|
||||||
s0 = c * self.state['size']
|
s0 = c * self.state['size']
|
||||||
s1 = c * newSize
|
s1 = c * newSize
|
||||||
cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))
|
cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))
|
||||||
|
|
||||||
|
## update state, do more boundary checks
|
||||||
newState['size'] = newSize
|
newState['size'] = newSize
|
||||||
newState['pos'] = newState['pos'] + cc
|
newState['pos'] = newState['pos'] + cc
|
||||||
if self.maxBounds is not None:
|
if self.maxBounds is not None:
|
||||||
@ -339,30 +372,31 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self.state = newState
|
self.state = newState
|
||||||
|
|
||||||
|
## move handles to their new locations
|
||||||
self.updateHandles()
|
self.updateHandles()
|
||||||
|
|
||||||
elif h['type'] == 'r':
|
elif h['type'] in ['r', 'rf']:
|
||||||
#newState = self.stateCopy()
|
## If the handle is directly over its center point, we can't compute an angle.
|
||||||
#c = h['center']
|
|
||||||
#cs = c * self.state['size']
|
|
||||||
#p0 = Point(h['item'].pos()) - cs
|
|
||||||
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
|
|
||||||
if lp1.length() == 0 or lp0.length() == 0:
|
if lp1.length() == 0 or lp0.length() == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
## determine new rotation angle, constrained if necessary
|
||||||
ang = newState['angle'] + lp0.angle(lp1)
|
ang = newState['angle'] + lp0.angle(lp1)
|
||||||
if ang is None:
|
if ang is None: ## this should never happen..
|
||||||
return
|
return
|
||||||
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
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.)
|
||||||
|
|
||||||
|
## create rotation transform
|
||||||
tr = QtGui.QTransform()
|
tr = QtGui.QTransform()
|
||||||
tr.rotate(-ang * 180. / np.pi)
|
tr.rotate(-ang * 180. / np.pi)
|
||||||
|
|
||||||
|
## mvoe ROI so that center point remains stationary after rotate
|
||||||
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
|
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
|
||||||
newState['angle'] = ang
|
newState['angle'] = ang
|
||||||
newState['pos'] = newState['pos'] + cc
|
newState['pos'] = newState['pos'] + cc
|
||||||
|
|
||||||
|
## check boundaries, update
|
||||||
if self.maxBounds is not None:
|
if self.maxBounds is not None:
|
||||||
r = self.stateRect(newState)
|
r = self.stateRect(newState)
|
||||||
if not self.maxBounds.contains(r):
|
if not self.maxBounds.contains(r):
|
||||||
@ -371,6 +405,45 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
self.setPos(newState['pos'], update=False)
|
self.setPos(newState['pos'], update=False)
|
||||||
self.state = newState
|
self.state = newState
|
||||||
|
|
||||||
|
## If this is a free-rotate handle, its distance from the center may change.
|
||||||
|
|
||||||
|
if h['type'] == 'rf':
|
||||||
|
h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle
|
||||||
|
|
||||||
|
|
||||||
|
#elif h['type'] == 'rf':
|
||||||
|
### If the handle is directly over its center point, we can't compute an angle.
|
||||||
|
#if lp1.length() == 0 or lp0.length() == 0:
|
||||||
|
#return
|
||||||
|
|
||||||
|
### determine new rotation angle, constrained if necessary
|
||||||
|
#pos = Point(pos)
|
||||||
|
#ang = newState['angle'] + lp0.angle(lp1)
|
||||||
|
#if ang is None:
|
||||||
|
##h['item'].setPos(self.mapFromScene(Point(pos[0], 0.0))) ## changes ROI coordinates of handle
|
||||||
|
#h['item'].setPos(self.mapFromScene(pos))
|
||||||
|
#return
|
||||||
|
#if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
||||||
|
#ang = round(ang / (np.pi/12.)) * (np.pi/12.)
|
||||||
|
|
||||||
|
|
||||||
|
#tr = QtGui.QTransform()
|
||||||
|
#tr.rotate(-ang * 180. / np.pi)
|
||||||
|
|
||||||
|
#cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
|
||||||
|
#newState['angle'] = ang
|
||||||
|
#newState['pos'] = newState['pos'] + cc
|
||||||
|
#if self.maxBounds is not None:
|
||||||
|
#r = self.stateRect(newState)
|
||||||
|
#if not self.maxBounds.contains(r):
|
||||||
|
#return
|
||||||
|
#self.setTransform(tr)
|
||||||
|
#self.setPos(newState['pos'], update=False)
|
||||||
|
#self.state = newState
|
||||||
|
|
||||||
|
#h['item'].setPos(self.mapFromScene(pos)) ## changes ROI coordinates of handle
|
||||||
|
##self.emit(QtCore.SIGNAL('regionChanged'), self)
|
||||||
|
|
||||||
elif h['type'] == 'sr':
|
elif h['type'] == 'sr':
|
||||||
#newState = self.stateCopy()
|
#newState = self.stateCopy()
|
||||||
if h['center'][0] == h['pos'][0]:
|
if h['center'][0] == h['pos'][0]:
|
||||||
@ -419,6 +492,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
self.handleChange()
|
self.handleChange()
|
||||||
|
|
||||||
def handleChange(self):
|
def handleChange(self):
|
||||||
|
"""The state of the ROI has changed; redraw if needed."""
|
||||||
#print "handleChange() called."
|
#print "handleChange() called."
|
||||||
changed = False
|
changed = False
|
||||||
#print "self.lastState:", self.lastState
|
#print "self.lastState:", self.lastState
|
||||||
@ -447,6 +521,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
self.setPos(self.state['pos'])
|
self.setPos(self.state['pos'])
|
||||||
self.updateHandles()
|
self.updateHandles()
|
||||||
|
|
||||||
|
|
||||||
def translate(self, *args, **kargs):
|
def translate(self, *args, **kargs):
|
||||||
"""accepts either (x, y, snap) or ([x,y], snap) as arguments"""
|
"""accepts either (x, y, snap) or ([x,y], snap) as arguments"""
|
||||||
if 'snap' not in kargs:
|
if 'snap' not in kargs:
|
||||||
@ -659,6 +734,16 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
|||||||
|
|
||||||
|
|
||||||
class Handle(QtGui.QGraphicsItem):
|
class Handle(QtGui.QGraphicsItem):
|
||||||
|
|
||||||
|
types = { ## defines number of sides, start angle for each handle type
|
||||||
|
't': (4, np.pi/4),
|
||||||
|
'f': (4, np.pi/4),
|
||||||
|
's': (4, 0),
|
||||||
|
'r': (12, 0),
|
||||||
|
'sr': (12, 0),
|
||||||
|
'rf': (12, 0),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, radius, typ=None, pen=QtGui.QPen(QtGui.QColor(200, 200, 220)), parent=None):
|
def __init__(self, radius, typ=None, pen=QtGui.QPen(QtGui.QColor(200, 200, 220)), parent=None):
|
||||||
#print " create item with parent", parent
|
#print " create item with parent", parent
|
||||||
self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10)
|
self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10)
|
||||||
@ -672,24 +757,8 @@ class Handle(QtGui.QGraphicsItem):
|
|||||||
self.pen = pen
|
self.pen = pen
|
||||||
self.pen.setWidth(0)
|
self.pen.setWidth(0)
|
||||||
self.pen.setCosmetic(True)
|
self.pen.setCosmetic(True)
|
||||||
if typ == 't':
|
self.isMoving = False
|
||||||
self.sides = 4
|
self.sides, self.startAng = self.types[typ]
|
||||||
self.startAng = np.pi/4
|
|
||||||
elif typ == 'f':
|
|
||||||
self.sides = 4
|
|
||||||
self.startAng = np.pi/4
|
|
||||||
elif typ == 's':
|
|
||||||
self.sides = 4
|
|
||||||
self.startAng = 0
|
|
||||||
elif typ == 'r':
|
|
||||||
self.sides = 12
|
|
||||||
self.startAng = 0
|
|
||||||
elif typ == 'sr':
|
|
||||||
self.sides = 12
|
|
||||||
self.startAng = 0
|
|
||||||
else:
|
|
||||||
self.sides = 4
|
|
||||||
self.startAng = np.pi/4
|
|
||||||
|
|
||||||
def connectROI(self, roi, i):
|
def connectROI(self, roi, i):
|
||||||
self.roi.append((roi, i))
|
self.roi.append((roi, i))
|
||||||
@ -699,30 +768,44 @@ class Handle(QtGui.QGraphicsItem):
|
|||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
#print "handle press"
|
#print "handle press"
|
||||||
if ev.button() != QtCore.Qt.LeftButton:
|
if ev.button() == QtCore.Qt.LeftButton:
|
||||||
|
self.isMoving = True
|
||||||
|
self.cursorOffset = self.scenePos() - ev.scenePos()
|
||||||
|
for r in self.roi:
|
||||||
|
r[0].pointPressEvent(r[1], ev)
|
||||||
|
#print " accepted."
|
||||||
|
ev.accept()
|
||||||
|
elif ev.button() == QtCore.Qt.RightButton:
|
||||||
|
if self.isMoving:
|
||||||
|
self.isMoving = False ## prevents any further motion
|
||||||
|
for r in self.roi:
|
||||||
|
r[0].cancelMove()
|
||||||
|
ev.accept()
|
||||||
|
else:
|
||||||
|
ev.ignore()
|
||||||
|
else:
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
return
|
|
||||||
self.cursorOffset = self.scenePos() - ev.scenePos()
|
|
||||||
for r in self.roi:
|
|
||||||
r[0].pointPressEvent(r[1], ev)
|
|
||||||
#print " accepted."
|
|
||||||
ev.accept()
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
#print "release"
|
#print "release"
|
||||||
for r in self.roi:
|
if ev.button() == QtCore.Qt.LeftButton:
|
||||||
r[0].pointReleaseEvent(r[1], ev)
|
self.isMoving = False
|
||||||
|
for r in self.roi:
|
||||||
|
r[0].pointReleaseEvent(r[1], ev)
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
#print "handle mouseMove", ev.pos()
|
#print "handle mouseMove", ev.pos()
|
||||||
pos = ev.scenePos() + self.cursorOffset
|
if self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
|
||||||
self.movePoint(pos, ev.modifiers())
|
pos = ev.scenePos() + self.cursorOffset
|
||||||
|
self.movePoint(pos, ev.modifiers())
|
||||||
|
|
||||||
def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
||||||
for r in self.roi:
|
for r in self.roi:
|
||||||
if not r[0].checkPointMove(r[1], pos, modifiers):
|
if not r[0].checkPointMove(r[1], pos, modifiers):
|
||||||
return
|
return
|
||||||
#print "point moved; inform %d ROIs" % len(self.roi)
|
#print "point moved; inform %d ROIs" % len(self.roi)
|
||||||
|
# A handle can be used by multiple ROIs; tell each to update its handle position
|
||||||
for r in self.roi:
|
for r in self.roi:
|
||||||
r[0].movePoint(r[1], pos, modifiers)
|
r[0].movePoint(r[1], pos, modifiers)
|
||||||
|
|
||||||
@ -915,6 +998,7 @@ class PolygonROI(ROI):
|
|||||||
#ROI.__init__(self, positions[0])
|
#ROI.__init__(self, positions[0])
|
||||||
for p in positions:
|
for p in positions:
|
||||||
self.addFreeHandle(p)
|
self.addFreeHandle(p)
|
||||||
|
self.setZValue(1000)
|
||||||
|
|
||||||
def listPoints(self):
|
def listPoints(self):
|
||||||
return [p['item'].pos() for p in self.handles]
|
return [p['item'].pos() for p in self.handles]
|
||||||
@ -922,6 +1006,8 @@ class PolygonROI(ROI):
|
|||||||
def movePoint(self, *args, **kargs):
|
def movePoint(self, *args, **kargs):
|
||||||
ROI.movePoint(self, *args, **kargs)
|
ROI.movePoint(self, *args, **kargs)
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
|
for h in self.handles:
|
||||||
|
h['pos'] = h['item'].pos()
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||||
@ -934,7 +1020,7 @@ class PolygonROI(ROI):
|
|||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
r = QtCore.QRectF()
|
r = QtCore.QRectF()
|
||||||
for h in self.handles:
|
for h in self.handles:
|
||||||
r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect()
|
r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def shape(self):
|
def shape(self):
|
||||||
@ -944,3 +1030,88 @@ class PolygonROI(ROI):
|
|||||||
p.lineTo(self.handles[i]['item'].pos())
|
p.lineTo(self.handles[i]['item'].pos())
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
def stateCopy(self):
|
||||||
|
sc = {}
|
||||||
|
sc['pos'] = Point(self.state['pos'])
|
||||||
|
sc['size'] = Point(self.state['size'])
|
||||||
|
sc['angle'] = self.state['angle']
|
||||||
|
#sc['handles'] = self.handles
|
||||||
|
return sc
|
||||||
|
|
||||||
|
class SpiralROI(ROI):
|
||||||
|
def __init__(self, pos=None, size=None, **args):
|
||||||
|
if size == None:
|
||||||
|
size = [100e-6,100e-6]
|
||||||
|
if pos == None:
|
||||||
|
pos = [0,0]
|
||||||
|
ROI.__init__(self, pos, size, **args)
|
||||||
|
self.translateSnap = False
|
||||||
|
self.addFreeHandle([0.25,0], name='a')
|
||||||
|
self.addRotateFreeHandle([1,0], [0,0], name='r')
|
||||||
|
#self.getRadius()
|
||||||
|
#QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self.
|
||||||
|
|
||||||
|
|
||||||
|
def getRadius(self):
|
||||||
|
radius = Point(self.handles[1]['item'].pos()).length()
|
||||||
|
#r2 = radius[1]
|
||||||
|
#r3 = r2[0]
|
||||||
|
return radius
|
||||||
|
|
||||||
|
def boundingRect(self):
|
||||||
|
r = self.getRadius()
|
||||||
|
return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r)
|
||||||
|
#return self.bounds
|
||||||
|
|
||||||
|
def movePoint(self, *args, **kargs):
|
||||||
|
ROI.movePoint(self, *args, **kargs)
|
||||||
|
self.prepareGeometryChange()
|
||||||
|
for h in self.handles:
|
||||||
|
h['pos'] = h['item'].pos()/self.state['size'][0]
|
||||||
|
|
||||||
|
def handleChange(self):
|
||||||
|
ROI.handleChange(self)
|
||||||
|
if len(self.handles) > 1:
|
||||||
|
self.path = QtGui.QPainterPath()
|
||||||
|
h0 = Point(self.handles[0]['item'].pos()).length()
|
||||||
|
a = h0/(2.0*np.pi)
|
||||||
|
theta = 30.0*(2.0*np.pi)/360.0
|
||||||
|
self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta)))
|
||||||
|
x0 = a*theta*cos(theta)
|
||||||
|
y0 = a*theta*sin(theta)
|
||||||
|
radius = self.getRadius()
|
||||||
|
theta += 20.0*(2.0*np.pi)/360.0
|
||||||
|
i = 0
|
||||||
|
while Point(x0, y0).length() < radius and i < 1000:
|
||||||
|
x1 = a*theta*cos(theta)
|
||||||
|
y1 = a*theta*sin(theta)
|
||||||
|
self.path.lineTo(QtCore.QPointF(x1,y1))
|
||||||
|
theta += 20.0*(2.0*np.pi)/360.0
|
||||||
|
x0 = x1
|
||||||
|
y0 = y1
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
def shape(self):
|
||||||
|
p = QtGui.QPainterPath()
|
||||||
|
p.addEllipse(self.boundingRect())
|
||||||
|
return p
|
||||||
|
|
||||||
|
def paint(self, p, *args):
|
||||||
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||||
|
#path = self.shape()
|
||||||
|
p.setPen(self.pen)
|
||||||
|
p.drawPath(self.path)
|
||||||
|
p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
|
||||||
|
p.drawPath(self.shape())
|
||||||
|
p.setPen(QtGui.QPen(QtGui.QColor(0,0,255)))
|
||||||
|
p.drawRect(self.boundingRect())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user