Numerous fixes

added scatter plots
This commit is contained in:
Luke Campagnola 2010-11-21 22:50:04 -05:00
parent dbcdf7ec91
commit 661e4411e2
14 changed files with 2667 additions and 148 deletions

View File

@ -163,10 +163,16 @@ class TickSlider(QtGui.QGraphicsView):
class GradientWidget(TickSlider):
def __init__(self, *args, **kargs):
TickSlider.__init__(self, *args, **kargs)
self.currentTick = None
self.currentTickColor = None
self.rectSize = 15
self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
self.colorMode = 'rgb'
self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged)
QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected)
#self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
self.scene.addItem(self.gradRect)
@ -200,16 +206,27 @@ class GradientWidget(TickSlider):
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
self.updateGradient()
def currentColorChanged(self, color):
if color.isValid() and self.currentTick is not None:
self.setTickColor(self.currentTick, color)
self.updateGradient()
def currentColorRejected(self):
self.setTickColor(self.currentTick, self.currentTickColor)
self.updateGradient()
def tickClicked(self, tick, ev):
if ev.button() == QtCore.Qt.LeftButton:
if not tick.colorChangeAllowed:
return
color = QtGui.QColorDialog.getColor(tick.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
if color.isValid():
self.setTickColor(tick, color)
self.updateGradient()
self.currentTick = tick
self.currentTickColor = tick.color
self.colorDialog.setCurrentColor(tick.color)
self.colorDialog.open()
#color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
#if color.isValid():
#self.setTickColor(tick, color)
#self.updateGradient()
elif ev.button() == QtCore.Qt.RightButton:
if not tick.removeAllowed:
return
@ -267,7 +284,8 @@ class GradientWidget(TickSlider):
r = c1.red() * (1.-f) + c2.red() * f
g = c1.green() * (1.-f) + c2.green() * f
b = c1.blue() * (1.-f) + c2.blue() * f
return QtGui.QColor(r, g, b)
a = c1.alpha() * (1.-f) + c2.alpha() * f
return QtGui.QColor(r, g, b,a)
elif self.colorMode == 'hsv':
h1,s1,v1,_ = c1.getHsv()
h2,s2,v2,_ = c2.getHsv()
@ -292,6 +310,43 @@ class GradientWidget(TickSlider):
t.removeAllowed = True
return t
def saveState(self):
ticks = []
for t in self.ticks:
c = t.color
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
state = {'mode': self.colorMode, 'ticks': ticks}
return state
def restoreState(self, state):
self.setColorMode(state['mode'])
for t in self.ticks.keys():
self.removeTick(t)
for t in state['ticks']:
c = QtGui.QColor(*t[1])
self.addTick(t[0], c)
self.updateGradient()
class BlackWhiteSlider(GradientWidget):
def __init__(self, parent):
GradientWidget.__init__(self, parent)
self.getTick(0).colorChangeAllowed = False
self.getTick(1).colorChangeAllowed = False
self.allowAdd = False
self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255))
self.setOrientation('right')
def getLevels(self):
return (self.tickValue(0), self.tickValue(1))
def setLevels(self, black, white):
self.setTickValue(0, black)
self.setTickValue(1, white)
class GammaWidget(TickSlider):
pass
@ -361,4 +416,31 @@ class Tick(QtGui.QGraphicsPolygonItem):
#self.view.tickChanged(self)
if __name__ == '__main__':
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = TickSlider(orientation='left')
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from GradientWidget import *
from PyQt4 import QtGui
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = TickSlider(orientation='left')
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)

View File

@ -27,6 +27,8 @@ class GraphicsView(QtGui.QGraphicsView):
enabled via enableMouse()."""
QtGui.QGraphicsView.__init__(self, parent)
if 'linux' in sys.platform: ## linux has bugs in opengl implementation
useOpenGL = False
self.useOpenGL(useOpenGL)
palette = QtGui.QPalette()
@ -139,6 +141,7 @@ class GraphicsView(QtGui.QGraphicsView):
#print " translate:", st
self.setMatrix(m)
self.currentScale = scale
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
if propagate:
for v in self.lockedViewports:
@ -190,7 +193,6 @@ class GraphicsView(QtGui.QGraphicsView):
#print "New Range:", self.range
self.centralWidget.setGeometry(self.range)
self.updateMatrix(propagate)
self.emit(QtCore.SIGNAL('viewChanged'), self.range)
def lockXRange(self, v1):

View File

@ -18,8 +18,9 @@ from graphicsItems import *
from widgets import ROI
from PyQt4 import QtCore, QtGui
import sys
from numpy import ndarray
#from numpy import ndarray
import ptime
import numpy as np
from SignalProxy import proxyConnect
@ -52,7 +53,7 @@ class ImageView(QtGui.QWidget):
self.ui.graphicsView.invertY()
self.ui.graphicsView.enableMouse()
self. ticks = [t[0] for t in self.ui.gradientWidget.listTicks()]
self.ticks = [t[0] for t in self.ui.gradientWidget.listTicks()]
self.ticks[0].colorChangeAllowed = False
self.ticks[1].colorChangeAllowed = False
self.ui.gradientWidget.allowAdd = False
@ -301,14 +302,14 @@ class ImageView(QtGui.QWidget):
axes = (1, 2)
else:
return
data = self.roi.getArrayRegion(image.view(ndarray), self.imageItem, axes)
data = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes)
if data is not None:
while data.ndim > 1:
data = data.mean(axis=1)
self.roiCurve.setData(y=data, x=self.tVals)
#self.ui.roiPlot.replot()
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None):
def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None):
"""Set the image to be displayed in the widget.
Options are:
img: ndarray; the image to be displayed.
@ -319,16 +320,19 @@ class ImageView(QtGui.QWidget):
This is only needed to override the default guess.
"""
if not isinstance(img, ndarray):
if not isinstance(img, np.ndarray):
raise Exception("Image must be specified as ndarray.")
self.image = img
if xvals is not None:
self.tVals = xvals
elif hasattr(img, 'xvals'):
self.tVals = img.xvals(0)
try:
self.tVals = img.xvals(0)
except:
self.tVals = np.arange(img.shape[0])
else:
self.tVals = arange(img.shape[0])
self.tVals = np.arange(img.shape[0])
#self.ui.timeSlider.setValue(0)
#self.ui.normStartSlider.setValue(0)
#self.ui.timeSlider.setMaximum(img.shape[0]-1)
@ -346,13 +350,13 @@ class ImageView(QtGui.QWidget):
self.imageDisp = None
if autoRange:
self.autoRange()
if autoLevels:
self.autoLevels()
if levels is not None:
self.levelMax = levels[1]
self.levelMin = levels[0]
self.currentIndex = 0
self.updateImage()
if self.ui.roiBtn.isChecked():
self.roiChanged()
@ -361,6 +365,7 @@ class ImageView(QtGui.QWidget):
if self.axes['t'] is not None:
#self.ui.roiPlot.show()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
self.timeLine.setValue(0)
#self.ui.roiPlot.setMouseEnabled(False, False)
if len(self.tVals) > 1:
start = self.tVals.min()
@ -376,6 +381,14 @@ class ImageView(QtGui.QWidget):
#else:
#self.ui.roiPlot.hide()
self.imageItem.resetTransform()
if scale is not None:
self.imageItem.scale(*scale)
if scale is not None:
self.imageItem.setPos(*pos)
if autoRange:
self.autoRange()
self.roiClicked()
@ -389,10 +402,12 @@ class ImageView(QtGui.QWidget):
self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0)
self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel())
def autoRange(self):
image = self.getProcessedImage()
self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True)
#self.ui.graphicsView.setRange(QtCore.QRectF(0, 0, image.shape[self.axes['x']], image.shape[self.axes['y']]), padding=0., lockAspect=True)
self.ui.graphicsView.setRange(self.imageItem.sceneBoundingRect(), padding=0., lockAspect=True)
def getProcessedImage(self):
if self.imageDisp is None:
@ -408,7 +423,7 @@ class ImageView(QtGui.QWidget):
return image
div = self.ui.normDivideRadio.isChecked()
norm = image.view(ndarray).copy()
norm = image.view(np.ndarray).copy()
#if div:
#norm = ones(image.shape)
#else:
@ -498,7 +513,7 @@ class ImageView(QtGui.QWidget):
return (0,0)
totTime = xv[-1] + (xv[-1]-xv[-2])
#t = f * totTime
inds = argwhere(xv < t)
inds = np.argwhere(xv < t)
if len(inds) < 1:
return (0,t)
ind = inds[-1,0]

2129
PIL_Fix/Image.py-1.7 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ from PyQt4 import QtGui, QtCore, QtSvg
#from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui)
import weakref
import numpy as np
try:
from WidgetGroup import *
@ -37,8 +38,6 @@ except:
HAVE_METAARRAY = False
class PlotItem(QtGui.QGraphicsWidget):
"""Plot graphics item that can be added to any graphics scene. Implements axis titles, scales, interactive viewbox."""
lastFileDir = None
@ -132,6 +131,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.items = []
self.curves = []
self.dataItems = []
self.paramList = {}
self.avgCurves = {}
@ -436,7 +436,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.vb.setMouseEnabled(*state)
def xRangeChanged(self, _, range):
if any(isnan(range)) or any(isinf(range)):
if any(np.isnan(range)) or any(np.isinf(range)):
raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
self.ctrl.xMinText.setText('%0.5g' % range[0])
self.ctrl.xMaxText.setText('%0.5g' % range[1])
@ -455,7 +455,7 @@ class PlotItem(QtGui.QGraphicsWidget):
self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
def yRangeChanged(self, _, range):
if any(isnan(range)) or any(isinf(range)):
if any(np.isnan(range)) or any(np.isinf(range)):
raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
self.ctrl.yMinText.setText('%0.5g' % range[0])
self.ctrl.yMaxText.setText('%0.5g' % range[1])
@ -545,6 +545,9 @@ class PlotItem(QtGui.QGraphicsWidget):
if not item in self.items:
return
self.items.remove(item)
if item in self.dataItems:
self.dataItems.remove(item)
if item.scene() is not None:
self.vb.removeItem(item)
if item in self.curves:
@ -571,12 +574,12 @@ class PlotItem(QtGui.QGraphicsWidget):
params = {}
if HAVE_METAARRAY and isinstance(data, MetaArray):
curve = self._plotMetaArray(data, x=x)
elif isinstance(data, ndarray):
elif isinstance(data, np.ndarray):
curve = self._plotArray(data, x=x)
elif isinstance(data, list):
if x is not None:
x = array(x)
curve = self._plotArray(array(data), x=x)
x = np.array(x)
curve = self._plotArray(np.array(data), x=x)
elif data is None:
curve = PlotCurveItem()
else:
@ -589,6 +592,10 @@ class PlotItem(QtGui.QGraphicsWidget):
return curve
def addDataItem(self, item):
self.addItem(item)
self.dataItems.append(item)
def addCurve(self, c, params=None):
if params is None:
params = {}
@ -622,7 +629,7 @@ class PlotItem(QtGui.QGraphicsWidget):
percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01
mn = None
mx = None
for c in self.curves + [c[1] for c in self.avgCurves.values()]:
for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems:
if not c.isVisible():
continue
cmn, cmx = c.getRange(ax, percentScale)
@ -630,7 +637,7 @@ class PlotItem(QtGui.QGraphicsWidget):
mn = cmn
if mx is None or cmx > mx:
mx = cmx
if mn is None or mx is None or any(isnan([mn, mx])) or any(isinf([mn, mx])):
if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
continue
if mn == mx:
mn -= 1
@ -1013,7 +1020,7 @@ class PlotItem(QtGui.QGraphicsWidget):
else:
xv = x
c = PlotCurveItem()
c.setData(x=xv, y=arr.view(ndarray))
c.setData(x=xv, y=arr.view(np.ndarray))
if autoLabel:
name = arr._info[0].get('name', None)

View File

@ -20,7 +20,10 @@ class Point(QtCore.QPointF):
def __init__(self, *args):
if len(args) == 1:
if hasattr(args[0], '__getitem__'):
if isinstance(args[0], QtCore.QSizeF):
QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height()))
return
elif hasattr(args[0], '__getitem__'):
QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1]))
return
elif type(args[0]) in [float, int]:
@ -31,6 +34,9 @@ class Point(QtCore.QPointF):
return
QtCore.QPointF.__init__(self, *args)
def __reduce__(self):
return (Point, (self.x(), self.y()))
def __getitem__(self, i):
if i == 0:
return self.x()

View File

@ -24,14 +24,17 @@ class SignalProxy(QtCore.QObject):
self.args = None
self.timers = 0
self.signal = signal
self.block = False
def setDelay(self, delay):
self.delay = delay
def flush(self):
"""If there is a signal queued up, send it now."""
if self.args is None:
if self.args is None or self.block:
return False
if self.block:
return
self.emit(self.signal, *self.args)
self.args = None
return True
@ -39,6 +42,8 @@ class SignalProxy(QtCore.QObject):
def signal(self, *args):
"""Received signal, queue to be forwarded later."""
if self.block:
return
self.waitUntil = time() + self.delay
self.args = args
self.timers += 1
@ -46,7 +51,7 @@ class SignalProxy(QtCore.QObject):
def tryEmit(self):
"""Emit signal if it has been long enougn since receiving the last signal."""
if self.args is None:
if self.args is None or self.block:
return False
self.timers -= 1
t = time()
@ -59,4 +64,6 @@ class SignalProxy(QtCore.QObject):
return True
def disconnect(self):
self.block = True

View File

@ -14,6 +14,7 @@ app = QtGui.QApplication([])
## Create window with GraphicsView widget
win = QtGui.QMainWindow()
view = GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view)
win.show()

38
examples/test_draw.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path = [os.path.join(os.path.dirname(__file__), '..', '..')] + sys.path
from pyqtgraph.GraphicsView import *
from pyqtgraph.graphicsItems import *
#from numpy import random
from PyQt4 import QtCore, QtGui
from scipy.ndimage import *
import numpy as np
app = QtGui.QApplication([])
## Create window with GraphicsView widget
win = QtGui.QMainWindow()
view = GraphicsView()
#view.useOpenGL(True)
win.setCentralWidget(view)
win.show()
## Allow mouse scale/pan
view.enableMouse()
## ..But lock the aspect ratio
view.setAspectLocked(True)
## Create image item
img = ImageItem(np.zeros((200,200)))
view.scene().addItem(img)
## Set initial view bounds
view.setRange(QtCore.QRectF(0, 0, 200, 200))
img.setDrawKernel(1)
img.setLevels(10,0)
#app.exec_()

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from PyQt4 import QtGui, QtCore
from pyqtgraph.PlotWidget import *
from pyqtgraph.graphicsItems import *
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
cw = PlotWidget()
mw.setCentralWidget(cw)
mw.show()
#s1 = SpotItem(5, pxMode=True, brush=QtGui.QBrush(QtGui.QColor(0, 0, 200)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s1.setPos(1, 0)
#s2 = SpotItem(.1, pxMode=False, brush=QtGui.QBrush(QtGui.QColor(0, 200, 0)), pen=QtGui.QPen(QtGui.QColor(100,100,100)))
#s2.setPos(0, 1)
#cw.addItem(s1)
#cw.addItem(s2)
import numpy as np
s1 = ScatterPlotItem(size=10, pen=QtGui.QPen(QtCore.Qt.NoPen), brush=QtGui.QBrush(QtGui.QColor(255, 255, 255, 20)))
pos = np.random.normal(size=(2,3000))
spots = [{'pos': pos[:,i]} for i in range(3000)]
s1.addPoints(spots)
cw.addDataItem(s1)

View File

@ -11,7 +11,8 @@ Provides ImageItem, PlotCurveItem, and ViewBox, amongst others.
from PyQt4 import QtGui, QtCore
from ObjectWorkaround import *
#tryWorkaround(QtCore, QtGui)
from numpy import *
#from numpy import *
import numpy as np
try:
import scipy.weave as weave
from scipy.weave import converters
@ -171,10 +172,14 @@ class GraphicsObject(QGraphicsObject):
class ImageItem(QtGui.QGraphicsPixmapItem):
useWeave = True
class ImageItem(QtGui.QGraphicsPixmapItem, QObjectWorkaround):
if 'linux' not in sys.platform: ## disable weave optimization on linux--broken there.
useWeave = True
else:
useWeave = False
def __init__(self, image=None, copy=True, parent=None, *args):
QObjectWorkaround.__init__(self)
self.qimage = QtGui.QImage()
self.pixmap = None
#self.useWeave = True
@ -183,6 +188,8 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
self.alpha = 1.0
self.image = None
self.clipLevel = None
self.drawKernel = None
QtGui.QGraphicsPixmapItem.__init__(self, parent, *args)
#self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
if image is not None:
@ -222,6 +229,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
if black is not None:
self.blackLevel = black
self.updateImage()
def getLevels(self):
return self.whiteLevel, self.blackLevel
def updateImage(self, image=None, copy=True, autoRange=False, clipMask=None, white=None, black=None):
axh = {'x': 0, 'y': 1, 'c': 2}
@ -231,11 +241,12 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
if black is not None:
self.blackLevel = black
gotNewData = False
if image is None:
if self.image is None:
return
else:
gotNewData = True
if copy:
self.image = image.copy()
else:
@ -261,9 +272,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
try:
if not ImageItem.useWeave:
raise Exception('Skipping weave compile')
sim = ascontiguousarray(self.image)
sim = np.ascontiguousarray(self.image)
sim.shape = sim.size
im = zeros(sim.shape, dtype=ubyte)
im = np.empty(sim.shape, dtype=np.ubyte)
n = im.size
code = """
@ -287,15 +298,15 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
#print "=============================================================================="
print "Weave compile failed, falling back to slower version."
self.image.shape = shape
im = ((self.image - black) * scale).clip(0.,255.).astype(ubyte)
im = ((self.image - black) * scale).clip(0.,255.).astype(np.ubyte)
try:
im1 = empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=ubyte)
im1 = np.empty((im.shape[axh['y']], im.shape[axh['x']], 4), dtype=np.ubyte)
except:
print im.shape, axh
raise
alpha = clip(int(255 * self.alpha), 0, 255)
alpha = np.clip(int(255 * self.alpha), 0, 255)
# Fill image
if im.ndim == 2:
im2 = im.transpose(axh['y'], axh['x'])
@ -303,7 +314,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
im1[..., 1] = im2
im1[..., 2] = im2
im1[..., 3] = alpha
elif im.ndim == 3:
elif im.ndim == 3: #color image
im2 = im.transpose(axh['y'], axh['x'], axh['c'])
for i in range(0, im.shape[axh['c']]):
@ -335,10 +346,43 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
self.setPixmap(self.pixmap)
self.update()
if gotNewData:
self.emit(QtCore.SIGNAL('imageChanged'))
def getPixmap(self):
return self.pixmap.copy()
def getHistogram(self, bins=500, step=3):
"""returns an x and y arrays containing the histogram values for the current image.
The step argument causes pixels to be skipped when computing the histogram to save time."""
stepData = self.image[::step, ::step]
hist = np.histogram(stepData, bins=bins)
return hist[1][:-1], hist[0]
def mousePressEvent(self, ev):
if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton:
self.drawAt(ev.pos())
ev.accept()
else:
ev.ignore()
def mouseMoveEvent(self, ev):
#print "mouse move", ev.pos()
if self.drawKernel is not None:
self.drawAt(ev.pos())
def mouseReleaseEvent(self, ev):
pass
def drawAt(self, pos):
self.image[int(pos.x()), int(pos.y())] += 1
self.updateImage()
def setDrawKernel(self, kernel=None):
self.drawKernel = kernel
class PlotCurveItem(GraphicsObject):
"""Class representing a single plot curve."""
@ -376,9 +420,13 @@ class PlotCurveItem(GraphicsObject):
if self.xData is None:
return (None, None)
if self.xDisp is None:
nanMask = isnan(self.xData) | isnan(self.yData)
x = self.xData[~nanMask]
y = self.yData[~nanMask]
nanMask = np.isnan(self.xData) | np.isnan(self.yData)
if any(nanMask):
x = self.xData[~nanMask]
y = self.yData[~nanMask]
else:
x = self.xData
y = self.yData
ds = self.opts['downsample']
if ds > 1:
x = x[::ds]
@ -387,11 +435,11 @@ class PlotCurveItem(GraphicsObject):
f = fft(y) / len(y)
y = abs(f[1:len(f)/2])
dt = x[-1] - x[0]
x = linspace(0, 0.5*len(x)/dt, len(y))
x = np.linspace(0, 0.5*len(x)/dt, len(y))
if self.opts['logMode'][0]:
x = log10(x)
x = np.log10(x)
if self.opts['logMode'][1]:
y = log10(y)
y = np.log10(y)
self.xDisp = x
self.yDisp = y
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
@ -494,10 +542,10 @@ class PlotCurveItem(GraphicsObject):
def updateData(self, data, x=None, copy=False):
if isinstance(data, list):
data = array(data)
data = np.array(data)
if isinstance(x, list):
x = array(x)
if not isinstance(data, ndarray) or data.ndim > 2:
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 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:
@ -542,9 +590,9 @@ class PlotCurveItem(GraphicsObject):
## Create all vertices in path. The method used below creates a binary format so that all
## vertices can be read in at once. This binary format may change in future versions of Qt,
## so the original (slower) method is left here for emergencies:
#self.path.moveTo(x[0], y[0])
#path.moveTo(x[0], y[0])
#for i in range(1, y.shape[0]):
#self.path.lineTo(x[i], y[i])
# path.lineTo(x[i], y[i])
## Speed this up using >> operator
## Format is:
@ -558,7 +606,7 @@ class PlotCurveItem(GraphicsObject):
#
n = x.shape[0]
# create empty array, pad with extra space on either end
arr = empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# write first two integers
arr.data[12:20] = struct.pack('>ii', n, 0)
# Fill array with vertex values
@ -643,7 +691,113 @@ 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):
QtGui.QGraphicsItem.__init__(self)
self.spots = []
self.range = [[0,0], [0,0]]
if brush is None:
brush = QtGui.QBrush(QtGui.QColor(100, 100, 150))
self.brush = brush
if pen is None:
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
self.pen = pen
self.size = size
self.pxMode = pxMode
if spots is not None:
self.setPoints(spots)
def setPxMode(self, mode):
self.pxMode = mode
def clear(self):
for i in self.spots:
i.setParentItem(None)
s = i.scene()
if s is not None:
s.removeItem(i)
self.spots = []
def getRange(self, ax, percent):
return self.range[ax]
def setPoints(self, spots):
self.clear()
self.range = [[0,0],[0,0]]
self.addPoints(spots)
def addPoints(self, spots):
xmn = ymn = xmx = ymx = None
for s in spots:
pos = Point(s['pos'])
size = s.get('size', self.size)
brush = s.get('brush', self.brush)
pen = s.get('pen', self.pen)
pen.setCosmetic(True)
item = self.mkSpot(pos, size, self.pxMode, brush, pen)
self.spots.append(item)
if xmn is None:
xmn = pos[0]-size
xmx = pos[0]+size
ymn = pos[1]-size
ymx = pos[1]+size
else:
xmn = min(xmn, pos[0]-size)
xmx = max(xmx, pos[0]+size)
ymn = min(ymn, pos[1]-size)
ymx = max(ymx, pos[1]+size)
self.range = [[xmn, xmx], [ymn, ymx]]
def mkSpot(self, pos, size, pxMode, brush, pen):
item = SpotItem(size, pxMode, brush, pen)
item.setParentItem(self)
item.setPos(pos)
return item
def boundingRect(self):
((xmn, xmx), (ymn, ymx)) = self.range
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
def paint(self, p, *args):
pass
class SpotItem(QtGui.QGraphicsItem):
def __init__(self, size, pxMode, brush, pen):
QtGui.QGraphicsItem.__init__(self)
if pxMode:
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
#self.setCacheMode(self.DeviceCoordinateCache) ## causes crash on linux
self.pen = pen
self.brush = brush
self.path = QtGui.QPainterPath()
s2 = size/2.
self.path.addEllipse(QtCore.QRectF(-s2, -s2, size, size))
def boundingRect(self):
return self.path.boundingRect()
def shape(self):
return self.path
def paint(self, p, *opts):
p.setPen(self.pen)
p.setBrush(self.brush)
p.drawPath(self.path)
class ROIPlotItem(PlotCurveItem):
def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None):
self.roi = roi
@ -978,7 +1132,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.update()
def setRange(self, mn, mx):
if mn in [nan, inf, -inf] or mx in [nan, inf, -inf]:
if mn in [np.nan, np.inf, -np.inf] or mx in [np.nan, np.inf, -np.inf]:
raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx)))
self.range = [mn, mx]
if self.autoScale:
@ -1049,7 +1203,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
if dif == 0.0:
return
#print "dif:", dif
pw = 10 ** (floor(log10(dif))-1)
pw = 10 ** (np.floor(np.log10(dif))-1)
for i in range(len(intervals)):
i1 = i
if dif / (pw*intervals[i]) < 10:
@ -1075,7 +1229,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
sp = pw*intervals[i]
## determine starting tick
start = ceil(self.range[0] / sp) * sp
start = np.ceil(self.range[0] / sp) * sp
## determine number of ticks
num = int(dif / sp) + 1
@ -1085,7 +1239,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
## Number of decimal places to print
maxVal = max(abs(start), abs(last))
places = max(0, 1-int(log10(sp*self.scale)))
places = max(0, 1-int(np.log10(sp*self.scale)))
## length of tick
h = min(self.tickLength, (self.tickLength*3 / num) - 1.)
@ -1292,11 +1446,11 @@ class ViewBox(QtGui.QGraphicsWidget):
yd = pr[1][1] - pr[1][0]
if xd == 0 or yd == 0:
print "Warning: 0 range in view:", xd, yd
return array([1,1])
return np.array([1,1])
#cs = self.canvas().size()
cs = self.boundingRect()
scale = array([cs.width() / xd, cs.height() / yd])
scale = np.array([cs.width() / xd, cs.height() / yd])
#print "view scale:", scale
return scale
@ -1324,7 +1478,7 @@ class ViewBox(QtGui.QGraphicsWidget):
#print self.range
def translateBy(self, t, viewCoords=False):
t = t.astype(float)
t = t.astype(np.float)
#print "translate:", t, self.viewScale()
if viewCoords: ## scale from pixels
t /= self.viewScale()
@ -1339,25 +1493,25 @@ class ViewBox(QtGui.QGraphicsWidget):
def mouseMoveEvent(self, ev):
pos = array([ev.pos().x(), ev.pos().y()])
pos = np.array([ev.pos().x(), ev.pos().y()])
dif = pos - self.mousePos
dif *= -1
self.mousePos = pos
## Ignore axes if mouse is disabled
mask = array(self.mouseEnabled, dtype=float)
mask = np.array(self.mouseEnabled, dtype=np.float)
## Scale or translate based on mouse button
if ev.buttons() & QtCore.Qt.LeftButton:
if not self.yInverted:
mask *= array([1, -1])
mask *= np.array([1, -1])
tr = dif*mask
self.translateBy(tr, viewCoords=True)
self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled)
ev.accept()
elif ev.buttons() & QtCore.Qt.RightButton:
dif = ev.screenPos() - ev.lastScreenPos()
dif = array([dif.x(), dif.y()])
dif = np.array([dif.x(), dif.y()])
dif[0] *= -1
s = ((mask * 0.02) + 1) ** dif
#print mask, dif, s
@ -1369,12 +1523,12 @@ class ViewBox(QtGui.QGraphicsWidget):
ev.ignore()
def mousePressEvent(self, ev):
self.mousePos = array([ev.pos().x(), ev.pos().y()])
self.mousePos = np.array([ev.pos().x(), ev.pos().y()])
self.pressPos = self.mousePos.copy()
ev.accept()
def mouseReleaseEvent(self, ev):
pos = 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 ev.button() == QtCore.Qt.RightButton:
#self.ctrlMenu.popup(self.mapToGlobal(ev.pos()))
@ -1396,7 +1550,7 @@ class ViewBox(QtGui.QGraphicsWidget):
min -= dy*0.5
max += dy*0.5
#raise Exception("Tried to set range with 0 width.")
if any(isnan([min, max])) or any(isinf([min, max])):
if any(np.isnan([min, max])) or any(np.isinf([min, max])):
raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
padding = (max-min) * padding
@ -1422,7 +1576,7 @@ class ViewBox(QtGui.QGraphicsWidget):
max += dx*0.5
#print "Warning: Tried to set range with 0 width."
#raise Exception("Tried to set range with 0 width.")
if any(isnan([min, max])) or any(isinf([min, max])):
if any(np.isnan([min, max])) or any(np.isinf([min, max])):
raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
padding = (max-min) * padding
min -= padding
@ -1474,6 +1628,7 @@ class InfiniteLine(GraphicsObject):
if movable:
self.setAcceptHoverEvents(True)
self.hasMoved = False
if pen is None:
@ -1585,13 +1740,13 @@ class InfiniteLine(GraphicsObject):
#print 'before', self.bounds
if self.angle > 45:
m = tan((90-self.angle) * pi / 180.)
m = np.tan((90-self.angle) * np.pi / 180.)
y2 = vr.bottom()
y1 = vr.top()
x1 = self.p[0] + (y1 - self.p[1]) * m
x2 = self.p[0] + (y2 - self.p[1]) * m
else:
m = tan(self.angle * pi / 180.)
m = np.tan(self.angle * np.pi / 180.)
x1 = vr.left()
x2 = vr.right()
y2 = self.p[1] + (x1 - self.p[0]) * m
@ -1650,12 +1805,18 @@ class InfiniteLine(GraphicsObject):
def mouseMoveEvent(self, ev):
self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
self.emit(QtCore.SIGNAL('dragged'), self)
self.hasMoved = True
def mouseReleaseEvent(self, ev):
if self.hasMoved and ev.button() == QtCore.Qt.LeftButton:
self.hasMoved = False
self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
class LinearRegionItem(GraphicsObject):
"""Used for marking a horizontal or vertical region in plots."""
def __init__(self, view, orientation="horizontal", vals=[0,1], brush=None, movable=True, bounds=None):
def __init__(self, view, orientation="vertical", vals=[0,1], brush=None, movable=True, bounds=None):
GraphicsObject.__init__(self)
self.orientation = orientation
if hasattr(self, "ItemHasNoContents"):
@ -1680,6 +1841,7 @@ 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)
if brush is None:
@ -1698,12 +1860,16 @@ class LinearRegionItem(GraphicsObject):
self.updateBounds()
self.emit(QtCore.SIGNAL('regionChanged'), self)
def lineMoveFinished(self):
self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
def updateBounds(self):
vb = self.view().viewRect()
vals = [self.lines[0].value(), self.lines[1].value()]
if self.orientation[0] == 'h':
vb.setTop(max(vals))
vb.setBottom(min(vals))
vb.setTop(min(vals))
vb.setBottom(max(vals))
else:
vb.setLeft(min(vals))
vb.setRight(max(vals))
@ -1713,14 +1879,19 @@ class LinearRegionItem(GraphicsObject):
def mousePressEvent(self, ev):
for l in self.lines:
l.mousePressEvent(ev)
l.mousePressEvent(ev) ## pass event to both lines so they move together
#if self.movable and ev.button() == QtCore.Qt.LeftButton:
#ev.accept()
#self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p)
#else:
#ev.ignore()
def mouseReleaseEvent(self, ev):
for l in self.lines:
l.mouseReleaseEvent(ev)
def mouseMoveEvent(self, ev):
#print "move", ev.pos()
self.lines[0].blockSignals(True) # only want to update once
for l in self.lines:
l.mouseMoveEvent(ev)
@ -1858,8 +2029,10 @@ class GridItem(UIGraphicsItem):
self.picture = None
def viewChangedEvent(self, newRect, oldRect):
def viewChangedEvent(self):
self.picture = None
UIGraphicsItem.viewChangedEvent(self)
#self.update()
def paint(self, p, opt, widget):
#p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100)))
@ -1883,8 +2056,8 @@ class GridItem(UIGraphicsItem):
unit = self.unitRect()
dim = [vr.width(), vr.height()]
lvr = self.boundingRect()
ul = array([lvr.left(), lvr.top()])
br = array([lvr.right(), lvr.bottom()])
ul = np.array([lvr.left(), lvr.top()])
br = np.array([lvr.right(), lvr.bottom()])
texts = []
@ -1897,22 +2070,22 @@ class GridItem(UIGraphicsItem):
dist = br-ul
nlTarget = 10.**i
d = 10. ** floor(log10(abs(dist/nlTarget))+0.5)
ul1 = floor(ul / d) * d
br1 = ceil(br / d) * d
d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5)
ul1 = np.floor(ul / d) * d
br1 = np.ceil(br / d) * d
dist = br1-ul1
nl = (dist / d) + 0.5
for ax in range(0,2): ## Draw grid for both axes
ppl = dim[ax] / nl[ax]
c = clip(3.*(ppl-3), 0., 30.)
c = np.clip(3.*(ppl-3), 0., 30.)
linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c))
textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2))
bx = (ax+1) % 2
for x in range(0, int(nl[ax])):
p.setPen(linePen)
p1 = array([0.,0.])
p2 = array([0.,0.])
p1 = np.array([0.,0.])
p2 = np.array([0.,0.])
p1[ax] = ul1[ax] + x * d[ax]
p2[ax] = p1[ax]
p1[bx] = ul[bx]
@ -1969,7 +2142,7 @@ class ScaleBar(UIGraphicsItem):
p.scale(rect.width(), rect.height())
p.drawRect(0, 0, 1, 1)
alpha = clip(((self.size/unit.width()) - 40.) * 255. / 80., 0, 255)
alpha = np.clip(((self.size/unit.width()) - 40.) * 255. / 80., 0, 255)
p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha)))
for i in range(1, 10):
#x2 = x + (x1-x) * 0.1 * i

View File

@ -10,7 +10,8 @@ of array data from ImageItems.
"""
from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg
from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2
#from numpy import array, arccos, dot, pi, zeros, vstack, ubyte, fromfunction, ceil, floor, arctan2
import numpy as np
from numpy.linalg import norm
import scipy.ndimage as ndimage
from Point import *
@ -50,7 +51,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.state = {'pos': pos, 'size': size, 'angle': angle}
self.lastState = None
self.setPos(pos)
self.rotate(-angle * 180. / pi)
self.rotate(-angle * 180. / np.pi)
self.setZValue(10)
self.handleSize = 5
@ -62,7 +63,15 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.rotateSnap = rotateSnap
self.scaleSnap = scaleSnap
self.setFlag(self.ItemIsSelectable, True)
def getState(self):
return self.state.copy()
def setState(self, state):
self.setPos(state['pos'], update=False)
self.setSize(state['size'], update=False)
self.setAngle(state['angle'])
def setZValue(self, z):
QtGui.QGraphicsItem.setZValue(self, z)
for h in self.handles:
@ -79,10 +88,12 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.update()
def setPos(self, pos, update=True):
#print "setPos() called."
pos = Point(pos)
self.state['pos'] = pos
QtGui.QGraphicsItem.setPos(self, pos)
if update:
self.updateHandles()
self.handleChange()
def setSize(self, size, update=True):
@ -93,35 +104,45 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.updateHandles()
self.handleChange()
def addTranslateHandle(self, pos, axes=None, item=None):
def setAngle(self, angle, update=True):
self.state['angle'] = angle
tr = QtGui.QTransform()
tr.rotate(-angle * 180 / np.pi)
self.setTransform(tr)
if update:
self.updateHandles()
self.handleChange()
def addTranslateHandle(self, pos, axes=None, item=None, name=None):
pos = Point(pos)
return self.addHandle({'type': 't', 'pos': pos, 'item': item})
return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item})
def addFreeHandle(self, pos, axes=None, item=None):
def addFreeHandle(self, pos, axes=None, item=None, name=None):
pos = Point(pos)
return self.addHandle({'type': 'f', 'pos': pos, 'item': item})
return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item})
def addScaleHandle(self, pos, center, axes=None, item=None):
def addScaleHandle(self, pos, center, axes=None, item=None, name=None):
pos = Point(pos)
center = Point(center)
info = {'type': 's', 'center': center, 'pos': pos, 'item': item}
info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item}
if pos.x() == center.x():
info['xoff'] = True
if pos.y() == center.y():
info['yoff'] = True
return self.addHandle(info)
def addRotateHandle(self, pos, center, item=None):
def addRotateHandle(self, pos, center, item=None, name=None):
pos = Point(pos)
center = Point(center)
return self.addHandle({'type': 'r', 'center': center, 'pos': pos, 'item': item})
return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item})
def addScaleRotateHandle(self, pos, center, item=None):
def addScaleRotateHandle(self, pos, center, item=None, name=None):
pos = Point(pos)
center = Point(center)
if pos[0] != center[0] and pos[1] != center[1]:
raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.")
return self.addHandle({'type': 'sr', 'center': center, 'pos': pos, 'item': item})
return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item})
def addHandle(self, info):
if not info.has_key('item') or info['item'] is None:
@ -144,6 +165,26 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
#else:
#h.hide()
return h
def getLocalHandlePositions(self, index=None):
"""Returns the position of a handle in ROI coordinates"""
if index == None:
positions = []
for h in self.handles:
positions.append((h['name'], h['pos']))
return positions
else:
return (self.handles[index]['name'], self.handles[index]['pos'])
def getSceneHandlePositions(self, index = None):
if index == None:
positions = []
for h in self.handles:
positions.append((h['name'], h['item'].scenePos()))
return positions
else:
return (self.handles[index]['name'], self.handles[index]['item'].scenePos())
def mapSceneToParent(self, pt):
return self.mapToParent(self.mapFromScene(pt))
@ -220,7 +261,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
#print "movePoint", pos
#print "movePoint() called."
newState = self.stateCopy()
h = self.handles[pt]
#p0 = self.mapToScene(h['item'].pos())
@ -248,6 +289,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
elif h['type'] == 'f':
h['item'].setPos(self.mapFromScene(pos))
self.emit(QtCore.SIGNAL('regionChanged'), self)
elif h['type'] == 's':
#c = h['center']
@ -312,11 +354,11 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if ang is None:
return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (pi/12.)) * (pi/12.)
ang = round(ang / (np.pi/12.)) * (np.pi/12.)
tr = QtGui.QTransform()
tr.rotate(-ang * 180. / pi)
tr.rotate(-ang * 180. / np.pi)
cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
newState['angle'] = ang
@ -347,7 +389,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
if ang is None:
return
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
ang = round(ang / (pi/12.)) * (pi/12.)
ang = round(ang / (np.pi/12.)) * (np.pi/12.)
hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
newState['size'][scaleAxis] = lp1.length() / hs
@ -358,7 +400,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
c1 = c * newState['size']
tr = QtGui.QTransform()
tr.rotate(-ang * 180. / pi)
tr.rotate(-ang * 180. / np.pi)
cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
newState['angle'] = ang
@ -377,16 +419,21 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
self.handleChange()
def handleChange(self):
#print "handleChange() called."
changed = False
#print "self.lastState:", self.lastState
if self.lastState is None:
changed = True
else:
for k in self.state.keys():
#print k, self.state[k], self.lastState[k]
if self.state[k] != self.lastState[k]:
#print "state %s has changed; emit signal" % k
changed = True
self.lastState = self.stateCopy()
#print "changed =", changed
if changed:
#print "handle changed."
self.update()
self.emit(QtCore.SIGNAL('regionChanged'), self)
@ -442,7 +489,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
def stateRect(self, state):
r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
tr = QtGui.QTransform()
tr.rotate(-state['angle'] * 180 / pi)
tr.rotate(-state['angle'] * 180 / np.pi)
r = tr.mapRect(r)
return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])
@ -533,10 +580,10 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
## Rotate array
if abs(self.state['angle']) > 1e-5:
arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / pi, order=1)
arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
## update data transforms to reflect this rotation
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / pi)
rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
roiDataTransform *= rot
## The rotation also causes a shift which must be accounted for:
@ -557,7 +604,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
# 3. pad array with 0s before shifting
arr2a = zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
arr2a[1:-1, 1:-1] = arr2
# 4. shift array and udpate transforms
@ -588,7 +635,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
### Create zero array in size of ROI
arr5 = zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
## Fill array with ROI data
orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
@ -597,7 +644,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
## figure out the reverse transpose order
tr2 = array(tr1)
tr2 = np.array(tr1)
for i in range(0, len(tr2)):
tr2[tr1[i]] = i
tr2 = tuple(tr2)
@ -605,7 +652,7 @@ class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
## Untranspose array before returning
return arr5.transpose(tr2)
@ -627,10 +674,10 @@ class Handle(QtGui.QGraphicsItem):
self.pen.setCosmetic(True)
if typ == 't':
self.sides = 4
self.startAng = pi/4
self.startAng = np.pi/4
elif typ == 'f':
self.sides = 4
self.startAng = pi/4
self.startAng = np.pi/4
elif typ == 's':
self.sides = 4
self.startAng = 0
@ -642,7 +689,7 @@ class Handle(QtGui.QGraphicsItem):
self.startAng = 0
else:
self.sides = 4
self.startAng = pi/4
self.startAng = np.pi/4
def connectROI(self, roi, i):
self.roi.append((roi, i))
@ -684,7 +731,7 @@ class Handle(QtGui.QGraphicsItem):
m = self.sceneTransform()
#mi = m.inverted()[0]
v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0))
va = arctan2(v.y(), v.x())
va = np.arctan2(v.y(), v.x())
## Determine length of unit vector in painter's coords
#size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0))
@ -698,7 +745,7 @@ class Handle(QtGui.QGraphicsItem):
p.setRenderHints(p.Antialiasing, True)
p.setPen(self.pen)
ang = self.startAng + va
dt = 2*pi / self.sides
dt = 2*np.pi / self.sides
for i in range(0, self.sides):
x1 = size * cos(ang)
y1 = size * sin(ang)
@ -751,7 +798,7 @@ class LineROI(ROI):
c = Point(-width/2. * sin(ang), -width/2. * cos(ang))
pos1 = pos1 + c
ROI.__init__(self, pos1, size=Point(l, width), angle=ang*180/pi, **args)
ROI.__init__(self, pos1, size=Point(l, width), angle=ang*180/np.pi, **args)
self.addScaleRotateHandle([0, 0.5], [1, 0.5])
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
self.addScaleHandle([0.5, 1], [0.5, 0.5])
@ -816,7 +863,7 @@ class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
rgns.append(rgn)
#print l.state['size']
#print [(r.shape) for r in rgns]
return vstack(rgns)
return np.vstack(rgns)
class EllipseROI(ROI):
@ -843,7 +890,7 @@ class EllipseROI(ROI):
w = arr.shape[0]
h = arr.shape[1]
## generate an ellipsoidal mask
mask = fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h))
mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h))
return arr * mask
@ -861,11 +908,17 @@ class CircleROI(EllipseROI):
self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
class PolygonROI(ROI):
def __init__(self, positions, **args):
ROI.__init__(self, [0,0], [100,100], **args)
def __init__(self, positions, pos=None, **args):
if pos is None:
pos = [0,0]
ROI.__init__(self, pos, [1,1], **args)
#ROI.__init__(self, positions[0])
for p in positions:
self.addFreeHandle(p)
def listPoints(self):
return [p['item'].pos() for p in self.handles]
def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange()