Merge pull request #586 from acq4/parametertree-updates

Parametertree updates
This commit is contained in:
Luke Campagnola 2017-10-04 09:26:34 -07:00 committed by GitHub
commit 7d992a56ee
3 changed files with 72 additions and 18 deletions

View File

@ -162,7 +162,11 @@ class Parameter(QtCore.QObject):
'title': None,
#'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits.
}
value = opts.get('value', None)
name = opts.get('name', None)
self.opts.update(opts)
self.opts['value'] = None # will be set later.
self.opts['name'] = None
self.childs = []
self.names = {} ## map name:child
@ -172,17 +176,19 @@ class Parameter(QtCore.QObject):
self.blockTreeChangeEmit = 0
#self.monitoringChildren = False ## prevent calling monitorChildren more than once
if 'value' not in self.opts:
self.opts['value'] = None
if 'name' not in self.opts or not isinstance(self.opts['name'], basestring):
if not isinstance(name, basestring):
raise Exception("Parameter must have a string name specified in opts.")
self.setName(opts['name'])
self.setName(name)
self.addChildren(self.opts.get('children', []))
if 'value' in self.opts and 'default' not in self.opts:
self.opts['default'] = self.opts['value']
self.opts['value'] = None
if value is not None:
self.setValue(value)
if 'default' not in self.opts:
self.opts['default'] = None
self.setDefault(self.opts['value'])
## Connect all state changed signals to the general sigStateChanged
self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data))
@ -647,18 +653,19 @@ class Parameter(QtCore.QObject):
"""Return a child parameter.
Accepts the name of the child or a tuple (path, to, child)
Added in version 0.9.9. Ealier versions used the 'param' method, which is still
implemented for backward compatibility."""
Added in version 0.9.9. Earlier versions used the 'param' method, which is still
implemented for backward compatibility.
"""
try:
param = self.names[names[0]]
except KeyError:
raise Exception("Parameter %s has no child named %s" % (self.name(), names[0]))
raise KeyError("Parameter %s has no child named %s" % (self.name(), names[0]))
if len(names) > 1:
return param.param(*names[1:])
return param.child(*names[1:])
else:
return param
def param(self, *names):
# for backward compatibility.
return self.child(*names)

View File

@ -1,5 +1,7 @@
from ..pgcollections import OrderedDict
import numpy as np
import copy
class SystemSolver(object):
"""
@ -73,6 +75,12 @@ class SystemSolver(object):
self.__dict__['_currentGets'] = set()
self.reset()
def copy(self):
sys = type(self)()
sys.__dict__['_vars'] = copy.deepcopy(self.__dict__['_vars'])
sys.__dict__['_currentGets'] = copy.deepcopy(self.__dict__['_currentGets'])
return sys
def reset(self):
"""
Reset all variables in the solver to their default state.
@ -167,6 +175,16 @@ class SystemSolver(object):
elif constraint == 'fixed':
if 'f' not in var[3]:
raise TypeError("Fixed constraints not allowed for '%s'" % name)
# This is nice, but not reliable because sometimes there is 1 DOF but we set 2
# values simultaneously.
# if var[2] is None:
# try:
# self.get(name)
# # has already been computed by the system; adding a fixed constraint
# # would overspecify the system.
# raise ValueError("Cannot fix parameter '%s'; system would become overconstrained." % name)
# except RuntimeError:
# pass
var[2] = constraint
elif isinstance(constraint, tuple):
if 'r' not in var[3]:
@ -177,7 +195,7 @@ class SystemSolver(object):
raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint)
# type checking / massaging
if var[1] is np.ndarray:
if var[1] is np.ndarray and value is not None:
value = np.array(value, dtype=float)
elif var[1] in (int, float, tuple) and value is not None:
value = var[1](value)
@ -185,9 +203,9 @@ class SystemSolver(object):
# constraint checks
if constraint is True and not self.check_constraint(name, value):
raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2]))
# invalidate other dependent values
if var[0] is not None:
if var[0] is not None or value is None:
# todo: we can make this more clever..(and might need to)
# we just know that a value of None cannot have dependencies
# (because if anyone else had asked for this value, it wouldn't be
@ -237,6 +255,31 @@ class SystemSolver(object):
for k in self._vars:
getattr(self, k)
def checkOverconstraint(self):
"""Check whether the system is overconstrained. If so, return the name of
the first overconstrained parameter.
Overconstraints occur when any fixed parameter can be successfully computed by the system.
(Ideally, all parameters are either fixed by the user or constrained by the
system, but never both).
"""
for k,v in self._vars.items():
if v[2] == 'fixed' and 'n' in v[3]:
oldval = v[:]
self.set(k, None, None)
try:
self.get(k)
return k
except RuntimeError:
pass
finally:
self._vars[k] = oldval
return False
def __repr__(self):
state = OrderedDict()
for name, var in self._vars.items():
@ -378,4 +421,4 @@ if __name__ == '__main__':
camera.solve()
print(camera.saveState())

View File

@ -462,12 +462,15 @@ class GroupParameter(Parameter):
instead of a button.
"""
itemClass = GroupParameterItem
sigAddNew = QtCore.Signal(object, object) # self, type
def addNew(self, typ=None):
"""
This method is called when the user has requested to add a new item to the group.
By default, it emits ``sigAddNew(self, typ)``.
"""
raise Exception("Must override this function in subclass.")
self.sigAddNew.emit(self, typ)
def setAddList(self, vals):
"""Change the list of options available for the user to add to the group."""
@ -605,6 +608,7 @@ class ActionParameterItem(ParameterItem):
ParameterItem.__init__(self, param, depth)
self.layoutWidget = QtGui.QWidget()
self.layout = QtGui.QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layoutWidget.setLayout(self.layout)
self.button = QtGui.QPushButton(param.name())
#self.layout.addSpacing(100)