Merge pull request #1310 from campagnola/acq4-merge

Acq4 merge
This commit is contained in:
Luke Campagnola 2020-07-13 16:13:38 -07:00 committed by GitHub
commit dce9a5bf0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 87 additions and 73 deletions

View File

@ -3,6 +3,7 @@ from .Qt import QtCore
from .ptime import time from .ptime import time
from . import ThreadsafeTimer from . import ThreadsafeTimer
import weakref import weakref
from .functions import SignalBlock
__all__ = ['SignalProxy'] __all__ = ['SignalProxy']
@ -34,7 +35,7 @@ class SignalProxy(QtCore.QObject):
self.args = None self.args = None
self.timer = ThreadsafeTimer.ThreadsafeTimer() self.timer = ThreadsafeTimer.ThreadsafeTimer()
self.timer.timeout.connect(self.flush) self.timer.timeout.connect(self.flush)
self.block = False self.blockSignal = False
self.slot = weakref.ref(slot) self.slot = weakref.ref(slot)
self.lastFlushTime = None self.lastFlushTime = None
if slot is not None: if slot is not None:
@ -45,7 +46,7 @@ class SignalProxy(QtCore.QObject):
def signalReceived(self, *args): def signalReceived(self, *args):
"""Received signal. Cancel previous timer and store args to be forwarded later.""" """Received signal. Cancel previous timer and store args to be forwarded later."""
if self.block: if self.blockSignal:
return return
self.args = args self.args = args
if self.rateLimit == 0: if self.rateLimit == 0:
@ -61,11 +62,10 @@ class SignalProxy(QtCore.QObject):
self.timer.stop() self.timer.stop()
self.timer.start((min(leakTime, self.delay)*1000)+1) self.timer.start((min(leakTime, self.delay)*1000)+1)
def flush(self): def flush(self):
"""If there is a signal queued up, send it now.""" """If there is a signal queued up, send it now."""
if self.args is None or self.block: if self.args is None or self.blockSignal:
return False return False
args, self.args = self.args, None args, self.args = self.args, None
self.timer.stop() self.timer.stop()
@ -75,7 +75,7 @@ class SignalProxy(QtCore.QObject):
return True return True
def disconnect(self): def disconnect(self):
self.block = True self.blockSignal = True
try: try:
self.signal.disconnect(self.signalReceived) self.signal.disconnect(self.signalReceived)
except: except:
@ -85,18 +85,10 @@ class SignalProxy(QtCore.QObject):
except: except:
pass pass
def block(self):
"""Return a SignalBlocker that temporarily blocks input signals to this proxy.
#def proxyConnect(source, signal, slot, delay=0.3): """
#"""Connect a signal to a slot with delay. Returns the SignalProxy return SignalBlock(self.signal, self.signalReceived)
#object that was created. Be sure to store this object so it is not
#garbage-collected immediately."""
#sp = SignalProxy(source, signal, delay)
#if source is None:
#sp.connect(sp, QtCore.SIGNAL('signal'), slot)
#else:
#sp.connect(sp, signal, slot)
#return sp
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -14,22 +14,33 @@ class Vector(QtGui.QVector3D):
def __init__(self, *args): def __init__(self, *args):
if len(args) == 1: if len(args) == 1:
if isinstance(args[0], QtCore.QSizeF): if isinstance(args[0], QtCore.QSizeF):
QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) x = float(args[0].width())
return y = float(args[0].height())
z = 0
elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF):
QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) x = float(args[0].x())
y = float(args[0].y())
z = 0
elif isinstance(args[0], QtGui.QVector3D):
x = args[0].x()
y = args[0].y()
z = args[0].z()
elif hasattr(args[0], '__getitem__'): elif hasattr(args[0], '__getitem__'):
vals = list(args[0]) vals = list(args[0])
if len(vals) == 2: if len(vals) == 2:
vals.append(0) x, y = vals
if len(vals) != 3: z = 0
raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) elif len(vals) == 3:
QtGui.QVector3D.__init__(self, *vals) x, y, z = vals
return else:
raise ValueError('Cannot init Vector with sequence of length %d' % len(args[0]))
elif len(args) == 2: elif len(args) == 2:
QtGui.QVector3D.__init__(self, args[0], args[1], 0) x, y = args
return z = 0
QtGui.QVector3D.__init__(self, *args) else:
x, y, z = args # Could raise ValueError
QtGui.QVector3D.__init__(self, x, y, z)
def __len__(self): def __len__(self):
return 3 return 3

View File

@ -447,7 +447,7 @@ class ConsoleWidget(QtGui.QWidget):
filterStr = str(self.ui.filterText.text()) filterStr = str(self.ui.filterText.text())
if filterStr != '': if filterStr != '':
if isinstance(exc, Exception): if isinstance(exc, Exception):
msg = exc.message msg = traceback.format_exception_only(type(exc), exc)
elif isinstance(exc, basestring): elif isinstance(exc, basestring):
msg = exc msg = exc
else: else:

View File

@ -1151,7 +1151,22 @@ class ThreadTrace(object):
for id, frame in sys._current_frames().items(): for id, frame in sys._current_frames().items():
if id == threading.current_thread().ident: if id == threading.current_thread().ident:
continue continue
print("<< thread %d >>" % id)
# try to determine a thread name
try:
name = threading._active.get(id, None)
except:
name = None
if name is None:
try:
# QThread._names must be manually set by thread creators.
name = QtCore.QThread._names.get(id)
except:
name = None
if name is None:
name = "???"
print("<< thread %d \"%s\" >>" % (id, name))
traceback.print_stack(frame) traceback.print_stack(frame)
print("===============================================\n") print("===============================================\n")

View File

@ -222,8 +222,8 @@ class InfiniteLine(GraphicsObject):
def setPos(self, pos): def setPos(self, pos):
if type(pos) in [list, tuple]: if type(pos) in [list, tuple, np.ndarray]:
newPos = pos newPos = list(pos)
elif isinstance(pos, QtCore.QPointF): elif isinstance(pos, QtCore.QPointF):
newPos = [pos.x(), pos.y()] newPos = [pos.x(), pos.y()]
else: else:

View File

@ -5,12 +5,12 @@ from .ParameterItem import ParameterItem
from ..widgets.SpinBox import SpinBox from ..widgets.SpinBox import SpinBox
from ..widgets.ColorButton import ColorButton from ..widgets.ColorButton import ColorButton
from ..colormap import ColorMap from ..colormap import ColorMap
#from ..widgets.GradientWidget import GradientWidget ## creates import loop
from .. import pixmaps as pixmaps from .. import pixmaps as pixmaps
from .. import functions as fn from .. import functions as fn
import os, sys import os, sys
from ..pgcollections import OrderedDict from ..pgcollections import OrderedDict
class WidgetParameterItem(ParameterItem): class WidgetParameterItem(ParameterItem):
""" """
ParameterTree item with: ParameterTree item with:
@ -38,7 +38,6 @@ class WidgetParameterItem(ParameterItem):
self.hideWidget = True ## hide edit widget, replace with label when not selected self.hideWidget = True ## hide edit widget, replace with label when not selected
## set this to False to keep the editor widget always visible ## set this to False to keep the editor widget always visible
## build widget into column 1 with a display label and default button. ## build widget into column 1 with a display label and default button.
w = self.makeWidget() w = self.makeWidget()
self.widget = w self.widget = w
@ -162,8 +161,6 @@ class WidgetParameterItem(ParameterItem):
self.focusNext(forward=False) self.focusNext(forward=False)
return True ## don't let anyone else see this event return True ## don't let anyone else see this event
#elif ev.type() == ev.FocusOut:
#self.hideEditor()
return False return False
def setFocus(self): def setFocus(self):
@ -272,7 +269,6 @@ class WidgetParameterItem(ParameterItem):
def optsChanged(self, param, opts): def optsChanged(self, param, opts):
"""Called when any options are changed that are not """Called when any options are changed that are not
name, value, default, or limits""" name, value, default, or limits"""
#print "opts changed:", opts
ParameterItem.optsChanged(self, param, opts) ParameterItem.optsChanged(self, param, opts)
if 'readonly' in opts: if 'readonly' in opts:
@ -317,6 +313,11 @@ class SimpleParameter(Parameter):
self.value = self.colorValue self.value = self.colorValue
self.saveState = self.saveColorState self.saveState = self.saveColorState
def setValue(self, value, blockSignal=None):
if self.opts['type'] == 'int':
value = int(value)
Parameter.setValue(self, value, blockSignal)
def colorValue(self): def colorValue(self):
return fn.mkColor(Parameter.value(self)) return fn.mkColor(Parameter.value(self))
@ -343,9 +344,8 @@ class SimpleParameter(Parameter):
if not isinstance(v, ColorMap): if not isinstance(v, ColorMap):
raise TypeError("Cannot set colormap parameter from object %r" % v) raise TypeError("Cannot set colormap parameter from object %r" % v)
return v return v
registerParameterType('int', SimpleParameter, override=True) registerParameterType('int', SimpleParameter, override=True)
registerParameterType('float', SimpleParameter, override=True) registerParameterType('float', SimpleParameter, override=True)
registerParameterType('bool', SimpleParameter, override=True) registerParameterType('bool', SimpleParameter, override=True)
@ -354,8 +354,6 @@ registerParameterType('color', SimpleParameter, override=True)
registerParameterType('colormap', SimpleParameter, override=True) registerParameterType('colormap', SimpleParameter, override=True)
class GroupParameterItem(ParameterItem): class GroupParameterItem(ParameterItem):
""" """
Group parameters are used mainly as a generic parent item that holds (and groups!) a set Group parameters are used mainly as a generic parent item that holds (and groups!) a set
@ -383,7 +381,6 @@ class GroupParameterItem(ParameterItem):
w.setLayout(l) w.setLayout(l)
l.addWidget(self.addWidget) l.addWidget(self.addWidget)
l.addStretch() l.addStretch()
#l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum))
self.addWidgetBox = w self.addWidgetBox = w
self.addItem = QtGui.QTreeWidgetItem([]) self.addItem = QtGui.QTreeWidgetItem([])
self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) self.addItem.setFlags(QtCore.Qt.ItemIsEnabled)
@ -458,7 +455,8 @@ class GroupParameterItem(ParameterItem):
self.addWidget.addItem(t) self.addWidget.addItem(t)
finally: finally:
self.addWidget.blockSignals(False) self.addWidget.blockSignals(False)
class GroupParameter(Parameter): class GroupParameter(Parameter):
""" """
Group parameters are used mainly as a generic parent item that holds (and groups!) a set Group parameters are used mainly as a generic parent item that holds (and groups!) a set
@ -486,14 +484,10 @@ class GroupParameter(Parameter):
"""Change the list of options available for the user to add to the group.""" """Change the list of options available for the user to add to the group."""
self.setOpts(addList=vals) self.setOpts(addList=vals)
registerParameterType('group', GroupParameter, override=True) registerParameterType('group', GroupParameter, override=True)
class ListParameterItem(WidgetParameterItem): class ListParameterItem(WidgetParameterItem):
""" """
WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. WidgetParameterItem subclass providing comboBox that lets the user select from a list of options.
@ -503,7 +497,6 @@ class ListParameterItem(WidgetParameterItem):
self.targetValue = None self.targetValue = None
WidgetParameterItem.__init__(self, param, depth) WidgetParameterItem.__init__(self, param, depth)
def makeWidget(self): def makeWidget(self):
opts = self.param.opts opts = self.param.opts
t = opts['type'] t = opts['type']
@ -551,7 +544,6 @@ class ListParameterItem(WidgetParameterItem):
self.updateDisplayLabel() self.updateDisplayLabel()
finally: finally:
self.widget.blockSignals(False) self.widget.blockSignals(False)
class ListParameter(Parameter): class ListParameter(Parameter):
@ -561,7 +553,7 @@ class ListParameter(Parameter):
self.forward = OrderedDict() ## {name: value, ...} self.forward = OrderedDict() ## {name: value, ...}
self.reverse = ([], []) ## ([value, ...], [name, ...]) self.reverse = ([], []) ## ([value, ...], [name, ...])
## Parameter uses 'limits' option to define the set of allowed values # Parameter uses 'limits' option to define the set of allowed values
if 'values' in opts: if 'values' in opts:
opts['limits'] = opts['values'] opts['limits'] = opts['values']
if opts.get('limits', None) is None: if opts.get('limits', None) is None:
@ -576,24 +568,9 @@ class ListParameter(Parameter):
if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]: if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]:
self.setValue(self.reverse[0][0]) self.setValue(self.reverse[0][0])
#def addItem(self, name, value=None):
#if name in self.forward:
#raise Exception("Name '%s' is already in use for this parameter" % name)
#limits = self.opts['limits']
#if isinstance(limits, dict):
#limits = limits.copy()
#limits[name] = value
#self.setLimits(limits)
#else:
#if value is not None:
#raise Exception ## raise exception or convert to dict?
#limits = limits[:]
#limits.append(name)
## what if limits == None?
@staticmethod @staticmethod
def mapping(limits): def mapping(limits):
## Return forward and reverse mapping objects given a limit specification # Return forward and reverse mapping objects given a limit specification
forward = OrderedDict() ## {name: value, ...} forward = OrderedDict() ## {name: value, ...}
reverse = ([], []) ## ([value, ...], [name, ...]) reverse = ([], []) ## ([value, ...], [name, ...])
if isinstance(limits, dict): if isinstance(limits, dict):
@ -658,7 +635,6 @@ class ActionParameter(Parameter):
registerParameterType('action', ActionParameter, override=True) registerParameterType('action', ActionParameter, override=True)
class TextParameterItem(WidgetParameterItem): class TextParameterItem(WidgetParameterItem):
def __init__(self, param, depth): def __init__(self, param, depth):
WidgetParameterItem.__init__(self, param, depth) WidgetParameterItem.__init__(self, param, depth)
@ -693,7 +669,6 @@ class TextParameterItem(WidgetParameterItem):
class TextParameter(Parameter): class TextParameter(Parameter):
"""Editable string; displayed as large text box in the tree.""" """Editable string; displayed as large text box in the tree."""
itemClass = TextParameterItem itemClass = TextParameterItem
registerParameterType('text', TextParameter, override=True) registerParameterType('text', TextParameter, override=True)

View File

@ -98,6 +98,19 @@ def check_interpolateArray(order):
assert_array_almost_equal(r1, r2) assert_array_almost_equal(r1, r2)
def test_subArray():
a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0])
b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1))
c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]])
assert np.all(b == c)
# operate over first axis; broadcast over the rest
aa = np.vstack([a, a/100.]).T
cc = np.empty(c.shape + (2,))
cc[..., 0] = c
cc[..., 1] = c / 100.
bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1))
assert np.all(bb == cc)
def test_subArray(): def test_subArray():
a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0])

View File

@ -10,7 +10,7 @@
## No unicode variable names (μ,Ω) allowed until python 3 ## No unicode variable names (μ,Ω) allowed until python 3
SI_PREFIXES = 'yzafpnum kMGTPEZY' SI_PREFIXES = 'yzafpnum kMGTPEZY'
UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B,Pa'.split(',')
allUnits = {} allUnits = {}
def addUnit(p, n): def addUnit(p, n):

View File

@ -31,4 +31,5 @@ class BusyCursor(object):
def __exit__(self, *args): def __exit__(self, *args):
if self._active: if self._active:
BusyCursor.active.pop(-1) BusyCursor.active.pop(-1)
QtGui.QApplication.restoreOverrideCursor() if len(BusyCursor.active) == 0:
QtGui.QApplication.restoreOverrideCursor()

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..Qt import QtGui, QtCore from ..Qt import QtGui, QtCore
from .. import ptime
__all__ = ['ProgressDialog'] __all__ = ['ProgressDialog']
@ -40,7 +41,7 @@ class ProgressDialog(QtGui.QProgressDialog):
nested (bool) If True, then this progress bar will be displayed inside nested (bool) If True, then this progress bar will be displayed inside
any pre-existing progress dialogs that also allow nesting. any pre-existing progress dialogs that also allow nesting.
============== ================================================================ ============== ================================================================
""" """
# attributes used for nesting dialogs # attributes used for nesting dialogs
self.nestedLayout = None self.nestedLayout = None
self._nestableWidgets = None self._nestableWidgets = None
@ -48,6 +49,9 @@ class ProgressDialog(QtGui.QProgressDialog):
self._topDialog = None self._topDialog = None
self._subBars = [] self._subBars = []
self.nested = nested self.nested = nested
# for rate-limiting Qt event processing during progress bar update
self._lastProcessEvents = None
isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread()
self.disabled = disable or (not isGuiThread) self.disabled = disable or (not isGuiThread)
@ -203,7 +207,10 @@ class ProgressDialog(QtGui.QProgressDialog):
# Qt docs say this should happen automatically, but that doesn't seem # Qt docs say this should happen automatically, but that doesn't seem
# to be the case. # to be the case.
if self.windowModality() == QtCore.Qt.WindowModal: if self.windowModality() == QtCore.Qt.WindowModal:
QtGui.QApplication.processEvents() now = ptime.time()
if self._lastProcessEvents is None or (now - self._lastProcessEvents) > 0.2:
QtGui.QApplication.processEvents()
self._lastProcessEvents = now
def setLabelText(self, val): def setLabelText(self, val):
if self.disabled: if self.disabled: