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.sceneObj = QtGui.QGraphicsScene()
|
||||
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.setCentralItem(QtGui.QGraphicsWidget())
|
||||
self.centralLayout = QtGui.QGraphicsGridLayout()
|
||||
self.centralWidget.setLayout(self.centralLayout)
|
||||
|
||||
self.mouseEnabled = False
|
||||
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
|
||||
self.clickAccepted = False
|
||||
@ -85,6 +91,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
ev.ignore()
|
||||
|
||||
def setCentralItem(self, item):
|
||||
"""Sets a QGraphicsWidget to automatically fill the entire view."""
|
||||
if self.centralWidget is not None:
|
||||
self.scene().removeItem(self.centralWidget)
|
||||
self.centralWidget = item
|
||||
@ -304,21 +311,20 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
#self.currentItem = None
|
||||
|
||||
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)
|
||||
if not self.mouseEnabled:
|
||||
return
|
||||
self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
|
||||
#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.
|
||||
return
|
||||
|
||||
delta = Point(ev.pos()) - self.lastMousePos
|
||||
|
||||
self.lastMousePos = Point(ev.pos())
|
||||
|
||||
if ev.buttons() == QtCore.Qt.RightButton:
|
||||
delta = Point(clip(delta[0], -50, 50), clip(-delta[1], -50, 50))
|
||||
@ -328,7 +334,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
|
||||
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.
|
||||
tr = -delta / self.currentScale
|
||||
|
||||
@ -376,6 +381,10 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
self.render(painter)
|
||||
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):
|
||||
|
||||
|
20
ImageView.py
20
ImageView.py
@ -106,12 +106,12 @@ class ImageView(QtGui.QWidget):
|
||||
setattr(self, fn, getattr(self.ui.graphicsView, fn))
|
||||
|
||||
#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.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
|
||||
QtCore.QObject.connect(self.ui.gradientWidget, QtCore.SIGNAL('gradientChanged'), self.updateImage)
|
||||
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.normDivideRadio, 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.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), 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')
|
||||
|
||||
@ -347,7 +347,19 @@ class ImageView(QtGui.QWidget):
|
||||
self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
|
||||
elif img.ndim == 4:
|
||||
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
|
||||
if autoLevels:
|
||||
|
@ -1,9 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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:
|
||||
def __init__(self):
|
||||
self._qObj_ = QtCore.QObject()
|
||||
self._qObj_ = Obj()
|
||||
self.connect(QtCore.SIGNAL('event'), self.event)
|
||||
def connect(self, *args):
|
||||
if args[0] is self:
|
||||
return QtCore.QObject.connect(self._qObj_, *args[1:])
|
||||
@ -15,8 +23,20 @@ class QObjectWorkaround:
|
||||
return QtCore.QObject.emit(self._qObj_, *args)
|
||||
def blockSignals(self, 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):
|
||||
def __init__(self, *args):
|
||||
QtGui.QGraphicsItem.__init__(self, *args)
|
||||
QObjectWorkaround.__init__(self)
|
||||
#class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
#def __init__(self, *args):
|
||||
#QtGui.QGraphicsItem.__init__(self, *args)
|
||||
#QObjectWorkaround.__init__(self)
|
||||
|
||||
class QGraphicsObject(QtGui.QGraphicsWidget):
|
||||
def shape(self):
|
||||
return QtGui.QGraphicsItem.shape(self)
|
||||
|
||||
#QGraphicsObject = QtGui.QGraphicsObject
|
@ -51,4 +51,7 @@ class PlotWidget(GraphicsView):
|
||||
return self.plotItem.restoreState(state)
|
||||
|
||||
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]))
|
||||
|
||||
def length(self):
|
||||
"""Returns the vector length of this Point."""
|
||||
return (self[0]**2 + self[1]**2) ** 0.5
|
||||
|
||||
def angle(self, a):
|
||||
"""Returns the angle between this vector and the vector a."""
|
||||
n1 = self.length()
|
||||
n2 = a.length()
|
||||
if n1 == 0. or n2 == 0.:
|
||||
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)
|
||||
if c > 0:
|
||||
ang *= -1.
|
||||
return ang
|
||||
|
||||
def dot(self, a):
|
||||
"""Returns the dot product of a and this Point."""
|
||||
a = Point(a)
|
||||
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 j in range(0, 3):
|
||||
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_()
|
||||
|
@ -4,7 +4,8 @@
|
||||
import sys, os
|
||||
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.graphicsItems 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(CircleROI([110, 50], [20, 20], pen=mkPen(4)))
|
||||
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:
|
||||
s.addItem(r)
|
||||
c = pi1.plot(pen=r.pen)
|
||||
|
104
functions.py
104
functions.py
@ -5,6 +5,18 @@ Copyright 2010 Luke Campagnola
|
||||
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 numpy import clip, floor, log
|
||||
|
||||
@ -26,11 +38,25 @@ def siScale(x, minVal=1e-25):
|
||||
p = .001**m
|
||||
return (p, pref)
|
||||
|
||||
def mkBrush(color):
|
||||
return QtGui.QBrush(mkColor(color))
|
||||
|
||||
|
||||
def mkPen(color=None, width=1, style=None, cosmetic=True, hsv=None, ):
|
||||
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
|
||||
|
||||
if color is None:
|
||||
color = [255, 255, 255]
|
||||
color = mkColor(200, 200, 200)
|
||||
if hsv is not None:
|
||||
color = hsvColor(*hsv)
|
||||
else:
|
||||
@ -48,20 +74,64 @@ def hsvColor(h, s=1.0, v=1.0, a=1.0):
|
||||
return c
|
||||
|
||||
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)
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], QtGui.QColor):
|
||||
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__'):
|
||||
if len(args[0]) == 3:
|
||||
(r, g, b) = args[0]
|
||||
a = 255
|
||||
elif len(args[0]) == 4:
|
||||
(r, g, b, a) = args[0]
|
||||
elif len(args[0]) == 2:
|
||||
return intColor(*args[0])
|
||||
else:
|
||||
raise Exception(err)
|
||||
if type(args[0]) == int:
|
||||
elif type(args[0]) == int:
|
||||
return intColor(args[0])
|
||||
else:
|
||||
raise Exception(err)
|
||||
@ -74,19 +144,27 @@ def mkColor(*args):
|
||||
raise Exception(err)
|
||||
return QtGui.QColor(r, g, b, a)
|
||||
|
||||
def colorTuple(c):
|
||||
return (c.red(), c.blue(), c.green(), c.alpha())
|
||||
|
||||
def colorStr(c):
|
||||
"""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):
|
||||
"""Creates a QColor from a single index. Useful for stepping through a predefined list of colors."""
|
||||
colors = int(colors)
|
||||
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.
|
||||
- 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)
|
||||
ind = int(ind) % (colors * values)
|
||||
indh = ind % colors
|
||||
indv = ind / colors
|
||||
ind = int(index) % (hues * values)
|
||||
indh = ind % hues
|
||||
indv = ind / hues
|
||||
v = minValue + indv * ((maxValue-minValue) / (values-1))
|
||||
h = (indh * 360) / colors
|
||||
h = minHue + (indh * (maxHue-minHue)) / hues
|
||||
|
||||
c = QtGui.QColor()
|
||||
c.setHsv(h, sat, v)
|
||||
|
321
graphicsItems.py
321
graphicsItems.py
@ -9,6 +9,8 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
|
||||
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
if not hasattr(QtCore, 'Signal'):
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
from ObjectWorkaround import *
|
||||
#tryWorkaround(QtCore, QtGui)
|
||||
#from numpy import *
|
||||
@ -178,7 +180,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
||||
else:
|
||||
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)
|
||||
self.qimage = QtGui.QImage()
|
||||
self.pixmap = None
|
||||
@ -189,6 +191,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
||||
self.image = None
|
||||
self.clipLevel = None
|
||||
self.drawKernel = None
|
||||
if border is not None:
|
||||
border = mkPen(border)
|
||||
self.border = border
|
||||
|
||||
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
|
||||
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
|
||||
@ -248,9 +253,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
||||
else:
|
||||
gotNewData = True
|
||||
if copy:
|
||||
self.image = image.copy()
|
||||
self.image = image.view(np.ndarray).copy()
|
||||
else:
|
||||
self.image = image
|
||||
self.image = image.view(np.ndarray)
|
||||
#print " image max:", self.image.max(), "min:", self.image.min()
|
||||
|
||||
# Determine scale factors
|
||||
@ -381,22 +386,36 @@ class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
|
||||
def setDrawKernel(self, kernel=None):
|
||||
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 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)
|
||||
#GraphicsWidget.__init__(self, parent)
|
||||
self.free()
|
||||
#self.dispPath = None
|
||||
|
||||
if pen is None:
|
||||
if color is None:
|
||||
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
|
||||
self.setPen((200,200,200))
|
||||
else:
|
||||
pen = QtGui.QPen(color)
|
||||
self.pen = pen
|
||||
self.setPen(color)
|
||||
else:
|
||||
self.setPen(pen)
|
||||
|
||||
self.shadow = shadow
|
||||
if y is not None:
|
||||
@ -414,8 +433,13 @@ class PlotCurveItem(GraphicsObject):
|
||||
'alphaMode': False
|
||||
}
|
||||
|
||||
self.setClickable(clickable)
|
||||
#self.fps = None
|
||||
|
||||
def setClickable(self, s):
|
||||
self.clickable = s
|
||||
|
||||
|
||||
def getData(self):
|
||||
if self.xData is None:
|
||||
return (None, None)
|
||||
@ -497,7 +521,7 @@ class PlotCurveItem(GraphicsObject):
|
||||
return self.metaData
|
||||
|
||||
def setPen(self, pen):
|
||||
self.pen = pen
|
||||
self.pen = mkPen(pen)
|
||||
self.update()
|
||||
|
||||
def setColor(self, color):
|
||||
@ -547,6 +571,13 @@ class PlotCurveItem(GraphicsObject):
|
||||
x = np.array(x)
|
||||
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))
|
||||
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 x is not None:
|
||||
raise Exception("Plot data may be 2D only if no x argument is supplied.")
|
||||
@ -691,10 +722,181 @@ class PlotCurveItem(GraphicsObject):
|
||||
self.path = None
|
||||
#del self.xData, self.yData, self.xDisp, self.yDisp, self.path
|
||||
|
||||
|
||||
class ScatterPlotItem(QtGui.QGraphicsItem):
|
||||
def __init__(self, spots=None, pxMode=True, pen=None, brush=None, size=5):
|
||||
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"""
|
||||
|
||||
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.range = [[0,0], [0,0]]
|
||||
|
||||
@ -740,7 +942,8 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
||||
brush = s.get('brush', self.brush)
|
||||
pen = s.get('pen', self.pen)
|
||||
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)
|
||||
if xmn is None:
|
||||
xmn = pos[0]-size
|
||||
@ -755,10 +958,11 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
||||
self.range = [[xmn, xmx], [ymn, ymx]]
|
||||
|
||||
|
||||
def mkSpot(self, pos, size, pxMode, brush, pen):
|
||||
item = SpotItem(size, pxMode, brush, pen)
|
||||
def mkSpot(self, pos, size, pxMode, brush, pen, data):
|
||||
item = SpotItem(size, pxMode, brush, pen, data)
|
||||
item.setParentItem(self)
|
||||
item.setPos(pos)
|
||||
item.sigClicked.connect(self.pointClicked)
|
||||
return item
|
||||
|
||||
def boundingRect(self):
|
||||
@ -769,11 +973,17 @@ class ScatterPlotItem(QtGui.QGraphicsItem):
|
||||
def paint(self, p, *args):
|
||||
pass
|
||||
|
||||
def pointClicked(self, point):
|
||||
self.sigPointClicked.emit(point)
|
||||
|
||||
def points(self):
|
||||
return self.spots[:]
|
||||
|
||||
class SpotItem(QtGui.QGraphicsItem):
|
||||
def __init__(self, size, pxMode, brush, pen):
|
||||
QtGui.QGraphicsItem.__init__(self)
|
||||
class SpotItem(QtGui.QGraphicsWidget):
|
||||
sigClicked = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, size, pxMode, brush, pen, data):
|
||||
QtGui.QGraphicsWidget.__init__(self)
|
||||
if pxMode:
|
||||
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
|
||||
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
|
||||
@ -782,6 +992,15 @@ class SpotItem(QtGui.QGraphicsItem):
|
||||
self.path = QtGui.QPainterPath()
|
||||
s2 = size/2.
|
||||
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):
|
||||
return self.path.boundingRect()
|
||||
@ -794,8 +1013,26 @@ class SpotItem(QtGui.QGraphicsItem):
|
||||
p.setBrush(self.brush)
|
||||
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):
|
||||
@ -806,7 +1043,7 @@ class ROIPlotItem(PlotCurveItem):
|
||||
self.axes = axes
|
||||
self.xVals = xVals
|
||||
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()
|
||||
|
||||
def getRoiData(self):
|
||||
@ -1371,7 +1608,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
#self.picture = None
|
||||
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
|
||||
|
||||
self.drawFrame = True
|
||||
self.drawFrame = False
|
||||
|
||||
self.mouseEnabled = [True, True]
|
||||
|
||||
@ -1519,6 +1756,7 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
ev.accept()
|
||||
|
||||
def mouseMoveEvent(self, ev):
|
||||
QtGui.QGraphicsWidget.mouseMoveEvent(self, ev)
|
||||
pos = np.array([ev.pos().x(), ev.pos().y()])
|
||||
dif = pos - self.mousePos
|
||||
dif *= -1
|
||||
@ -1549,11 +1787,14 @@ class ViewBox(QtGui.QGraphicsWidget):
|
||||
ev.ignore()
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
QtGui.QGraphicsWidget.mousePressEvent(self, ev)
|
||||
|
||||
self.mousePos = np.array([ev.pos().x(), ev.pos().y()])
|
||||
self.pressPos = self.mousePos.copy()
|
||||
ev.accept()
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
QtGui.QGraphicsWidget.mouseReleaseEvent(self, ev)
|
||||
pos = np.array([ev.pos().x(), ev.pos().y()])
|
||||
#if sum(abs(self.pressPos - pos)) < 3: ## Detect click
|
||||
#if ev.button() == QtCore.Qt.RightButton:
|
||||
@ -1645,14 +1886,12 @@ class InfiniteLine(GraphicsObject):
|
||||
self.maxRange = [None, None]
|
||||
else:
|
||||
self.maxRange = bounds
|
||||
self.movable = movable
|
||||
self.setMovable(movable)
|
||||
self.view = weakref.ref(view)
|
||||
self.p = [0, 0]
|
||||
self.setAngle(angle)
|
||||
self.setPos(pos)
|
||||
|
||||
if movable:
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
self.hasMoved = False
|
||||
|
||||
@ -1665,7 +1904,12 @@ class InfiniteLine(GraphicsObject):
|
||||
#for p in self.getBoundingParents():
|
||||
#QtCore.QObject.connect(p, 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):
|
||||
self.maxRange = bounds
|
||||
self.setValue(self.value())
|
||||
@ -1851,7 +2095,6 @@ class LinearRegionItem(GraphicsObject):
|
||||
self.rect.setParentItem(self)
|
||||
self.bounds = QtCore.QRectF()
|
||||
self.view = weakref.ref(view)
|
||||
|
||||
self.setBrush = self.rect.setBrush
|
||||
self.brush = self.rect.brush
|
||||
|
||||
@ -1867,18 +2110,23 @@ class LinearRegionItem(GraphicsObject):
|
||||
|
||||
for l in self.lines:
|
||||
l.setParentItem(self)
|
||||
l.connect(QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
|
||||
l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved)
|
||||
l.connect(l, QtCore.SIGNAL('positionChangeFinished'), self.lineMoveFinished)
|
||||
l.connect(l, QtCore.SIGNAL('positionChanged'), self.lineMoved)
|
||||
|
||||
if brush is None:
|
||||
brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
|
||||
self.setBrush(brush)
|
||||
self.setMovable(movable)
|
||||
|
||||
def setBounds(self, bounds):
|
||||
for l in self.lines:
|
||||
l.setBounds(bounds)
|
||||
|
||||
|
||||
def setMovable(self, m):
|
||||
for l in self.lines:
|
||||
l.setMovable(m)
|
||||
self.movable = m
|
||||
|
||||
def boundingRect(self):
|
||||
return self.rect.boundingRect()
|
||||
|
||||
@ -1904,6 +2152,9 @@ class LinearRegionItem(GraphicsObject):
|
||||
self.rect.setRect(vb)
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
if not self.movable:
|
||||
ev.ignore()
|
||||
return
|
||||
for l in self.lines:
|
||||
l.mousePressEvent(ev) ## pass event to both lines so they move together
|
||||
#if self.movable and ev.button() == QtCore.Qt.LeftButton:
|
||||
@ -1918,6 +2169,8 @@ class LinearRegionItem(GraphicsObject):
|
||||
|
||||
def mouseMoveEvent(self, ev):
|
||||
#print "move", ev.pos()
|
||||
if not self.movable:
|
||||
return
|
||||
self.lines[0].blockSignals(True) # only want to update once
|
||||
for l in self.lines:
|
||||
l.mouseMoveEvent(ev)
|
||||
@ -1945,7 +2198,7 @@ class VTickGroup(QtGui.QGraphicsPathItem):
|
||||
if xvals is None:
|
||||
xvals = []
|
||||
if pen is None:
|
||||
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
|
||||
pen = (200, 200, 200)
|
||||
self.ticks = []
|
||||
self.xvals = []
|
||||
if view is None:
|
||||
@ -1958,14 +2211,9 @@ class VTickGroup(QtGui.QGraphicsPathItem):
|
||||
self.setXVals(xvals)
|
||||
self.valid = False
|
||||
|
||||
|
||||
#def setPen(self, pen=None):
|
||||
#if pen is None:
|
||||
#pen = self.pen
|
||||
#self.pen = pen
|
||||
#for t in self.ticks:
|
||||
#t.setPen(pen)
|
||||
##self.update()
|
||||
def setPen(self, pen):
|
||||
pen = mkPen(pen)
|
||||
QtGui.QGraphicsPathItem.setPen(self, pen)
|
||||
|
||||
def setXVals(self, vals):
|
||||
self.xvals = vals
|
||||
@ -2091,7 +2339,6 @@ class GridItem(UIGraphicsItem):
|
||||
x = ul[1]
|
||||
ul[1] = br[1]
|
||||
br[1] = x
|
||||
|
||||
for i in range(2, -1, -1): ## Draw three different scales of grid
|
||||
|
||||
dist = br-ul
|
||||
|
@ -9,30 +9,119 @@ from PyQt4 import QtCore, QtGui
|
||||
from PlotWidget import *
|
||||
from ImageView import *
|
||||
QAPP = None
|
||||
class PlotWindow(QtGui.QMainWindow):
|
||||
def __init__(self, title=None):
|
||||
if QtGui.QApplication.instance() is None:
|
||||
global QAPP
|
||||
QAPP = QtGui.QApplication([])
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
self.cw = PlotWidget()
|
||||
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:
|
||||
self.setWindowTitle(title)
|
||||
self.show()
|
||||
|
||||
class ImageWindow(QtGui.QMainWindow):
|
||||
def __init__(self, title=None):
|
||||
if QtGui.QApplication.instance() is None:
|
||||
global QAPP
|
||||
QAPP = QtGui.QApplication([])
|
||||
def mkQApp():
|
||||
if QtGui.QApplication.instance() is None:
|
||||
global QAPP
|
||||
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)
|
||||
self.cw = ImageView()
|
||||
self.resize(*size)
|
||||
self.cw = QtGui.QTabWidget()
|
||||
self.setCentralWidget(self.cw)
|
||||
for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']:
|
||||
setattr(self, m, getattr(self.cw, m))
|
||||
if title is not None:
|
||||
self.setWindowTitle(title)
|
||||
self.show()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if hasattr(self.cw, attr):
|
||||
return getattr(self.cw, attr)
|
||||
else:
|
||||
raise NameError(attr)
|
||||
|
||||
|
||||
#class PlotWindow(QtGui.QMainWindow):
|
||||
#def __init__(self, title=None, **kargs):
|
||||
#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:
|
||||
self.win.setWindowTitle(title)
|
||||
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()
|
||||
|
253
widgets.py
253
widgets.py
@ -53,6 +53,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.setPos(pos)
|
||||
self.rotate(-angle * 180. / np.pi)
|
||||
self.setZValue(10)
|
||||
self.isMoving = False
|
||||
|
||||
self.handleSize = 5
|
||||
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.")
|
||||
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):
|
||||
if not info.has_key('item') or info['item'] is None:
|
||||
#print "BEFORE ADD CHILD:", self.childItems()
|
||||
@ -203,15 +209,23 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
self.setSelected(True)
|
||||
if self.translatable:
|
||||
self.isMoving = True
|
||||
self.preMoveState = self.getState()
|
||||
self.cursorOffset = self.scenePos() - ev.scenePos()
|
||||
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
||||
ev.accept()
|
||||
elif ev.button() == QtCore.Qt.RightButton:
|
||||
if self.isMoving:
|
||||
ev.accept()
|
||||
self.cancelMove()
|
||||
else:
|
||||
ev.ignore()
|
||||
else:
|
||||
ev.ignore()
|
||||
|
||||
def mouseMoveEvent(self, ev):
|
||||
#print "mouse move", ev.pos()
|
||||
if self.translatable:
|
||||
if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
|
||||
snap = None
|
||||
if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
|
||||
snap = Point(self.snapSize, self.snapSize)
|
||||
@ -221,18 +235,25 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
if self.translatable:
|
||||
self.isMoving = False
|
||||
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
||||
|
||||
|
||||
def cancelMove(self):
|
||||
self.isMoving = False
|
||||
self.setState(self.preMoveState)
|
||||
|
||||
def pointPressEvent(self, pt, ev):
|
||||
#print "press"
|
||||
self.isMoving = True
|
||||
self.preMoveState = self.getState()
|
||||
|
||||
self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
|
||||
#self.pressPos = self.mapFromScene(ev.scenePos())
|
||||
#self.pressHandlePos = self.handles[pt]['item'].pos()
|
||||
|
||||
def pointReleaseEvent(self, pt, ev):
|
||||
#print "release"
|
||||
self.isMoving = False
|
||||
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
|
||||
|
||||
def stateCopy(self):
|
||||
@ -262,14 +283,20 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
||||
#print "movePoint() called."
|
||||
## pos is the new position of the handle in scene coords, as requested by the handle.
|
||||
|
||||
newState = self.stateCopy()
|
||||
h = self.handles[pt]
|
||||
#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'])
|
||||
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)
|
||||
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'):
|
||||
c = h['center']
|
||||
cs = c * self.state['size']
|
||||
@ -296,15 +323,18 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
#cs = c * self.state['size']
|
||||
#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]:
|
||||
lp1[0] = 0
|
||||
if h['center'][1] == h['pos'][1]:
|
||||
lp1[1] = 0
|
||||
|
||||
## snap
|
||||
if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
|
||||
lp1[0] = round(lp1[0] / 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
|
||||
if hs[0] == 0:
|
||||
hs[0] = 1
|
||||
@ -312,6 +342,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
hs[1] = 1
|
||||
newSize = lp1 / hs
|
||||
|
||||
## Perform some corrections and limit checks
|
||||
if newSize[0] == 0:
|
||||
newSize[0] = newState['size'][0]
|
||||
if newSize[1] == 0:
|
||||
@ -324,10 +355,12 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
if self.aspectLocked:
|
||||
newSize[0] = newSize[1]
|
||||
|
||||
## Move ROI so the center point occupies the same scene location after the scale
|
||||
s0 = c * self.state['size']
|
||||
s1 = c * newSize
|
||||
cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))
|
||||
|
||||
## update state, do more boundary checks
|
||||
newState['size'] = newSize
|
||||
newState['pos'] = newState['pos'] + cc
|
||||
if self.maxBounds is not None:
|
||||
@ -339,30 +372,31 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.prepareGeometryChange()
|
||||
self.state = newState
|
||||
|
||||
## move handles to their new locations
|
||||
self.updateHandles()
|
||||
|
||||
elif h['type'] == 'r':
|
||||
#newState = self.stateCopy()
|
||||
#c = h['center']
|
||||
#cs = c * self.state['size']
|
||||
#p0 = Point(h['item'].pos()) - cs
|
||||
#p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
|
||||
elif h['type'] in ['r', '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
|
||||
ang = newState['angle'] + lp0.angle(lp1)
|
||||
if ang is None:
|
||||
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.)
|
||||
|
||||
|
||||
## create rotation transform
|
||||
tr = QtGui.QTransform()
|
||||
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'])
|
||||
newState['angle'] = ang
|
||||
newState['pos'] = newState['pos'] + cc
|
||||
|
||||
## check boundaries, update
|
||||
if self.maxBounds is not None:
|
||||
r = self.stateRect(newState)
|
||||
if not self.maxBounds.contains(r):
|
||||
@ -370,6 +404,45 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.setTransform(tr)
|
||||
self.setPos(newState['pos'], update=False)
|
||||
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':
|
||||
#newState = self.stateCopy()
|
||||
@ -419,6 +492,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.handleChange()
|
||||
|
||||
def handleChange(self):
|
||||
"""The state of the ROI has changed; redraw if needed."""
|
||||
#print "handleChange() called."
|
||||
changed = False
|
||||
#print "self.lastState:", self.lastState
|
||||
@ -446,7 +520,8 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
self.state['pos'] = self.state['pos'] + c - c1
|
||||
self.setPos(self.state['pos'])
|
||||
self.updateHandles()
|
||||
|
||||
|
||||
|
||||
def translate(self, *args, **kargs):
|
||||
"""accepts either (x, y, snap) or ([x,y], snap) as arguments"""
|
||||
if 'snap' not in kargs:
|
||||
@ -659,6 +734,16 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
|
||||
|
||||
|
||||
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):
|
||||
#print " create item with parent", parent
|
||||
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.setWidth(0)
|
||||
self.pen.setCosmetic(True)
|
||||
if typ == 't':
|
||||
self.sides = 4
|
||||
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
|
||||
self.isMoving = False
|
||||
self.sides, self.startAng = self.types[typ]
|
||||
|
||||
def connectROI(self, roi, i):
|
||||
self.roi.append((roi, i))
|
||||
@ -699,30 +768,44 @@ class Handle(QtGui.QGraphicsItem):
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
#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()
|
||||
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):
|
||||
#print "release"
|
||||
for r in self.roi:
|
||||
r[0].pointReleaseEvent(r[1], ev)
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
self.isMoving = False
|
||||
for r in self.roi:
|
||||
r[0].pointReleaseEvent(r[1], ev)
|
||||
|
||||
def mouseMoveEvent(self, ev):
|
||||
#print "handle mouseMove", ev.pos()
|
||||
pos = ev.scenePos() + self.cursorOffset
|
||||
self.movePoint(pos, ev.modifiers())
|
||||
if self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
|
||||
pos = ev.scenePos() + self.cursorOffset
|
||||
self.movePoint(pos, ev.modifiers())
|
||||
|
||||
def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier()):
|
||||
for r in self.roi:
|
||||
if not r[0].checkPointMove(r[1], pos, modifiers):
|
||||
return
|
||||
#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:
|
||||
r[0].movePoint(r[1], pos, modifiers)
|
||||
|
||||
@ -915,6 +998,7 @@ class PolygonROI(ROI):
|
||||
#ROI.__init__(self, positions[0])
|
||||
for p in positions:
|
||||
self.addFreeHandle(p)
|
||||
self.setZValue(1000)
|
||||
|
||||
def listPoints(self):
|
||||
return [p['item'].pos() for p in self.handles]
|
||||
@ -922,6 +1006,8 @@ class PolygonROI(ROI):
|
||||
def movePoint(self, *args, **kargs):
|
||||
ROI.movePoint(self, *args, **kargs)
|
||||
self.prepareGeometryChange()
|
||||
for h in self.handles:
|
||||
h['pos'] = h['item'].pos()
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
@ -934,7 +1020,7 @@ class PolygonROI(ROI):
|
||||
def boundingRect(self):
|
||||
r = QtCore.QRectF()
|
||||
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
|
||||
|
||||
def shape(self):
|
||||
@ -943,4 +1029,89 @@ class PolygonROI(ROI):
|
||||
for i in range(len(self.handles)):
|
||||
p.lineTo(self.handles[i]['item'].pos())
|
||||
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