merged updates from acq4

This commit is contained in:
Luke Campagnola 2013-03-07 15:29:56 -05:00
parent 21dff0525a
commit 2a27687fb2
7 changed files with 97 additions and 23 deletions

View File

@ -97,6 +97,6 @@ Miscellaneous Functions
.. autofunction:: pyqtgraph.systemInfo .. autofunction:: pyqtgraph.systemInfo
.. autofunction:: pyqtgraph.exit

View File

@ -54,6 +54,7 @@ CONFIG_OPTIONS = {
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
'useWeave': True, ## Use weave to speed up some operations, if it is available 'useWeave': True, ## Use weave to speed up some operations, if it is available
'weaveDebug': False, ## Print full error message if weave compile fails 'weaveDebug': False, ## Print full error message if weave compile fails
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
} }
@ -190,9 +191,20 @@ from .SignalProxy import *
from .colormap import * from .colormap import *
from .ptime import time from .ptime import time
##############################################################
## PyQt and PySide both are prone to crashing on exit.
## There are two general approaches to dealing with this:
## 1. Install atexit handlers that assist in tearing down to avoid crashes.
## This helps, but is never perfect.
## 2. Terminate the process before python starts tearing down
## This is potentially dangerous
## Attempts to work around exit crashes:
import atexit import atexit
def cleanup(): def cleanup():
if not getConfigOption('exitCleanup'):
return
ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore.
## Workaround for Qt exit crash: ## Workaround for Qt exit crash:
@ -212,6 +224,38 @@ def cleanup():
atexit.register(cleanup) atexit.register(cleanup)
## Optional function for exiting immediately (with some manual teardown)
def exit():
"""
Causes python to exit without garbage-collecting any objects, and thus avoids
calling object destructor methods. This is a sledgehammer workaround for
a variety of bugs in PyQt and Pyside that cause crashes on exit.
This function does the following in an attempt to 'safely' terminate
the process:
* Invoke atexit callbacks
* Close all open file handles
* os._exit()
Note: there is some potential for causing damage with this function if you
are using objects that _require_ their destructors to be called (for example,
to properly terminate log files, disconnect from devices, etc). Situations
like this are probably quite rare, but use at your own risk.
"""
## first disable our own cleanup function; won't be needing it.
setConfigOptions(exitCleanup=False)
## invoke atexit callbacks
atexit._run_exitfuncs()
## close file handles
os.closerange(3, 4096) ## just guessing on the maximum descriptor count..
os._exit(os.EX_OK)
## Convenience functions for command-line use ## Convenience functions for command-line use

View File

@ -169,7 +169,7 @@ class ConsoleWidget(QtGui.QWidget):
def execMulti(self, nextLine): def execMulti(self, nextLine):
self.stdout.write(nextLine+"\n") #self.stdout.write(nextLine+"\n")
if nextLine.strip() != '': if nextLine.strip() != '':
self.multiline += "\n" + nextLine self.multiline += "\n" + nextLine
return return

View File

@ -521,6 +521,8 @@ class ConnectionItem(GraphicsObject):
self.target = target self.target = target
self.length = 0 self.length = 0
self.hovered = False self.hovered = False
self.path = None
self.shapePath = None
#self.line = QtGui.QGraphicsLineItem(self) #self.line = QtGui.QGraphicsLineItem(self)
self.source.getViewBox().addItem(self) self.source.getViewBox().addItem(self)
self.updateLine() self.updateLine()
@ -544,13 +546,18 @@ class ConnectionItem(GraphicsObject):
else: else:
return return
self.prepareGeometryChange() self.prepareGeometryChange()
self.resetTransform()
ang = (stop-start).angle(Point(0, 1)) self.path = QtGui.QPainterPath()
if ang is None: self.path.moveTo(start)
ang = 0 self.path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y()))
self.rotate(ang) self.shapePath = None
self.setPos(start) #self.resetTransform()
self.length = (start-stop).length() #ang = (stop-start).angle(Point(0, 1))
#if ang is None:
#ang = 0
#self.rotate(ang)
#self.setPos(start)
#self.length = (start-stop).length()
self.update() self.update()
#self.line.setLine(start.x(), start.y(), stop.x(), stop.y()) #self.line.setLine(start.x(), start.y(), stop.x(), stop.y())
@ -582,12 +589,23 @@ class ConnectionItem(GraphicsObject):
def boundingRect(self): def boundingRect(self):
#return self.line.boundingRect() return self.shape().boundingRect()
px = self.pixelWidth() ##return self.line.boundingRect()
return QtCore.QRectF(-5*px, 0, 10*px, self.length) #px = self.pixelWidth()
#return QtCore.QRectF(-5*px, 0, 10*px, self.length)
def viewRangeChanged(self):
self.shapePath = None
self.prepareGeometryChange()
#def shape(self): def shape(self):
#return self.line.shape() if self.shapePath is None:
if self.path is None:
return QtGui.QPainterPath()
stroker = QtGui.QPainterPathStroker()
px = self.pixelWidth()
stroker.setWidth(px*8)
self.shapePath = stroker.createStroke(self.path)
return self.shapePath
def paint(self, p, *args): def paint(self, p, *args):
if self.isSelected(): if self.isSelected():
@ -598,4 +616,6 @@ class ConnectionItem(GraphicsObject):
else: else:
p.setPen(fn.mkPen(100, 100, 250, width=1)) p.setPen(fn.mkPen(100, 100, 250, width=1))
p.drawLine(0, 0, 0, self.length) #p.drawLine(0, 0, 0, self.length)
p.drawPath(self.path)

View File

@ -1046,10 +1046,10 @@ class ViewBox(GraphicsWidget):
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding()
if xr is None or xr == (None, None): if xr is None or (xr[0] is None and xr[1] is None):
useX = False useX = False
xr = (0,0) xr = (0,0)
if yr is None or yr == (None, None): if yr is None or (yr[0] is None and yr[1] is None):
useY = False useY = False
yr = (0,0) yr = (0,0)

View File

@ -169,6 +169,13 @@ class EnumColorMapItem(ptree.types.GroupParameter):
self.fieldName = name self.fieldName = name
vals = opts.get('values', []) vals = opts.get('values', [])
childs = [{'name': v, 'type': 'color'} for v in vals] childs = [{'name': v, 'type': 'color'} for v in vals]
childs = []
for v in vals:
ch = ptree.Parameter.create(name=str(v), type='color')
ch.maskValue = v
childs.append(ch)
ptree.types.GroupParameter.__init__(self, ptree.types.GroupParameter.__init__(self,
name=name, autoIncrementName=True, removable=True, renamable=True, name=name, autoIncrementName=True, removable=True, renamable=True,
children=[ children=[
@ -191,8 +198,7 @@ class EnumColorMapItem(ptree.types.GroupParameter):
colors[:] = default colors[:] = default
for v in self.param('Values'): for v in self.param('Values'):
n = v.name() mask = data == v.maskValue
mask = data == n
c = np.array(fn.colorTuple(v.value())) / 255. c = np.array(fn.colorTuple(v.value())) / 255.
colors[mask] = c colors[mask] = c
#scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1)

View File

@ -92,14 +92,18 @@ class RangeFilterItem(ptree.types.SimpleParameter):
def generateMask(self, data): def generateMask(self, data):
vals = data[self.fieldName] vals = data[self.fieldName]
return (vals >= mn) & (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections return (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections
class EnumFilterItem(ptree.types.SimpleParameter): class EnumFilterItem(ptree.types.SimpleParameter):
def __init__(self, name, opts): def __init__(self, name, opts):
self.fieldName = name self.fieldName = name
vals = opts.get('values', []) vals = opts.get('values', [])
childs = [{'name': v, 'type': 'bool', 'value': True} for v in vals] childs = []
for v in vals:
ch = ptree.Parameter.create(name=str(v), type='bool', value=True)
ch.maskValue = v
childs.append(ch)
ptree.types.SimpleParameter.__init__(self, ptree.types.SimpleParameter.__init__(self,
name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True,
children=childs) children=childs)
@ -110,6 +114,6 @@ class EnumFilterItem(ptree.types.SimpleParameter):
for c in self: for c in self:
if c.value() is True: if c.value() is True:
continue continue
key = c.name() key = c.maskValue
mask &= vals != key mask &= vals != key
return mask return mask