import fabio from pipeline import calibration from xicam.widgets.calibrationpanel import calibrationpanel # Globals so Timeline can share the same rightmodes configtree = ParameterTree() configtree.setParameters(config.activeExperiment, showTop=False) def tiltStyleMenuRequested(pos): config.activeExperiment.tiltStyleMenu.exec_(configtree.mapToGlobal(pos)) configtree.customContextMenuRequested.connect(tiltStyleMenuRequested) configtree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) rightmodes = [(configtree, QtGui.QFileIconProvider().icon(QtGui.QFileIconProvider.File))] class ViewerPlugin(base.plugin): name = 'Viewer' sigUpdateExperiment = QtCore.Signal() def __init__(self, *args, **kwargs): self.centerwidget = QtGui.QTabWidget() self.centerwidget.currentChanged.connect(self.currentChanged) self.centerwidget.setDocumentMode(True) self.centerwidget.setTabsClosable(True)
class FunctionWidget(fw.FeatureWidget): """ Subclass of FeatureWidget that defines attributes to show parameters to a given function and run the function with the given parameters. These should be used with the corresponding FunctionManager to run Tomography pipeline workflows Attributes ---------- func_name : str Function name subfunc_name : str Specific function name input_functions : dict dictionary with keys being parameters of this function to be overriden, and values being a FunctionWidget whose function will override said parameter param_dict : dict Dictionary with parameter names and values _function : function Function object corresponding to the function represented by widget package : str name of package to which function belongs params : pyqtgraph.Parameter Parameter instance with function parameter exposed in UI missing_args : list of str Names of missing arguments not contained in param_dict Signals ------- sigTestRange(QtGui.QWidget, str, tuple, dict) Emitted when parameter range test is requested. Emits the sending widget, a string with a message to log, and a tuple with the range values for the parameter Parameters ---------- name : str generic name of function subname : str specific name of function under the generic name category package : python package package input_functions : dict, optional dictionary with keys being parameters of this function to be overriden, and values being a FunctionWidget whose function will override said parameter checkable : bool, optional bool to set the function to be toggled on and of when running constructed workflows closeable : bool, optional bool to set if the function can be deleted from the pipeline editor parent : QWidget parent of this FunctionWidget """ sigTestRange = QtCore.Signal(QtGui.QWidget, str, tuple) # TODO perhaps its better to not pass in the package object but only a string, package object can be retrived from reconpkgs.packages dict def __init__(self, name, subname, package, input_functions=None, checkable=True, closeable=True, parent=None): self.name = name if name != subname: self.name += ' (' + subname + ')' super(FunctionWidget, self).__init__(self.name, checkable=checkable, closeable=closeable, parent=parent) self.func_name = name self.subfunc_name = subname self.input_functions = {} self.param_dict = {} self._function = getattr(package, config.names[self.subfunc_name][0]) #perhaps unnecessary self.package = package.__name__ # TODO have the children kwarg be passed to __init__ self.params = Parameter.create( name=self.name, children=config.parameters[self.subfunc_name], type='group', readonly=False) self.form = ParameterTree(showHeader=False) self.form.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.form.customContextMenuRequested.connect(self.paramMenuRequested) self.form.setParameters(self.params, showTop=True) # Initialize parameter dictionary with keys and default values self.updateParamsDict() argspec = inspect.getargspec(self._function) default_argnum = len(argspec[3]) self.param_dict.update({ key: val for (key, val) in zip(argspec[0][-default_argnum:], argspec[3]) }) for key, val in self.param_dict.iteritems(): if key in [p.name() for p in self.params.children()]: self.params.param(key).setValue(val) self.params.param(key).setDefault(val) # Create a list of argument names (this will most generally be the data passed to the function) self.missing_args = [ i for i in argspec[0] if i not in self.param_dict.keys() ] self.parammenu = QtGui.QMenu() action = QtGui.QAction('Test Parameter Range', self) action.triggered.connect(self.testParamTriggered) self.parammenu.addAction(action) self.previewButton.customContextMenuRequested.connect( self.menuRequested) self.menu = QtGui.QMenu() if input_functions is not None: for param, ipf in input_functions.iteritems(): self.addInputFunction(param, ipf) # wire up param changed signals for param in self.params.children(): param.sigValueChanged.connect(self.paramChanged) # change on/off icons icon = QtGui.QIcon() if checkable: icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_51.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_45.png"), QtGui.QIcon.Normal, QtGui.QIcon.On) self.previewButton.setCheckable(True) else: icon.addPixmap(QtGui.QPixmap("xicam/gui/icons_45.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.previewButton.setCheckable(False) self.previewButton.setChecked(True) self.previewButton.setIcon(icon) self.previewButton.setFlat(True) self.previewButton.setChecked(True) self.previewButton.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.allowed_types = { 'str': str, 'int': int, 'float': float, 'bool': bool, 'unicode': unicode } # set widgets to never hide their subfunctions self.expand() def collapse(self): """ This catches all "collapse" requests and passes them, so that FunctionWidgets are not collapsable """ pass @property def enabled(self): """ Boolean showing if the function widget is enabled (eye open/closed) """ if self.previewButton.isChecked( ) or not self.previewButton.isCheckable(): return True return False @enabled.setter def enabled(self, val): """ Set enabled value by toggling the previewButton only if the widget is checkable """ if val and self.previewButton.isCheckable(): self.previewButton.setChecked(True) else: self.previewButton.setChecked(False) @property def exposed_param_dict(self): """ Parameter dictionary with only the parameters that are shown in GUI """ param_dict = { key: val for (key, val) in self.updated_param_dict.iteritems() if key in [param.name() for param in self.params.children()] } return param_dict @property def partial(self): """ Package up all parameters into a functools.partial """ return partial(self._function, **self.updated_param_dict) @property def updated_param_dict(self): """ Return the param dict of the FunctionWidget updated with proper types """ if hasattr(self, 'defaults'): param_dict = {} for key, val in self.param_dict.iteritems(): if type(val) is str and 'None' in val: pass elif key in self.defaults.iterkeys(): arg_type = self.defaults[key]['type'] try: param_dict[key] = arg_type(val) except ValueError: param_dict[key] = None else: param_dict[key] = val return param_dict else: return self.param_dict @property def func_signature(self): """ String for function signature. Hopefully this can eventually be used to save workflows as scripts :) """ signature = str(self._function.__name__) + '(' for arg in self.missing_args: signature += '{},'.format(arg) for param, value in self.param_dict.iteritems(): signature += '{0}={1},'.format(param, value) if not isinstance(value, str) else \ '{0}=\'{1}\','.format(param, value) return signature[:-1] + ')' def updateParamsDict(self): """ Update the values of the parameter dictionary with the current values in UI """ self.param_dict.update( {param.name(): param.value() for param in self.params.children()}) for p, ipf in self.input_functions.iteritems(): ipf.updateParamsDict() def addInputFunction(self, parameter, functionwidget): """ Add an input function widget Parameters ---------- parameter : str Parameter name that will be overriden by return value of the input function functionwidget : FunctionWidget FunctionWidget representing the input function """ if parameter in self.input_functions: # Check to see if parameter already has input function if functionwidget.subfunc_name == self.input_functions[ parameter].subfunc_name: raise AttributeError( 'Input function already exists' ) # skip if the input function already exists self.removeInputFunction( parameter) # Remove it if it will be replaced self.input_functions[parameter] = functionwidget self.addSubFeature(functionwidget) functionwidget.sigDelete.connect( lambda: self.removeInputFunction(parameter)) def removeInputFunction(self, parameter): """ Remove the input function for the given parameter Parameters ---------- parameter : str Parameter name that will be overriden by return value of the input function """ function = self.input_functions.pop(parameter) self.removeSubFeature(function) def paramChanged(self, param): """ Slot connected to a pg.Parameter.sigChanged signal """ if hasattr(self, 'defaults'): try: arg_type = self.defaults[param.name()]['type'] try: self.allowed_types[arg_type](param.value()) self.param_dict.update({param.name(): param.value()}) except ValueError: if param.value() == "None": self.param_dict.update({param.name(): param.value()}) else: param.setValue(self.param_dict[param.name()]) except KeyError: self.param_dict.update({param.name(): param.value()}) else: self.param_dict.update({param.name(): param.value()}) def allReadOnly(self, boolean): """ Make all parameter read only """ for param in self.params.children(): param.setReadonly(boolean) def menuRequested(self): """ Context menu for functionWidget. Default is not menu. """ pass def paramMenuRequested(self, pos): """ Menus when a parameter in the form is right clicked """ if self.form.currentItem().parent(): self.parammenu.exec_(self.form.mapToGlobal(pos)) def testParamTriggered(self): """ Slot when a parameter range is clicked. Will emit the parameter name and the chosen range """ param = self.form.currentItem().param if param.type() == 'int' or param.type() == 'float': start, end, step = None, None, None if 'limits' in param.opts: start, end = param.opts['limits'] step = (end - start) / 3 + 1 elif param.value() is not None: start, end, step = param.value() / 2, 4 * ( param.value()) / 2, param.value() / 2 test = TestRangeDialog(param.type(), (start, end, step)) elif param.type() == 'list': test = TestListRangeDialog(param.opts['values']) else: return if test.exec_(): self.sigTestRange.emit(self, param.name(), test.selectedRange())