Commit 2ca08c69 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

merged many changes in from acq4:

 - added vertical lines / regions for plots
 - added gradient editor widget
 - many bugfixes
 - cleaned up imageview a bit
parent 7efc9754
# -*- 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
# -*- 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)
......@@ -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
......
......@@ -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.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.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.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 updateNorm(self):
for l, sl in zip(self.normLines, [self.ui.normStartSlider, self.ui.normStopSlider]):