Commit 3b820c29 authored by Ingo B.'s avatar Ingo B.
Browse files

sync with lp:~luke-campagnola/pyqtgraph/main

parents a1edf60e 397a1c8a
......@@ -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):
......
......@@ -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:
......
# -*- 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
\ No newline at end of file
......@@ -51,4 +51,7 @@ class PlotWidget(GraphicsView):
return self.plotItem.restoreState(state)
def getPlotItem(self):
return self.plotItem
\ No newline at end of file
return self.plotItem
\ No newline at end of file
......@@ -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]
......
# -*- 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([])
\ No newline at end of file
......@@ -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)
......
......@@ -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)
......
......@@ -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
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())