diff --git a/GradientWidget.py b/GradientWidget.py
new file mode 100644
index 00000000..5685edfb
--- /dev/null
+++ b/GradientWidget.py
@@ -0,0 +1,364 @@
+# -*- coding: utf-8 -*-
+from PyQt4 import QtGui, QtCore
+
+
+class TickSlider(QtGui.QGraphicsView):
+ def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs):
+ QtGui.QGraphicsView.__init__(self, parent)
+ #self.orientation = orientation
+ self.allowAdd = allowAdd
+ self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
+ self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
+ self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
+ self.length = 100
+ self.tickSize = 15
+ self.orientations = {
+ 'left': (270, 1, -1),
+ 'right': (270, 1, 1),
+ 'top': (0, 1, -1),
+ 'bottom': (0, 1, 1)
+ }
+
+ self.scene = QtGui.QGraphicsScene()
+ self.setScene(self.scene)
+
+ self.ticks = {}
+ self.maxDim = 20
+ self.setOrientation(orientation)
+ self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
+ self.setBackgroundRole(QtGui.QPalette.NoRole)
+ self.setMouseTracking(True)
+
+ def keyPressEvent(self, ev):
+ ev.ignore()
+
+ def setMaxDim(self, mx=None):
+ if mx is None:
+ mx = self.maxDim
+ else:
+ self.maxDim = mx
+
+ if self.orientation in ['bottom', 'top']:
+ self.setFixedHeight(mx)
+ self.setMaximumWidth(16777215)
+ else:
+ self.setFixedWidth(mx)
+ self.setMaximumHeight(16777215)
+
+ def setOrientation(self, ort):
+ self.orientation = ort
+ self.resetTransform()
+ self.rotate(self.orientations[ort][0])
+ self.scale(*self.orientations[ort][1:])
+ self.setMaxDim()
+
+ def addTick(self, x, color=None, movable=True):
+ if color is None:
+ color = QtGui.QColor(255,255,255)
+ tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize)
+ self.ticks[tick] = x
+ self.scene.addItem(tick)
+ return tick
+
+ def removeTick(self, tick):
+ del self.ticks[tick]
+ self.scene.removeItem(tick)
+
+ def tickMoved(self, tick, pos):
+ #print "tick changed"
+ ## Correct position of tick if it has left bounds.
+ newX = min(max(0, pos.x()), self.length)
+ pos.setX(newX)
+ tick.setPos(pos)
+ self.ticks[tick] = float(newX) / self.length
+
+ def tickClicked(self, tick, ev):
+ if ev.button() == QtCore.Qt.RightButton:
+ self.removeTick(tick)
+
+ def widgetLength(self):
+ if self.orientation in ['bottom', 'top']:
+ return self.width()
+ else:
+ return self.height()
+
+ def resizeEvent(self, ev):
+ wlen = max(40, self.widgetLength())
+ self.setLength(wlen-self.tickSize)
+ bounds = self.scene.itemsBoundingRect()
+ bounds.setLeft(min(-self.tickSize*0.5, bounds.left()))
+ bounds.setRight(max(self.length + self.tickSize, bounds.right()))
+ #bounds.setTop(min(bounds.top(), self.tickSize))
+ #bounds.setBottom(max(0, bounds.bottom()))
+ self.setSceneRect(bounds)
+ self.fitInView(bounds, QtCore.Qt.KeepAspectRatio)
+
+ def setLength(self, newLen):
+ for t, x in self.ticks.items():
+ t.setPos(x * newLen, t.pos().y())
+ self.length = float(newLen)
+
+ def mousePressEvent(self, ev):
+ QtGui.QGraphicsView.mousePressEvent(self, ev)
+ self.ignoreRelease = False
+ if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks
+ self.ignoreRelease = True
+
+ def mouseReleaseEvent(self, ev):
+ QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
+ if self.ignoreRelease:
+ return
+
+ pos = self.mapToScene(ev.pos())
+ if pos.x() < 0 or pos.x() > self.length:
+ return
+ if pos.y() < 0 or pos.y() > self.tickSize:
+ return
+
+ if ev.button() == QtCore.Qt.LeftButton and self.allowAdd:
+ pos.setX(min(max(pos.x(), 0), self.length))
+ self.addTick(pos.x()/self.length)
+ elif ev.button() == QtCore.Qt.RightButton:
+ self.showMenu(ev)
+
+
+ def showMenu(self, ev):
+ pass
+
+ def setTickColor(self, tick, color):
+ tick = self.getTick(tick)
+ tick.color = color
+ tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color)))
+
+ def setTickValue(self, tick, val):
+ tick = self.getTick(tick)
+ val = min(max(0.0, val), 1.0)
+ x = val * self.length
+ pos = tick.pos()
+ pos.setX(x)
+ tick.setPos(pos)
+ self.ticks[tick] = val
+
+ def tickValue(self, tick):
+ tick = self.getTick(tick)
+ return self.ticks[tick]
+
+ def getTick(self, tick):
+ if type(tick) is int:
+ tick = self.listTicks()[tick][0]
+ return tick
+
+ def mouseMoveEvent(self, ev):
+ QtGui.QGraphicsView.mouseMoveEvent(self, ev)
+ #print ev.pos(), ev.buttons()
+
+ def listTicks(self):
+ ticks = self.ticks.items()
+ ticks.sort(lambda a,b: cmp(a[1], b[1]))
+ return ticks
+
+
+class GradientWidget(TickSlider):
+ def __init__(self, *args, **kargs):
+ TickSlider.__init__(self, *args, **kargs)
+ self.rectSize = 15
+ self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
+ self.colorMode = 'rgb'
+
+
+ #self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
+ self.scene.addItem(self.gradRect)
+ self.addTick(0, QtGui.QColor(0,0,0), True)
+ self.addTick(1, QtGui.QColor(255,0,0), True)
+
+ self.setMaxDim(self.rectSize + self.tickSize)
+ self.updateGradient()
+
+ #self.btn = QtGui.QPushButton('RGB')
+ #self.btnProxy = self.scene.addWidget(self.btn)
+ #self.btnProxy.setFlag(self.btnProxy.ItemIgnoresTransformations)
+ #self.btnProxy.scale(0.7, 0.7)
+ #self.btnProxy.translate(-self.btnProxy.sceneBoundingRect().width()+self.tickSize/2., 0)
+ #if self.orientation == 'bottom':
+ #self.btnProxy.translate(0, -self.rectSize)
+
+ def setColorMode(self, cm):
+ if cm not in ['rgb', 'hsv']:
+ raise Exception("Unknown color mode %s" % str(cm))
+ self.colorMode = cm
+ self.updateGradient()
+
+ def updateGradient(self):
+ self.gradient = self.getGradient()
+ self.gradRect.setBrush(QtGui.QBrush(self.gradient))
+ self.emit(QtCore.SIGNAL('gradientChanged'), self)
+
+ def setLength(self, newLen):
+ TickSlider.setLength(self, newLen)
+ self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
+ 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()
+ elif ev.button() == QtCore.Qt.RightButton:
+ if not tick.removeAllowed:
+ return
+ if len(self.ticks) > 2:
+ self.removeTick(tick)
+ self.updateGradient()
+
+ def tickMoved(self, tick, pos):
+ TickSlider.tickMoved(self, tick, pos)
+ self.updateGradient()
+
+
+ def getGradient(self):
+ g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0))
+ if self.colorMode == 'rgb':
+ ticks = self.listTicks()
+ g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks])
+ elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop
+ ticks = self.listTicks()
+ stops = []
+ stops.append((ticks[0][1], ticks[0][0].color))
+ for i in range(1,len(ticks)):
+ x1 = ticks[i-1][1]
+ x2 = ticks[i][1]
+ dx = (x2-x1) / 10.
+ for j in range(1,10):
+ x = x1 + dx*j
+ stops.append((x, self.getColor(x)))
+ stops.append((x2, self.getColor(x2)))
+ g.setStops(stops)
+ return g
+
+ def getColor(self, x):
+ ticks = self.listTicks()
+ if x <= ticks[0][1]:
+ return QtGui.QColor(ticks[0][0].color) # always copy colors before handing them out
+ if x >= ticks[-1][1]:
+ return QtGui.QColor(ticks[-1][0].color)
+
+ x2 = ticks[0][1]
+ for i in range(1,len(ticks)):
+ x1 = x2
+ x2 = ticks[i][1]
+ if x1 <= x and x2 >= x:
+ break
+
+ dx = (x2-x1)
+ if dx == 0:
+ f = 0.
+ else:
+ f = (x-x1) / dx
+ c1 = ticks[i-1][0].color
+ c2 = ticks[i][0].color
+ if self.colorMode == 'rgb':
+ 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)
+ elif self.colorMode == 'hsv':
+ h1,s1,v1,_ = c1.getHsv()
+ h2,s2,v2,_ = c2.getHsv()
+ h = h1 * (1.-f) + h2 * f
+ s = s1 * (1.-f) + s2 * f
+ v = v1 * (1.-f) + v2 * f
+ c = QtGui.QColor()
+ c.setHsv(h,s,v)
+ return c
+
+
+
+ def mouseReleaseEvent(self, ev):
+ TickSlider.mouseReleaseEvent(self, ev)
+ self.updateGradient()
+
+ def addTick(self, x, color=None, movable=True):
+ if color is None:
+ color = self.getColor(x)
+ t = TickSlider.addTick(self, x, color=color, movable=movable)
+ t.colorChangeAllowed = True
+ t.removeAllowed = True
+ return t
+
+
+class GammaWidget(TickSlider):
+ pass
+
+
+class Tick(QtGui.QGraphicsPolygonItem):
+ def __init__(self, view, pos, color, movable=True, scale=10):
+ #QObjectWorkaround.__init__(self)
+ self.movable = movable
+ self.view = view
+ self.scale = scale
+ self.color = color
+ #self.endTick = endTick
+ self.pg = QtGui.QPolygonF([QtCore.QPointF(0,0), QtCore.QPointF(-scale/3**0.5,scale), QtCore.QPointF(scale/3**0.5,scale)])
+ QtGui.QGraphicsPolygonItem.__init__(self, self.pg)
+ self.setPos(pos[0], pos[1])
+ self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsSelectable)
+ self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
+ if self.movable:
+ self.setZValue(1)
+ else:
+ self.setZValue(0)
+
+ #def x(self):
+ #return self.pos().x()/100.
+
+ def mouseMoveEvent(self, ev):
+ #print self, "move", ev.scenePos()
+ if not self.movable:
+ return
+ if not ev.buttons() & QtCore.Qt.LeftButton:
+ return
+
+
+ newPos = ev.scenePos() + self.mouseOffset
+ newPos.setY(self.pos().y())
+ #newPos.setX(min(max(newPos.x(), 0), 100))
+ self.setPos(newPos)
+ self.view.tickMoved(self, newPos)
+ self.movedSincePress = True
+ #self.emit(QtCore.SIGNAL('tickChanged'), self)
+ ev.accept()
+
+ def mousePressEvent(self, ev):
+ self.movedSincePress = False
+ if ev.button() == QtCore.Qt.LeftButton:
+ ev.accept()
+ self.mouseOffset = self.pos() - ev.scenePos()
+ self.pressPos = ev.scenePos()
+ elif ev.button() == QtCore.Qt.RightButton:
+ ev.accept()
+ #if self.endTick:
+ #return
+ #self.view.tickChanged(self, delete=True)
+
+ def mouseReleaseEvent(self, ev):
+ #print self, "release", ev.scenePos()
+ if not self.movedSincePress:
+ self.view.tickClicked(self, ev)
+
+ #if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
+ #color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
+ #if color.isValid():
+ #self.color = color
+ #self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
+ ##self.emit(QtCore.SIGNAL('tickChanged'), self)
+ #self.view.tickChanged(self)
+
+
+
\ No newline at end of file
diff --git a/GradientWidgetTest.py b/GradientWidgetTest.py
new file mode 100644
index 00000000..95b9eeae
--- /dev/null
+++ b/GradientWidgetTest.py
@@ -0,0 +1,26 @@
+# -*- 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)
diff --git a/GraphicsView.py b/GraphicsView.py
index cb6c23fb..89467f13 100644
--- a/GraphicsView.py
+++ b/GraphicsView.py
@@ -10,11 +10,11 @@ from PyQt4 import QtCore, QtGui, QtOpenGL, QtSvg
#import time
from Point import *
#from vector import *
-
+import sys
class GraphicsView(QtGui.QGraphicsView):
- def __init__(self, *args):
+ def __init__(self, parent=None, useOpenGL=True):
"""Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the
viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget
that is automatically scaled to the full view geometry.
@@ -26,8 +26,9 @@ class GraphicsView(QtGui.QGraphicsView):
The view can be panned using the middle mouse button and scaled using the right mouse button if
enabled via enableMouse()."""
- QtGui.QGraphicsView.__init__(self, *args)
- self.setViewport(QtOpenGL.QGLWidget())
+ QtGui.QGraphicsView.__init__(self, parent)
+ self.useOpenGL(useOpenGL)
+
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(0,0,0))
brush.setStyle(QtCore.Qt.SolidPattern)
@@ -49,6 +50,7 @@ class GraphicsView(QtGui.QGraphicsView):
#self.setResizeAnchor(QtGui.QGraphicsView.NoAnchor)
self.setViewportUpdateMode(QtGui.QGraphicsView.SmartViewportUpdate)
self.setSceneRect(QtCore.QRectF(-1e10, -1e10, 2e10, 2e10))
+ #self.setSceneRect(1, 1, 0, 0) ## Set an empty (but non-zero) scene rect so that the view doesn't try to automatically update for us.
#self.setInteractive(False)
self.lockedViewports = []
self.lastMousePos = None
@@ -68,6 +70,18 @@ class GraphicsView(QtGui.QGraphicsView):
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
self.clickAccepted = False
+ def useOpenGL(self, b=True):
+ if b:
+ v = QtOpenGL.QGLWidget()
+ else:
+ v = QtGui.QWidget()
+
+ #v.setStyleSheet("background-color: #000000;")
+ self.setViewport(v)
+
+ def keyPressEvent(self, ev):
+ ev.ignore()
+
def setCentralItem(self, item):
if self.centralWidget is not None:
self.scene().removeItem(self.centralWidget)
@@ -77,6 +91,9 @@ class GraphicsView(QtGui.QGraphicsView):
def addItem(self, *args):
return self.scene().addItem(*args)
+ def removeItem(self, *args):
+ return self.scene().removeItem(*args)
+
def enableMouse(self, b=True):
self.mouseEnabled = b
self.autoPixelRange = (not b)
@@ -128,6 +145,8 @@ class GraphicsView(QtGui.QGraphicsView):
v.setXRange(self.range, padding=0)
def visibleRange(self):
+ """Return the boundaries of the view in scene coordinates"""
+ ## easier to just return self.range ?
r = QtCore.QRectF(self.rect())
return self.viewportTransform().inverted()[0].mapRect(r)
@@ -347,6 +366,16 @@ class GraphicsView(QtGui.QGraphicsView):
self.setRenderHints(rh)
self.png.save(fileName)
+ def writePs(self, fileName=None):
+ if fileName is None:
+ fileName = str(QtGui.QFileDialog.getSaveFileName())
+ printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
+ printer.setOutputFileName(fileName)
+ painter = QtGui.QPainter(printer)
+ self.render(painter)
+ painter.end()
+
+
#def getFreehandLine(self):
## Wait for click
diff --git a/ImageView.py b/ImageView.py
index 21be5819..f7dadfe4 100644
--- a/ImageView.py
+++ b/ImageView.py
@@ -17,6 +17,11 @@ from ImageViewTemplate import *
from graphicsItems import *
from widgets import ROI
from PyQt4 import QtCore, QtGui
+import sys
+from numpy import ndarray
+import ptime
+
+from SignalProxy import proxyConnect
class PlotROI(ROI):
def __init__(self, size):
@@ -35,12 +40,25 @@ class ImageView(QtGui.QWidget):
self.ui = Ui_Form()
self.ui.setupUi(self)
self.scene = self.ui.graphicsView.sceneObj
+
+ self.ignoreTimeLine = False
+
+ if 'linux' in sys.platform.lower(): ## Stupid GL bug in linux.
+ self.ui.graphicsView.setViewport(QtGui.QWidget())
+
self.ui.graphicsView.enableMouse(True)
self.ui.graphicsView.autoPixelRange = False
self.ui.graphicsView.setAspectLocked(True)
self.ui.graphicsView.invertY()
self.ui.graphicsView.enableMouse()
+ 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
+ self.ui.gradientWidget.setTickColor(self.ticks[1], QtGui.QColor(255,255,255))
+ self.ui.gradientWidget.setOrientation('right')
+
self.imageItem = ImageItem()
self.scene.addItem(self.imageItem)
self.currentIndex = 0
@@ -51,26 +69,46 @@ class ImageView(QtGui.QWidget):
self.roi.setZValue(20)
self.scene.addItem(self.roi)
self.roi.hide()
- self.ui.roiPlot.hide()
+ self.normRoi = PlotROI(10)
+ self.normRoi.setPen(QtGui.QPen(QtGui.QColor(255,255,0)))
+ self.normRoi.setZValue(20)
+ self.scene.addItem(self.normRoi)
+ self.normRoi.hide()
+ #self.ui.roiPlot.hide()
self.roiCurve = self.ui.roiPlot.plot()
- self.roiTimeLine = InfiniteLine(self.ui.roiPlot, 0)
- self.roiTimeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200)))
- self.ui.roiPlot.addItem(self.roiTimeLine)
+ self.timeLine = InfiniteLine(self.ui.roiPlot, 0, movable=True)
+ self.timeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200)))
+ self.timeLine.setZValue(1)
+ self.ui.roiPlot.addItem(self.timeLine)
+ self.ui.splitter.setSizes([self.height()-35, 35])
+ self.ui.roiPlot.showScale('left', False)
- self.normLines = []
- for i in [0,1]:
- l = InfiniteLine(self.ui.roiPlot, 0)
- l.setPen(QtGui.QPen(QtGui.QColor(0, 100, 200, 200)))
- self.ui.roiPlot.addItem(l)
- self.normLines.append(l)
- l.hide()
+ self.keysPressed = {}
+ self.playTimer = QtCore.QTimer()
+ self.playRate = 0
+ self.lastPlayTime = 0
+
+ #self.normLines = []
+ #for i in [0,1]:
+ #l = InfiniteLine(self.ui.roiPlot, 0)
+ #l.setPen(QtGui.QPen(QtGui.QColor(0, 100, 200, 200)))
+ #self.ui.roiPlot.addItem(l)
+ #self.normLines.append(l)
+ #l.hide()
+ self.normRgn = LinearRegionItem(self.ui.roiPlot, 'vertical')
+ self.normRgn.setZValue(0)
+ self.ui.roiPlot.addItem(self.normRgn)
+ self.normRgn.hide()
- for fn in ['addItem']:
+ ## wrap functions from graphics view
+ for fn in ['addItem', 'removeItem']:
setattr(self, fn, getattr(self.ui.graphicsView, fn))
- QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged)
- QtCore.QObject.connect(self.ui.whiteSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
- QtCore.QObject.connect(self.ui.blackSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateImage)
+ #QtCore.QObject.connect(self.ui.timeSlider, QtCore.SIGNAL('valueChanged(int)'), self.timeChanged)
+ self.timeLine.connect(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)
QtCore.QObject.connect(self.ui.normBtn, QtCore.SIGNAL('toggled(bool)'), self.normToggled)
@@ -80,21 +118,151 @@ class ImageView(QtGui.QWidget):
QtCore.QObject.connect(self.ui.normROICheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normFrameCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
QtCore.QObject.connect(self.ui.normTimeRangeCheck, QtCore.SIGNAL('clicked()'), self.updateNorm)
- QtCore.QObject.connect(self.ui.normStartSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
- QtCore.QObject.connect(self.ui.normStopSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateNorm)
+ QtCore.QObject.connect(self.playTimer, QtCore.SIGNAL('timeout()'), self.timeout)
+
+ ##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.ui.roiPlot.registerPlot(self.name + '_ROI')
+
+ self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]
+
+ #def __dtor__(self):
+ ##print "Called ImageView sip destructor"
+ #self.quit()
+ #QtGui.QWidget.__dtor__(self)
+
+ def quit(self):
+ self.scene.clear()
+ del self.image
+ del self.imageDisp
+
+
+ def keyPressEvent(self, ev):
+ if ev.key() == QtCore.Qt.Key_Space:
+ if self.playRate == 0:
+ fps = (self.getProcessedImage().shape[0]-1) / (self.tVals[-1] - self.tVals[0])
+ self.play(fps)
+ #print fps
+ else:
+ self.play(0)
+ ev.accept()
+ elif ev.key() == QtCore.Qt.Key_Home:
+ self.setCurrentIndex(0)
+ self.play(0)
+ ev.accept()
+ elif ev.key() == QtCore.Qt.Key_End:
+ self.setCurrentIndex(self.getProcessedImage().shape[0]-1)
+ self.play(0)
+ ev.accept()
+ elif ev.key() in self.noRepeatKeys:
+ ev.accept()
+ if ev.isAutoRepeat():
+ return
+ self.keysPressed[ev.key()] = 1
+ self.evalKeyState()
+ else:
+ QtGui.QWidget.keyPressEvent(self, ev)
+
+ def keyReleaseEvent(self, ev):
+ if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]:
+ ev.accept()
+ elif ev.key() in self.noRepeatKeys:
+ ev.accept()
+ if ev.isAutoRepeat():
+ return
+ try:
+ del self.keysPressed[ev.key()]
+ except:
+ self.keysPressed = {}
+ self.evalKeyState()
+ else:
+ QtGui.QWidget.keyReleaseEvent(self, ev)
+
+
+ def evalKeyState(self):
+ if len(self.keysPressed) == 1:
+ key = self.keysPressed.keys()[0]
+ if key == QtCore.Qt.Key_Right:
+ self.play(20)
+ self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start
+ self.jumpFrames(1)
+ elif key == QtCore.Qt.Key_Left:
+ self.play(-20)
+ self.lastPlayTime = ptime.time() + 0.2
+ self.jumpFrames(-1)
+ elif key == QtCore.Qt.Key_Up:
+ self.play(-100)
+ elif key == QtCore.Qt.Key_Down:
+ self.play(100)
+ elif key == QtCore.Qt.Key_PageUp:
+ self.play(-1000)
+ elif key == QtCore.Qt.Key_PageDown:
+ self.play(1000)
+ else:
+ self.play(0)
+
+ def play(self, rate):
+ #print "play:", rate
+ self.playRate = rate
+ if rate == 0:
+ self.playTimer.stop()
+ return
+
+ self.lastPlayTime = ptime.time()
+ if not self.playTimer.isActive():
+ self.playTimer.start(16)
+
+
+ def timeout(self):
+ now = ptime.time()
+ dt = now - self.lastPlayTime
+ if dt < 0:
+ return
+ n = int(self.playRate * dt)
+ #print n, dt
+ if n != 0:
+ #print n, dt, self.lastPlayTime
+ self.lastPlayTime += (float(n)/self.playRate)
+ if self.currentIndex+n > self.image.shape[0]:
+ self.play(0)
+ self.jumpFrames(n)
+
+ def setCurrentIndex(self, ind):
+ self.currentIndex = clip(ind, 0, self.getProcessedImage().shape[0]-1)
+ self.updateImage()
+ self.ignoreTimeLine = True
+ self.timeLine.setValue(self.tVals[self.currentIndex])
+ self.ignoreTimeLine = False
+
+ def jumpFrames(self, n):
+ """If this is a video, move ahead n frames"""
+ if self.axes['t'] is not None:
+ self.setCurrentIndex(self.currentIndex + n)
def updateNorm(self):
- for l, sl in zip(self.normLines, [self.ui.normStartSlider, self.ui.normStopSlider]):
- if self.ui.normTimeRangeCheck.isChecked():
- l.show()
- else:
- l.hide()
+ #for l, sl in zip(self.normLines, [self.ui.normStartSlider, self.ui.normStopSlider]):
+ #if self.ui.normTimeRangeCheck.isChecked():
+ #l.show()
+ #else:
+ #l.hide()
- i, t = self.timeIndex(sl)
- l.setPos(t)
+ #i, t = self.timeIndex(sl)
+ #l.setPos(t)
+ if self.ui.normTimeRangeCheck.isChecked():
+ #print "show!"
+ self.normRgn.show()
+ else:
+ self.normRgn.hide()
+
+ if self.ui.normROICheck.isChecked():
+ #print "show!"
+ self.normRoi.show()
+ else:
+ self.normRoi.hide()
self.imageDisp = None
self.updateImage()
@@ -102,15 +270,25 @@ class ImageView(QtGui.QWidget):
def normToggled(self, b):
self.ui.normGroup.setVisible(b)
+ self.normRoi.setVisible(b and self.ui.normROICheck.isChecked())
+ self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked())
def roiClicked(self):
if self.ui.roiBtn.isChecked():
self.roi.show()
- self.ui.roiPlot.show()
+ #self.ui.roiPlot.show()
+ self.ui.roiPlot.setMouseEnabled(True, True)
+ self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4])
+ self.roiCurve.show()
self.roiChanged()
+ self.ui.roiPlot.showScale('left', True)
else:
self.roi.hide()
- self.ui.roiPlot.hide()
+ self.ui.roiPlot.setMouseEnabled(False, False)
+ self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
+ self.ui.splitter.setSizes([self.height()-35, 35])
+ self.roiCurve.hide()
+ self.ui.roiPlot.showScale('left', False)
def roiChanged(self):
if self.image is None:
@@ -130,25 +308,41 @@ class ImageView(QtGui.QWidget):
self.roiCurve.setData(y=data, x=self.tVals)
#self.ui.roiPlot.replot()
- def setImage(self, img, autoRange=True, autoLevels=True, levels=None):
+ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None):
+ """Set the image to be displayed in the widget.
+ Options are:
+ img: ndarray; the image to be displayed.
+ autoRange: bool; whether to scale/pan the view to fit the image.
+ autoLevels: bool; whether to update the white/black levels to fit the image.
+ levels: (min, max); the white and black level values to use.
+ axes: {'t':0, 'x':1, 'y':2, 'c':3}; Dictionary indicating the interpretation for each axis.
+ This is only needed to override the default guess.
+ """
+
+ if not isinstance(img, ndarray):
+ raise Exception("Image must be specified as ndarray.")
self.image = img
- if hasattr(img, 'xvals'):
+
+ if xvals is not None:
+ self.tVals = xvals
+ elif hasattr(img, 'xvals'):
self.tVals = img.xvals(0)
else:
self.tVals = arange(img.shape[0])
- self.ui.timeSlider.setValue(0)
+ #self.ui.timeSlider.setValue(0)
#self.ui.normStartSlider.setValue(0)
#self.ui.timeSlider.setMaximum(img.shape[0]-1)
-
- if img.ndim == 2:
- self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None}
- elif img.ndim == 3:
- if img.shape[2] <= 3:
- self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2}
- else:
- self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
- elif img.ndim == 4:
- self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
+
+ if axes is None:
+ if img.ndim == 2:
+ self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None}
+ elif img.ndim == 3:
+ if img.shape[2] <= 4:
+ self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2}
+ else:
+ self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None}
+ elif img.ndim == 4:
+ self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3}
self.imageDisp = None
@@ -163,11 +357,36 @@ class ImageView(QtGui.QWidget):
if self.ui.roiBtn.isChecked():
self.roiChanged()
+
+ if self.axes['t'] is not None:
+ #self.ui.roiPlot.show()
+ self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
+ #self.ui.roiPlot.setMouseEnabled(False, False)
+ if len(self.tVals) > 1:
+ start = self.tVals.min()
+ stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02
+ elif len(self.tVals) == 1:
+ start = self.tVals[0] - 0.5
+ stop = self.tVals[0] + 0.5
+ else:
+ start = 0
+ stop = 1
+ for s in [self.timeLine, self.normRgn]:
+ s.setBounds([start, stop])
+ #else:
+ #self.ui.roiPlot.hide()
+
+ self.roiClicked()
+
+
def autoLevels(self):
image = self.getProcessedImage()
- self.ui.whiteSlider.setValue(self.ui.whiteSlider.maximum())
- self.ui.blackSlider.setValue(0)
+ #self.ui.whiteSlider.setValue(self.ui.whiteSlider.maximum())
+ #self.ui.blackSlider.setValue(0)
+
+ self.ui.gradientWidget.setTickValue(self.ticks[0], 0.0)
+ self.ui.gradientWidget.setTickValue(self.ticks[1], 1.0)
self.imageItem.setLevels(white=self.whiteLevel(), black=self.blackLevel())
def autoRange(self):
@@ -189,7 +408,7 @@ class ImageView(QtGui.QWidget):
return image
div = self.ui.normDivideRadio.isChecked()
- norm = image.copy()
+ norm = image.view(ndarray).copy()
#if div:
#norm = ones(image.shape)
#else:
@@ -198,8 +417,8 @@ class ImageView(QtGui.QWidget):
norm = norm.astype(float32)
if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3:
- (sind, start) = self.timeIndex(self.ui.normStartSlider)
- (eind, end) = self.timeIndex(self.ui.normStopSlider)
+ (sind, start) = self.timeIndex(self.normRgn.lines[0])
+ (eind, end) = self.timeIndex(self.normRgn.lines[1])
#print start, end, sind, eind
n = image[sind:eind+1].mean(axis=0)
n.shape = (1,) + n.shape
@@ -216,17 +435,27 @@ class ImageView(QtGui.QWidget):
else:
norm -= n
+ if self.ui.normROICheck.isChecked() and image.ndim == 3:
+ n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1)
+ n = n[:,newaxis,newaxis]
+ #print start, end, sind, eind
+ if div:
+ norm /= n
+ else:
+ norm -= n
+
return norm
-
-
- def timeChanged(self):
- (ind, time) = self.timeIndex(self.ui.timeSlider)
+ def timeLineChanged(self):
+ #(ind, time) = self.timeIndex(self.ui.timeSlider)
+ if self.ignoreTimeLine:
+ return
+ self.play(0)
+ (ind, time) = self.timeIndex(self.timeLine)
if ind != self.currentIndex:
self.currentIndex = ind
self.updateImage()
- self.roiTimeLine.setPos(time)
- #self.ui.roiPlot.replot()
+ #self.timeLine.setPos(time)
self.emit(QtCore.SIGNAL('timeChanged'), ind, time)
def updateImage(self):
@@ -237,29 +466,38 @@ class ImageView(QtGui.QWidget):
image = self.getProcessedImage()
#print "update:", image.ndim, image.max(), image.min(), self.blackLevel(), self.whiteLevel()
if self.axes['t'] is None:
- self.ui.timeSlider.hide()
+ #self.ui.timeSlider.hide()
self.imageItem.updateImage(image, white=self.whiteLevel(), black=self.blackLevel())
+ self.ui.roiPlot.hide()
+ self.ui.roiBtn.hide()
else:
- self.ui.timeSlider.show()
+ self.ui.roiBtn.show()
+ self.ui.roiPlot.show()
+ #self.ui.timeSlider.show()
self.imageItem.updateImage(image[self.currentIndex], white=self.whiteLevel(), black=self.blackLevel())
+
def timeIndex(self, slider):
"""Return the time and frame index indicated by a slider"""
if self.image is None:
return (0,0)
- v = slider.value()
- vmax = slider.maximum()
- f = float(v) / vmax
- t = 0.0
+ #v = slider.value()
+ #vmax = slider.maximum()
+ #f = float(v) / vmax
+
+ t = slider.value()
+
+ #t = 0.0
#xv = self.image.xvals('Time')
xv = self.tVals
if xv is None:
- ind = int(f * self.image.shape[0])
+ ind = int(t)
+ #ind = int(f * self.image.shape[0])
else:
if len(xv) < 2:
return (0,0)
totTime = xv[-1] + (xv[-1]-xv[-2])
- t = f * totTime
+ #t = f * totTime
inds = argwhere(xv < t)
if len(inds) < 1:
return (0,t)
@@ -268,8 +506,10 @@ class ImageView(QtGui.QWidget):
return ind, t
def whiteLevel(self):
- return self.levelMin + (self.levelMax-self.levelMin) * self.ui.whiteSlider.value() / self.ui.whiteSlider.maximum()
+ return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[1])
+ #return self.levelMin + (self.levelMax-self.levelMin) * self.ui.whiteSlider.value() / self.ui.whiteSlider.maximum()
def blackLevel(self):
- return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value()
+ return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[0])
+ #return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value()
\ No newline at end of file
diff --git a/ImageViewTemplate.py b/ImageViewTemplate.py
index 7521dd53..6e2cdc9f 100644
--- a/ImageViewTemplate.py
+++ b/ImageViewTemplate.py
@@ -1,188 +1,167 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'ImageViewTemplate.ui'
-#
-# Created: Mon Mar 29 22:40:48 2010
-# by: PyQt4 UI code generator 4.6
-#
-# WARNING! All changes made in this file will be lost!
-
-from PyQt4 import QtCore, QtGui
-
-class Ui_Form(object):
- def setupUi(self, Form):
- Form.setObjectName("Form")
- Form.resize(757, 495)
- self.verticalLayout = QtGui.QVBoxLayout(Form)
- self.verticalLayout.setSpacing(0)
- self.verticalLayout.setMargin(0)
- self.verticalLayout.setObjectName("verticalLayout")
- self.splitter = QtGui.QSplitter(Form)
- self.splitter.setOrientation(QtCore.Qt.Vertical)
- self.splitter.setObjectName("splitter")
- self.layoutWidget = QtGui.QWidget(self.splitter)
- self.layoutWidget.setObjectName("layoutWidget")
- self.gridLayout = QtGui.QGridLayout(self.layoutWidget)
- self.gridLayout.setSpacing(0)
- self.gridLayout.setObjectName("gridLayout")
- self.graphicsView = GraphicsView(self.layoutWidget)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth())
- self.graphicsView.setSizePolicy(sizePolicy)
- self.graphicsView.setObjectName("graphicsView")
- self.gridLayout.addWidget(self.graphicsView, 0, 0, 3, 1)
- self.blackSlider = QtGui.QSlider(self.layoutWidget)
- self.blackSlider.setMaximum(4096)
- self.blackSlider.setOrientation(QtCore.Qt.Vertical)
- self.blackSlider.setInvertedAppearance(False)
- self.blackSlider.setInvertedControls(False)
- self.blackSlider.setTickPosition(QtGui.QSlider.TicksBelow)
- self.blackSlider.setTickInterval(410)
- self.blackSlider.setObjectName("blackSlider")
- self.gridLayout.addWidget(self.blackSlider, 0, 1, 1, 1)
- self.whiteSlider = QtGui.QSlider(self.layoutWidget)
- self.whiteSlider.setMaximum(4096)
- self.whiteSlider.setProperty("value", 4096)
- self.whiteSlider.setOrientation(QtCore.Qt.Vertical)
- self.whiteSlider.setObjectName("whiteSlider")
- self.gridLayout.addWidget(self.whiteSlider, 0, 2, 1, 2)
- self.label = QtGui.QLabel(self.layoutWidget)
- self.label.setObjectName("label")
- self.gridLayout.addWidget(self.label, 1, 1, 1, 1)
- self.label_2 = QtGui.QLabel(self.layoutWidget)
- self.label_2.setObjectName("label_2")
- self.gridLayout.addWidget(self.label_2, 1, 2, 1, 1)
- self.roiBtn = QtGui.QPushButton(self.layoutWidget)
- self.roiBtn.setMaximumSize(QtCore.QSize(40, 16777215))
- self.roiBtn.setCheckable(True)
- self.roiBtn.setObjectName("roiBtn")
- self.gridLayout.addWidget(self.roiBtn, 2, 1, 1, 3)
- self.timeSlider = QtGui.QSlider(self.layoutWidget)
- self.timeSlider.setMaximum(65535)
- self.timeSlider.setOrientation(QtCore.Qt.Horizontal)
- self.timeSlider.setObjectName("timeSlider")
- self.gridLayout.addWidget(self.timeSlider, 4, 0, 1, 1)
- self.normBtn = QtGui.QPushButton(self.layoutWidget)
- self.normBtn.setMaximumSize(QtCore.QSize(50, 16777215))
- self.normBtn.setCheckable(True)
- self.normBtn.setObjectName("normBtn")
- self.gridLayout.addWidget(self.normBtn, 4, 1, 1, 2)
- self.normGroup = QtGui.QGroupBox(self.layoutWidget)
- self.normGroup.setObjectName("normGroup")
- self.gridLayout_2 = QtGui.QGridLayout(self.normGroup)
- self.gridLayout_2.setMargin(0)
- self.gridLayout_2.setSpacing(0)
- self.gridLayout_2.setObjectName("gridLayout_2")
- self.normSubtractRadio = QtGui.QRadioButton(self.normGroup)
- self.normSubtractRadio.setObjectName("normSubtractRadio")
- self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1)
- self.normDivideRadio = QtGui.QRadioButton(self.normGroup)
- self.normDivideRadio.setChecked(False)
- self.normDivideRadio.setObjectName("normDivideRadio")
- self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1)
- self.label_5 = QtGui.QLabel(self.normGroup)
- font = QtGui.QFont()
- font.setWeight(75)
- font.setBold(True)
- self.label_5.setFont(font)
- self.label_5.setObjectName("label_5")
- self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1)
- self.label_3 = QtGui.QLabel(self.normGroup)
- font = QtGui.QFont()
- font.setWeight(75)
- font.setBold(True)
- self.label_3.setFont(font)
- self.label_3.setObjectName("label_3")
- self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
- self.label_4 = QtGui.QLabel(self.normGroup)
- font = QtGui.QFont()
- font.setWeight(75)
- font.setBold(True)
- self.label_4.setFont(font)
- self.label_4.setObjectName("label_4")
- self.gridLayout_2.addWidget(self.label_4, 4, 0, 1, 1)
- self.normROICheck = QtGui.QCheckBox(self.normGroup)
- self.normROICheck.setObjectName("normROICheck")
- self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1)
- self.normStartSlider = QtGui.QSlider(self.normGroup)
- self.normStartSlider.setMaximum(65535)
- self.normStartSlider.setOrientation(QtCore.Qt.Horizontal)
- self.normStartSlider.setObjectName("normStartSlider")
- self.gridLayout_2.addWidget(self.normStartSlider, 2, 0, 1, 6)
- self.normStopSlider = QtGui.QSlider(self.normGroup)
- self.normStopSlider.setMaximum(65535)
- self.normStopSlider.setOrientation(QtCore.Qt.Horizontal)
- self.normStopSlider.setObjectName("normStopSlider")
- self.gridLayout_2.addWidget(self.normStopSlider, 3, 0, 1, 6)
- self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
- self.normXBlurSpin.setObjectName("normXBlurSpin")
- self.gridLayout_2.addWidget(self.normXBlurSpin, 4, 2, 1, 1)
- self.label_8 = QtGui.QLabel(self.normGroup)
- self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.label_8.setObjectName("label_8")
- self.gridLayout_2.addWidget(self.label_8, 4, 1, 1, 1)
- self.label_9 = QtGui.QLabel(self.normGroup)
- self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.label_9.setObjectName("label_9")
- self.gridLayout_2.addWidget(self.label_9, 4, 3, 1, 1)
- self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
- self.normYBlurSpin.setObjectName("normYBlurSpin")
- self.gridLayout_2.addWidget(self.normYBlurSpin, 4, 4, 1, 1)
- self.label_10 = QtGui.QLabel(self.normGroup)
- self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.label_10.setObjectName("label_10")
- self.gridLayout_2.addWidget(self.label_10, 4, 5, 1, 1)
- self.normStopLabel = QtGui.QLabel(self.normGroup)
- self.normStopLabel.setObjectName("normStopLabel")
- self.gridLayout_2.addWidget(self.normStopLabel, 3, 6, 1, 1)
- self.normStartLabel = QtGui.QLabel(self.normGroup)
- self.normStartLabel.setObjectName("normStartLabel")
- self.gridLayout_2.addWidget(self.normStartLabel, 2, 6, 1, 1)
- self.normOffRadio = QtGui.QRadioButton(self.normGroup)
- self.normOffRadio.setChecked(True)
- self.normOffRadio.setObjectName("normOffRadio")
- self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1)
- self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup)
- self.normTimeRangeCheck.setObjectName("normTimeRangeCheck")
- self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1)
- self.normFrameCheck = QtGui.QCheckBox(self.normGroup)
- self.normFrameCheck.setObjectName("normFrameCheck")
- self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1)
- self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
- self.normTBlurSpin.setObjectName("normTBlurSpin")
- self.gridLayout_2.addWidget(self.normTBlurSpin, 4, 6, 1, 1)
- self.gridLayout.addWidget(self.normGroup, 5, 0, 1, 4)
- self.roiPlot = PlotWidget(self.splitter)
- self.roiPlot.setMinimumSize(QtCore.QSize(0, 40))
- self.roiPlot.setObjectName("roiPlot")
- self.verticalLayout.addWidget(self.splitter)
-
- self.retranslateUi(Form)
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.label.setText(QtGui.QApplication.translate("Form", "B", None, QtGui.QApplication.UnicodeUTF8))
- self.label_2.setText(QtGui.QApplication.translate("Form", "W", None, QtGui.QApplication.UnicodeUTF8))
- self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
- self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8))
- self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8))
- self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8))
- self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8))
- self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8))
- self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8))
- self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8))
- self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
- self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8))
- self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8))
- self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8))
- self.normStopLabel.setText(QtGui.QApplication.translate("Form", "Stop", None, QtGui.QApplication.UnicodeUTF8))
- self.normStartLabel.setText(QtGui.QApplication.translate("Form", "Start", None, QtGui.QApplication.UnicodeUTF8))
- self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8))
- self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8))
- self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8))
-
-from GraphicsView import GraphicsView
-from PlotWidget import PlotWidget
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'ImageViewTemplate.ui'
+#
+# Created: Sat Jul 17 13:05:44 2010
+# by: PyQt4 UI code generator 4.5.4
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt4 import QtCore, QtGui
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(726, 588)
+ self.verticalLayout = QtGui.QVBoxLayout(Form)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setMargin(0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.splitter = QtGui.QSplitter(Form)
+ self.splitter.setOrientation(QtCore.Qt.Vertical)
+ self.splitter.setObjectName("splitter")
+ self.layoutWidget = QtGui.QWidget(self.splitter)
+ self.layoutWidget.setObjectName("layoutWidget")
+ self.gridLayout = QtGui.QGridLayout(self.layoutWidget)
+ self.gridLayout.setMargin(0)
+ self.gridLayout.setSpacing(0)
+ self.gridLayout.setObjectName("gridLayout")
+ self.graphicsView = GraphicsView(self.layoutWidget)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(10)
+ sizePolicy.setVerticalStretch(10)
+ sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth())
+ self.graphicsView.setSizePolicy(sizePolicy)
+ self.graphicsView.setObjectName("graphicsView")
+ self.gridLayout.addWidget(self.graphicsView, 1, 0, 3, 1)
+ self.roiBtn = QtGui.QPushButton(self.layoutWidget)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(1)
+ sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth())
+ self.roiBtn.setSizePolicy(sizePolicy)
+ self.roiBtn.setMaximumSize(QtCore.QSize(30, 16777215))
+ self.roiBtn.setCheckable(True)
+ self.roiBtn.setObjectName("roiBtn")
+ self.gridLayout.addWidget(self.roiBtn, 3, 3, 1, 1)
+ self.gradientWidget = GradientWidget(self.layoutWidget)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(100)
+ sizePolicy.setHeightForWidth(self.gradientWidget.sizePolicy().hasHeightForWidth())
+ self.gradientWidget.setSizePolicy(sizePolicy)
+ self.gradientWidget.setObjectName("gradientWidget")
+ self.gridLayout.addWidget(self.gradientWidget, 1, 3, 1, 1)
+ self.normBtn = QtGui.QPushButton(self.layoutWidget)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(1)
+ sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth())
+ self.normBtn.setSizePolicy(sizePolicy)
+ self.normBtn.setMaximumSize(QtCore.QSize(30, 16777215))
+ self.normBtn.setCheckable(True)
+ self.normBtn.setObjectName("normBtn")
+ self.gridLayout.addWidget(self.normBtn, 2, 3, 1, 1)
+ self.normGroup = QtGui.QGroupBox(self.layoutWidget)
+ self.normGroup.setObjectName("normGroup")
+ self.gridLayout_2 = QtGui.QGridLayout(self.normGroup)
+ self.gridLayout_2.setMargin(0)
+ self.gridLayout_2.setSpacing(0)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.normSubtractRadio = QtGui.QRadioButton(self.normGroup)
+ self.normSubtractRadio.setObjectName("normSubtractRadio")
+ self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1)
+ self.normDivideRadio = QtGui.QRadioButton(self.normGroup)
+ self.normDivideRadio.setChecked(False)
+ self.normDivideRadio.setObjectName("normDivideRadio")
+ self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1)
+ self.label_5 = QtGui.QLabel(self.normGroup)
+ font = QtGui.QFont()
+ font.setWeight(75)
+ font.setBold(True)
+ self.label_5.setFont(font)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1)
+ self.label_3 = QtGui.QLabel(self.normGroup)
+ font = QtGui.QFont()
+ font.setWeight(75)
+ font.setBold(True)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
+ self.label_4 = QtGui.QLabel(self.normGroup)
+ font = QtGui.QFont()
+ font.setWeight(75)
+ font.setBold(True)
+ self.label_4.setFont(font)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
+ self.normROICheck = QtGui.QCheckBox(self.normGroup)
+ self.normROICheck.setObjectName("normROICheck")
+ self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1)
+ self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
+ self.normXBlurSpin.setObjectName("normXBlurSpin")
+ self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1)
+ self.label_8 = QtGui.QLabel(self.normGroup)
+ self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.label_8.setObjectName("label_8")
+ self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1)
+ self.label_9 = QtGui.QLabel(self.normGroup)
+ self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.label_9.setObjectName("label_9")
+ self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1)
+ self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
+ self.normYBlurSpin.setObjectName("normYBlurSpin")
+ self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1)
+ self.label_10 = QtGui.QLabel(self.normGroup)
+ self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.label_10.setObjectName("label_10")
+ self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1)
+ self.normOffRadio = QtGui.QRadioButton(self.normGroup)
+ self.normOffRadio.setChecked(True)
+ self.normOffRadio.setObjectName("normOffRadio")
+ self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1)
+ self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup)
+ self.normTimeRangeCheck.setObjectName("normTimeRangeCheck")
+ self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1)
+ self.normFrameCheck = QtGui.QCheckBox(self.normGroup)
+ self.normFrameCheck.setObjectName("normFrameCheck")
+ self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1)
+ self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup)
+ self.normTBlurSpin.setObjectName("normTBlurSpin")
+ self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1)
+ self.gridLayout.addWidget(self.normGroup, 0, 0, 1, 4)
+ self.roiPlot = PlotWidget(self.splitter)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth())
+ self.roiPlot.setSizePolicy(sizePolicy)
+ self.roiPlot.setMinimumSize(QtCore.QSize(0, 40))
+ self.roiPlot.setObjectName("roiPlot")
+ self.verticalLayout.addWidget(self.splitter)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
+ self.roiBtn.setText(QtGui.QApplication.translate("Form", "R", None, QtGui.QApplication.UnicodeUTF8))
+ self.normBtn.setText(QtGui.QApplication.translate("Form", "N", None, QtGui.QApplication.UnicodeUTF8))
+ self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8))
+ self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8))
+ self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8))
+ self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8))
+ self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8))
+ self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8))
+ self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8))
+
+from GraphicsView import GraphicsView
+from pyqtgraph.GradientWidget import GradientWidget
+from PlotWidget import PlotWidget
diff --git a/ImageViewTemplate.ui b/ImageViewTemplate.ui
index eb2c3f03..98d58a1f 100644
--- a/ImageViewTemplate.ui
+++ b/ImageViewTemplate.ui
@@ -6,8 +6,8 @@
0
0
- 757
- 495
+ 726
+ 588
@@ -27,113 +27,77 @@
+
+ 0
+
0
- -
+
-
- 0
- 0
+ 10
+ 10
- normGroup
- normGroup
- -
-
-
- 4096
-
-
- Qt::Vertical
-
-
- false
-
-
- false
-
-
- QSlider::TicksBelow
-
-
- 410
-
-
-
- -
-
-
- 4096
-
-
- 4096
-
-
- Qt::Vertical
-
-
-
- -
-
-
- B
-
-
-
- -
-
-
- W
-
-
-
- -
+
-
+
+
+ 0
+ 1
+
+
- 40
+ 30
16777215
- ROI
+ R
true
- -
-
-
- 65535
-
-
- Qt::Horizontal
+
-
+
+
+
+ 0
+ 100
+
- -
+
-
+
+
+ 0
+ 1
+
+
- 50
+ 30
16777215
- Norm
+ N
true
- -
+
-
Normalization
@@ -188,7 +152,7 @@
- -
+
-
@@ -208,30 +172,10 @@
- -
-
-
- 65535
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- 65535
-
-
- Qt::Horizontal
-
-
-
- -
+
-
- -
+
-
X
@@ -241,7 +185,7 @@
- -
+
-
Y
@@ -251,10 +195,10 @@
- -
+
-
- -
+
-
T
@@ -264,20 +208,6 @@
- -
-
-
- Stop
-
-
-
- -
-
-
- Start
-
-
-
-
@@ -302,7 +232,7 @@
- -
+
-
@@ -311,6 +241,12 @@
+
+
+ 0
+ 0
+
+
0
@@ -323,6 +259,12 @@
+
+ GradientWidget
+ QWidget
+
+ 1
+
GraphicsView
QWidget
diff --git a/ObjectWorkaround.py b/ObjectWorkaround.py
new file mode 100644
index 00000000..67982dd4
--- /dev/null
+++ b/ObjectWorkaround.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from PyQt4 import QtGui, QtCore
+
+class QObjectWorkaround:
+ def __init__(self):
+ self._qObj_ = QtCore.QObject()
+ def connect(self, *args):
+ if args[0] is self:
+ return QtCore.QObject.connect(self._qObj_, *args[1:])
+ else:
+ return QtCore.QObject.connect(self._qObj_, *args)
+ def disconnect(self, *args):
+ return QtCore.QObject.disconnect(self._qObj_, *args)
+ def emit(self, *args):
+ return QtCore.QObject.emit(self._qObj_, *args)
+ def blockSignals(self, b):
+ return self._qObj_.blockSignals(b)
+
+class QGraphicsObject(QtGui.QGraphicsItem, QObjectWorkaround):
+ def __init__(self, *args):
+ QtGui.QGraphicsItem.__init__(self, *args)
+ QObjectWorkaround.__init__(self)
diff --git a/PlotItem.py b/PlotItem.py
index ce6d70cf..72ab48c5 100644
--- a/PlotItem.py
+++ b/PlotItem.py
@@ -20,6 +20,8 @@ This class is very heavily featured:
from graphicsItems import *
from plotConfigTemplate import *
from PyQt4 import QtGui, QtCore, QtSvg
+#from ObjectWorkaround import *
+#tryWorkaround(QtCore, QtGui)
import weakref
try:
@@ -42,7 +44,7 @@ class PlotItem(QtGui.QGraphicsWidget):
lastFileDir = None
managers = {}
- def __init__(self, parent=None):
+ def __init__(self, parent=None, name=None):
QtGui.QGraphicsWidget.__init__(self, parent)
## Set up control buttons
@@ -56,6 +58,7 @@ class PlotItem(QtGui.QGraphicsWidget):
for b in [self.ctrlBtn, self.autoBtn]:
proxy = QtGui.QGraphicsProxyWidget(self)
proxy.setWidget(b)
+ proxy.setAcceptHoverEvents(False)
b.setStyleSheet("background-color: #000000; color: #888; font-size: 6pt")
QtCore.QObject.connect(self.ctrlBtn, QtCore.SIGNAL('clicked()'), self.ctrlBtnClicked)
QtCore.QObject.connect(self.autoBtn, QtCore.SIGNAL('clicked()'), self.enableAutoScale)
@@ -124,7 +127,7 @@ class PlotItem(QtGui.QGraphicsWidget):
## Wrap a few methods from viewBox
- for m in ['setXRange', 'setYRange', 'setRange', 'autoRange', 'viewRect']:
+ for m in ['setXRange', 'setYRange', 'setRange', 'autoRange', 'viewRect', 'setMouseEnabled']:
setattr(self, m, getattr(self.vb, m))
self.items = []
@@ -178,9 +181,13 @@ class PlotItem(QtGui.QGraphicsWidget):
QtCore.QObject.connect(c.alphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateAlpha)
QtCore.QObject.connect(c.autoAlphaCheck, QtCore.SIGNAL('toggled(bool)'), self.updateAlpha)
+ QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
+ QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid)
+
QtCore.QObject.connect(c.powerSpectrumGroup, QtCore.SIGNAL('toggled(bool)'), self.updateSpectrumMode)
QtCore.QObject.connect(c.saveSvgBtn, QtCore.SIGNAL('clicked()'), self.saveSvgClicked)
QtCore.QObject.connect(c.saveImgBtn, QtCore.SIGNAL('clicked()'), self.saveImgClicked)
+ QtCore.QObject.connect(c.saveCsvBtn, QtCore.SIGNAL('clicked()'), self.saveCsvClicked)
#QtCore.QObject.connect(c.gridGroup, QtCore.SIGNAL('toggled(bool)'), self.updateGrid)
#QtCore.QObject.connect(c.gridAlphaSlider, QtCore.SIGNAL('valueChanged(int)'), self.updateGrid)
@@ -215,7 +222,11 @@ class PlotItem(QtGui.QGraphicsWidget):
self.showScale('top', False)
self.showScale('left', True)
self.showScale('bottom', True)
-
+
+ if name is not None:
+ self.registerPlot(name)
+
+
def __del__(self):
if self.manager is not None:
self.manager.removeWidget(self.name)
@@ -250,6 +261,13 @@ class PlotItem(QtGui.QGraphicsWidget):
refs= gc.get_referrers(self)
print " error during update. Referrers are:", refs
raise
+
+ def updateGrid(self, *args):
+ g = self.ctrl.gridGroup.isChecked()
+ if g:
+ g = self.ctrl.gridAlphaSlider.value()
+ for k in self.scales:
+ self.scales[k]['item'].setGrid(g)
def viewGeometry(self):
"""return the screen geometry of the viewbox"""
@@ -261,6 +279,8 @@ class PlotItem(QtGui.QGraphicsWidget):
return wr
+
+
def viewChanged(self, *args):
self.emit(QtCore.SIGNAL('viewChanged'), *args)
@@ -273,23 +293,31 @@ class PlotItem(QtGui.QGraphicsWidget):
def yLinkComboChanged(self):
self.setYLink(str(self.ctrl.yLinkCombo.currentText()))
- def setXLink(self, plotName=None):
- if self.manager is None:
- return
- if self.xLinkPlot is not None:
- self.manager.unlinkX(self, self.xLinkPlot)
- plot = self.manager.getWidget(plotName)
+ def setXLink(self, plot=None):
+ """Link this plot's X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
+ if isinstance(plot, basestring):
+ if self.manager is None:
+ return
+ if self.xLinkPlot is not None:
+ self.manager.unlinkX(self, self.xLinkPlot)
+ plot = self.manager.getWidget(plot)
+ if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
+ plot = plot.getPlotItem()
self.xLinkPlot = plot
if plot is not None:
self.setManualXScale()
self.manager.linkX(self, plot)
- def setYLink(self, plotName=None):
- if self.manager is None:
- return
- if self.yLinkPlot is not None:
- self.manager.unlinkY(self, self.yLinkPlot)
- plot = self.manager.getWidget(plotName)
+ def setYLink(self, plot=None):
+ """Link this plot's Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
+ if isinstance(plot, basestring):
+ if self.manager is None:
+ return
+ if self.yLinkPlot is not None:
+ self.manager.unlinkY(self, self.yLinkPlot)
+ plot = self.manager.getWidget(plot)
+ if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
+ plot = plot.getPlotItem()
self.yLinkPlot = plot
if plot is not None:
self.setManualYScale()
@@ -339,6 +367,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.recomputeAverages()
def recomputeAverages(self):
+ if not self.ctrl.averageGroup.isChecked():
+ return
for k in self.avgCurves:
self.removeItem(self.avgCurves[k][1])
#Qwt.QwtPlotCurve.detach(self.avgCurves[k][1])
@@ -406,6 +436,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.vb.setMouseEnabled(*state)
def xRangeChanged(self, _, range):
+ if any(isnan(range)) or any(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])
@@ -423,6 +455,8 @@ class PlotItem(QtGui.QGraphicsWidget):
self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
def yRangeChanged(self, _, range):
+ if any(isnan(range)) or any(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])
@@ -511,18 +545,25 @@ class PlotItem(QtGui.QGraphicsWidget):
if not item in self.items:
return
self.items.remove(item)
- self.vb.removeItem(item)
+ if item.scene() is not None:
+ self.vb.removeItem(item)
if item in self.curves:
self.curves.remove(item)
self.updateDecimation()
self.updateParamList()
- QtCore.QObject.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged)
+ item.connect(QtCore.SIGNAL('plotChanged'), self.plotChanged)
def clear(self):
for i in self.items[:]:
self.removeItem(i)
self.avgCurves = {}
+
+ def clearPlots(self):
+ for i in self.curves[:]:
+ self.removeItem(i)
+ self.avgCurves = {}
+
def plot(self, data=None, x=None, clear=False, params=None, pen=None):
if clear:
self.clear()
@@ -549,6 +590,8 @@ class PlotItem(QtGui.QGraphicsWidget):
return curve
def addCurve(self, c, params=None):
+ if params is None:
+ params = {}
c.setMeta(params)
self.curves.append(c)
#Qwt.QwtPlotCurve.attach(c, self)
@@ -569,7 +612,7 @@ class PlotItem(QtGui.QGraphicsWidget):
if self.ctrl.averageGroup.isChecked():
self.addAvgCurve(c)
- QtCore.QObject.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
+ c.connect(QtCore.SIGNAL('plotChanged'), self.plotChanged)
self.plotChanged()
def plotChanged(self, curve=None):
@@ -587,7 +630,7 @@ class PlotItem(QtGui.QGraphicsWidget):
mn = cmn
if mx is None or cmx > mx:
mx = cmx
- if mn is None or mx is None:
+ if mn is None or mx is None or any(isnan([mn, mx])) or any(isinf([mn, mx])):
continue
if mn == mx:
mn -= 1
@@ -632,24 +675,58 @@ class PlotItem(QtGui.QGraphicsWidget):
if fileName is None:
fileName = QtGui.QFileDialog.getSaveFileName()
fileName = str(fileName)
-
+ PlotItem.lastFileDir = os.path.dirname(fileName)
self.svg = QtSvg.QSvgGenerator()
self.svg.setFileName(fileName)
res = 120.
- self.svg.setResolution(res)
- self.svg.setSize(QtCore.QSize(self.size().width(), self.size().height()))
- painter = QtGui.QPainter(self.svg)
- #self.scene().render(painter, QtCore.QRectF(), self.mapRectToScene(self.boundingRect()))
- items = self.scene().items()
- self.scene().views()[0].drawItems(painter, len(items), items)
+ #bounds = self.mapRectToScene(self.boundingRect())
+ view = self.scene().views()[0]
+ bounds = view.viewport().rect()
+ bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height())
+ self.svg.setResolution(res)
+ #self.svg.setSize(QtCore.QSize(self.size().width(), self.size().height()))
+ self.svg.setViewBox(bounds)
+
+ self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height()))
+
+ painter = QtGui.QPainter(self.svg)
+ #self.scene().render(painter, QtCore.QRectF(), view.mapToScene(bounds).boundingRect())
+
+ #items = self.scene().items()
+ #self.scene().views()[0].drawItems(painter, len(items), items)
+
+ #print view, type(view)
+ view.render(painter, bounds)
+
+ painter.end()
+
+ ## Workaround to set pen widths correctly
+ import re
+ data = open(fileName).readlines()
+ for i in range(len(data)):
+ line = data[i]
+ m = re.match(r'( 0. (got %s)" % str(frac))
else:
- bins = 1000
- h = histogram(d, bins)
- s = len(d) * (1.0-frac)
- mnTot = mxTot = 0
- mnInd = mxInd = 0
- for i in range(bins):
- mnTot += h[0][i]
- if mnTot > s:
- mnInd = i
- break
- for i in range(bins):
- mxTot += h[0][-i-1]
- if mxTot > s:
- mxInd = -i-1
- break
- #print mnInd, mxInd, h[1][mnInd], h[1][mxInd]
- return(h[1][mnInd], h[1][mxInd])
+ return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
+ #bins = 1000
+ #h = histogram(d, bins)
+ #s = len(d) * (1.0-frac)
+ #mnTot = mxTot = 0
+ #mnInd = mxInd = 0
+ #for i in range(bins):
+ #mnTot += h[0][i]
+ #if mnTot > s:
+ #mnInd = i
+ #break
+ #for i in range(bins):
+ #mxTot += h[0][-i-1]
+ #if mxTot > s:
+ #mxInd = -i-1
+ #break
+ ##print mnInd, mxInd, h[1][mnInd], h[1][mxInd]
+ #return(h[1][mnInd], h[1][mxInd])
@@ -446,10 +576,18 @@ class PlotCurveItem(QtGui.QGraphicsWidget):
if x is None or y is None or len(x) == 0 or len(y) == 0:
return QtCore.QRectF()
- xmin = x.min()
- xmax = x.max()
- ymin = y.min()
- ymax = y.max()
+
+ if self.shadow is not None:
+ lineWidth = (max(self.pen.width(), self.shadow.width()) + 1)
+ else:
+ lineWidth = (self.pen.width()+1)
+
+
+ pixels = self.pixelVectors()
+ xmin = x.min() - pixels[0].x() * lineWidth
+ xmax = x.max() + pixels[0].x() * lineWidth
+ ymin = y.min() - abs(pixels[1].y()) * lineWidth
+ ymax = y.max() + abs(pixels[1].y()) * lineWidth
return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
def paint(self, p, opt, widget):
@@ -486,6 +624,10 @@ class PlotCurveItem(QtGui.QGraphicsWidget):
p.setPen(cp)
p.drawPath(path)
+ #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
+ #p.drawRect(self.boundingRect())
+
+
def free(self):
self.xData = None ## raw values
self.yData = None
@@ -521,38 +663,39 @@ class ROIPlotItem(PlotCurveItem):
-class UIGraphicsItem(QtGui.QGraphicsItem):
+class UIGraphicsItem(GraphicsObject):
"""Base class for graphics items with boundaries relative to a GraphicsView widget"""
def __init__(self, view, bounds=None):
- QtGui.QGraphicsItem.__init__(self)
- self._view = view
+ GraphicsObject.__init__(self)
+ self._view = weakref.ref(view)
if bounds is None:
self._bounds = QtCore.QRectF(0, 0, 1, 1)
else:
self._bounds = bounds
- self._viewRect = self._view.rect()
+ self._viewRect = self._view().rect()
self._viewTransform = self.viewTransform()
self.setNewBounds()
+ QtCore.QObject.connect(view, QtCore.SIGNAL('viewChanged'), self.viewChangedEvent)
def viewRect(self):
"""Return the viewport widget rect"""
- return self._view.rect()
+ return self._view().rect()
def viewTransform(self):
"""Returns a matrix that maps viewport coordinates onto scene coordinates"""
- if self._view is None:
+ if self._view() is None:
return QtGui.QTransform()
else:
- return self._view.viewportTransform()
+ return self._view().viewportTransform()
def boundingRect(self):
- if self._view is None:
+ if self._view() is None:
self.bounds = self._bounds
else:
- vr = self._view.rect()
+ vr = self._view().rect()
tr = self.viewTransform()
if vr != self._viewRect or tr != self._viewTransform:
- self.viewChangedEvent(vr, self._viewRect)
+ #self.viewChangedEvent(vr, self._viewRect)
self._viewRect = vr
self._viewTransform = tr
self.setNewBounds()
@@ -569,9 +712,10 @@ class UIGraphicsItem(QtGui.QGraphicsItem):
self.bounds = self.viewTransform().inverted()[0].mapRect(bounds)
self.prepareGeometryChange()
- def viewChangedEvent(self, newRect, oldRect):
+ def viewChangedEvent(self):
"""Called when the view widget is resized"""
- pass
+ self.boundingRect()
+ self.update()
def unitRect(self):
return self.viewTransform().inverted()[0].mapRect(QtCore.QRectF(0, 0, 1, 1))
@@ -664,7 +808,8 @@ class LabelItem(QtGui.QGraphicsWidget):
class ScaleItem(QtGui.QGraphicsWidget):
def __init__(self, orientation, pen=None, linkView=None, parent=None):
"""GraphicsItem showing a single plot axis with ticks, values, and label.
- Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items."""
+ Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items.
+ Ticks can be extended to make a grid."""
QtGui.QGraphicsWidget.__init__(self, parent)
self.label = QtGui.QGraphicsTextItem(self)
self.orientation = orientation
@@ -706,8 +851,16 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.linkToView(linkView)
self.showLabel(False)
+
+ self.grid = False
+ def setGrid(self, grid):
+ """Set the alpha value for the grid, or False to disable."""
+ self.grid = grid
+ self.update()
+
+
def resizeEvent(self, ev=None):
#s = self.size()
@@ -818,6 +971,8 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.update()
def setRange(self, mn, mx):
+ if mn in [nan, inf, -inf] or mx in [nan, inf, -inf]:
+ raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx)))
self.range = [mn, mx]
if self.autoScale:
self.setScale()
@@ -838,37 +993,51 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.setRange(*newRange)
def boundingRect(self):
- return self.mapRectFromParent(self.geometry())
+ if self.linkedView is None or self.grid is False:
+ return self.mapRectFromParent(self.geometry())
+ else:
+ return self.mapRectFromParent(self.geometry()) | self.mapRectFromScene(self.linkedView.mapRectToScene(self.linkedView.boundingRect()))
def paint(self, p, opt, widget):
p.setPen(self.pen)
- bounds = self.boundingRect()
- #p.setPen(QtGui.QPen(QtGui.QColor(255, 0, 0)))
- #p.drawRect(bounds)
+
+ #bounds = self.boundingRect()
+ bounds = self.mapRectFromParent(self.geometry())
+
+ if self.linkedView is None or self.grid is False:
+ tbounds = bounds
+ else:
+ tbounds = self.mapRectFromScene(self.linkedView.mapRectToScene(self.linkedView.boundingRect()))
if self.orientation == 'left':
p.drawLine(bounds.topRight(), bounds.bottomRight())
- tickStart = bounds.right()
+ tickStart = tbounds.right()
+ tickStop = bounds.right()
tickDir = -1
axis = 0
elif self.orientation == 'right':
p.drawLine(bounds.topLeft(), bounds.bottomLeft())
- tickStart = bounds.left()
+ tickStart = tbounds.left()
+ tickStop = bounds.left()
tickDir = 1
axis = 0
elif self.orientation == 'top':
p.drawLine(bounds.bottomLeft(), bounds.bottomRight())
- tickStart = bounds.bottom()
+ tickStart = tbounds.bottom()
+ tickStop = bounds.bottom()
tickDir = -1
axis = 1
elif self.orientation == 'bottom':
p.drawLine(bounds.topLeft(), bounds.topRight())
- tickStart = bounds.top()
+ tickStart = tbounds.top()
+ tickStop = bounds.top()
tickDir = 1
axis = 1
## Determine optimal tick spacing
- intervals = [1., 2., 5., 10., 20., 50.]
+ #intervals = [1., 2., 5., 10., 20., 50.]
+ #intervals = [1., 2.5, 5., 10., 25., 50.]
+ intervals = [1., 2., 10., 20., 100.]
dif = abs(self.range[1] - self.range[0])
if dif == 0.0:
return
@@ -879,6 +1048,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
if dif / (pw*intervals[i]) < 10:
break
+ textLevel = i1 ## draw text at this scale level
#print "range: %s dif: %f power: %f interval: %f spacing: %f" % (str(self.range), dif, pw, intervals[i1], sp)
@@ -892,6 +1062,8 @@ class ScaleItem(QtGui.QGraphicsWidget):
## draw ticks and text
for i in [i1, i1+1, i1+2]: ## draw three different intervals
+ if i > len(intervals):
+ continue
## spacing for this interval
sp = pw*intervals[i]
@@ -925,7 +1097,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
p1 = [0, 0]
p2 = [0, 0]
p1[axis] = tickStart
- p2[axis] = tickStart + h*tickDir
+ p2[axis] = tickStop + h*tickDir
p1[1-axis] = p2[1-axis] = x
if p1[1-axis] > [bounds.width(), bounds.height()][1-axis]:
@@ -934,7 +1106,7 @@ class ScaleItem(QtGui.QGraphicsWidget):
continue
p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100, a)))
p.drawLine(Point(p1), Point(p2))
- if i == i1+1:
+ if i == textLevel:
if abs(v) < .001 or abs(v) >= 10000:
vstr = "%g" % (v * self.scale)
else:
@@ -945,16 +1117,16 @@ class ScaleItem(QtGui.QGraphicsWidget):
self.textHeight = height
if self.orientation == 'left':
textFlags = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter
- rect = QtCore.QRectF(tickStart-100, x-(height/2), 100-self.tickLength, height)
+ rect = QtCore.QRectF(tickStop-100, x-(height/2), 100-self.tickLength, height)
elif self.orientation == 'right':
textFlags = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter
- rect = QtCore.QRectF(tickStart+self.tickLength, x-(height/2), 100-self.tickLength, height)
+ rect = QtCore.QRectF(tickStop+self.tickLength, x-(height/2), 100-self.tickLength, height)
elif self.orientation == 'top':
textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom
- rect = QtCore.QRectF(x-100, tickStart-self.tickLength-height, 200, height)
+ rect = QtCore.QRectF(x-100, tickStop-self.tickLength-height, 200, height)
elif self.orientation == 'bottom':
textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop
- rect = QtCore.QRectF(x-100, tickStart+self.tickLength, 200, height)
+ rect = QtCore.QRectF(x-100, tickStop+self.tickLength, 200, height)
p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100)))
p.drawText(rect, textFlags, vstr)
@@ -1006,24 +1178,20 @@ class ScaleItem(QtGui.QGraphicsWidget):
-#class ViewBox(QtGui.QGraphicsItem, QObjectWorkaround):
class ViewBox(QtGui.QGraphicsWidget):
"""Box that allows internal scaling/panning of children by mouse drag. Not compatible with GraphicsView having the same functionality."""
def __init__(self, parent=None):
- #QObjectWorkaround.__init__(self)
QtGui.QGraphicsWidget.__init__(self, parent)
#self.gView = view
#self.showGrid = showGrid
self.range = [[0,1], [0,1]] ## child coord. range visible [[xmin, xmax], [ymin, ymax]]
self.aspectLocked = False
- QtGui.QGraphicsItem.__init__(self, parent)
self.setFlag(QtGui.QGraphicsItem.ItemClipsChildrenToShape)
#self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape)
- #self.childScale = [1.0, 1.0]
- #self.childTranslate = [0.0, 0.0]
- self.childGroup = QtGui.QGraphicsItemGroup(self)
+ #self.childGroup = QtGui.QGraphicsItemGroup(self)
+ self.childGroup = ItemGroup(self)
self.currentScale = Point(1, 1)
self.yInverted = False
@@ -1179,6 +1347,7 @@ class ViewBox(QtGui.QGraphicsWidget):
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()])
@@ -1188,11 +1357,14 @@ class ViewBox(QtGui.QGraphicsWidget):
center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton)))
self.scaleBy(s, center)
self.emit(QtCore.SIGNAL('rangeChangedManually'), self.mouseEnabled)
+ ev.accept()
+ else:
+ ev.ignore()
def mousePressEvent(self, ev):
self.mousePos = array([ev.pos().x(), ev.pos().y()])
self.pressPos = self.mousePos.copy()
- #Qwt.QwtPlot.mousePressEvent(self, ev)
+ ev.accept()
def mouseReleaseEvent(self, ev):
pos = array([ev.pos().x(), ev.pos().y()])
@@ -1200,7 +1372,7 @@ class ViewBox(QtGui.QGraphicsWidget):
#if ev.button() == QtCore.Qt.RightButton:
#self.ctrlMenu.popup(self.mapToGlobal(ev.pos()))
self.mousePos = pos
- #Qwt.QwtPlot.mouseReleaseEvent(self, ev)
+ ev.accept()
def setRange(self, ax, min, max, padding=0.02, update=True):
if ax == 0:
@@ -1210,8 +1382,16 @@ class ViewBox(QtGui.QGraphicsWidget):
def setYRange(self, min, max, update=True, padding=0.02):
#print "setYRange:", min, max
- if min == max:
- raise Exception("Tried to set range with 0 width.")
+ if min == max: ## If we requested no range, try to preserve previous scale. Otherwise just pick an arbitrary scale.
+ dy = self.range[1][1] - self.range[1][0]
+ if dy == 0:
+ dy = 1
+ 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])):
+ raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
+
padding = (max-min) * padding
min -= padding
max += padding
@@ -1228,8 +1408,15 @@ class ViewBox(QtGui.QGraphicsWidget):
def setXRange(self, min, max, update=True, padding=0.02):
#print "setXRange:", min, max
if min == max:
- print "Warning: Tried to set range with 0 width."
+ dx = self.range[0][1] - self.range[0][0]
+ if dx == 0:
+ dx = 1
+ min -= dx*0.5
+ 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])):
+ raise Exception("Not setting range [%s, %s]" % (str(min), str(max)))
padding = (max-min) * padding
min -= padding
max += padding
@@ -1262,21 +1449,52 @@ class ViewBox(QtGui.QGraphicsWidget):
p.drawRect(bounds)
-class InfiniteLine(QtGui.QGraphicsItem):
- def __init__(self, view, pos, angle=90, pen=None):
- QtGui.QGraphicsItem.__init__(self)
- self.view = view
+class InfiniteLine(GraphicsObject):
+ def __init__(self, view, pos=0, angle=90, pen=None, movable=False, bounds=None):
+ GraphicsObject.__init__(self)
+ self.bounds = QtCore.QRectF() ## graphicsitem boundary
+
+ if bounds is None: ## allowed value boundaries for orthogonal lines
+ self.maxRange = [None, None]
+ else:
+ self.maxRange = bounds
+ self.movable = movable
+ self.view = weakref.ref(view)
self.p = [0, 0]
self.setAngle(angle)
self.setPos(pos)
+
+ if movable:
+ self.setAcceptHoverEvents(True)
+
+
if pen is None:
pen = QtGui.QPen(QtGui.QColor(200, 200, 100))
self.setPen(pen)
- QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.updateLine)
+ self.currentPen = self.pen
+ #self.setFlag(self.ItemSendsScenePositionChanges)
+ #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 setBounds(self, bounds):
+ self.maxRange = bounds
+ self.setValue(self.value())
+
+ def hoverEnterEvent(self, ev):
+ self.currentPen = QtGui.QPen(QtGui.QColor(255, 0,0))
+ self.update()
+ ev.ignore()
+
+ def hoverLeaveEvent(self, ev):
+ self.currentPen = self.pen
+ self.update()
+ ev.ignore()
def setPen(self, pen):
self.pen = pen
+ self.currentPen = self.pen
def setAngle(self, angle):
self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135
@@ -1284,43 +1502,111 @@ class InfiniteLine(QtGui.QGraphicsItem):
def setPos(self, pos):
if type(pos) in [list, tuple]:
- self.p = pos
+ newPos = pos
elif isinstance(pos, QtCore.QPointF):
- self.p = [pos.x(), pos.y()]
+ newPos = [pos.x(), pos.y()]
else:
if self.angle == 90:
- self.p = [pos, 0]
+ newPos = [pos, 0]
elif self.angle == 0:
- self.p = [0, pos]
+ newPos = [0, pos]
else:
raise Exception("Must specify 2D coordinate for non-orthogonal lines.")
- self.updateLine()
+
+ ## check bounds (only works for orthogonal lines)
+ if self.angle == 90:
+ if self.maxRange[0] is not None:
+ newPos[0] = max(newPos[0], self.maxRange[0])
+ if self.maxRange[1] is not None:
+ newPos[0] = min(newPos[0], self.maxRange[1])
+ elif self.angle == 0:
+ if self.maxRange[0] is not None:
+ newPos[1] = max(newPos[1], self.maxRange[0])
+ if self.maxRange[1] is not None:
+ newPos[1] = min(newPos[1], self.maxRange[1])
+
+
+ if self.p != newPos:
+ self.p = newPos
+ self.updateLine()
+ self.emit(QtCore.SIGNAL('positionChanged'), self)
+
+ def getXPos(self):
+ return self.p[0]
+
+ def getYPos(self):
+ return self.p[1]
+
+ def getPos(self):
+ return self.p
+
+ def value(self):
+ if self.angle%180 == 0:
+ return self.getYPos()
+ elif self.angle%180 == 90:
+ return self.getXPos()
+ else:
+ return self.getPos()
+
+ def setValue(self, v):
+ self.setPos(v)
+
+ ## broken in 4.7
+ #def itemChange(self, change, val):
+ #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]:
+ #self.updateLine()
+ #print "update", change
+ #print self.getBoundingParents()
+ #else:
+ #print "ignore", change
+ #return GraphicsObject.itemChange(self, change, val)
def updateLine(self):
- vr = self.view.viewRect()
+
+ #unit = QtCore.QRect(0, 0, 10, 10)
+ #if self.scene() is not None:
+ #gv = self.scene().views()[0]
+ #unit = gv.mapToScene(unit).boundingRect()
+ ##print unit
+ #unit = self.mapRectFromScene(unit)
+ ##print unit
+
+ vr = self.view().viewRect()
+ #vr = self.viewBounds()
+ if vr is None:
+ return
+ #print 'before', self.bounds
if self.angle > 45:
m = tan((90-self.angle) * pi / 180.)
- y1 = vr.bottom()
- y2 = vr.top()
+ 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.)
x1 = vr.left()
x2 = vr.right()
- y1 = self.p[1] + (x1 - self.p[0]) * m
- y2 = self.p[1] + (x2 - self.p[0]) * m
+ y2 = self.p[1] + (x1 - self.p[0]) * m
+ y1 = self.p[1] + (x2 - self.p[0]) * m
#print vr, x1, y1, x2, y2
self.prepareGeometryChange()
self.line = (QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2))
self.bounds = QtCore.QRectF(self.line[0], self.line[1])
## Stupid bug causes lines to disappear:
- if self.bounds.width() == 0:
- self.bounds.setWidth(1e-9)
- if self.bounds.height() == 0:
- self.bounds.setHeight(1e-9)
+ if self.angle % 180 == 90:
+ px = self.pixelWidth()
+ #self.bounds.setWidth(1e-9)
+ self.bounds.setX(x1 + px*-5)
+ self.bounds.setWidth(px*10)
+ if self.angle % 180 == 0:
+ px = self.pixelHeight()
+ #self.bounds.setHeight(1e-9)
+ self.bounds.setY(y1 + px*-5)
+ self.bounds.setHeight(px*10)
+
#QtGui.QGraphicsLineItem.setLine(self, x1, y1, x2, y2)
+ #self.update()
def boundingRect(self):
#self.updateLine()
@@ -1329,10 +1615,123 @@ class InfiniteLine(QtGui.QGraphicsItem):
return self.bounds
def paint(self, p, *args):
- p.setPen(self.pen)
- #print "paint", self.line
- p.drawLine(self.line[0], self.line[1])
+ w,h = self.pixelWidth()*5, self.pixelHeight()*5*1.1547
+ #self.updateLine()
+ l = self.line
+ p.setPen(self.currentPen)
+ #print "paint", self.line
+ p.drawLine(l[0], l[1])
+
+ p.setBrush(QtGui.QBrush(self.currentPen.color()))
+ p.drawConvexPolygon(QtGui.QPolygonF([
+ l[0] + QtCore.QPointF(-w, 0),
+ l[0] + QtCore.QPointF(0, h),
+ l[0] + QtCore.QPointF(w, 0),
+ ]))
+
+ #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
+ #p.drawRect(self.boundingRect())
+
+ def mousePressEvent(self, ev):
+ 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 mouseMoveEvent(self, ev):
+ self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
+ self.emit(QtCore.SIGNAL('dragged'), 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):
+ GraphicsObject.__init__(self)
+ self.orientation = orientation
+ if hasattr(self, "ItemHasNoContents"):
+ self.setFlag(self.ItemHasNoContents)
+ self.rect = QtGui.QGraphicsRectItem(self)
+ self.rect.setParentItem(self)
+ self.bounds = QtCore.QRectF()
+ self.view = weakref.ref(view)
+
+ self.setBrush = self.rect.setBrush
+ self.brush = self.rect.brush
+
+ if orientation[0] == 'h':
+ self.lines = [
+ InfiniteLine(view, QtCore.QPointF(0, vals[0]), 0, movable=movable, bounds=bounds),
+ InfiniteLine(view, QtCore.QPointF(0, vals[1]), 0, movable=movable, bounds=bounds)]
+ else:
+ self.lines = [
+ InfiniteLine(view, QtCore.QPointF(vals[0], 0), 90, movable=movable, bounds=bounds),
+ InfiniteLine(view, QtCore.QPointF(vals[1], 0), 90, movable=movable, bounds=bounds)]
+ QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.updateBounds)
+
+ for l in self.lines:
+ l.setParentItem(self)
+ l.connect(QtCore.SIGNAL('positionChanged'), self.lineMoved)
+
+ if brush is None:
+ brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50))
+ self.setBrush(brush)
+
+ def setBounds(self, bounds):
+ for l in self.lines:
+ l.setBounds(bounds)
+
+
+ def boundingRect(self):
+ return self.rect.boundingRect()
+
+ def lineMoved(self):
+ self.updateBounds()
+ self.emit(QtCore.SIGNAL('regionChanged'), 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))
+ else:
+ vb.setLeft(min(vals))
+ vb.setRight(max(vals))
+ if vb != self.bounds:
+ self.bounds = vb
+ self.rect.setRect(vb)
+
+ def mousePressEvent(self, ev):
+ for l in self.lines:
+ l.mousePressEvent(ev)
+ #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 mouseMoveEvent(self, ev):
+ self.lines[0].blockSignals(True) # only want to update once
+ for l in self.lines:
+ l.mouseMoveEvent(ev)
+ self.lines[0].blockSignals(False)
+ #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
+ #self.emit(QtCore.SIGNAL('dragged'), self)
+
+ def getRegion(self):
+ if self.orientation[0] == 'h':
+ r = (self.bounds.top(), self.bounds.bottom())
+ else:
+ r = (self.bounds.left(), self.bounds.right())
+ return (min(r), max(r))
+
+ def setRegion(self, rgn):
+ self.lines[0].setValue(rgn[0])
+ self.lines[1].setValue(rgn[1])
+
class VTickGroup(QtGui.QGraphicsPathItem):
def __init__(self, xvals=None, yrange=None, pen=None, relative=False, view=None):
@@ -1345,7 +1744,10 @@ class VTickGroup(QtGui.QGraphicsPathItem):
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
self.ticks = []
self.xvals = []
- self.view = view
+ if view is None:
+ self.view = None
+ else:
+ self.view = weakref.ref(view)
self.yrange = [0,1]
self.setPen(pen)
self.setYRange(yrange, relative)
@@ -1363,7 +1765,7 @@ class VTickGroup(QtGui.QGraphicsPathItem):
def setXVals(self, vals):
self.xvals = vals
- #self.rebuildTicks()
+ self.rebuildTicks()
self.valid = False
def setYRange(self, vals, relative=False):
@@ -1371,37 +1773,71 @@ class VTickGroup(QtGui.QGraphicsPathItem):
self.relative = relative
if self.view is not None:
if relative:
- QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
+ #QtCore.QObject.connect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
+ QtCore.QObject.connect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
else:
try:
- QtCore.QObject.disconnect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
+ #QtCore.QObject.disconnect(self.view, QtCore.SIGNAL('viewChanged'), self.rebuildTicks)
+ QtCore.QObject.disconnect(self.view(), QtCore.SIGNAL('viewChanged'), self.rescale)
except:
pass
- #self.rebuildTicks()
+ self.rebuildTicks()
self.valid = False
+ def rescale(self):
+ #print "RESCALE:"
+ self.resetTransform()
+ #height = self.view.size().height()
+ #p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0]))))
+ #p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1]))))
+ #yr = [p1.y(), p2.y()]
+ vb = self.view().viewRect()
+ p1 = vb.bottom() - vb.height() * self.yrange[0]
+ p2 = vb.bottom() - vb.height() * self.yrange[1]
+ yr = [p1, p2]
+
+ #print " ", vb, yr
+ self.translate(0.0, yr[0])
+ self.scale(1.0, (yr[1]-yr[0]))
+ #print " ", self.mapRectToScene(self.boundingRect())
+ self.boundingRect()
+ self.update()
+
+ def boundingRect(self):
+ #print "--request bounds:"
+ b = QtGui.QGraphicsPathItem.boundingRect(self)
+ #print " ", self.mapRectToScene(b)
+ return b
+
def yRange(self):
- if self.relative:
- height = self.view.size().height()
- p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0]))))
- p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1]))))
- return [p1.y(), p2.y()]
- else:
- return self.yrange
+ #if self.relative:
+ #height = self.view.size().height()
+ #p1 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[0]))))
+ #p2 = self.mapFromScene(self.view.mapToScene(QtCore.QPoint(0, height * (1.0-self.yrange[1]))))
+ #return [p1.y(), p2.y()]
+ #else:
+ #return self.yrange
+
+ return self.yrange
def rebuildTicks(self):
- path = QtGui.QPainterPath()
+ self.path = QtGui.QPainterPath()
yrange = self.yRange()
#print "rebuild ticks:", yrange
for x in self.xvals:
- path.moveTo(x, yrange[0])
- path.lineTo(x, yrange[1])
- self.setPath(path)
+ #path.moveTo(x, yrange[0])
+ #path.lineTo(x, yrange[1])
+ self.path.moveTo(x, 0.)
+ self.path.lineTo(x, 1.)
+ self.setPath(self.path)
self.valid = True
+ self.rescale()
+ #print " done..", self.boundingRect()
def paint(self, *args):
if not self.valid:
self.rebuildTicks()
+ #print "Paint", self.boundingRect()
QtGui.QGraphicsPathItem.paint(self, *args)
@@ -1491,3 +1927,136 @@ class GridItem(UIGraphicsItem):
x = tr.map(t[0])
p.drawText(x, t[1])
p.end()
+
+class ScaleBar(UIGraphicsItem):
+ def __init__(self, view, size, width=5, color=(100, 100, 255)):
+ self.size = size
+ UIGraphicsItem.__init__(self, view)
+ self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
+ #self.pen = QtGui.QPen(QtGui.QColor(*color))
+ #self.pen.setWidth(width)
+ #self.pen.setCosmetic(True)
+ #self.pen2 = QtGui.QPen(QtGui.QColor(0,0,0))
+ #self.pen2.setWidth(width+2)
+ #self.pen2.setCosmetic(True)
+ self.brush = QtGui.QBrush(QtGui.QColor(*color))
+ self.pen = QtGui.QPen(QtGui.QColor(0,0,0))
+ self.width = width
+
+ def paint(self, p, opt, widget):
+ rect = self.boundingRect()
+ unit = self.unitRect()
+ y = rect.bottom() + (rect.top()-rect.bottom()) * 0.02
+ y1 = y + unit.height()*self.width
+ x = rect.right() + (rect.left()-rect.right()) * 0.02
+ x1 = x - self.size
+
+
+ p.setPen(self.pen)
+ p.setBrush(self.brush)
+ rect = QtCore.QRectF(
+ QtCore.QPointF(x1, y1),
+ QtCore.QPointF(x, y)
+ )
+ p.translate(x1, y1)
+ p.scale(rect.width(), rect.height())
+ p.drawRect(0, 0, 1, 1)
+
+ alpha = 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
+ x2 = 0.1 * i
+ p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1))
+
+
+ def setSize(self, s):
+ self.size = s
+
+class ColorScaleBar(UIGraphicsItem):
+ def __init__(self, view, size, offset):
+ self.size = size
+ self.offset = offset
+ UIGraphicsItem.__init__(self, view)
+ self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
+ self.brush = QtGui.QBrush(QtGui.QColor(200,0,0))
+ self.pen = QtGui.QPen(QtGui.QColor(0,0,0))
+ self.labels = {'max': 1, 'min': 0}
+ self.gradient = QtGui.QLinearGradient()
+ self.gradient.setColorAt(0, QtGui.QColor(0,0,0))
+ self.gradient.setColorAt(1, QtGui.QColor(255,0,0))
+
+ def setGradient(self, g):
+ self.gradient = g
+ self.update()
+
+ def setLabels(self, l):
+ self.labels = l
+ self.update()
+
+ def paint(self, p, opt, widget):
+ rect = self.boundingRect() ## Boundaries of visible area in scene coords.
+ unit = self.unitRect() ## Size of one view pixel in scene coords.
+
+ ## determine max width of all labels
+ labelWidth = 0
+ labelHeight = 0
+ for k in self.labels:
+ b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, k)
+ labelWidth = max(labelWidth, b.width())
+ labelHeight = max(labelHeight, b.height())
+
+ labelWidth *= unit.width()
+ labelHeight *= unit.height()
+
+ textPadding = 2 # in px
+
+ if self.offset[0] < 0:
+ x3 = rect.right() + unit.width() * self.offset[0]
+ x2 = x3 - labelWidth - unit.width()*textPadding*2
+ x1 = x2 - unit.width() * self.size[0]
+ else:
+ x1 = rect.left() + unit.width() * self.offset[0]
+ x2 = x1 + unit.width() * self.size[0]
+ x3 = x2 + labelWidth + unit.width()*textPadding*2
+ if self.offset[1] < 0:
+ y2 = rect.top() - unit.height() * self.offset[1]
+ y1 = y2 + unit.height() * self.size[1]
+ else:
+ y1 = rect.bottom() - unit.height() * self.offset[1]
+ y2 = y1 - unit.height() * self.size[1]
+ self.b = [x1,x2,x3,y1,y2,labelWidth]
+
+ ## Draw background
+ p.setPen(self.pen)
+ p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100)))
+ rect = QtCore.QRectF(
+ QtCore.QPointF(x1 - unit.width()*textPadding, y1 + labelHeight/2 + unit.height()*textPadding),
+ QtCore.QPointF(x3, y2 - labelHeight/2 - unit.height()*textPadding)
+ )
+ p.drawRect(rect)
+
+
+ ## Have to scale painter so that text and gradients are correct size. Bleh.
+ p.scale(unit.width(), unit.height())
+
+ ## Draw color bar
+ self.gradient.setStart(0, y1/unit.height())
+ self.gradient.setFinalStop(0, y2/unit.height())
+ p.setBrush(self.gradient)
+ rect = QtCore.QRectF(
+ QtCore.QPointF(x1/unit.width(), y1/unit.height()),
+ QtCore.QPointF(x2/unit.width(), y2/unit.height())
+ )
+ p.drawRect(rect)
+
+
+ ## draw labels
+ p.setPen(QtGui.QPen(QtGui.QColor(0,0,0)))
+ tx = x2 + unit.width()*textPadding
+ lh = labelHeight/unit.height()
+ for k in self.labels:
+ y = y1 + self.labels[k] * (y2-y1)
+ p.drawText(QtCore.QRectF(tx/unit.width(), y/unit.height() - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, k)
+
+
diff --git a/graphicsWindows.py b/graphicsWindows.py
index 79faf1cc..55eba8e8 100644
--- a/graphicsWindows.py
+++ b/graphicsWindows.py
@@ -8,13 +8,16 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
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', 'setLabel', 'clear']:
+ for m in ['plot', 'autoRange', 'addItem', 'removeItem', 'setLabel', 'clear']:
setattr(self, m, getattr(self.cw, m))
if title is not None:
self.setWindowTitle(title)
@@ -22,10 +25,13 @@ class PlotWindow(QtGui.QMainWindow):
class ImageWindow(QtGui.QMainWindow):
def __init__(self, title=None):
+ if QtGui.QApplication.instance() is None:
+ global QAPP
+ QAPP = QtGui.QApplication([])
QtGui.QMainWindow.__init__(self)
self.cw = ImageView()
self.setCentralWidget(self.cw)
- for m in ['setImage', 'autoRange', 'addItem']:
+ 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)
diff --git a/plotConfigTemplate.py b/plotConfigTemplate.py
index 355d9347..5d30654a 100644
--- a/plotConfigTemplate.py
+++ b/plotConfigTemplate.py
@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'plotConfigTemplate.ui'
#
-# Created: Mon Mar 29 22:40:47 2010
-# by: PyQt4 UI code generator 4.6
+# Created: Sat Jul 17 00:28:43 2010
+# by: PyQt4 UI code generator 4.7.2
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,7 @@ from PyQt4 import QtCore, QtGui
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
- Form.resize(210, 340)
+ Form.resize(250, 340)
Form.setMaximumSize(QtCore.QSize(250, 350))
self.gridLayout_3 = QtGui.QGridLayout(Form)
self.gridLayout_3.setMargin(0)
@@ -187,6 +187,7 @@ class Ui_Form(object):
self.verticalLayout_3.addWidget(self.alphaGroup)
self.gridGroup = QtGui.QGroupBox(self.tab_3)
self.gridGroup.setCheckable(True)
+ self.gridGroup.setChecked(False)
self.gridGroup.setObjectName("gridGroup")
self.verticalLayout_4 = QtGui.QVBoxLayout(self.gridGroup)
self.verticalLayout_4.setObjectName("verticalLayout_4")
@@ -227,6 +228,9 @@ class Ui_Form(object):
self.saveMaBtn = QtGui.QPushButton(self.tab_4)
self.saveMaBtn.setObjectName("saveMaBtn")
self.gridLayout_6.addWidget(self.saveMaBtn, 2, 0, 1, 1)
+ self.saveCsvBtn = QtGui.QPushButton(self.tab_4)
+ self.saveCsvBtn.setObjectName("saveCsvBtn")
+ self.gridLayout_6.addWidget(self.saveCsvBtn, 3, 0, 1, 1)
self.gridLayout_7.addLayout(self.gridLayout_6, 0, 1, 1, 1)
spacerItem2 = QtGui.QSpacerItem(59, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout_7.addItem(spacerItem2, 0, 2, 1, 1)
@@ -281,5 +285,6 @@ class Ui_Form(object):
self.saveSvgBtn.setText(QtGui.QApplication.translate("Form", "SVG", None, QtGui.QApplication.UnicodeUTF8))
self.saveImgBtn.setText(QtGui.QApplication.translate("Form", "Image", None, QtGui.QApplication.UnicodeUTF8))
self.saveMaBtn.setText(QtGui.QApplication.translate("Form", "MetaArray", None, QtGui.QApplication.UnicodeUTF8))
+ self.saveCsvBtn.setText(QtGui.QApplication.translate("Form", "CSV", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8))
diff --git a/plotConfigTemplate.ui b/plotConfigTemplate.ui
index 59cfd119..7baeb337 100644
--- a/plotConfigTemplate.ui
+++ b/plotConfigTemplate.ui
@@ -6,7 +6,7 @@
0
0
- 210
+ 250
340
@@ -35,7 +35,7 @@
- 1
+ 0
@@ -419,6 +419,9 @@
true
+
+ false
+
-
@@ -514,6 +517,13 @@
+ -
+
+
+ CSV
+
+
+
-
diff --git a/widgets.py b/widgets.py
index 7ee8b2c2..c306740e 100644
--- a/widgets.py
+++ b/widgets.py
@@ -1,837 +1,889 @@
-# -*- coding: utf-8 -*-
-"""
-widgets.py - Interactive graphics items for GraphicsView (ROI widgets)
-Copyright 2010 Luke Campagnola
-Distributed under MIT/X11 license. See license.txt for more infomation.
-
-Implements a series of graphics items which display movable/scalable/rotatable shapes
-for use as region-of-interest markers. ROI class automatically handles extraction
-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
-from numpy.linalg import norm
-import scipy.ndimage as ndimage
-from Point import *
-from math import cos, sin
-
-def rectStr(r):
- return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height())
-
-## Multiple inheritance not allowed in PyQt. Retarded workaround:
-class QObjectWorkaround:
- def __init__(self):
- self._qObj_ = QtCore.QObject()
- def __getattr__(self, attr):
- if attr == '_qObj_':
- raise Exception("QObjectWorkaround not initialized!")
- return getattr(self._qObj_, attr)
- def connect(self, *args):
- return QtCore.QObject.connect(self._qObj_, *args)
-
-
-class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
- def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None):
- QObjectWorkaround.__init__(self)
- QtGui.QGraphicsItem.__init__(self, parent)
- pos = Point(pos)
- size = Point(size)
- self.aspectLocked = False
- self.translatable = True
-
- self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
- self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255))
- self.handles = []
- self.state = {'pos': pos, 'size': size, 'angle': angle}
- self.lastState = None
- self.setPos(pos)
- self.rotate(-angle)
- self.setZValue(10)
-
- self.handleSize = 4
- self.invertible = invertible
- self.maxBounds = maxBounds
-
- self.snapSize = snapSize
- self.translateSnap = translateSnap
- self.rotateSnap = rotateSnap
- self.scaleSnap = scaleSnap
- self.setFlag(self.ItemIsSelectable, True)
-
- def setZValue(self, z):
- QtGui.QGraphicsItem.setZValue(self, z)
- for h in self.handles:
- h['item'].setZValue(z+1)
-
- def sceneBounds(self):
- return self.sceneTransform().mapRect(self.boundingRect())
-
- def parentBounds(self):
- return self.mapToParent(self.boundingRect()).boundingRect()
-
- def setPen(self, pen):
- self.pen = pen
- self.update()
-
- def setPos(self, pos, update=True):
- pos = Point(pos)
- self.state['pos'] = pos
- QtGui.QGraphicsItem.setPos(self, pos)
- if update:
- self.handleChange()
-
- def setSize(self, size, update=True):
- size = Point(size)
- self.prepareGeometryChange()
- self.state['size'] = size
- if update:
- self.updateHandles()
- self.handleChange()
-
- def addTranslateHandle(self, pos, axes=None, item=None):
- pos = Point(pos)
- return self.addHandle({'type': 't', 'pos': pos, 'item': item})
-
- def addScaleHandle(self, pos, center, axes=None, item=None):
- pos = Point(pos)
- center = Point(center)
- info = {'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):
- pos = Point(pos)
- center = Point(center)
- return self.addHandle({'type': 'r', 'center': center, 'pos': pos, 'item': item})
-
- def addScaleRotateHandle(self, pos, center, item=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})
-
- def addHandle(self, info):
- if not info.has_key('item') or info['item'] is None:
- #print "BEFORE ADD CHILD:", self.childItems()
- h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self)
- #print "AFTER ADD CHILD:", self.childItems()
- h.setPos(info['pos'] * self.state['size'])
- info['item'] = h
- else:
- h = info['item']
- iid = len(self.handles)
- h.connectROI(self, iid)
- #h.mouseMoveEvent = lambda ev: self.pointMoveEvent(iid, ev)
- #h.mousePressEvent = lambda ev: self.pointPressEvent(iid, ev)
- #h.mouseReleaseEvent = lambda ev: self.pointReleaseEvent(iid, ev)
- self.handles.append(info)
- h.setZValue(self.zValue()+1)
- #if self.isSelected():
- #h.show()
- #else:
- #h.hide()
- return h
-
- def mapSceneToParent(self, pt):
- return self.mapToParent(self.mapFromScene(pt))
-
- def setSelected(self, s):
- QtGui.QGraphicsItem.setSelected(self, s)
- #print "select", self, s
- if s:
- for h in self.handles:
- h['item'].show()
- else:
- for h in self.handles:
- h['item'].hide()
-
- def mousePressEvent(self, ev):
- if ev.button() == QtCore.Qt.LeftButton:
- self.setSelected(True)
- if self.translatable:
- self.cursorOffset = self.scenePos() - ev.scenePos()
- self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
- ev.accept()
- else:
- ev.ignore()
-
- def mouseMoveEvent(self, ev):
- #print "mouse move", ev.pos()
- if self.translatable:
- snap = None
- if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
- snap = Point(self.snapSize, self.snapSize)
- newPos = ev.scenePos() + self.cursorOffset
- newPos = self.mapSceneToParent(newPos)
- self.translate(newPos - self.pos(), snap=snap)
-
- def mouseReleaseEvent(self, ev):
- if self.translatable:
- self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
-
-
-
- def pointPressEvent(self, pt, ev):
- #print "press"
- 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.emit(QtCore.SIGNAL('regionChangeFinished'), self)
-
- def stateCopy(self):
- sc = {}
- sc['pos'] = Point(self.state['pos'])
- sc['size'] = Point(self.state['size'])
- sc['angle'] = self.state['angle']
- return sc
-
- def updateHandles(self):
- #print "update", self.handles
- for h in self.handles:
- #print " try", h
- if h['item'] in self.childItems():
- p = h['pos']
- h['item'].setPos(h['pos'] * self.state['size'])
- #else:
- #print " Not child!", self.childItems()
-
-
- def checkPointMove(self, pt, pos, modifiers):
- return True
-
- def pointMoveEvent(self, pt, ev):
- self.movePoint(pt, ev.scenePos(), ev.modifiers())
-
-
- def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
- #print "movePoint", pos
- newState = self.stateCopy()
- h = self.handles[pt]
- #p0 = self.mapToScene(h['item'].pos())
- p0 = self.mapToScene(h['pos'] * self.state['size'])
- p1 = Point(pos)
- p0 = self.mapSceneToParent(p0)
- p1 = self.mapSceneToParent(p1)
-
- if h.has_key('center'):
- c = h['center']
- cs = c * self.state['size']
- #lpOrig = h['pos'] -
- #lp0 = self.mapFromScene(p0) - cs
- #lp1 = self.mapFromScene(p1) - cs
- lp0 = self.mapFromParent(p0) - cs
- lp1 = self.mapFromParent(p1) - cs
-
- if h['type'] == 't':
- #p0 = Point(self.mapToScene(h['item'].pos()))
- #p1 = Point(pos + self.mapToScene(self.pressHandlePos) - self.mapToScene(self.pressPos))
- snap = None
- if self.translateSnap or (modifiers & QtCore.Qt.ControlModifier):
- snap = Point(self.snapSize, self.snapSize)
- self.translate(p1-p0, snap=snap, update=False)
-
- elif h['type'] == 's':
- #c = h['center']
- #cs = c * self.state['size']
- #p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
-
- if h['center'][0] == h['pos'][0]:
- lp1[0] = 0
- if h['center'][1] == h['pos'][1]:
- lp1[1] = 0
-
- 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
-
- hs = h['pos'] - c
- if hs[0] == 0:
- hs[0] = 1
- if hs[1] == 0:
- hs[1] = 1
- newSize = lp1 / hs
-
- if newSize[0] == 0:
- newSize[0] = newState['size'][0]
- if newSize[1] == 0:
- newSize[1] = newState['size'][1]
- if not self.invertible:
- if newSize[0] < 0:
- newSize[0] = newState['size'][0]
- if newSize[1] < 0:
- newSize[1] = newState['size'][1]
- if self.aspectLocked:
- newSize[0] = newSize[1]
-
- s0 = c * self.state['size']
- s1 = c * newSize
- cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))
-
- newState['size'] = newSize
- newState['pos'] = newState['pos'] + cc
- if self.maxBounds is not None:
- r = self.stateRect(newState)
- if not self.maxBounds.contains(r):
- return
-
- self.setPos(newState['pos'], update=False)
- self.prepareGeometryChange()
- self.state = newState
-
- 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
- if lp1.length() == 0 or lp0.length() == 0:
- return
-
- ang = newState['angle'] + lp0.angle(lp1)
- if ang is None:
- return
- if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
- ang = round(ang / (pi/12.)) * (pi/12.)
-
-
- tr = QtGui.QTransform()
- tr.rotate(-ang * 180. / 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
-
- elif h['type'] == 'sr':
- #newState = self.stateCopy()
- if h['center'][0] == h['pos'][0]:
- scaleAxis = 1
- else:
- scaleAxis = 0
-
- #c = h['center']
- #cs = c * self.state['size']
- #p0 = Point(h['item'].pos()) - cs
- #p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
- if lp1.length() == 0 or lp0.length() == 0:
- return
-
- ang = newState['angle'] + lp0.angle(lp1)
- if ang is None:
- return
- if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
- ang = round(ang / (pi/12.)) * (pi/12.)
-
- hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
- newState['size'][scaleAxis] = lp1.length() / hs
- if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
- newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize
- if newState['size'][scaleAxis] == 0:
- newState['size'][scaleAxis] = 1
-
- c1 = c * newState['size']
- tr = QtGui.QTransform()
- tr.rotate(-ang * 180. / pi)
-
- cc = self.mapToParent(cs) - (tr.map(c1) + 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.prepareGeometryChange()
- self.state = newState
-
- self.updateHandles()
-
- self.handleChange()
-
- def handleChange(self):
- changed = False
- if self.lastState is None:
- changed = True
- else:
- for k in self.state.keys():
- if self.state[k] != self.lastState[k]:
- #print "state %s has changed; emit signal" % k
- changed = True
- self.lastState = self.stateCopy()
- if changed:
- self.update()
- self.emit(QtCore.SIGNAL('regionChanged'), self)
-
-
- def scale(self, s, center=[0,0]):
- c = self.mapToScene(Point(center) * self.state['size'])
- self.prepareGeometryChange()
- self.state['size'] = self.state['size'] * s
- c1 = self.mapToScene(Point(center) * self.state['size'])
- 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:
- snap = None
- else:
- snap = kargs['snap']
-
- if len(args) == 1:
- pt = args[0]
- else:
- pt = args
-
- newState = self.stateCopy()
- newState['pos'] = newState['pos'] + pt
- if snap != None:
- newState['pos'][0] = round(newState['pos'][0] / snap[0]) * snap[0]
- newState['pos'][1] = round(newState['pos'][1] / snap[1]) * snap[1]
-
-
- #d = ev.scenePos() - self.mapToScene(self.pressPos)
- if self.maxBounds is not None:
- r = self.stateRect(newState)
- #r0 = self.sceneTransform().mapRect(self.boundingRect())
- d = Point(0,0)
- if self.maxBounds.left() > r.left():
- d[0] = self.maxBounds.left() - r.left()
- elif self.maxBounds.right() < r.right():
- d[0] = self.maxBounds.right() - r.right()
- if self.maxBounds.top() > r.top():
- d[1] = self.maxBounds.top() - r.top()
- elif self.maxBounds.bottom() < r.bottom():
- d[1] = self.maxBounds.bottom() - r.bottom()
- newState['pos'] += d
-
- self.state['pos'] = newState['pos']
- self.setPos(self.state['pos'])
- #if 'update' not in kargs or kargs['update'] is True:
- self.handleChange()
-
- def stateRect(self, state):
- r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
- tr = QtGui.QTransform()
- tr.rotate(-state['angle'] * 180 / pi)
- r = tr.mapRect(r)
- return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])
-
- def boundingRect(self):
- return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1])
-
- def paint(self, p, opt, widget):
- r = self.boundingRect()
- p.setRenderHint(QtGui.QPainter.Antialiasing)
- p.setPen(self.pen)
- p.drawRect(r)
-
-
- def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
- """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI.
- Also returns the transform which maps the ROI into data coordinates.
-
- If returnSlice is set to False, the function returns a pair of tuples with the values that would have
- been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))"""
- #print "getArraySlice"
-
- ## Determine shape of array along ROI axes
- dShape = (data.shape[axes[0]], data.shape[axes[1]])
- #print " dshape", dShape
-
- ## Determine transform that maps ROI bounding box to image coordinates
- tr = self.sceneTransform() * img.sceneTransform().inverted()[0]
-
- ## Modify transform to scale from image coords to data coords
- #m = QtGui.QTransform()
- tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
- #tr = tr * m
-
- ## Transform ROI bounds into data bounds
- dataBounds = tr.mapRect(self.boundingRect())
- #print " boundingRect:", self.boundingRect()
- #print " dataBounds:", dataBounds
-
- ## Intersect transformed ROI bounds with data bounds
- intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1]))
- #print " intBounds:", intBounds
-
- ## Determine index values to use when referencing the array.
- bounds = (
- (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))),
- (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top())))
- )
- #print " bounds:", bounds
-
- if returnSlice:
- ## Create slice objects
- sl = [slice(None)] * data.ndim
- sl[axes[0]] = slice(*bounds[0])
- sl[axes[1]] = slice(*bounds[1])
- return tuple(sl), tr
- else:
- return bounds, tr
-
-
- def getArrayRegion(self, data, img, axes=(0,1)):
-
- ## transpose data so x and y are the first 2 axes
- trAx = range(0, data.ndim)
- trAx.remove(axes[0])
- trAx.remove(axes[1])
- tr1 = tuple(axes) + tuple(trAx)
- arr = data.transpose(tr1)
-
- ## Determine the minimal area of the data we will need
- (dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
-
- ## Pad data boundaries by 1px if possible
- dataBounds = (
- (max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
- (max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
- )
-
- ## Extract minimal data from array
- arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
-
- ## Update roiDataTransform to reflect this extraction
- roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
- ### (roiDataTransform now maps from ROI coords to extracted data coords)
-
-
- ## Rotate array
- if abs(self.state['angle']) > 1e-5:
- arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / pi, order=1)
-
- ## update data transforms to reflect this rotation
- rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / pi)
- roiDataTransform *= rot
-
- ## The rotation also causes a shift which must be accounted for:
- dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
- rotBound = rot.mapRect(dataBound)
- roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
-
- else:
- arr2 = arr1
-
-
-
- ### Shift off partial pixels
- # 1. map ROI into current data space
- roiBounds = roiDataTransform.mapRect(self.boundingRect())
-
- # 2. Determine amount to shift data
- 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[1:-1, 1:-1] = arr2
-
- # 4. shift array and udpate transforms
- arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
- roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
- else:
- arr3 = arr2
-
-
- ### Extract needed region from rotated/shifted array
- # 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
- roiBounds = roiDataTransform.mapRect(self.boundingRect())
- #print self, roiBounds.height()
- #import traceback
- #traceback.print_stack()
-
- roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
-
- #2. intersect ROI with data bounds
- dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
-
- #3. Extract data from array
- db = dataBounds
- bounds = (
- (db.left(), db.right()+1),
- (db.top(), db.bottom()+1)
- )
- 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)
-
- ## Fill array with ROI data
- orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
- subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
- subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
-
-
- ## figure out the reverse transpose order
- tr2 = array(tr1)
- for i in range(0, len(tr2)):
- tr2[tr1[i]] = i
- tr2 = tuple(tr2)
-
- ## Untranspose array before returning
- return arr5.transpose(tr2)
-
-
-
-
-
-
-
-class Handle(QtGui.QGraphicsItem):
- 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)
- QtGui.QGraphicsItem.__init__(self, parent)
- self.setZValue(11)
- self.roi = []
- self.radius = radius
- self.typ = typ
- self.prepareGeometryChange()
- self.pen = pen
- if typ == 't':
- self.sides = 4
- self.startAng = 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 = pi/4
-
- def connectROI(self, roi, i):
- self.roi.append((roi, i))
-
- def boundingRect(self):
- return self.bounds
-
- def mousePressEvent(self, ev):
- #print "handle press"
- if ev.button() != QtCore.Qt.LeftButton:
- 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)
-
- def mouseMoveEvent(self, ev):
- #print "handle mouseMove", ev.pos()
- 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)
- for r in self.roi:
- r[0].movePoint(r[1], pos, modifiers)
-
- def paint(self, p, opt, widget):
- m = p.transform()
- mi = m.inverted()[0]
-
- ## Determine length of unit vector in painter's coords
- size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0))
- size = (size.x()*size.x() + size.y() * size.y()) ** 0.5
-
- bounds = QtCore.QRectF(-size, -size, size*2, size*2)
- if bounds != self.bounds:
- self.bounds = bounds
- self.prepareGeometryChange()
- p.setRenderHint(QtGui.QPainter.Antialiasing)
- p.setPen(self.pen)
- ang = self.startAng
- dt = 2*pi / self.sides
- for i in range(0, self.sides):
- x1 = size * cos(ang)
- y1 = size * sin(ang)
- x2 = size * cos(ang+dt)
- y2 = size * sin(ang+dt)
- ang += dt
- p.drawLine(Point(x1, y1), Point(x2, y2))
-
-
-
-
-
-class TestROI(ROI):
- def __init__(self, pos, size, **args):
- #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1])
- ROI.__init__(self, pos, size, **args)
- #self.addTranslateHandle([0, 0])
- self.addTranslateHandle([0.5, 0.5])
- self.addScaleHandle([1, 1], [0, 0])
- self.addScaleHandle([0, 0], [1, 1])
- self.addScaleRotateHandle([1, 0.5], [0.5, 0.5])
- self.addScaleHandle([0.5, 1], [0.5, 0.5])
- self.addRotateHandle([1, 0], [0, 0])
- self.addRotateHandle([0, 1], [1, 1])
-
-
-
-class RectROI(ROI):
- def __init__(self, pos, size, centered=False, sideScalers=False, **args):
- #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
- ROI.__init__(self, pos, size, **args)
- if centered:
- center = [0.5, 0.5]
- else:
- center = [0, 0]
-
- #self.addTranslateHandle(center)
- self.addScaleHandle([1, 1], center)
- if sideScalers:
- self.addScaleHandle([1, 0.5], [center[0], 0.5])
- self.addScaleHandle([0.5, 1], [0.5, center[1]])
-
-class LineROI(ROI):
- def __init__(self, pos1, pos2, width, **args):
- pos1 = Point(pos1)
- pos2 = Point(pos2)
- d = pos2-pos1
- l = d.length()
- ang = Point(1, 0).angle(d)
- 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)
- 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])
-
-
-class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
- def __init__(self, points, width, **args):
- QObjectWorkaround.__init__(self)
- QtGui.QGraphicsItem.__init__(self)
- self.roiArgs = args
- if len(points) < 2:
- raise Exception("Must start with at least 2 points")
- self.lines = []
- self.lines.append(ROI([0, 0], [1, 5], parent=self))
- self.lines[-1].addScaleHandle([0.5, 1], [0.5, 0.5])
- h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
- h.movePoint(points[0])
- h.movePoint(points[0])
- for i in range(1, len(points)):
- h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
- if i < len(points)-1:
- self.lines.append(ROI([0, 0], [1, 5], parent=self))
- self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=h)
- h.movePoint(points[i])
- h.movePoint(points[i])
-
- for l in self.lines:
- l.translatable = False
- #self.addToGroup(l)
- l.connect(QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
- l.connect(QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent)
- l.connect(QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent)
-
- def paint(self, *args):
- pass
-
- def boundingRect(self):
- return QtCore.QRectF()
-
- def roiChangedEvent(self):
- w = self.lines[0].state['size'][1]
- for l in self.lines[1:]:
- w0 = l.state['size'][1]
- l.scale([1.0, w/w0], center=[0.5,0.5])
- self.emit(QtCore.SIGNAL('regionChanged'), self)
-
- def roiChangeStartedEvent(self):
- self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
-
- def roiChangeFinishedEvent(self):
- self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
-
-
- def getArrayRegion(self, arr, img=None):
- rgns = []
- for l in self.lines:
- rgn = l.getArrayRegion(arr, img)
- if rgn is None:
- continue
- #return None
- rgns.append(rgn)
- #print l.state['size']
- #print [(r.shape) for r in rgns]
- return vstack(rgns)
-
-
-class EllipseROI(ROI):
- def __init__(self, pos, size, **args):
- #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
- ROI.__init__(self, pos, size, **args)
- self.addRotateHandle([1.0, 0.5], [0.5, 0.5])
- self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
-
- def paint(self, p, opt, widget):
- r = self.boundingRect()
- p.setRenderHint(QtGui.QPainter.Antialiasing)
- p.setPen(self.pen)
-
- p.scale(r.width(), r.height())## workaround for GL bug
- r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1)
-
- p.drawEllipse(r)
-
- def getArrayRegion(self, arr, img=None):
- arr = ROI.getArrayRegion(self, arr, img)
- if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0:
- return None
- 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))
-
- return arr * mask
-
- def shape(self):
- self.path = QtGui.QPainterPath()
- self.path.addEllipse(self.boundingRect())
- return self.path
-
-
-class CircleROI(EllipseROI):
- def __init__(self, pos, size, **args):
- ROI.__init__(self, pos, size, **args)
- self.aspectLocked = True
- #self.addTranslateHandle([0.5, 0.5])
- self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
-
+# -*- coding: utf-8 -*-
+"""
+widgets.py - Interactive graphics items for GraphicsView (ROI widgets)
+Copyright 2010 Luke Campagnola
+Distributed under MIT/X11 license. See license.txt for more infomation.
+
+Implements a series of graphics items which display movable/scalable/rotatable shapes
+for use as region-of-interest markers. ROI class automatically handles extraction
+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.linalg import norm
+import scipy.ndimage as ndimage
+from Point import *
+from math import cos, sin
+from ObjectWorkaround import *
+
+def rectStr(r):
+ return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height())
+
+# Multiple inheritance not allowed in PyQt. Retarded workaround:
+#class QObjectWorkaround:
+ #def __init__(self):
+ #self._qObj_ = QtCore.QObject()
+ #def __getattr__(self, attr):
+ #if attr == '_qObj_':
+ #raise Exception("QObjectWorkaround not initialized!")
+ #return getattr(self._qObj_, attr)
+ #def connect(self, *args):
+ #return QtCore.QObject.connect(self._qObj_, *args)
+
+
+class ROI(QtGui.QGraphicsItem, QObjectWorkaround):
+ def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None):
+ QObjectWorkaround.__init__(self)
+ QtGui.QGraphicsItem.__init__(self, parent)
+ pos = Point(pos)
+ size = Point(size)
+ self.aspectLocked = False
+ self.translatable = True
+
+ self.pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
+ self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255))
+ self.handles = []
+ self.state = {'pos': pos, 'size': size, 'angle': angle}
+ self.lastState = None
+ self.setPos(pos)
+ self.rotate(-angle * 180. / pi)
+ self.setZValue(10)
+
+ self.handleSize = 5
+ self.invertible = invertible
+ self.maxBounds = maxBounds
+
+ self.snapSize = snapSize
+ self.translateSnap = translateSnap
+ self.rotateSnap = rotateSnap
+ self.scaleSnap = scaleSnap
+ self.setFlag(self.ItemIsSelectable, True)
+
+ def setZValue(self, z):
+ QtGui.QGraphicsItem.setZValue(self, z)
+ for h in self.handles:
+ h['item'].setZValue(z+1)
+
+ def sceneBounds(self):
+ return self.sceneTransform().mapRect(self.boundingRect())
+
+ def parentBounds(self):
+ return self.mapToParent(self.boundingRect()).boundingRect()
+
+ def setPen(self, pen):
+ self.pen = pen
+ self.update()
+
+ def setPos(self, pos, update=True):
+ pos = Point(pos)
+ self.state['pos'] = pos
+ QtGui.QGraphicsItem.setPos(self, pos)
+ if update:
+ self.handleChange()
+
+ def setSize(self, size, update=True):
+ size = Point(size)
+ self.prepareGeometryChange()
+ self.state['size'] = size
+ if update:
+ self.updateHandles()
+ self.handleChange()
+
+ def addTranslateHandle(self, pos, axes=None, item=None):
+ pos = Point(pos)
+ return self.addHandle({'type': 't', 'pos': pos, 'item': item})
+
+ def addFreeHandle(self, pos, axes=None, item=None):
+ pos = Point(pos)
+ return self.addHandle({'type': 'f', 'pos': pos, 'item': item})
+
+ def addScaleHandle(self, pos, center, axes=None, item=None):
+ pos = Point(pos)
+ center = Point(center)
+ info = {'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):
+ pos = Point(pos)
+ center = Point(center)
+ return self.addHandle({'type': 'r', 'center': center, 'pos': pos, 'item': item})
+
+ def addScaleRotateHandle(self, pos, center, item=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})
+
+ def addHandle(self, info):
+ if not info.has_key('item') or info['item'] is None:
+ #print "BEFORE ADD CHILD:", self.childItems()
+ h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self)
+ #print "AFTER ADD CHILD:", self.childItems()
+ h.setPos(info['pos'] * self.state['size'])
+ info['item'] = h
+ else:
+ h = info['item']
+ iid = len(self.handles)
+ h.connectROI(self, iid)
+ #h.mouseMoveEvent = lambda ev: self.pointMoveEvent(iid, ev)
+ #h.mousePressEvent = lambda ev: self.pointPressEvent(iid, ev)
+ #h.mouseReleaseEvent = lambda ev: self.pointReleaseEvent(iid, ev)
+ self.handles.append(info)
+ h.setZValue(self.zValue()+1)
+ #if self.isSelected():
+ #h.show()
+ #else:
+ #h.hide()
+ return h
+
+ def mapSceneToParent(self, pt):
+ return self.mapToParent(self.mapFromScene(pt))
+
+ def setSelected(self, s):
+ QtGui.QGraphicsItem.setSelected(self, s)
+ #print "select", self, s
+ if s:
+ for h in self.handles:
+ h['item'].show()
+ else:
+ for h in self.handles:
+ h['item'].hide()
+
+ def mousePressEvent(self, ev):
+ if ev.button() == QtCore.Qt.LeftButton:
+ self.setSelected(True)
+ if self.translatable:
+ self.cursorOffset = self.scenePos() - ev.scenePos()
+ self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
+ ev.accept()
+ else:
+ ev.ignore()
+
+ def mouseMoveEvent(self, ev):
+ #print "mouse move", ev.pos()
+ if self.translatable:
+ snap = None
+ if self.translateSnap or (ev.modifiers() & QtCore.Qt.ControlModifier):
+ snap = Point(self.snapSize, self.snapSize)
+ newPos = ev.scenePos() + self.cursorOffset
+ newPos = self.mapSceneToParent(newPos)
+ self.translate(newPos - self.pos(), snap=snap)
+
+ def mouseReleaseEvent(self, ev):
+ if self.translatable:
+ self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
+
+
+
+ def pointPressEvent(self, pt, ev):
+ #print "press"
+ 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.emit(QtCore.SIGNAL('regionChangeFinished'), self)
+
+ def stateCopy(self):
+ sc = {}
+ sc['pos'] = Point(self.state['pos'])
+ sc['size'] = Point(self.state['size'])
+ sc['angle'] = self.state['angle']
+ return sc
+
+ def updateHandles(self):
+ #print "update", self.handles
+ for h in self.handles:
+ #print " try", h
+ if h['item'] in self.childItems():
+ p = h['pos']
+ h['item'].setPos(h['pos'] * self.state['size'])
+ #else:
+ #print " Not child!", self.childItems()
+
+
+ def checkPointMove(self, pt, pos, modifiers):
+ return True
+
+ def pointMoveEvent(self, pt, ev):
+ self.movePoint(pt, ev.scenePos(), ev.modifiers())
+
+
+ def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier()):
+ #print "movePoint", pos
+ newState = self.stateCopy()
+ h = self.handles[pt]
+ #p0 = self.mapToScene(h['item'].pos())
+ p0 = self.mapToScene(h['pos'] * self.state['size'])
+ p1 = Point(pos)
+ p0 = self.mapSceneToParent(p0)
+ p1 = self.mapSceneToParent(p1)
+
+ if h.has_key('center'):
+ c = h['center']
+ cs = c * self.state['size']
+ #lpOrig = h['pos'] -
+ #lp0 = self.mapFromScene(p0) - cs
+ #lp1 = self.mapFromScene(p1) - cs
+ lp0 = self.mapFromParent(p0) - cs
+ lp1 = self.mapFromParent(p1) - cs
+
+ if h['type'] == 't':
+ #p0 = Point(self.mapToScene(h['item'].pos()))
+ #p1 = Point(pos + self.mapToScene(self.pressHandlePos) - self.mapToScene(self.pressPos))
+ snap = None
+ if self.translateSnap or (modifiers & QtCore.Qt.ControlModifier):
+ snap = Point(self.snapSize, self.snapSize)
+ self.translate(p1-p0, snap=snap, update=False)
+
+ elif h['type'] == 'f':
+ h['item'].setPos(self.mapFromScene(pos))
+
+ elif h['type'] == 's':
+ #c = h['center']
+ #cs = c * self.state['size']
+ #p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
+
+ if h['center'][0] == h['pos'][0]:
+ lp1[0] = 0
+ if h['center'][1] == h['pos'][1]:
+ lp1[1] = 0
+
+ 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
+
+ hs = h['pos'] - c
+ if hs[0] == 0:
+ hs[0] = 1
+ if hs[1] == 0:
+ hs[1] = 1
+ newSize = lp1 / hs
+
+ if newSize[0] == 0:
+ newSize[0] = newState['size'][0]
+ if newSize[1] == 0:
+ newSize[1] = newState['size'][1]
+ if not self.invertible:
+ if newSize[0] < 0:
+ newSize[0] = newState['size'][0]
+ if newSize[1] < 0:
+ newSize[1] = newState['size'][1]
+ if self.aspectLocked:
+ newSize[0] = newSize[1]
+
+ s0 = c * self.state['size']
+ s1 = c * newSize
+ cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))
+
+ newState['size'] = newSize
+ newState['pos'] = newState['pos'] + cc
+ if self.maxBounds is not None:
+ r = self.stateRect(newState)
+ if not self.maxBounds.contains(r):
+ return
+
+ self.setPos(newState['pos'], update=False)
+ self.prepareGeometryChange()
+ self.state = newState
+
+ 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
+ if lp1.length() == 0 or lp0.length() == 0:
+ return
+
+ ang = newState['angle'] + lp0.angle(lp1)
+ if ang is None:
+ return
+ if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
+ ang = round(ang / (pi/12.)) * (pi/12.)
+
+
+ tr = QtGui.QTransform()
+ tr.rotate(-ang * 180. / 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
+
+ elif h['type'] == 'sr':
+ #newState = self.stateCopy()
+ if h['center'][0] == h['pos'][0]:
+ scaleAxis = 1
+ else:
+ scaleAxis = 0
+
+ #c = h['center']
+ #cs = c * self.state['size']
+ #p0 = Point(h['item'].pos()) - cs
+ #p1 = (self.mapFromScene(ev.scenePos()) + self.pressHandlePos - self.pressPos) - cs
+ if lp1.length() == 0 or lp0.length() == 0:
+ return
+
+ ang = newState['angle'] + lp0.angle(lp1)
+ if ang is None:
+ return
+ if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
+ ang = round(ang / (pi/12.)) * (pi/12.)
+
+ hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
+ newState['size'][scaleAxis] = lp1.length() / hs
+ if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
+ newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize
+ if newState['size'][scaleAxis] == 0:
+ newState['size'][scaleAxis] = 1
+
+ c1 = c * newState['size']
+ tr = QtGui.QTransform()
+ tr.rotate(-ang * 180. / pi)
+
+ cc = self.mapToParent(cs) - (tr.map(c1) + 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.prepareGeometryChange()
+ self.state = newState
+
+ self.updateHandles()
+
+ self.handleChange()
+
+ def handleChange(self):
+ changed = False
+ if self.lastState is None:
+ changed = True
+ else:
+ for k in self.state.keys():
+ if self.state[k] != self.lastState[k]:
+ #print "state %s has changed; emit signal" % k
+ changed = True
+ self.lastState = self.stateCopy()
+ if changed:
+ self.update()
+ self.emit(QtCore.SIGNAL('regionChanged'), self)
+
+
+ def scale(self, s, center=[0,0]):
+ c = self.mapToScene(Point(center) * self.state['size'])
+ self.prepareGeometryChange()
+ self.state['size'] = self.state['size'] * s
+ c1 = self.mapToScene(Point(center) * self.state['size'])
+ 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:
+ snap = None
+ else:
+ snap = kargs['snap']
+
+ if len(args) == 1:
+ pt = args[0]
+ else:
+ pt = args
+
+ newState = self.stateCopy()
+ newState['pos'] = newState['pos'] + pt
+ if snap != None:
+ newState['pos'][0] = round(newState['pos'][0] / snap[0]) * snap[0]
+ newState['pos'][1] = round(newState['pos'][1] / snap[1]) * snap[1]
+
+
+ #d = ev.scenePos() - self.mapToScene(self.pressPos)
+ if self.maxBounds is not None:
+ r = self.stateRect(newState)
+ #r0 = self.sceneTransform().mapRect(self.boundingRect())
+ d = Point(0,0)
+ if self.maxBounds.left() > r.left():
+ d[0] = self.maxBounds.left() - r.left()
+ elif self.maxBounds.right() < r.right():
+ d[0] = self.maxBounds.right() - r.right()
+ if self.maxBounds.top() > r.top():
+ d[1] = self.maxBounds.top() - r.top()
+ elif self.maxBounds.bottom() < r.bottom():
+ d[1] = self.maxBounds.bottom() - r.bottom()
+ newState['pos'] += d
+
+ self.state['pos'] = newState['pos']
+ self.setPos(self.state['pos'])
+ #if 'update' not in kargs or kargs['update'] is True:
+ self.handleChange()
+
+ def stateRect(self, state):
+ r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
+ tr = QtGui.QTransform()
+ tr.rotate(-state['angle'] * 180 / pi)
+ r = tr.mapRect(r)
+ return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])
+
+ def boundingRect(self):
+ return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1])
+
+ def paint(self, p, opt, widget):
+ p.save()
+ r = self.boundingRect()
+ p.setRenderHint(QtGui.QPainter.Antialiasing)
+ p.setPen(self.pen)
+ p.translate(r.left(), r.top())
+ p.scale(r.width(), r.height())
+ p.drawRect(0, 0, 1, 1)
+ p.restore()
+
+ def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
+ """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI.
+ Also returns the transform which maps the ROI into data coordinates.
+
+ If returnSlice is set to False, the function returns a pair of tuples with the values that would have
+ been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))"""
+ #print "getArraySlice"
+
+ ## Determine shape of array along ROI axes
+ dShape = (data.shape[axes[0]], data.shape[axes[1]])
+ #print " dshape", dShape
+
+ ## Determine transform that maps ROI bounding box to image coordinates
+ tr = self.sceneTransform() * img.sceneTransform().inverted()[0]
+
+ ## Modify transform to scale from image coords to data coords
+ #m = QtGui.QTransform()
+ tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())
+ #tr = tr * m
+
+ ## Transform ROI bounds into data bounds
+ dataBounds = tr.mapRect(self.boundingRect())
+ #print " boundingRect:", self.boundingRect()
+ #print " dataBounds:", dataBounds
+
+ ## Intersect transformed ROI bounds with data bounds
+ intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1]))
+ #print " intBounds:", intBounds
+
+ ## Determine index values to use when referencing the array.
+ bounds = (
+ (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))),
+ (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top())))
+ )
+ #print " bounds:", bounds
+
+ if returnSlice:
+ ## Create slice objects
+ sl = [slice(None)] * data.ndim
+ sl[axes[0]] = slice(*bounds[0])
+ sl[axes[1]] = slice(*bounds[1])
+ return tuple(sl), tr
+ else:
+ return bounds, tr
+
+
+ def getArrayRegion(self, data, img, axes=(0,1)):
+
+ ## transpose data so x and y are the first 2 axes
+ trAx = range(0, data.ndim)
+ trAx.remove(axes[0])
+ trAx.remove(axes[1])
+ tr1 = tuple(axes) + tuple(trAx)
+ arr = data.transpose(tr1)
+
+ ## Determine the minimal area of the data we will need
+ (dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
+
+ ## Pad data boundaries by 1px if possible
+ dataBounds = (
+ (max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
+ (max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
+ )
+
+ ## Extract minimal data from array
+ arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
+
+ ## Update roiDataTransform to reflect this extraction
+ roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
+ ### (roiDataTransform now maps from ROI coords to extracted data coords)
+
+
+ ## Rotate array
+ if abs(self.state['angle']) > 1e-5:
+ arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / pi, order=1)
+
+ ## update data transforms to reflect this rotation
+ rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / pi)
+ roiDataTransform *= rot
+
+ ## The rotation also causes a shift which must be accounted for:
+ dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
+ rotBound = rot.mapRect(dataBound)
+ roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
+
+ else:
+ arr2 = arr1
+
+
+
+ ### Shift off partial pixels
+ # 1. map ROI into current data space
+ roiBounds = roiDataTransform.mapRect(self.boundingRect())
+
+ # 2. Determine amount to shift data
+ 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[1:-1, 1:-1] = arr2
+
+ # 4. shift array and udpate transforms
+ arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
+ roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
+ else:
+ arr3 = arr2
+
+
+ ### Extract needed region from rotated/shifted array
+ # 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
+ roiBounds = roiDataTransform.mapRect(self.boundingRect())
+ #print self, roiBounds.height()
+ #import traceback
+ #traceback.print_stack()
+
+ roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
+
+ #2. intersect ROI with data bounds
+ dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
+
+ #3. Extract data from array
+ db = dataBounds
+ bounds = (
+ (db.left(), db.right()+1),
+ (db.top(), db.bottom()+1)
+ )
+ 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)
+
+ ## Fill array with ROI data
+ orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
+ subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
+ subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
+
+
+ ## figure out the reverse transpose order
+ tr2 = array(tr1)
+ for i in range(0, len(tr2)):
+ tr2[tr1[i]] = i
+ tr2 = tuple(tr2)
+
+ ## Untranspose array before returning
+ return arr5.transpose(tr2)
+
+
+
+
+
+
+
+class Handle(QtGui.QGraphicsItem):
+ 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)
+ QtGui.QGraphicsItem.__init__(self, parent)
+ self.setFlag(self.ItemIgnoresTransformations)
+ self.setZValue(11)
+ self.roi = []
+ self.radius = radius
+ self.typ = typ
+ self.prepareGeometryChange()
+ self.pen = pen
+ self.pen.setWidth(0)
+ self.pen.setCosmetic(True)
+ if typ == 't':
+ self.sides = 4
+ self.startAng = pi/4
+ elif typ == 'f':
+ self.sides = 4
+ self.startAng = 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 = pi/4
+
+ def connectROI(self, roi, i):
+ self.roi.append((roi, i))
+
+ def boundingRect(self):
+ return self.bounds
+
+ def mousePressEvent(self, ev):
+ #print "handle press"
+ if ev.button() != QtCore.Qt.LeftButton:
+ 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)
+
+ def mouseMoveEvent(self, ev):
+ #print "handle mouseMove", ev.pos()
+ 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)
+ for r in self.roi:
+ r[0].movePoint(r[1], pos, modifiers)
+
+ def paint(self, p, opt, widget):
+ ## determine rotation of transform
+ 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())
+
+ ## Determine length of unit vector in painter's coords
+ #size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0))
+ #size = (size.x()*size.x() + size.y() * size.y()) ** 0.5
+ size = self.radius
+
+ bounds = QtCore.QRectF(-size, -size, size*2, size*2)
+ if bounds != self.bounds:
+ self.bounds = bounds
+ self.prepareGeometryChange()
+ p.setRenderHints(p.Antialiasing, True)
+ p.setPen(self.pen)
+ ang = self.startAng + va
+ dt = 2*pi / self.sides
+ for i in range(0, self.sides):
+ x1 = size * cos(ang)
+ y1 = size * sin(ang)
+ x2 = size * cos(ang+dt)
+ y2 = size * sin(ang+dt)
+ ang += dt
+ p.drawLine(Point(x1, y1), Point(x2, y2))
+
+
+
+
+
+class TestROI(ROI):
+ def __init__(self, pos, size, **args):
+ #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1])
+ ROI.__init__(self, pos, size, **args)
+ #self.addTranslateHandle([0, 0])
+ self.addTranslateHandle([0.5, 0.5])
+ self.addScaleHandle([1, 1], [0, 0])
+ self.addScaleHandle([0, 0], [1, 1])
+ self.addScaleRotateHandle([1, 0.5], [0.5, 0.5])
+ self.addScaleHandle([0.5, 1], [0.5, 0.5])
+ self.addRotateHandle([1, 0], [0, 0])
+ self.addRotateHandle([0, 1], [1, 1])
+
+
+
+class RectROI(ROI):
+ def __init__(self, pos, size, centered=False, sideScalers=False, **args):
+ #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
+ ROI.__init__(self, pos, size, **args)
+ if centered:
+ center = [0.5, 0.5]
+ else:
+ center = [0, 0]
+
+ #self.addTranslateHandle(center)
+ self.addScaleHandle([1, 1], center)
+ if sideScalers:
+ self.addScaleHandle([1, 0.5], [center[0], 0.5])
+ self.addScaleHandle([0.5, 1], [0.5, center[1]])
+
+class LineROI(ROI):
+ def __init__(self, pos1, pos2, width, **args):
+ pos1 = Point(pos1)
+ pos2 = Point(pos2)
+ d = pos2-pos1
+ l = d.length()
+ ang = Point(1, 0).angle(d)
+ 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)
+ 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])
+
+
+class MultiLineROI(QtGui.QGraphicsItem, QObjectWorkaround):
+ def __init__(self, points, width, **args):
+ QObjectWorkaround.__init__(self)
+ QtGui.QGraphicsItem.__init__(self)
+ self.roiArgs = args
+ if len(points) < 2:
+ raise Exception("Must start with at least 2 points")
+ self.lines = []
+ self.lines.append(ROI([0, 0], [1, 5], parent=self))
+ self.lines[-1].addScaleHandle([0.5, 1], [0.5, 0.5])
+ h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
+ h.movePoint(points[0])
+ h.movePoint(points[0])
+ for i in range(1, len(points)):
+ h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
+ if i < len(points)-1:
+ self.lines.append(ROI([0, 0], [1, 5], parent=self))
+ self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=h)
+ h.movePoint(points[i])
+ h.movePoint(points[i])
+
+ for l in self.lines:
+ l.translatable = False
+ #self.addToGroup(l)
+ l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent)
+ l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent)
+ l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent)
+
+ def paint(self, *args):
+ pass
+
+ def boundingRect(self):
+ return QtCore.QRectF()
+
+ def roiChangedEvent(self):
+ w = self.lines[0].state['size'][1]
+ for l in self.lines[1:]:
+ w0 = l.state['size'][1]
+ l.scale([1.0, w/w0], center=[0.5,0.5])
+ self.emit(QtCore.SIGNAL('regionChanged'), self)
+
+ def roiChangeStartedEvent(self):
+ self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
+
+ def roiChangeFinishedEvent(self):
+ self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
+
+
+ def getArrayRegion(self, arr, img=None):
+ rgns = []
+ for l in self.lines:
+ rgn = l.getArrayRegion(arr, img)
+ if rgn is None:
+ continue
+ #return None
+ rgns.append(rgn)
+ #print l.state['size']
+ #print [(r.shape) for r in rgns]
+ return vstack(rgns)
+
+
+class EllipseROI(ROI):
+ def __init__(self, pos, size, **args):
+ #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
+ ROI.__init__(self, pos, size, **args)
+ self.addRotateHandle([1.0, 0.5], [0.5, 0.5])
+ self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
+
+ def paint(self, p, opt, widget):
+ r = self.boundingRect()
+ p.setRenderHint(QtGui.QPainter.Antialiasing)
+ p.setPen(self.pen)
+
+ p.scale(r.width(), r.height())## workaround for GL bug
+ r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1)
+
+ p.drawEllipse(r)
+
+ def getArrayRegion(self, arr, img=None):
+ arr = ROI.getArrayRegion(self, arr, img)
+ if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0:
+ return None
+ 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))
+
+ return arr * mask
+
+ def shape(self):
+ self.path = QtGui.QPainterPath()
+ self.path.addEllipse(self.boundingRect())
+ return self.path
+
+
+class CircleROI(EllipseROI):
+ def __init__(self, pos, size, **args):
+ ROI.__init__(self, pos, size, **args)
+ self.aspectLocked = True
+ #self.addTranslateHandle([0.5, 0.5])
+ 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):
+ ROI.__init__(self, [0,0], [100,100])
+ for p in positions:
+ self.addFreeHandle(p)
+
+ def movePoint(self, *args, **kargs):
+ ROI.movePoint(self, *args, **kargs)
+ self.prepareGeometryChange()
+
+ def paint(self, p, *args):
+ p.setRenderHint(QtGui.QPainter.Antialiasing)
+ p.setPen(self.pen)
+ for i in range(len(self.handles)):
+ h1 = self.handles[i]['item'].pos()
+ h2 = self.handles[i-1]['item'].pos()
+ p.drawLine(h1, h2)
+
+ def boundingRect(self):
+ r = QtCore.QRectF()
+ for h in self.handles:
+ r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect()
+ return r
+
+ def shape(self):
+ p = QtGui.QPainterPath()
+ p.moveTo(self.handles[0]['item'].pos())
+ for i in range(len(self.handles)):
+ p.lineTo(self.handles[i]['item'].pos())
+ return p
+
\ No newline at end of file